GTK docking interfaces and more

By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 gpanthera.cc

View raw Download
text/x-c++ • 7.78 kiB
C++ source, ASCII text
        
            
1
#include "gpanthera.hh"
2
#include <iostream>
3
#include <utility>
4
#include <libintl.h>
5
#include <locale.h>
6
#include <filesystem>
7
#define _(STRING) gettext(STRING)
8
9
namespace gPanthera {
10
std::vector<Gtk::Widget*> collect_children(Gtk::Widget &widget) {
11
std::vector<Gtk::Widget*> children;
12
for(auto *child = widget.get_first_child(); child; child = child->get_next_sibling()) {
13
children.push_back(child);
14
}
15
return children;
16
}
17
18
void init() {
19
// Set up gettext configuration
20
setlocale(LC_ALL, "");
21
22
bindtextdomain("gpanthera", "./locales");
23
textdomain("gpanthera");
24
}
25
26
DockablePane::DockablePane(std::shared_ptr<LayoutManager> layout, Gtk::Widget &child, const Glib::ustring &name, const Glib::ustring &label, Gtk::Image *icon, DockStack *stack, Gtk::Widget *custom_header)
27
: Gtk::Box(Gtk::Orientation::VERTICAL, 0), name(name) {
28
if(icon) {
29
this->icon = icon;
30
}
31
if(stack) {
32
this->stack = stack;
33
}
34
this->layout = std::move(layout);
35
this->label.set_text(label);
36
header = std::make_unique<Gtk::HeaderBar>();
37
header->set_show_title_buttons(false);
38
if(custom_header) {
39
header->set_title_widget(*custom_header);
40
} else {
41
header->set_title_widget(this->label);
42
}
43
auto header_menu_button = Gtk::make_managed<Gtk::MenuButton>();
44
auto header_menu = Gio::Menu::create();
45
header_menu_button->set_direction(Gtk::ArrowType::NONE);
46
47
// Pane menu
48
auto action_group = Gio::SimpleActionGroup::create();
49
header_menu_button->insert_action_group("win", action_group);
50
51
// Close action
52
auto close_action = Gio::SimpleAction::create("close");
53
close_action->signal_activate().connect([this](const Glib::VariantBase&) {
54
if(this->stack) {
55
this->stack->set_visible_child("empty");
56
}
57
});
58
action_group->add_action(close_action);
59
header_menu->append(_("Close"), "win.close");
60
61
// Pop out action
62
auto pop_out_action = Gio::SimpleAction::create("pop_out");
63
pop_out_action->signal_activate().connect([this](const Glib::VariantBase&) {
64
if(this->stack) {
65
this->pop_out();
66
}
67
});
68
action_group->add_action(pop_out_action);
69
header_menu->append(_("Pop out"), "win.pop_out");
70
71
// Switch to traditional (nested) submenus, not sliding
72
auto popover_menu = Gtk::make_managed<Gtk::PopoverMenu>(header_menu, Gtk::PopoverMenu::Flags::NESTED);
73
header_menu_button->set_popover(*popover_menu);
74
75
// Move menu
76
auto move_menu = Gio::Menu::create();
77
for(auto &this_stack : this->layout->stacks) {
78
auto action_name = "move_" + this_stack->name;
79
auto move_action = Gio::SimpleAction::create(action_name);
80
move_action->signal_activate().connect([this, this_stack](const Glib::VariantBase&) {
81
this_stack->add_pane(*this);
82
});
83
action_group->add_action(move_action);
84
move_menu->append(this_stack->name, "win." + action_name);
85
}
86
87
// Add move submenu
88
header_menu->append_submenu(_("Move"), move_menu);
89
90
header->pack_end(*header_menu_button);
91
92
this->prepend(*header);
93
this->child = &child;
94
this->append(child);
95
}
96
97
void DockablePane::redock(DockStack *stack) {
98
if(this->window != nullptr) {
99
this->window->hide();
100
this->window->unset_titlebar();
101
this->window->set_decorated(false);
102
this->header->get_style_context()->remove_class("titlebar");
103
this->prepend(*this->header);
104
this->window->unset_child();
105
this->window->close();
106
} else if(this->stack == this->get_parent()) {
107
this->stack->remove(*this);
108
}
109
this->stack = stack;
110
this->stack->add(*this, this->get_identifier());
111
if(this->window != nullptr) {
112
this->window->destroy();
113
delete this->window;
114
this->window = nullptr;
115
}
116
}
117
118
void DockablePane::pop_out() {
119
if(this->stack != nullptr) {
120
this->stack->remove(*this);
121
this->stack = nullptr;
122
}
123
124
if(this->window == nullptr) {
125
this->remove(*this->header);
126
this->window = new DockWindow(this);
127
this->window->set_titlebar(*this->header);
128
this->window->set_child(*this);
129
this->window->set_decorated(true);
130
this->window->present();
131
}
132
}
133
134
Glib::ustring DockablePane::get_identifier() const {
135
return name;
136
}
137
138
Gtk::Image *DockablePane::get_icon() const {
139
return icon;
140
}
141
142
Gtk::Widget *DockablePane::get_child() const {
143
return child;
144
}
145
146
Gtk::Label *DockablePane::get_label() {
147
return &label;
148
}
149
150
LayoutManager::LayoutManager() : Glib::ObjectBase("LayoutManager") {
151
}
152
153
void LayoutManager::add_pane(DockablePane *pane) {
154
panes.push_back(pane);
155
}
156
157
void LayoutManager::add_stack(DockStack *stack) {
158
stacks.push_back(stack);
159
}
160
161
DockStack::DockStack(std::shared_ptr<LayoutManager> layout, const Glib::ustring &name) : Gtk::Stack(), layout(layout), name(name) {
162
auto *empty_child = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
163
add(*empty_child, "empty");
164
// Add the stack to a layout manager
165
this->layout->add_stack(this);
166
}
167
168
DockWindow::DockWindow(DockablePane *pane) {
169
this->pane = pane;
170
this->set_child(*pane);
171
}
172
173
DockStackSwitcher::DockStackSwitcher(DockStack *stack) : Gtk::Box(Gtk::Orientation::HORIZONTAL), stack(stack) {
174
auto update_callback = [this](Gtk::Widget*) {
175
this->update_buttons();
176
};
177
stack->signal_child_added.connect(update_callback);
178
stack->signal_child_removed.connect(update_callback);
179
}
180
181
DockStackSwitcher::~DockStackSwitcher() {
182
add_handler.disconnect();
183
remove_handler.disconnect();
184
}
185
186
DockButton::DockButton(DockablePane *pane) : Gtk::Button(), pane(pane) {
187
if(pane->get_icon()) {
188
auto new_image = Gtk::make_managed<Gtk::Image>(this->pane->get_icon()->get_paintable());
189
this->set_child(*new_image);
190
}
191
this->set_tooltip_text(pane->get_label()->get_text());
192
this->set_halign(Gtk::Align::CENTER);
193
}
194
195
void DockStackSwitcher::update_buttons() {
196
auto old_buttons = collect_children(*this);
197
for(auto *button : old_buttons) {
198
remove(*button);
199
}
200
for(auto *widget = stack->get_first_child(); widget; widget = widget->get_next_sibling()) {
201
if(auto pane = dynamic_cast<DockablePane*>(widget)) {
202
auto *button = Gtk::make_managed<DockButton>(pane);
203
button->signal_clicked().connect([this, pane]() {
204
if(stack->get_visible_child_name() == pane->get_identifier()) {
205
stack->set_visible_child("empty");
206
} else {
207
stack->set_visible_child(pane->get_identifier());
208
}
209
});
210
if(pane->get_identifier() != Glib::ustring("empty")) {
211
append(*button);
212
}
213
}
214
}
215
}
216
217
void DockStack::add_pane(DockablePane &child) {
218
child.redock(this);
219
}
220
221
void DockStack::add(Gtk::Widget &child, const Glib::ustring &name) {
222
Gtk::Stack::add(child, name);
223
signal_child_added.emit(&child);
224
}
225
226
void DockStack::remove(Gtk::Widget &child) {
227
Gtk::Stack::remove(child);
228
signal_child_removed.emit(&child);
229
}
230
} // namespace gPanthera
231