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