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++ • 10.35 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
Gtk::Image *copy_image(Gtk::Image *image) {
19
if(image->get_storage_type() == Gtk::Image::Type::PAINTABLE) {
20
return Gtk::make_managed<Gtk::Image>(image->get_paintable());
21
} else if(image->get_storage_type() == Gtk::Image::Type::ICON_NAME) {
22
auto new_image = Gtk::make_managed<Gtk::Image>();
23
new_image->set_from_icon_name(image->get_icon_name());
24
return new_image;
25
} else {
26
return nullptr;
27
}
28
}
29
30
void init() {
31
// Set up gettext configuration
32
setlocale(LC_ALL, "");
33
34
bindtextdomain("gpanthera", "./locales");
35
textdomain("gpanthera");
36
}
37
38
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)
39
: Gtk::Box(Gtk::Orientation::VERTICAL, 0), name(name) {
40
if(icon) {
41
this->icon = icon;
42
}
43
if(stack) {
44
this->stack = stack;
45
}
46
this->layout = std::move(layout);
47
this->label.set_text(label);
48
header = std::make_unique<Gtk::HeaderBar>();
49
header->set_show_title_buttons(false);
50
if(custom_header) {
51
header->set_title_widget(*custom_header);
52
} else {
53
header->set_title_widget(this->label);
54
}
55
header->add_css_class("gpanthera-dock-titlebar");
56
auto header_menu_button = Gtk::make_managed<Gtk::MenuButton>();
57
auto header_menu = Gio::Menu::create();
58
header_menu_button->set_direction(Gtk::ArrowType::NONE);
59
60
// Pane menu
61
this->action_group = Gio::SimpleActionGroup::create();
62
header_menu_button->insert_action_group("win", action_group);
63
64
// Close action
65
auto close_action = Gio::SimpleAction::create("close");
66
close_action->signal_activate().connect([this](const Glib::VariantBase&) {
67
if(this->stack) {
68
this->stack->set_visible_child("empty");
69
}
70
});
71
action_group->add_action(close_action);
72
header_menu->append(_("Close"), "win.close");
73
74
// Pop out action
75
auto pop_out_action = Gio::SimpleAction::create("pop_out");
76
pop_out_action->signal_activate().connect([this](const Glib::VariantBase&) {
77
if(this->stack) {
78
this->pop_out();
79
}
80
});
81
action_group->add_action(pop_out_action);
82
header_menu->append(_("Pop out"), "win.pop_out");
83
84
// Move menu
85
auto move_menu = Gio::Menu::create();
86
for(auto &this_stack : this->layout->stacks) {
87
auto action_name = "move_" + this_stack->name;
88
auto move_action = Gio::SimpleAction::create(action_name);
89
move_action->signal_activate().connect([this, this_stack](const Glib::VariantBase&) {
90
this_stack->add_pane(*this);
91
});
92
action_group->add_action(move_action);
93
move_menu->append(this_stack->name, "win." + action_name);
94
}
95
96
// Add move submenu
97
header_menu->append_submenu(_("Move"), move_menu);
98
99
// Switch to traditional (nested) submenus, not sliding
100
auto popover_menu = Gtk::make_managed<Gtk::PopoverMenu>(header_menu, Gtk::PopoverMenu::Flags::NESTED);
101
popover_menu->set_has_arrow(false);
102
header_menu_button->set_popover(*popover_menu);
103
104
// TODO: Add a context menu as well
105
106
header->pack_end(*header_menu_button);
107
108
this->prepend(*header);
109
this->child = &child;
110
this->append(child);
111
}
112
113
Gtk::Stack *DockablePane::get_stack() const {
114
return stack;
115
}
116
117
void DockablePane::redock(DockStack *stack) {
118
if(this->window != nullptr) {
119
this->window->hide();
120
this->window->unset_titlebar();
121
this->window->set_decorated(false);
122
this->header->get_style_context()->remove_class("titlebar");
123
this->prepend(*this->header);
124
this->window->unset_child();
125
this->window->close();
126
} else if(this->stack == this->get_parent()) {
127
this->stack->remove(*this);
128
}
129
this->stack = stack;
130
this->last_stack = stack;
131
this->stack->add(*this, this->get_identifier());
132
if(this->window != nullptr) {
133
this->window->destroy();
134
delete this->window;
135
this->window = nullptr;
136
auto action = std::dynamic_pointer_cast<Gio::SimpleAction>(this->action_group->lookup_action("pop_out"));
137
action->set_enabled(true);
138
this->header->get_style_context()->remove_class("gpanthera-dock-titlebar-popout");
139
}
140
}
141
142
void DockablePane::pop_out() {
143
if(this->stack != nullptr) {
144
this->stack->remove(*this);
145
this->stack = nullptr;
146
}
147
148
if(this->window == nullptr) {
149
this->remove(*this->header);
150
this->window = new DockWindow(this);
151
this->window->set_titlebar(*this->header);
152
this->window->set_child(*this);
153
this->window->set_decorated(true);
154
auto action = std::dynamic_pointer_cast<Gio::SimpleAction>(this->action_group->lookup_action("pop_out"));
155
action->set_enabled(false);
156
this->header->get_style_context()->add_class("gpanthera-dock-titlebar-popout");
157
}
158
this->window->present();
159
}
160
161
Glib::ustring DockablePane::get_identifier() const {
162
return name;
163
}
164
165
Gtk::Image *DockablePane::get_icon() const {
166
return icon;
167
}
168
169
Gtk::Widget *DockablePane::get_child() const {
170
return child;
171
}
172
173
Gtk::Label *DockablePane::get_label() {
174
return &label;
175
}
176
177
LayoutManager::LayoutManager() : Glib::ObjectBase("LayoutManager") {
178
}
179
180
void LayoutManager::add_pane(DockablePane *pane) {
181
panes.push_back(pane);
182
}
183
184
void LayoutManager::add_stack(DockStack *stack) {
185
stacks.push_back(stack);
186
}
187
188
DockStack::DockStack(std::shared_ptr<LayoutManager> layout, const Glib::ustring &name) : Gtk::Stack(), layout(layout), name(name) {
189
auto *empty_child = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
190
add(*empty_child, "empty");
191
// Add the stack to a layout manager
192
this->layout->add_stack(this);
193
194
// Hide the stack when the empty child is visible
195
this->property_visible_child_name().signal_changed().connect([this]() {
196
if(this->get_visible_child_name() == "empty") {
197
this->hide();
198
} else {
199
this->show();
200
}
201
});
202
203
this->set_visible_child("empty");
204
this->hide();
205
}
206
207
DockWindow::DockWindow(DockablePane *pane) {
208
this->pane = pane;
209
this->set_child(*pane);
210
// Attempting to close the window should redock the pane so it doesn't vanish
211
this->signal_close_request().connect([this]() {
212
this->pane->redock(this->pane->last_stack);
213
return true;
214
}, false);
215
}
216
217
DockStackSwitcher::DockStackSwitcher(DockStack *stack, Gtk::Orientation orientation) : Gtk::Box(orientation), stack(stack) {
218
auto update_callback = [this](Gtk::Widget*) {
219
this->update_buttons();
220
};
221
stack->signal_child_added.connect(update_callback);
222
stack->signal_child_removed.connect(update_callback);
223
this->get_style_context()->add_class("gpanthera-dock-switcher");
224
}
225
226
DockStackSwitcher::~DockStackSwitcher() {
227
add_handler.disconnect();
228
remove_handler.disconnect();
229
}
230
231
DockButton::DockButton(DockablePane *pane) : Gtk::Button(), pane(pane) {
232
if(pane->get_icon()) {
233
this->set_child(*copy_image(pane->get_icon()));
234
}
235
this->set_tooltip_text(pane->get_label()->get_text());
236
this->set_halign(Gtk::Align::CENTER);
237
this->set_valign(Gtk::Align::CENTER);
238
this->get_style_context()->add_class("toggle");
239
this->get_style_context()->add_class("gpanthera-dock-button");
240
active_style_handler = this->pane->get_stack()->property_visible_child_name().signal_changed().connect([this]() {
241
if(this->pane->get_stack()->get_visible_child_name() == this->pane->get_identifier()) {
242
this->add_css_class("checked");
243
this->add_css_class("gpanthera-dock-button-active");
244
} else {
245
this->remove_css_class("checked");
246
this->remove_css_class("gpanthera-dock-button-active");
247
}
248
});
249
}
250
251
DockButton::~DockButton() {
252
active_style_handler.disconnect();
253
}
254
255
void DockStackSwitcher::update_buttons() {
256
auto old_buttons = collect_children(*this);
257
for(auto *button : old_buttons) {
258
remove(*button);
259
}
260
for(auto *widget = stack->get_first_child(); widget; widget = widget->get_next_sibling()) {
261
if(auto pane = dynamic_cast<DockablePane*>(widget)) {
262
auto *button = Gtk::make_managed<DockButton>(pane);
263
button->signal_clicked().connect([this, pane]() {
264
if(stack->get_visible_child_name() == pane->get_identifier()) {
265
stack->set_visible_child("empty");
266
} else {
267
stack->set_visible_child(pane->get_identifier());
268
}
269
});
270
if(pane->get_identifier() != Glib::ustring("empty")) {
271
append(*button);
272
}
273
}
274
}
275
}
276
277
void DockStack::add_pane(DockablePane &child) {
278
child.redock(this);
279
}
280
281
void DockStack::add(Gtk::Widget &child, const Glib::ustring &name) {
282
Gtk::Stack::add(child, name);
283
signal_child_added.emit(&child);
284
}
285
286
void DockStack::remove(Gtk::Widget &child) {
287
Gtk::Stack::remove(child);
288
signal_child_removed.emit(&child);
289
}
290
} // namespace gPanthera
291