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