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++ • 9.8 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
191
DockWindow::DockWindow(DockablePane *pane) {
192
this->pane = pane;
193
this->set_child(*pane);
194
// Attempting to close the window should redock the pane so it doesn't vanish
195
this->signal_close_request().connect([this]() {
196
this->pane->redock(this->pane->last_stack);
197
return true;
198
}, false);
199
}
200
201
DockStackSwitcher::DockStackSwitcher(DockStack *stack, Gtk::Orientation orientation) : Gtk::Box(orientation), stack(stack) {
202
auto update_callback = [this](Gtk::Widget*) {
203
this->update_buttons();
204
};
205
stack->signal_child_added.connect(update_callback);
206
stack->signal_child_removed.connect(update_callback);
207
}
208
209
DockStackSwitcher::~DockStackSwitcher() {
210
add_handler.disconnect();
211
remove_handler.disconnect();
212
}
213
214
DockButton::DockButton(DockablePane *pane) : Gtk::Button(), pane(pane) {
215
if(pane->get_icon()) {
216
this->set_child(*copy_image(pane->get_icon()));
217
}
218
this->set_tooltip_text(pane->get_label()->get_text());
219
this->set_halign(Gtk::Align::CENTER);
220
this->set_valign(Gtk::Align::CENTER);
221
this->get_style_context()->add_class("toggle");
222
this->get_style_context()->add_class("gpanthera-dock-button");
223
this->pane->last_stack->property_visible_child_name().signal_changed().connect([this]() {
224
if(this->pane->last_stack->get_visible_child_name() == this->pane->get_identifier()) {
225
this->get_style_context()->add_class("checked");
226
this->get_style_context()->add_class("gpanthera-dock-button-active");
227
} else {
228
this->get_style_context()->remove_class("checked");
229
this->get_style_context()->remove_class("gpanthera-dock-button-active");
230
}
231
});
232
}
233
234
void DockStackSwitcher::update_buttons() {
235
auto old_buttons = collect_children(*this);
236
for(auto *button : old_buttons) {
237
remove(*button);
238
}
239
for(auto *widget = stack->get_first_child(); widget; widget = widget->get_next_sibling()) {
240
if(auto pane = dynamic_cast<DockablePane*>(widget)) {
241
auto *button = Gtk::make_managed<DockButton>(pane);
242
button->signal_clicked().connect([this, pane]() {
243
if(stack->get_visible_child_name() == pane->get_identifier()) {
244
stack->set_visible_child("empty");
245
} else {
246
stack->set_visible_child(pane->get_identifier());
247
}
248
});
249
if(pane->get_identifier() != Glib::ustring("empty")) {
250
append(*button);
251
}
252
}
253
}
254
}
255
256
void DockStack::add_pane(DockablePane &child) {
257
child.redock(this);
258
}
259
260
void DockStack::add(Gtk::Widget &child, const Glib::ustring &name) {
261
Gtk::Stack::add(child, name);
262
signal_child_added.emit(&child);
263
}
264
265
void DockStack::remove(Gtk::Widget &child) {
266
Gtk::Stack::remove(child);
267
signal_child_removed.emit(&child);
268
}
269
} // namespace gPanthera
270