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