gpanthera.cc
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 9namespace gPanthera { 10std::vector<Gtk::Widget*> collect_children(Gtk::Widget &widget) { 11std::vector<Gtk::Widget*> children; 12for(auto *child = widget.get_first_child(); child; child = child->get_next_sibling()) { 13children.push_back(child); 14} 15return children; 16} 17 18void init() { 19// Set up gettext configuration 20setlocale(LC_ALL, ""); 21 22bindtextdomain("gpanthera", "./locales"); 23textdomain("gpanthera"); 24} 25 26DockablePane::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) 27: Gtk::Box(Gtk::Orientation::VERTICAL, 0), name(name) { 28if(icon) { 29this->icon = icon; 30} 31if(stack) { 32this->stack = stack; 33} 34this->layout = std::move(layout); 35this->label.set_text(label); 36header = std::make_unique<Gtk::HeaderBar>(); 37header->set_show_title_buttons(false); 38if(custom_header) { 39header->set_title_widget(*custom_header); 40} else { 41header->set_title_widget(this->label); 42} 43auto header_menu_button = Gtk::make_managed<Gtk::MenuButton>(); 44auto header_menu = Gio::Menu::create(); 45header_menu_button->set_direction(Gtk::ArrowType::NONE); 46 47// Pane menu 48auto action_group = Gio::SimpleActionGroup::create(); 49header_menu_button->insert_action_group("win", action_group); 50 51// Close action 52auto close_action = Gio::SimpleAction::create("close"); 53close_action->signal_activate().connect([this](const Glib::VariantBase&) { 54if(this->stack) { 55this->stack->set_visible_child("empty"); 56} 57}); 58action_group->add_action(close_action); 59header_menu->append(_("Close"), "win.close"); 60 61// Pop out action 62auto pop_out_action = Gio::SimpleAction::create("pop_out"); 63pop_out_action->signal_activate().connect([this](const Glib::VariantBase&) { 64if(this->stack) { 65this->pop_out(); 66} 67}); 68action_group->add_action(pop_out_action); 69header_menu->append(_("Pop out"), "win.pop_out"); 70 71// Switch to traditional (nested) submenus, not sliding 72auto popover_menu = Gtk::make_managed<Gtk::PopoverMenu>(header_menu, Gtk::PopoverMenu::Flags::NESTED); 73header_menu_button->set_popover(*popover_menu); 74 75// Move menu 76auto move_menu = Gio::Menu::create(); 77for(auto &this_stack : this->layout->stacks) { 78auto action_name = "move_" + this_stack->name; 79auto move_action = Gio::SimpleAction::create(action_name); 80move_action->signal_activate().connect([this, this_stack](const Glib::VariantBase&) { 81this_stack->add_pane(*this); 82}); 83action_group->add_action(move_action); 84move_menu->append(this_stack->name, "win." + action_name); 85} 86 87// Add move submenu 88header_menu->append_submenu(_("Move"), move_menu); 89 90header->pack_end(*header_menu_button); 91 92this->prepend(*header); 93this->child = &child; 94this->append(child); 95} 96 97void DockablePane::redock(DockStack *stack) { 98if(this->window != nullptr) { 99this->window->hide(); 100this->window->unset_titlebar(); 101this->window->set_decorated(false); 102this->header->get_style_context()->remove_class("titlebar"); 103this->prepend(*this->header); 104this->window->unset_child(); 105this->window->close(); 106} else if(this->stack == this->get_parent()) { 107this->stack->remove(*this); 108} 109this->stack = stack; 110this->stack->add(*this, this->get_identifier()); 111if(this->window != nullptr) { 112this->window->destroy(); 113delete this->window; 114this->window = nullptr; 115} 116} 117 118void DockablePane::pop_out() { 119if(this->stack != nullptr) { 120this->stack->remove(*this); 121this->stack = nullptr; 122} 123 124if(this->window == nullptr) { 125this->remove(*this->header); 126this->window = new DockWindow(this); 127this->window->set_titlebar(*this->header); 128this->window->set_child(*this); 129this->window->set_decorated(true); 130this->window->present(); 131} 132} 133 134Glib::ustring DockablePane::get_identifier() const { 135return name; 136} 137 138Gtk::Image *DockablePane::get_icon() const { 139return icon; 140} 141 142Gtk::Widget *DockablePane::get_child() const { 143return child; 144} 145 146Gtk::Label *DockablePane::get_label() { 147return &label; 148} 149 150LayoutManager::LayoutManager() : Glib::ObjectBase("LayoutManager") { 151} 152 153void LayoutManager::add_pane(DockablePane *pane) { 154panes.push_back(pane); 155} 156 157void LayoutManager::add_stack(DockStack *stack) { 158stacks.push_back(stack); 159} 160 161DockStack::DockStack(std::shared_ptr<LayoutManager> layout, const Glib::ustring &name) : Gtk::Stack(), layout(layout), name(name) { 162auto *empty_child = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0); 163add(*empty_child, "empty"); 164// Add the stack to a layout manager 165this->layout->add_stack(this); 166} 167 168DockWindow::DockWindow(DockablePane *pane) { 169this->pane = pane; 170this->set_child(*pane); 171} 172 173DockStackSwitcher::DockStackSwitcher(DockStack *stack) : Gtk::Box(Gtk::Orientation::HORIZONTAL), stack(stack) { 174auto update_callback = [this](Gtk::Widget*) { 175this->update_buttons(); 176}; 177stack->signal_child_added.connect(update_callback); 178stack->signal_child_removed.connect(update_callback); 179} 180 181DockStackSwitcher::~DockStackSwitcher() { 182add_handler.disconnect(); 183remove_handler.disconnect(); 184} 185 186DockButton::DockButton(DockablePane *pane) : Gtk::Button(), pane(pane) { 187if(pane->get_icon()) { 188auto new_image = Gtk::make_managed<Gtk::Image>(this->pane->get_icon()->get_paintable()); 189this->set_child(*new_image); 190} 191this->set_tooltip_text(pane->get_label()->get_text()); 192this->set_halign(Gtk::Align::CENTER); 193} 194 195void DockStackSwitcher::update_buttons() { 196auto old_buttons = collect_children(*this); 197for(auto *button : old_buttons) { 198remove(*button); 199} 200for(auto *widget = stack->get_first_child(); widget; widget = widget->get_next_sibling()) { 201if(auto pane = dynamic_cast<DockablePane*>(widget)) { 202auto *button = Gtk::make_managed<DockButton>(pane); 203button->signal_clicked().connect([this, pane]() { 204if(stack->get_visible_child_name() == pane->get_identifier()) { 205stack->set_visible_child("empty"); 206} else { 207stack->set_visible_child(pane->get_identifier()); 208} 209}); 210if(pane->get_identifier() != Glib::ustring("empty")) { 211append(*button); 212} 213} 214} 215} 216 217void DockStack::add_pane(DockablePane &child) { 218child.redock(this); 219} 220 221void DockStack::add(Gtk::Widget &child, const Glib::ustring &name) { 222Gtk::Stack::add(child, name); 223signal_child_added.emit(&child); 224} 225 226void DockStack::remove(Gtk::Widget &child) { 227Gtk::Stack::remove(child); 228signal_child_removed.emit(&child); 229} 230} // namespace gPanthera 231