roundabout,
created on Wednesday, 19 March 2025, 17:16:56 (1742404616),
received on Wednesday, 19 March 2025, 17:16:59 (1742404619)
Author identity: vlad <vlad.muntoiu@gmail.com>
8a73668a4a8196e18f7769e582ed7fb61121162b
gpanthera.cc
@@ -1,4 +1,6 @@
#include "gpanthera.hh"
#include <cassert>
#include <iostream>
#include <utility>
#include <libintl.h>
@@ -30,6 +32,7 @@ namespace gPanthera {
}
void init() {
g_log_set_always_fatal(G_LOG_LEVEL_CRITICAL);
// Set up gettext configurationIsn't
bindtextdomain("gpanthera", "./locales");
textdomain("gpanthera");
@@ -430,7 +433,98 @@ namespace gPanthera {
ContentStack::ContentStack(std::shared_ptr<ContentManager> content_manager)
: BaseStack(), content_manager(std::move(content_manager)) {
this->set_name("gpanthera_content_stack");
this->content_manager->add_stack(this);
drop_target = Gtk::DropTarget::create(ContentPage::get_type(), Gdk::DragAction::MOVE);
this->add_controller(drop_target);
// Process dropped buttons
drop_target->signal_drop().connect([this](const Glib::ValueBase& value, double x, double y) {
const auto &widget = static_cast<const Glib::Value<ContentPage*>&>(value).get();
if(auto page = dynamic_cast<ContentPage*>(widget)) {
if(page->get_stack() == this && !this->get_first_child()->get_next_sibling()) {
// Don't allow splitting if there are no more pages
return false;
}
if(!(page->content_manager == this->content_manager)) {
// If the page is not in the same content manager, reject
return false;
}
double width = this->get_allocated_width(), height = this->get_allocated_height();
// Split based on the drop position
if(!(x < width / 4 || x > width * 3 / 4 || y < height / 4 || y > height * 3 / 4)) {
// If the drop position is not at a quarter to the edges, move to the same stack
this->add_page(*page);
return true;
}
auto new_stack = Gtk::make_managed<ContentStack>(this->content_manager);
auto new_switcher = Gtk::make_managed<ContentTabBar>(new_stack, Gtk::Orientation::HORIZONTAL);
auto new_notebook = Gtk::make_managed<ContentNotebook>(new_stack, new_switcher);
new_stack->add_page(*page);
new_stack->set_visible_child(*page);
if(this->get_first_child()) {
this->set_visible_child(*this->get_first_child());
}
if(x < width / 4) {
this->make_paned(Gtk::Orientation::HORIZONTAL, Gtk::PackType::START);
if(auto paned = dynamic_cast<Gtk::Paned*>(this->get_parent()->get_parent())) {
paned->set_start_child(*new_notebook);
}
} else if(x > width * 3 / 4) {
this->make_paned(Gtk::Orientation::HORIZONTAL, Gtk::PackType::END);
if(auto paned = dynamic_cast<Gtk::Paned*>(this->get_parent()->get_parent())) {
paned->set_end_child(*new_notebook);
}
} else if(y < height / 4) {
this->make_paned(Gtk::Orientation::VERTICAL, Gtk::PackType::START);
if(auto paned = dynamic_cast<Gtk::Paned*>(this->get_parent()->get_parent())) {
paned->set_start_child(*new_notebook);
}
} else if(y > height * 3 / 4) {
this->make_paned(Gtk::Orientation::VERTICAL, Gtk::PackType::END);
if(auto paned = dynamic_cast<Gtk::Paned*>(this->get_parent()->get_parent())) {
paned->set_end_child(*new_notebook);
}
}
}
return true; // Drop OK
}, false);
}
ContentNotebook::ContentNotebook(ContentStack *stack, ContentTabBar *switcher) : Gtk::Box(Gtk::Orientation::VERTICAL), stack(stack), switcher(switcher) {
this->prepend(*switcher);
this->append(*stack);
}
void ContentStack::make_paned(Gtk::Orientation orientation, Gtk::PackType pack_type) {
auto *parent = this->get_parent();
if(auto notebook = dynamic_cast<ContentNotebook*>(parent)) {
auto *paned = Gtk::make_managed<Gtk::Paned>(orientation);
if(auto parent_paned = dynamic_cast<Gtk::Paned*>(notebook->get_parent())) {
if(parent_paned->get_start_child() == notebook) {
notebook->unparent();
parent_paned->set_start_child(*paned);
} else if(parent_paned->get_end_child() == notebook) {
notebook->unparent();
parent_paned->set_end_child(*paned);
}
} else if(auto box = dynamic_cast<Gtk::Box*>(notebook->get_parent())) {
auto previous_child = notebook->get_prev_sibling();
box->remove(*notebook);
if(previous_child) {
paned->insert_after(*box, *previous_child);
} else {
box->prepend(*paned);
}
}
if(pack_type == Gtk::PackType::START) {
paned->set_end_child(*notebook);
} else if(pack_type == Gtk::PackType::END) {
paned->set_start_child(*notebook);
}
}
}
ContentTabBar::ContentTabBar(ContentStack *stack, Gtk::Orientation orientation) : Gtk::Box(orientation), stack(stack) {
@@ -450,9 +544,7 @@ namespace gPanthera {
this->add_controller(drop_target);
// Process dropped buttons
drop_target->signal_drop().connect([this](const Glib::ValueBase& value, double x, double y) {
const auto &widget = static_cast<const Glib::Value<ContentPage*>&>(value).get();
if(widget) {
if(const auto &widget = static_cast<const Glib::Value<ContentPage*>&>(value).get()) {
if(auto page = dynamic_cast<ContentPage*>(widget)) {
this->stack->add_page(*page);
}
@@ -498,6 +590,32 @@ namespace gPanthera {
child.redock(this);
}
void ContentStack::remove_with_paned() {
if(auto paned = dynamic_cast<Gtk::Paned*>(this->get_parent()->get_parent())) {
Gtk::Widget *child = nullptr;
if(this->get_parent() == paned->get_start_child()) {
child = paned->get_end_child();
paned->property_end_child().reset_value();
} else if(this->get_parent() == paned->get_end_child()) {
child = paned->get_start_child();
paned->property_start_child().reset_value();
} else {
return;
}
if(auto parent_paned = dynamic_cast<Gtk::Paned*>(paned->get_parent())) {
if(parent_paned->get_start_child() == paned) {
parent_paned->set_start_child(*child);
} else if(parent_paned->get_end_child() == paned) {
parent_paned->set_end_child(*child);
}
} else if(auto box = dynamic_cast<Gtk::Box*>(paned->get_parent())) {
child->insert_after(*box, *paned);
paned->unparent();
}
}
}
ContentTab::ContentTab(ContentPage *page) : Gtk::ToggleButton(), page(page) {
this->set_child(*page->get_tab_widget());
this->property_accessible_role().set_value(Gtk::Accessible::Role::TAB);
@@ -589,6 +707,10 @@ namespace gPanthera {
this->set_opacity(1);
return false;
}, false);
// Pop out if dragged to an external location
drag_source->signal_drag_end().connect([this](const Glib::RefPtr<Gdk::Drag>&, bool) {
this->set_opacity(1);
}, false);
}
void ContentTab::update_active_style() {
@@ -616,10 +738,19 @@ namespace gPanthera {
}
void ContentPage::redock(ContentStack *stack) {
this->stack = stack;
if(this->last_stack != nullptr) {
this->last_stack->remove(*this);
// Check if the stack is now empty, in which case we should remove it
if(this->stack == stack) {
return;
}
if(this->stack != nullptr) {
if(dynamic_cast<ContentNotebook*>(this->stack->get_parent()) && (!this->stack->get_first_child() || (this->stack->get_first_child() == this && !this->stack->get_first_child()->get_next_sibling()))) {
this->stack->remove(*this);
this->stack->remove_with_paned();
} else if(this->get_parent() == this->stack) {
this->stack->remove(*this);
}
}
this->stack = stack;
this->last_stack = stack;
this->stack->add(*this);
}
@@ -629,7 +760,8 @@ namespace gPanthera {
}
ContentPage::ContentPage(std::shared_ptr<ContentManager> content_manager, ContentStack *stack, Gtk::Widget *child, Gtk::Widget *tab_widget) :
Gtk::Box(Gtk::Orientation::VERTICAL, 0), content_manager(std::move(content_manager)), stack(stack), child(child), tab_widget(tab_widget) {
Gtk::Box(Gtk::Orientation::VERTICAL, 0), content_manager(std::move(content_manager)), child(child), tab_widget(tab_widget) {
this->set_name("gpanthera_content_page");
this->append(*child);
this->set_tab_widget(tab_widget);
this->set_margin_top(0);
@@ -637,8 +769,8 @@ namespace gPanthera {
this->set_margin_start(0);
this->set_margin_end(0);
if(stack) {
stack->add_page(*this);
this->content_manager->add_stack(this->stack);
this->stack->add_page(*this);
}
}
@@ -648,5 +780,4 @@ namespace gPanthera {
void ContentPage::set_tab_widget(Gtk::Widget *tab_widget) {
this->tab_widget = tab_widget;
}
} // namespace gPanthera
gpanthera.hh
@@ -151,8 +151,11 @@ namespace gPanthera {
public:
sigc::signal<bool(ContentPage*)> signal_detach;
std::shared_ptr<ContentManager> content_manager;
std::shared_ptr<Gtk::DropTarget> drop_target;
explicit ContentStack(std::shared_ptr<ContentManager> content_manager);
void make_paned(Gtk::Orientation orientation, Gtk::PackType pack_type);
void add_page(ContentPage &child);
void remove_with_paned();
};
class ContentTabBar : public Gtk::Box {
@@ -169,6 +172,14 @@ namespace gPanthera {
ContentStack *get_stack() const;
~ContentTabBar() override;
};
class ContentNotebook : public Gtk::Box {
private:
ContentStack *stack;
ContentTabBar *switcher;
public:
ContentNotebook(ContentStack *stack, ContentTabBar *switcher);
};
} // namespace gPanthera
#endif // GPANTHERA_LIBRARY_H
panthera-www.cc
@@ -57,8 +57,6 @@ protected:
auto content_stack = Gtk::make_managed<gPanthera::ContentStack>(content_manager);
auto content_stack_switcher = Gtk::make_managed<gPanthera::ContentTabBar>(content_stack, Gtk::Orientation::HORIZONTAL);
content_manager->add_stack(content_stack);
content->append(*content_stack_switcher);
content->append(*content_stack);
auto page_1_content = Gtk::make_managed<Gtk::Label>("Page 1...");
auto page_1_tab = new Gtk::Label("Page 1");
auto page_1 = Gtk::make_managed<gPanthera::ContentPage>(content_manager, content_stack, page_1_content, page_1_tab);
@@ -69,6 +67,9 @@ protected:
std::cout << "Detaching " << widget->get_name() << std::endl;
return false; // Widget not actually detached
});
content->set_name("content_box");
auto content_notebook = Gtk::make_managed<gPanthera::ContentNotebook>(content_stack, content_stack_switcher);
content->append(*content_notebook);
inner_paned->set_start_child(*content);
inner_paned->set_end_child(*dock_stack_1);
outer_paned->set_end_child(*inner_paned);