roundabout,
created on Sunday, 2 March 2025, 09:59:16 (1740909556),
received on Sunday, 2 March 2025, 09:59:19 (1740909559)
Author identity: vlad <vlad.muntoiu@gmail.com>
6a2064fec41439e22696702e190838a03aa42999
CMakeLists.txt
@@ -9,7 +9,7 @@ add_executable(panthera-www panthera-www.cc)
target_link_libraries(panthera-www gpanthera) find_package(PkgConfig REQUIRED) pkg_check_modules(GTKMM REQUIRED gtkmm-3.0)pkg_check_modules(GTKMM REQUIRED gtkmm-4.0)include_directories(${GTKMM_INCLUDE_DIRS}) link_directories(${GTKMM_LIBRARY_DIRS})
gpanthera.cc
@@ -1,21 +1,29 @@
#include "gpanthera.hh" #include <iostream> #include <utility> #include <gtk/gtk.h>#include <libintl.h> #include <locale.h> #include <filesystem> #define _(STRING) gettext(STRING) namespace gPanthera { std::vector<Gtk::Widget*> collect_children(Gtk::Widget &widget) { std::vector<Gtk::Widget*> children; for(auto *child = widget.get_first_child(); child; child = child->get_next_sibling()) { children.push_back(child); } return children; }void init() { setlocale(LC_ALL, "ro_RO.utf8"); bindtextdomain("gpanthera", "./locales"); textdomain("gpanthera"); } 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) : Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0), name(name) {: Gtk::Box(Gtk::Orientation::VERTICAL, 0), name(name) {if(icon) { this->icon = icon; }
@@ -25,68 +33,83 @@ namespace gPanthera {
this->layout = std::move(layout); this->label.set_text(label); header = std::make_unique<Gtk::HeaderBar>(); header->set_show_close_button(false);header->set_show_title_buttons(false);if(custom_header) { header->set_custom_title(*custom_header);header->set_title_widget(*custom_header);} else { header->set_custom_title(this->label);header->set_title_widget(this->label);} auto header_menu_button = Gtk::make_managed<Gtk::MenuButton>(); auto header_menu = Gtk::make_managed<Gtk::Menu>();header_menu_button->set_direction(Gtk::ARROW_NONE);header_menu_button->set_popup(*header_menu);auto close_menu_item = Gtk::make_managed<Gtk::MenuItem>(_("Close"));auto pop_out_menu_item = Gtk::make_managed<Gtk::MenuItem>(_("Pop out"));auto move_menu = Gtk::make_managed<Gtk::Menu>();close_menu_item->signal_activate().connect([this]() {auto header_menu = Gio::Menu::create(); header_menu_button->set_direction(Gtk::ArrowType::NONE); header_menu_button->set_menu_model(header_menu); auto action_group = Gio::SimpleActionGroup::create(); header_menu_button->insert_action_group("win", action_group); // Close action auto close_action = Gio::SimpleAction::create("close"); close_action->signal_activate().connect([this](const Glib::VariantBase&) {if(this->stack) { this->stack->set_visible_child("empty"); } }); pop_out_menu_item->signal_activate().connect([this]() {action_group->add_action(close_action); header_menu->append(_("Close"), "win.close"); // Pop out action auto pop_out_action = Gio::SimpleAction::create("pop_out"); pop_out_action->signal_activate().connect([this](const Glib::VariantBase&) {if(this->stack) { this->pop_out(); } }); for(auto &this_stack: this->layout->stacks) {auto menu_item = Gtk::make_managed<Gtk::MenuItem>(this_stack->name);menu_item->signal_activate().connect([this, this_stack]() {action_group->add_action(pop_out_action); header_menu->append(_("Pop out"), "win.pop_out"); // Move menu auto move_menu = Gio::Menu::create(); for(auto &this_stack : this->layout->stacks) { auto action_name = "move_" + this_stack->name; auto move_action = Gio::SimpleAction::create(action_name); move_action->signal_activate().connect([this, this_stack](const Glib::VariantBase&) {this_stack->add_pane(*this); }); move_menu->append(*menu_item);action_group->add_action(move_action); move_menu->append(this_stack->name, "win." + action_name);} auto move_menu_item = Gtk::make_managed<Gtk::MenuItem>(_("Move"));move_menu_item->set_submenu(*move_menu);header_menu->append(*close_menu_item);header_menu->append(*pop_out_menu_item);header_menu->append(*move_menu_item);header_menu->show_all();// Add move section auto move_menu_item = Gio::Menu::create(); move_menu_item->append_section(_("Move"), move_menu); header_menu->append_section({}, move_menu_item);header->pack_end(*header_menu_button); this->pack_start(*header, Gtk::PACK_SHRINK);this->prepend(*header);this->child = &child; this->pack_end(child, Gtk::PACK_EXPAND_WIDGET);show_all_children();this->append(child);} void DockablePane::redock(DockStack *stack) { if(this->window != nullptr) { this->window->hide(); gtk_window_set_titlebar(GTK_WINDOW(this->window->gobj()), nullptr);this->header->get_style_context()->remove_class("titlebar");this->header->unset_background_color();this->window->remove();this->window->unset_titlebar();this->window->set_decorated(false); this->header->get_style_context()->remove_class("titlebar"); this->prepend(*this->header); this->window->unset_child();this->window->close(); delete this->window;this->window = nullptr;this->pack_start(*this->header, Gtk::PACK_SHRINK);this->header->show_all();} else if(this->stack == this->get_parent()) { this->stack->remove(*this); } this->stack = stack; this->stack->add(*this, this->get_identifier()); this->show_all();if(this->window != nullptr) { this->window->destroy(); delete this->window; this->window = nullptr; }} void DockablePane::pop_out() {
@@ -99,7 +122,9 @@ namespace gPanthera {
this->remove(*this->header); this->window = new DockWindow(this); this->window->set_titlebar(*this->header); this->window->show_all();this->window->set_child(*this); this->window->set_decorated(true); this->window->present();} }
@@ -131,7 +156,7 @@ namespace gPanthera {
} DockStack::DockStack(std::shared_ptr<LayoutManager> layout, const Glib::ustring &name) : Gtk::Stack(), layout(layout), name(name) { auto *empty_child = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_VERTICAL, 0);auto *empty_child = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);add(*empty_child, "empty"); // Add the stack to a layout manager this->layout->add_stack(this);
@@ -139,17 +164,15 @@ namespace gPanthera {
DockWindow::DockWindow(DockablePane *pane) { this->pane = pane; this->add(*pane);this->set_child(*pane);} DockStackSwitcher::DockStackSwitcher(DockStack *stack) : Gtk::ButtonBox(Gtk::ORIENTATION_HORIZONTAL), stack(stack) {add_handler = this->stack->signal_add().connect([this](Gtk::Widget *child) {this->update_buttons();});remove_handler = this->stack->signal_remove().connect([this](Gtk::Widget *child) {DockStackSwitcher::DockStackSwitcher(DockStack *stack) : Gtk::Box(Gtk::Orientation::HORIZONTAL), stack(stack) { auto update_callback = [this](Gtk::Widget*) {this->update_buttons(); });this->set_layout(Gtk::BUTTONBOX_START);}; stack->signal_child_added.connect(update_callback); stack->signal_child_removed.connect(update_callback);} DockStackSwitcher::~DockStackSwitcher() {
@@ -159,20 +182,19 @@ namespace gPanthera {
DockButton::DockButton(DockablePane *pane) : Gtk::Button(), pane(pane) { if(pane->get_icon()) { this->set_image(*pane->get_icon());auto new_image = Gtk::make_managed<Gtk::Image>(this->pane->get_icon()->get_paintable()); this->set_child(*new_image);} this->set_tooltip_text(pane->get_label()->get_text()); this->set_halign(Gtk::ALIGN_CENTER);this->set_halign(Gtk::Align::CENTER);} void DockStackSwitcher::update_buttons() { auto children = get_children();for(auto &child: children) {if(child) {remove(*child);}auto old_buttons = collect_children(*this); for(auto *button : old_buttons) { remove(*button);} for(auto const &widget : stack->get_children()) {for(auto *widget = stack->get_first_child(); widget; widget = widget->get_next_sibling()) {if(auto pane = dynamic_cast<DockablePane*>(widget)) { auto *button = Gtk::make_managed<DockButton>(pane); button->signal_clicked().connect([this, pane]() {
@@ -183,15 +205,23 @@ namespace gPanthera {
} }); if(pane->get_identifier() != Glib::ustring("empty")) { pack_start(*button, Gtk::PACK_SHRINK, 0);set_child_non_homogeneous(*button, true);append(*button);} } } this->show_all();} void DockStack::add_pane(DockablePane &child) { child.redock(this); } void DockStack::add(Gtk::Widget &child, const Glib::ustring &name) { Gtk::Stack::add(child, name); signal_child_added.emit(&child); } void DockStack::remove(Gtk::Widget &child) { Gtk::Stack::remove(child); signal_child_removed.emit(&child); }} // namespace gPanthera
gpanthera.hh
@@ -57,11 +57,17 @@ namespace gPanthera {
class DockStack : public Gtk::Stack { private: public: sigc::signal<void(Gtk::Widget*)> signal_child_added; sigc::signal<void(Gtk::Widget*)> signal_child_removed;std::shared_ptr<LayoutManager> layout; Glib::ustring name; explicit DockStack(std::shared_ptr<LayoutManager> layout, const Glib::ustring &name); void add_pane(DockablePane &child); void add(Gtk::Widget &child, const Glib::ustring &name); void remove(Gtk::Widget &child); void add(Gtk::Widget &child) = delete; void add(Gtk::Widget &child, const Glib::ustring &name, const Glib::ustring &title) = delete;}; class DockButton : public Gtk::Button {
@@ -71,7 +77,7 @@ namespace gPanthera {
explicit DockButton(DockablePane *pane); }; class DockStackSwitcher : public Gtk::ButtonBox {class DockStackSwitcher : public Gtk::Box {private: DockStack *stack; sigc::connection add_handler, remove_handler;
panthera-www.cc
@@ -7,44 +7,54 @@
#include <libintl.h> #include <locale.h> int main(int argc, char *argv[]) {gPanthera::init();auto app = Gtk::Application::create(argc, argv, "org.gpanthera.demo");class PantheraWww : public Gtk::Application { Gtk::Window *window = Gtk::make_managed<Gtk::Window>(); protected: void on_startup() override { Gtk::Application::on_startup(); add_window(*window); window->set_default_size(600, 400); auto layout_manager = std::make_shared<gPanthera::LayoutManager>(); auto dock_stack_1 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, "One"); auto switcher_1 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_1); auto dock_stack_2 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, "Two"); auto switcher_2 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_2); auto pane_1_content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0); pane_1_content->append(*Gtk::make_managed<Gtk::Label>("Pane 1 content")); auto pane_2_content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0); pane_2_content->append(*Gtk::make_managed<Gtk::Label>("Pane 2 content")); auto pane_1_icon = Gtk::make_managed<Gtk::Image>(); pane_1_icon->set_from_icon_name("go-home-symbolic"); auto pane_2_icon = Gtk::make_managed<Gtk::Image>(); pane_2_icon->set_from_icon_name("folder-symbolic"); auto pane_1 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_1_content, "pane1", "Pane 1", pane_1_icon); auto pane_2 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_2_content, "pane2", "Pane 2", pane_2_icon);Gtk::Window window;window.set_default_size(600, 400);window.signal_delete_event().connect([&app](GdkEventAny *event) {app->quit();return false;});dock_stack_1->add_pane(*pane_1); dock_stack_2->add_pane(*pane_2);auto layout_manager = std::make_shared<gPanthera::LayoutManager>();auto dock_stack_1 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, "One");auto switcher_1 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_1);auto dock_stack_2 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, "Two");auto switcher_2 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_2);auto pane_1_content = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_VERTICAL, 0);pane_1_content->pack_start(*Gtk::make_managed<Gtk::Label>("Pane 1 content"), Gtk::PACK_EXPAND_WIDGET);auto pane_2_content = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_VERTICAL, 0);pane_2_content->pack_start(*Gtk::make_managed<Gtk::Label>("Pane 2 content"), Gtk::PACK_EXPAND_WIDGET);auto pane_1_icon = Gtk::make_managed<Gtk::Image>();pane_1_icon->set_from_icon_name("go-home-symbolic", Gtk::ICON_SIZE_MENU);auto pane_2_icon = Gtk::make_managed<Gtk::Image>();pane_2_icon->set_from_icon_name("folder-symbolic", Gtk::ICON_SIZE_MENU);auto pane_1 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_1_content, "pane1", "Pane 1", pane_1_icon);auto pane_2 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_2_content, "pane2", "Pane 2", pane_2_icon);Gtk::Box vbox(Gtk::Orientation::VERTICAL, 0); vbox.append(*switcher_1); vbox.append(*dock_stack_1); vbox.append(*dock_stack_2); vbox.append(*switcher_2);dock_stack_1->add_pane(*pane_1);dock_stack_2->add_pane(*pane_2);dock_stack_1->set_visible_child("empty");Gtk::Box vbox(Gtk::ORIENTATION_VERTICAL);vbox.pack_start(*switcher_1, Gtk::PACK_SHRINK);vbox.pack_start(*dock_stack_1);vbox.pack_start(*dock_stack_2);vbox.pack_start(*switcher_2, Gtk::PACK_SHRINK);window->set_child(vbox); }window.add(vbox);window.show_all();void on_activate() override { window->present(); } public: static Glib::RefPtr<PantheraWww> create() { return Glib::make_refptr_for_instance<PantheraWww>(new PantheraWww()); } };return app->run(window);int main(int argc, char *argv[]) { gPanthera::init(); auto app = PantheraWww::create(); return app->run(argc, argv);}