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.54 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
auto 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
}
132
}
133
134
void DockablePane::pop_out() {
135
if(this->stack != nullptr) {
136
this->stack->remove(*this);
137
this->stack = nullptr;
138
}
139
140
if(this->window == nullptr) {
141
this->remove(*this->header);
142
this->window = new DockWindow(this);
143
this->window->set_titlebar(*this->header);
144
this->window->set_child(*this);
145
this->window->set_decorated(true);
146
this->window->present();
147
}
148
}
149
150
Glib::ustring DockablePane::get_identifier() const {
151
return name;
152
}
153
154
Gtk::Image *DockablePane::get_icon() const {
155
return icon;
156
}
157
158
Gtk::Widget *DockablePane::get_child() const {
159
return child;
160
}
161
162
Gtk::Label *DockablePane::get_label() {
163
return &label;
164
}
165
166
LayoutManager::LayoutManager() : Glib::ObjectBase("LayoutManager") {
167
}
168
169
void LayoutManager::add_pane(DockablePane *pane) {
170
panes.push_back(pane);
171
}
172
173
void LayoutManager::add_stack(DockStack *stack) {
174
stacks.push_back(stack);
175
}
176
177
DockStack::DockStack(std::shared_ptr<LayoutManager> layout, const Glib::ustring &name) : Gtk::Stack(), layout(layout), name(name) {
178
auto *empty_child = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
179
add(*empty_child, "empty");
180
// Add the stack to a layout manager
181
this->layout->add_stack(this);
182
}
183
184
DockWindow::DockWindow(DockablePane *pane) {
185
this->pane = pane;
186
this->set_child(*pane);
187
// Can redock, but not disappear
188
this->set_deletable(false);
189
190
this->signal_close_request().connect([this]() {
191
this->pane->redock(this->pane->last_stack);
192
return true;
193
}, false);
194
}
195
196
DockStackSwitcher::DockStackSwitcher(DockStack *stack, Gtk::Orientation orientation) : Gtk::Box(orientation), stack(stack) {
197
auto update_callback = [this](Gtk::Widget*) {
198
this->update_buttons();
199
};
200
stack->signal_child_added.connect(update_callback);
201
stack->signal_child_removed.connect(update_callback);
202
}
203
204
DockStackSwitcher::~DockStackSwitcher() {
205
add_handler.disconnect();
206
remove_handler.disconnect();
207
}
208
209
DockButton::DockButton(DockablePane *pane) : Gtk::Button(), pane(pane) {
210
if(pane->get_icon()) {
211
this->set_child(*copy_image(pane->get_icon()));
212
}
213
this->set_tooltip_text(pane->get_label()->get_text());
214
this->set_halign(Gtk::Align::CENTER);
215
}
216
217
void DockStackSwitcher::update_buttons() {
218
auto old_buttons = collect_children(*this);
219
for(auto *button : old_buttons) {
220
remove(*button);
221
}
222
for(auto *widget = stack->get_first_child(); widget; widget = widget->get_next_sibling()) {
223
if(auto pane = dynamic_cast<DockablePane*>(widget)) {
224
auto *button = Gtk::make_managed<DockButton>(pane);
225
button->signal_clicked().connect([this, pane]() {
226
if(stack->get_visible_child_name() == pane->get_identifier()) {
227
stack->set_visible_child("empty");
228
} else {
229
stack->set_visible_child(pane->get_identifier());
230
}
231
});
232
if(pane->get_identifier() != Glib::ustring("empty")) {
233
append(*button);
234
}
235
}
236
}
237
}
238
239
void DockStack::add_pane(DockablePane &child) {
240
child.redock(this);
241
}
242
243
void DockStack::add(Gtk::Widget &child, const Glib::ustring &name) {
244
Gtk::Stack::add(child, name);
245
signal_child_added.emit(&child);
246
}
247
248
void DockStack::remove(Gtk::Widget &child) {
249
Gtk::Stack::remove(child);
250
signal_child_removed.emit(&child);
251
}
252
} // namespace gPanthera
253