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.17 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
void DockablePane::redock(DockStack *stack) {
114
if(this->window != nullptr) {
115
this->window->hide();
116
this->window->unset_titlebar();
117
this->window->set_decorated(false);
118
this->header->get_style_context()->remove_class("titlebar");
119
this->prepend(*this->header);
120
this->window->unset_child();
121
this->window->close();
122
} else if(this->stack == this->get_parent()) {
123
this->stack->remove(*this);
124
}
125
this->stack = stack;
126
this->last_stack = stack;
127
this->stack->add(*this, this->get_identifier());
128
if(this->window != nullptr) {
129
this->window->destroy();
130
delete this->window;
131
this->window = nullptr;
132
auto action = std::dynamic_pointer_cast<Gio::SimpleAction>(this->action_group->lookup_action("pop_out"));
133
action->set_enabled(true);
134
this->header->get_style_context()->remove_class("gpanthera-dock-titlebar-popout");
135
}
136
}
137
138
void DockablePane::pop_out() {
139
if(this->stack != nullptr) {
140
this->stack->remove(*this);
141
this->stack = nullptr;
142
}
143
144
if(this->window == nullptr) {
145
this->remove(*this->header);
146
this->window = new DockWindow(this);
147
this->window->set_titlebar(*this->header);
148
this->window->set_child(*this);
149
this->window->set_decorated(true);
150
auto action = std::dynamic_pointer_cast<Gio::SimpleAction>(this->action_group->lookup_action("pop_out"));
151
action->set_enabled(false);
152
this->header->get_style_context()->add_class("gpanthera-dock-titlebar-popout");
153
}
154
this->window->present();
155
}
156
157
Glib::ustring DockablePane::get_identifier() const {
158
return name;
159
}
160
161
Gtk::Image *DockablePane::get_icon() const {
162
return icon;
163
}
164
165
Gtk::Widget *DockablePane::get_child() const {
166
return child;
167
}
168
169
Gtk::Label *DockablePane::get_label() {
170
return &label;
171
}
172
173
LayoutManager::LayoutManager() : Glib::ObjectBase("LayoutManager") {
174
}
175
176
void LayoutManager::add_pane(DockablePane *pane) {
177
panes.push_back(pane);
178
}
179
180
void LayoutManager::add_stack(DockStack *stack) {
181
stacks.push_back(stack);
182
}
183
184
DockStack::DockStack(std::shared_ptr<LayoutManager> layout, const Glib::ustring &name) : Gtk::Stack(), layout(layout), name(name) {
185
auto *empty_child = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
186
add(*empty_child, "empty");
187
// Add the stack to a layout manager
188
this->layout->add_stack(this);
189
190
// Hide the stack when the empty child is visible
191
this->property_visible_child_name().signal_changed().connect([this]() {
192
if(this->get_visible_child_name() == "empty") {
193
this->hide();
194
} else {
195
this->show();
196
}
197
});
198
199
this->set_visible_child("empty");
200
this->hide();
201
}
202
203
DockWindow::DockWindow(DockablePane *pane) {
204
this->pane = pane;
205
this->set_child(*pane);
206
// Attempting to close the window should redock the pane so it doesn't vanish
207
this->signal_close_request().connect([this]() {
208
this->pane->redock(this->pane->last_stack);
209
return true;
210
}, false);
211
}
212
213
DockStackSwitcher::DockStackSwitcher(DockStack *stack, Gtk::Orientation orientation) : Gtk::Box(orientation), stack(stack) {
214
auto update_callback = [this](Gtk::Widget*) {
215
this->update_buttons();
216
};
217
stack->signal_child_added.connect(update_callback);
218
stack->signal_child_removed.connect(update_callback);
219
}
220
221
DockStackSwitcher::~DockStackSwitcher() {
222
add_handler.disconnect();
223
remove_handler.disconnect();
224
}
225
226
DockButton::DockButton(DockablePane *pane) : Gtk::Button(), pane(pane) {
227
if(pane->get_icon()) {
228
this->set_child(*copy_image(pane->get_icon()));
229
}
230
this->set_tooltip_text(pane->get_label()->get_text());
231
this->set_halign(Gtk::Align::CENTER);
232
this->set_valign(Gtk::Align::CENTER);
233
this->get_style_context()->add_class("toggle");
234
this->get_style_context()->add_class("gpanthera-dock-button");
235
this->pane->last_stack->property_visible_child_name().signal_changed().connect([this]() {
236
if(this->pane->last_stack->get_visible_child_name() == this->pane->get_identifier()) {
237
this->get_style_context()->add_class("checked");
238
this->get_style_context()->add_class("gpanthera-dock-button-active");
239
} else {
240
this->get_style_context()->remove_class("checked");
241
this->get_style_context()->remove_class("gpanthera-dock-button-active");
242
}
243
});
244
}
245
246
void DockStackSwitcher::update_buttons() {
247
auto old_buttons = collect_children(*this);
248
for(auto *button : old_buttons) {
249
remove(*button);
250
}
251
for(auto *widget = stack->get_first_child(); widget; widget = widget->get_next_sibling()) {
252
if(auto pane = dynamic_cast<DockablePane*>(widget)) {
253
auto *button = Gtk::make_managed<DockButton>(pane);
254
button->signal_clicked().connect([this, pane]() {
255
if(stack->get_visible_child_name() == pane->get_identifier()) {
256
stack->set_visible_child("empty");
257
} else {
258
stack->set_visible_child(pane->get_identifier());
259
}
260
});
261
if(pane->get_identifier() != Glib::ustring("empty")) {
262
append(*button);
263
}
264
}
265
}
266
}
267
268
void DockStack::add_pane(DockablePane &child) {
269
child.redock(this);
270
}
271
272
void DockStack::add(Gtk::Widget &child, const Glib::ustring &name) {
273
Gtk::Stack::add(child, name);
274
signal_child_added.emit(&child);
275
}
276
277
void DockStack::remove(Gtk::Widget &child) {
278
Gtk::Stack::remove(child);
279
signal_child_removed.emit(&child);
280
}
281
} // namespace gPanthera
282