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++ • 8.86 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
auto header_menu_button = Gtk::make_managed<Gtk::MenuButton>();
56
auto header_menu = Gio::Menu::create();
57
header_menu_button->set_direction(Gtk::ArrowType::NONE);
58
59
// Pane menu
60
this->action_group = Gio::SimpleActionGroup::create();
61
header_menu_button->insert_action_group("win", action_group);
62
63
// Close action
64
auto close_action = Gio::SimpleAction::create("close");
65
close_action->signal_activate().connect([this](const Glib::VariantBase&) {
66
if(this->stack) {
67
this->stack->set_visible_child("empty");
68
}
69
});
70
action_group->add_action(close_action);
71
header_menu->append(_("Close"), "win.close");
72
73
// Pop out action
74
auto pop_out_action = Gio::SimpleAction::create("pop_out");
75
pop_out_action->signal_activate().connect([this](const Glib::VariantBase&) {
76
if(this->stack) {
77
this->pop_out();
78
}
79
});
80
action_group->add_action(pop_out_action);
81
header_menu->append(_("Pop out"), "win.pop_out");
82
83
// Move menu
84
auto move_menu = Gio::Menu::create();
85
for(auto &this_stack : this->layout->stacks) {
86
auto action_name = "move_" + this_stack->name;
87
auto move_action = Gio::SimpleAction::create(action_name);
88
move_action->signal_activate().connect([this, this_stack](const Glib::VariantBase&) {
89
this_stack->add_pane(*this);
90
});
91
action_group->add_action(move_action);
92
move_menu->append(this_stack->name, "win." + action_name);
93
}
94
95
// Add move submenu
96
header_menu->append_submenu(_("Move"), move_menu);
97
98
// Switch to traditional (nested) submenus, not sliding
99
auto popover_menu = Gtk::make_managed<Gtk::PopoverMenu>(header_menu, Gtk::PopoverMenu::Flags::NESTED);
100
popover_menu->set_has_arrow(false);
101
header_menu_button->set_popover(*popover_menu);
102
103
// TODO: Add a context menu as well
104
105
header->pack_end(*header_menu_button);
106
107
this->prepend(*header);
108
this->child = &child;
109
this->append(child);
110
}
111
112
void DockablePane::redock(DockStack *stack) {
113
if(this->window != nullptr) {
114
this->window->hide();
115
this->window->unset_titlebar();
116
this->window->set_decorated(false);
117
this->header->get_style_context()->remove_class("titlebar");
118
this->prepend(*this->header);
119
this->window->unset_child();
120
this->window->close();
121
} else if(this->stack == this->get_parent()) {
122
this->stack->remove(*this);
123
}
124
this->stack = stack;
125
this->last_stack = stack;
126
this->stack->add(*this, this->get_identifier());
127
if(this->window != nullptr) {
128
this->window->destroy();
129
delete this->window;
130
this->window = nullptr;
131
auto action = std::dynamic_pointer_cast<Gio::SimpleAction>(this->action_group->lookup_action("pop_out"));
132
action->set_enabled(true);
133
}
134
}
135
136
void DockablePane::pop_out() {
137
if(this->stack != nullptr) {
138
this->stack->remove(*this);
139
this->stack = nullptr;
140
}
141
142
if(this->window == nullptr) {
143
this->remove(*this->header);
144
this->window = new DockWindow(this);
145
this->window->set_titlebar(*this->header);
146
this->window->set_child(*this);
147
this->window->set_decorated(true);
148
auto action = std::dynamic_pointer_cast<Gio::SimpleAction>(this->action_group->lookup_action("pop_out"));
149
action->set_enabled(false);
150
}
151
this->window->present();
152
}
153
154
Glib::ustring DockablePane::get_identifier() const {
155
return name;
156
}
157
158
Gtk::Image *DockablePane::get_icon() const {
159
return icon;
160
}
161
162
Gtk::Widget *DockablePane::get_child() const {
163
return child;
164
}
165
166
Gtk::Label *DockablePane::get_label() {
167
return &label;
168
}
169
170
LayoutManager::LayoutManager() : Glib::ObjectBase("LayoutManager") {
171
}
172
173
void LayoutManager::add_pane(DockablePane *pane) {
174
panes.push_back(pane);
175
}
176
177
void LayoutManager::add_stack(DockStack *stack) {
178
stacks.push_back(stack);
179
}
180
181
DockStack::DockStack(std::shared_ptr<LayoutManager> layout, const Glib::ustring &name) : Gtk::Stack(), layout(layout), name(name) {
182
auto *empty_child = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
183
add(*empty_child, "empty");
184
// Add the stack to a layout manager
185
this->layout->add_stack(this);
186
}
187
188
DockWindow::DockWindow(DockablePane *pane) {
189
this->pane = pane;
190
this->set_child(*pane);
191
// Attempting to close the window should redock the pane so it doesn't vanish
192
this->signal_close_request().connect([this]() {
193
this->pane->redock(this->pane->last_stack);
194
return true;
195
}, false);
196
}
197
198
DockStackSwitcher::DockStackSwitcher(DockStack *stack, Gtk::Orientation orientation) : Gtk::Box(orientation), stack(stack) {
199
auto update_callback = [this](Gtk::Widget*) {
200
this->update_buttons();
201
};
202
stack->signal_child_added.connect(update_callback);
203
stack->signal_child_removed.connect(update_callback);
204
}
205
206
DockStackSwitcher::~DockStackSwitcher() {
207
add_handler.disconnect();
208
remove_handler.disconnect();
209
}
210
211
DockButton::DockButton(DockablePane *pane) : Gtk::Button(), pane(pane) {
212
if(pane->get_icon()) {
213
this->set_child(*copy_image(pane->get_icon()));
214
}
215
this->set_tooltip_text(pane->get_label()->get_text());
216
this->set_halign(Gtk::Align::CENTER);
217
}
218
219
void DockStackSwitcher::update_buttons() {
220
auto old_buttons = collect_children(*this);
221
for(auto *button : old_buttons) {
222
remove(*button);
223
}
224
for(auto *widget = stack->get_first_child(); widget; widget = widget->get_next_sibling()) {
225
if(auto pane = dynamic_cast<DockablePane*>(widget)) {
226
auto *button = Gtk::make_managed<DockButton>(pane);
227
button->signal_clicked().connect([this, pane]() {
228
if(stack->get_visible_child_name() == pane->get_identifier()) {
229
stack->set_visible_child("empty");
230
} else {
231
stack->set_visible_child(pane->get_identifier());
232
}
233
});
234
if(pane->get_identifier() != Glib::ustring("empty")) {
235
append(*button);
236
}
237
}
238
}
239
}
240
241
void DockStack::add_pane(DockablePane &child) {
242
child.redock(this);
243
}
244
245
void DockStack::add(Gtk::Widget &child, const Glib::ustring &name) {
246
Gtk::Stack::add(child, name);
247
signal_child_added.emit(&child);
248
}
249
250
void DockStack::remove(Gtk::Widget &child) {
251
Gtk::Stack::remove(child);
252
signal_child_removed.emit(&child);
253
}
254
} // namespace gPanthera
255