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);
}