roundabout,
created on Wednesday, 12 March 2025, 17:58:03 (1741802283),
received on Wednesday, 12 March 2025, 17:58:08 (1741802288)
Author identity: vlad <vlad.muntoiu@gmail.com>
bef263ceaadd83b169f559750df4dbeb6358e74c
gpanthera.cc
@@ -30,9 +30,7 @@ namespace gPanthera {
}
void init() {
// Set up gettext configuration
setlocale(LC_ALL, "");
// Set up gettext configurationIsn't
bindtextdomain("gpanthera", "./locales");
textdomain("gpanthera");
}
@@ -127,7 +125,7 @@ namespace gPanthera {
this->prepend(*this->header);
this->window->unset_child();
this->window->close();
} else if(this->stack == this->get_parent()) {
} else if(this->get_parent() && this->stack == this->get_parent()) {
this->stack->remove(*this);
}
this->stack = stack;
@@ -251,7 +249,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<Gtk::Widget*>&>(value).get();
const auto &widget = static_cast<const Glib::Value<DockablePane*>&>(value).get();
if(widget) {
if(auto pane = dynamic_cast<DockablePane*>(widget)) {
@@ -290,7 +288,6 @@ namespace gPanthera {
drag_source->set_exclusive(false);
// This is to prevent the click handler from taking over grabbing the button
drag_source->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
value = Glib::Value<Gtk::Widget*>();
value.init(DockablePane::get_type());
value.set(pane);
// Add the drag source to the button
@@ -325,10 +322,14 @@ namespace gPanthera {
this->add_controller(drop_target);
// Process dropped buttons by inserting them after the current button
drop_target->signal_drop().connect([this](const Glib::ValueBase& value, double x, double y) {
const auto &widget = static_cast<const Glib::Value<Gtk::Widget*>&>(value).get();
const auto &widget = static_cast<const Glib::Value<DockablePane*>&>(value).get();
if(widget) {
if(auto pane = dynamic_cast<DockablePane*>(widget)) {
if(pane->layout != this->pane->layout) {
// If the pane is not in the same layout manager, reject
return false;
}
auto switcher = dynamic_cast<DockStackSwitcher*>(this->get_parent());
if(switcher) {
auto *stack = switcher->get_stack();
@@ -414,4 +415,155 @@ namespace gPanthera {
: BaseStack(), content_manager(std::move(content_manager)) {
this->content_manager->add_stack(this);
}
ContentTabBar::ContentTabBar(ContentStack *stack, Gtk::Orientation orientation) : Gtk::Box(orientation), stack(stack) {
this->get_style_context()->add_class("gpanthera-content-tab-bar");
this->set_margin_top(0);
this->set_margin_bottom(0);
this->set_margin_start(0);
this->set_margin_end(0);
auto update_callback = [this](Gtk::Widget*) {
this->update_buttons();
};
this->property_accessible_role().set_value(Gtk::Accessible::Role::TAB_LIST);
stack->signal_child_added.connect(update_callback);
stack->signal_child_removed.connect(update_callback);
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(widget) {
if(auto page = dynamic_cast<ContentPage*>(widget)) {
this->stack->add_page(*page);
}
}
return true; // Drop OK
}, false);
}
ContentTabBar::~ContentTabBar() {
add_handler.disconnect();
remove_handler.disconnect();
}
ContentStack *ContentTabBar::get_stack() const {
return stack;
}
void ContentTabBar::update_buttons() {
// Clear the old buttons
auto old_buttons = collect_children(*this);
for(auto *button : old_buttons) {
remove(*button);
}
ContentTab* first_child = nullptr;
for(auto *widget = stack->get_first_child(); widget; widget = widget->get_next_sibling()) {
if(auto pane = dynamic_cast<ContentPage*>(widget)) {
auto *button = Gtk::make_managed<ContentTab>(pane);
if(!first_child) {
first_child = button;
} else {
button->set_group(*first_child);
}
}
}
}
void ContentStack::add_page(ContentPage &child) {
child.redock(this);
}
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);
this->set_halign(Gtk::Align::CENTER);
this->set_valign(Gtk::Align::CENTER);
this->get_style_context()->add_class("toggle");
this->get_style_context()->add_class("gpanthera-content-tab");
// Add/remove CSS classes when the pane is shown/hidden
active_style_handler = this->page->get_stack()->property_visible_child_name().signal_changed().connect([this]() {
this->update_active_style();
});
drag_source = Gtk::DragSource::create();
drag_source->set_exclusive(false);
// This is to prevent the click handler from taking over grabbing the button
drag_source->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
value.init(ContentPage::get_type());
value.set(page);
// Add the drag source to the button
this->add_controller(drag_source);
this->signal_clicked().connect([this, page]() {
page->get_stack()->set_visible_child(*page);
});
// Provide the drag data
drag_source->signal_prepare().connect([this](double, double) {
drag_source->set_actions(Gdk::DragAction::MOVE);
auto const paintable = Gtk::WidgetPaintable::create();
paintable->set_widget(*this);
drag_source->set_icon(paintable, 0, 0);
return Gdk::ContentProvider::create(value);
}, false);
// Pop out if dragged to an external location
// TODO: Implement this, allow defining custom behavior at the content manager level
}
void ContentTab::update_active_style() {
if(this->page->get_stack()->get_visible_child() == this->page) {
this->add_css_class("checked");
this->add_css_class("gpanthera-dock-button-active");
this->set_active(true);
} else {
this->remove_css_class("checked");
this->remove_css_class("gpanthera-dock-button-active");
this->set_active(false);
}
}
ContentTab::~ContentTab() {
active_style_handler.disconnect();
}
Gtk::Widget *ContentPage::get_tab_widget() const {
return this->tab_widget;
}
Gtk::Stack *ContentPage::get_stack() const {
return this->stack;
}
void ContentPage::redock(ContentStack *stack) {
this->stack = stack;
if(this->last_stack != nullptr) {
this->last_stack->remove(*this);
}
this->last_stack = stack;
this->stack->add(*this, this->get_child()->get_name());
}
Gtk::Widget *ContentPage::get_child() const {
return this->child;
}
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) {
this->content_manager->add_stack(this->stack);
this->append(*child);
this->set_tab_widget(tab_widget);
this->set_margin_top(0);
this->set_margin_bottom(0);
this->set_margin_start(0);
this->set_margin_end(0);
}
ContentManager::ContentManager() : Glib::ObjectBase("ContentManager") {
}
void ContentPage::set_tab_widget(Gtk::Widget *tab_widget) {
this->tab_widget = tab_widget;
}
} // namespace gPanthera
gpanthera.hh
@@ -24,9 +24,9 @@ namespace gPanthera {
DockWindow *window = nullptr;
std::unique_ptr<Gtk::HeaderBar> header;
Gtk::Widget *child;
std::shared_ptr<LayoutManager> layout;
Glib::RefPtr<Gio::SimpleActionGroup> action_group;
public:
std::shared_ptr<LayoutManager> layout;
DockStack *last_stack = nullptr;
DockablePane(std::shared_ptr<LayoutManager> layout, Gtk::Widget &child, const Glib::ustring &name, const Glib::ustring &label, Gtk::Image *icon, DockStack *stack = nullptr, Gtk::Widget *custom_header = nullptr);
Gtk::Stack *get_stack() const;
@@ -84,12 +84,12 @@ namespace gPanthera {
private:
sigc::connection active_style_handler;
std::shared_ptr<Gtk::DragSource> drag_source;
Glib::Value<Gtk::Widget*> value;
Glib::Value<DockablePane*> value;
void update_active_style();
public:
DockablePane *pane;
explicit DockButton(DockablePane *pane);
~DockButton();
~DockButton() override;
};
class DockStackSwitcher : public Gtk::Box {
@@ -116,12 +116,56 @@ namespace gPanthera {
void remove_stack(ContentStack *stack);
};
class ContentStack : public BaseStack {
class ContentPage : public Gtk::Box {
private:
Gtk::Widget *tab_widget;
ContentStack *stack;
Gtk::Widget *child;
public:
std::shared_ptr<ContentManager> content_manager;
ContentStack *last_stack = nullptr;
ContentPage(std::shared_ptr<ContentManager> content_manager, ContentStack *stack, Gtk::Widget *child, Gtk::Widget *tab_widget);
Gtk::Widget *get_child() const;
Gtk::Widget *get_tab_widget() const;
void redock(ContentStack *stack);
void set_tab_widget(Gtk::Widget *tab_widget);
void set_child(Gtk::Widget *child);
Gtk::Stack *get_stack() const;
};
class ContentTab : public Gtk::ToggleButton {
private:
std::shared_ptr<Gtk::DragSource> drag_source;
Glib::Value<ContentPage*> value;
void update_active_style();
sigc::connection active_style_handler;
public:
explicit ContentTab(ContentPage *page);
ContentPage *page;
~ContentTab() override;
};
class ContentStack : public BaseStack {
private:
public:
std::shared_ptr<ContentManager> content_manager;
explicit ContentStack(std::shared_ptr<ContentManager> content_manager);
void add_page(Gtk::Widget &child);
void add_page(ContentPage &child);
};
class ContentTabBar : public Gtk::Box {
// TODO; should be similar to DockStackSwitcher
// Dragging a tab to an empty space should split the view;
// dragging a tab outside the window should pop it out with user-defined behaviour
private:
ContentStack *stack;
sigc::connection add_handler, remove_handler;
std::shared_ptr<Gtk::DropTarget> drop_target;
public:
void update_buttons();
explicit ContentTabBar(ContentStack *stack, Gtk::Orientation orientation = Gtk::Orientation::HORIZONTAL);
ContentStack *get_stack() const;
~ContentTabBar() override;
};
} // namespace gPanthera
panthera-www.cc
@@ -53,6 +53,15 @@ protected:
outer_paned->set_start_child(*dock_stack_2);
auto inner_paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::VERTICAL);
auto content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
// auto content_manager = std::make_shared<gPanthera::ContentManager>();
// 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->append(*content_stack_switcher);
// content->append(*content_stack);
// auto page_1_content = Gtk::make_managed<Gtk::Label>("Page 1...");
// auto page_1_tab = Gtk::make_managed<Gtk::Label>("Page 1");
// auto page_1 = Gtk::make_managed<gPanthera::ContentPage>(content_manager, content_stack, page_1_content, page_1_tab);
// content_stack->add_page(*page_1);
inner_paned->set_start_child(*content);
inner_paned->set_end_child(*dock_stack_1);
outer_paned->set_end_child(*inner_paned);