From 3c27a322431c96202199e7982123ab2ec4db29b2 Mon Sep 17 00:00:00 2001 From: Martin Owens Date: Sat, 29 Nov 2025 14:52:29 -0500 Subject: [PATCH 1/2] Create a Custom Widget standard and document it This moves parts of SpinButton's registration into a standard format and ensures this all works by implementing it for the PaintOrder -> Reorderable -> TabStrip chain. Cleaned up various problems with the spin button registration. --- src/inkscape.cpp | 5 +- src/ui/CMakeLists.txt | 4 ++ src/ui/widget/generic/README | 74 ++++++++++++++++++++ src/ui/widget/generic/reorderable-stack.cpp | 18 ++++- src/ui/widget/generic/reorderable-stack.h | 8 ++- src/ui/widget/generic/spin-button.cpp | 12 +--- src/ui/widget/generic/spin-button.h | 26 +------ src/ui/widget/generic/tab-strip.cpp | 24 +++++-- src/ui/widget/generic/tab-strip.h | 7 +- src/ui/widget/gtk-registry.cpp | 40 +++++++++++ src/ui/widget/gtk-registry.h | 52 ++++++++++++++ src/ui/widget/status-bar.cpp | 1 - src/ui/widget/style/paint-order.cpp | 76 +++++++++++++++++++++ src/ui/widget/style/paint-order.h | 42 +++--------- 14 files changed, 304 insertions(+), 85 deletions(-) create mode 100644 src/ui/widget/gtk-registry.cpp create mode 100644 src/ui/widget/gtk-registry.h create mode 100644 src/ui/widget/style/paint-order.cpp diff --git a/src/inkscape.cpp b/src/inkscape.cpp index ae9169f428..8929f06194 100644 --- a/src/inkscape.cpp +++ b/src/inkscape.cpp @@ -54,7 +54,7 @@ #include "ui/themes.h" #include "ui/tools/tool-base.h" #include "ui/util.h" -#include "ui/widget/generic/spin-button.h" +#include "ui/widget/gtk-registry.h" #include "util/font-discovery.h" static bool desktop_is_active(SPDesktop const *d) @@ -164,8 +164,9 @@ Application::Application(bool use_gui) : auto scale = prefs->getDoubleLimited(UI::ThemeContext::get_font_scale_pref_path(), 100, 50, 200); themecontext->adjustGlobalFontScale(scale / 100.0); Inkscape::UI::ThemeContext::initialize_source_syntax_styles(); + // register custom widget types - Inkscape::UI::Widget::InkSpinButton::register_type(); + Inkscape::UI::Widget::register_all(); } /* set language for user interface according setting in preferences */ diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 0dcefa1157..feade3e22e 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -246,6 +246,7 @@ target_sources(inkscape_base PRIVATE widget/gradient-selector.cpp widget/gradient-vector-selector.cpp widget/gradient-with-stops.cpp + widget/gtk-registry.cpp widget/icon-combobox.cpp widget/handle-preview.cpp widget/image-properties.cpp @@ -293,6 +294,7 @@ target_sources(inkscape_base PRIVATE widget/style/marker-combo-box.cpp widget/style-subject.cpp widget/style-swatch.cpp + widget/style/paint-order.cpp widget/swatch-editor.cpp widget/swatch-selector.cpp widget/tabs-widget.cpp @@ -577,6 +579,7 @@ target_sources(inkscape_base PRIVATE widget/gradient-selector.h widget/gradient-vector-selector.h widget/gradient-with-stops.h + widget/gtk-registry.h widget/icon-combobox.h widget/handle-preview.h widget/image-properties.h @@ -625,6 +628,7 @@ target_sources(inkscape_base PRIVATE widget/style/marker-combo-box.h widget/style-subject.h widget/style-swatch.h + widget/style/paint-order.h widget/swatch-editor.h widget/swatch-selector.h widget/tabs-widget.h diff --git a/src/ui/widget/generic/README b/src/ui/widget/generic/README index 2a3a2336b0..d7197d08df 100644 --- a/src/ui/widget/generic/README +++ b/src/ui/widget/generic/README @@ -38,4 +38,78 @@ Because of how Gtk works, if you inherit from Glib or CssNameClassInit, or imple * All sub-widgets should come from this directory OR Gtkmm NOT from parent directories. * All sub-widgets should be deconstructed by regular Gtkmm referencing (destroyed when unparented for example) +## Template + +Because the creation of widgets is quite delicate, this template should help you make a new widget, while understanding why +each parent of the copypasta is necessary to interact with gtkmm and glibmm. + +### Header file + +``` +class MyNewWidget : public ParentWidget + // ParentWidget may be any other generic widget, or any widget from the Gtk:: namespace + +public: + // Bare constructor is used in registration to initalise the GType + MyNewWidget(); + + // The Gtk builder constructor allows this widget to be created by ux xml files + MyNewWidget(GtkWidget *cobject, const Glib::RefPtr& builder = {}); + + // We store the new gtype for our registered class here, public so register_type<>() can modify it. + static GType gtype; + +private: + // Optional constructor method for duplicated constructor logic + void construct(); +}; +``` + +### Code file + +``` +// First the bare constructor, it must be EXACTLY the same as the builder constructor. +MyNewWidget::MyNewWidget() +// This allows the new widget to be correctly registered and for gobject properties of the parent +// to be accessable to this class too. + , Glib::ObjectBase("MyNewWidget") +// The parent constructor must omit the cobject entirely because the chain of construction generates +// a new cobject only if there is no argument, and not if that argument is nullptr. +// : ParentWidget() // This isn't required, just noted here for explicit symmetry + // More construction here +{ + construct(); // optional +} + +// This constructor means we already have a gobject, and the builder is passing it +// to us so we can bind it to our C++ code correctly. +MyNewWidget::MyNewWidget(GtkWidget *cobject, const Glib::RefPtr& builder) + , Glib::ObjectBase("MyNewWidget") +// As above the cobject must be passed into the parent widget. builder can be omitted +// if the parent is a Gtk widget and not a custom widget. + : ParentWidget(cobject, builder) + // The same construction here, you may want to use a define to reduce duplication +{ + construct(); // optional +} + +// Ensure that the gtype static is constructed in the object file to avoid linking errors +GType MyNewWidget::gtype = 0; +``` + +### Registration in gtk-registry.cpp + +``` + +#include "generic/my-new-widget.h" + +void register_all() +{ + // This makes sure the ctype is ready for the Gtk Builder and adds the required + // names to the lookup. Our widget will be available as gtkmm__CustomObject_MyNewWidget + register_type(); +} + +``` + diff --git a/src/ui/widget/generic/reorderable-stack.cpp b/src/ui/widget/generic/reorderable-stack.cpp index 587a9ce0cb..4e2c81492b 100644 --- a/src/ui/widget/generic/reorderable-stack.cpp +++ b/src/ui/widget/generic/reorderable-stack.cpp @@ -10,9 +10,9 @@ namespace Inkscape::UI::Widget { -ReorderableStack::ReorderableStack(Gtk::Orientation orientation) - : Glib::ObjectBase("ReorderableStack") - , TabStrip(orientation) +GType ReorderableStack::gtype = 0; + +void ReorderableStack::construct() { set_name("ReorderableStack"); set_hexpand(); @@ -30,6 +30,18 @@ ReorderableStack::ReorderableStack(Gtk::Orientation orientation) set_new_tab_popup(nullptr); } +ReorderableStack::ReorderableStack() + : Glib::ObjectBase("ReorderableStack") +{ + construct(); +} +ReorderableStack::ReorderableStack(GtkWidget* cobject, const Glib::RefPtr& builder) + : Glib::ObjectBase("ReorderableStack") + , TabStrip(cobject, builder) +{ + construct(); +} + /** * Add an option to the stack, this should be done on construction. */ diff --git a/src/ui/widget/generic/reorderable-stack.h b/src/ui/widget/generic/reorderable-stack.h index e85d5c2887..22c652e027 100644 --- a/src/ui/widget/generic/reorderable-stack.h +++ b/src/ui/widget/generic/reorderable-stack.h @@ -12,8 +12,6 @@ #include #include -#include - #include "tab-strip.h" namespace Inkscape::UI::Widget { @@ -21,7 +19,8 @@ namespace Inkscape::UI::Widget { class ReorderableStack : public TabStrip { public: - ReorderableStack(Gtk::Orientation orientation = Gtk::Orientation::HORIZONTAL); + ReorderableStack(); + explicit ReorderableStack(GtkWidget* cobject, const Glib::RefPtr& builder = {}); void add_option(std::string const &label, std::string const &icon, std::string const &tooltip, int value); @@ -31,7 +30,10 @@ public: sigc::signal& signal_values_changed() { return _signal_values_changed; } + static GType gtype; private: + void construct(); + sigc::signal _signal_values_changed; std::vector> _rows; diff --git a/src/ui/widget/generic/spin-button.cpp b/src/ui/widget/generic/spin-button.cpp index e5a1d88c95..b36ab8f965 100644 --- a/src/ui/widget/generic/spin-button.cpp +++ b/src/ui/widget/generic/spin-button.cpp @@ -58,6 +58,8 @@ constexpr int icon_margin = 2; static Glib::RefPtr g_resizing_cursor; static Glib::RefPtr g_text_cursor; +GType InkSpinButton::gtype = 0; + void InkSpinButton::construct() { set_name("InkSpinButton"); @@ -291,14 +293,6 @@ InkSpinButton::InkSpinButton(BaseObjectType* cobject, const Glib::RefPtr input, std::f update(false); // apply transformer } -GType InkSpinButton::gtype = 0; - // a fade-out mask for overflowing numbers void InkSpinButton::FadeOut::snapshot_vfunc(const Glib::RefPtr& snapshot) { auto rect = Gdk::Graphene::Rect(0, 0, get_width(), get_height()); diff --git a/src/ui/widget/generic/spin-button.h b/src/ui/widget/generic/spin-button.h index cb2f3331a0..c457a38d77 100644 --- a/src/ui/widget/generic/spin-button.h +++ b/src/ui/widget/generic/spin-button.h @@ -30,8 +30,7 @@ namespace Inkscape::UI::Widget { class InkSpinButton : public CssNameClassInit, public Gtk::Widget { public: InkSpinButton(); - InkSpinButton(BaseObjectType* cobject, const Glib::RefPtr& builder); - explicit InkSpinButton(BaseObjectType* cobject); + explicit InkSpinButton(BaseObjectType* cobject, const Glib::RefPtr& builder = {}); ~InkSpinButton() override; @@ -223,30 +222,7 @@ public: Glib::PropertyProxy property_enter_exit() { return _enter_exit.get_proxy(); } Glib::PropertyProxy property_wrap_around() { return _wrap_around.get_proxy(); } - // Construct a C++ object from a parent (=base) C class object - - static Glib::ObjectBase* wrap_new(GObject* o) { - auto obj = new InkSpinButton(GTK_WIDGET(o)); - - //TODO: check if this is needed - // // if (gtk_widget_is_toplevel(GTK_WIDGET(o))) - // return obj; - // else - return Gtk::manage(obj); - } - - // Register a "new" type in Glib and bind it to the C++ wrapper function - static void register_type() { - if (gtype) return; - - InkSpinButton dummy; - gtype = G_OBJECT_TYPE(dummy.gobj()); - - Glib::wrap_register(gtype, InkSpinButton::wrap_new); - } -private: static GType gtype; - }; } // namespace Inkscape::UI::Widget diff --git a/src/ui/widget/generic/tab-strip.cpp b/src/ui/widget/generic/tab-strip.cpp index 21b3a44d35..3b90e78eef 100644 --- a/src/ui/widget/generic/tab-strip.cpp +++ b/src/ui/widget/generic/tab-strip.cpp @@ -542,14 +542,9 @@ static std::shared_ptr get_tab_drag(Gtk::DropTarget &droptarget) return content->lock(); } -TabStrip::TabStrip(Gtk::Orientation orientation) - : Glib::ObjectBase("TabStrip") - , Gtk::Orientable() - , Gtk::Widget() - , _overlay{Gtk::make_managed()} +void TabStrip::construct() { set_name("TabStrip"); - set_orientation(orientation); set_overflow(Gtk::Overflow::HIDDEN); containerize(*this); @@ -673,6 +668,23 @@ TabStrip::TabStrip(Gtk::Orientation orientation) _updateVisibility(); } +GType TabStrip::gtype = 0; + +TabStrip::TabStrip() + : Glib::ObjectBase("TabStrip") + , _overlay{Gtk::make_managed()} +{ + construct(); +} + +TabStrip::TabStrip(GtkWidget* cobject, const Glib::RefPtr& /*builder*/) + : Glib::ObjectBase("TabStrip") + , Gtk::Widget(cobject) + , _overlay{Gtk::make_managed()} +{ + construct(); +} + TabStrip::~TabStrip() { // Note: This code will fail if TabStrip becomes a managed widget, in which diff --git a/src/ui/widget/generic/tab-strip.h b/src/ui/widget/generic/tab-strip.h index 91babd555e..b5e7128145 100644 --- a/src/ui/widget/generic/tab-strip.h +++ b/src/ui/widget/generic/tab-strip.h @@ -16,6 +16,7 @@ #define INKSCAPE_UI_WIDGET_TAB_STRIP_H #include <2geom/point.h> +#include #include #include @@ -30,7 +31,8 @@ class TabWidgetDrag; class TabStrip : public Gtk::Orientable, public Gtk::Widget { public: - TabStrip(Gtk::Orientation orientation = Gtk::Orientation::HORIZONTAL); + TabStrip(); + explicit TabStrip(GtkWidget* cobject, const Glib::RefPtr& builder = {}); ~TabStrip() override; // create a new tab @@ -85,7 +87,10 @@ public: // tab d&d has ended; bool argument is true if it was cancelled sigc::signal signal_dnd_end() { return _signal_dnd_end; } + static GType gtype; private: + void construct(); + Gtk::Widget *const _overlay; Gtk::Popover *_popover = nullptr; Gtk::MenuButton _plus_btn; diff --git a/src/ui/widget/gtk-registry.cpp b/src/ui/widget/gtk-registry.cpp new file mode 100644 index 0000000000..42a22186bc --- /dev/null +++ b/src/ui/widget/gtk-registry.cpp @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2025 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "gtk-registry.h" + +#include "generic/reorderable-stack.h" +#include "generic/spin-button.h" +#include "generic/tab-strip.h" + +#include "style/paint-order.h" + +namespace Inkscape::UI::Widget { + +void register_all() +{ + // Add generic and reusable widgets here + register_type(); + register_type(); + register_type(); + + // Specific widgets + register_type(); +} + +} // namespace Dialog::UI::Widget + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/widget/gtk-registry.h b/src/ui/widget/gtk-registry.h new file mode 100644 index 0000000000..ae5d0b1e26 --- /dev/null +++ b/src/ui/widget/gtk-registry.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright (C) 2025 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_INKSCAPE_UI_WIDGET_GTK_REGISTRY_H +#define SEEN_INKSCAPE_UI_WIDGET_GTK_REGISTRY_H + +#include "glibmm/wrap.h" +#include "gtkmm/widget.h" + +namespace Inkscape::UI::Widget { + +// Add all custom widgets to the Gtk Builder registry so they can be +// Used from glade/ui xml files. +void register_all(); + +// Construct a C++ object from a parent (=base) C class object +template +Glib::ObjectBase* wrap_new(GObject* o) { + auto obj = new T(GTK_WIDGET(o)); + return Gtk::manage(obj); +} + +/** + * Register a "new" type in Glib and bind it to the C++ wrapper function + */ +template +static void register_type() +{ + if (T::gtype) return; + auto dummy = T(); + T::gtype = G_OBJECT_TYPE(dummy.Gtk::Widget::gobj()); + Glib::wrap_register(T::gtype, wrap_new); +} + +} // namespace Dialog::UI::Widget + +#endif // SEEN_INKSCAPE_UI_WIDGET_GTK_REGISTRY_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/widget/status-bar.cpp b/src/ui/widget/status-bar.cpp index 0d0301faf6..139a2a0786 100644 --- a/src/ui/widget/status-bar.cpp +++ b/src/ui/widget/status-bar.cpp @@ -28,7 +28,6 @@ namespace Inkscape::UI::Widget { StatusBar::StatusBar() : Gtk::Box(Gtk::Orientation::HORIZONTAL) { - UI::Widget::InkSpinButton::register_type(); auto builder = Inkscape::UI::create_builder("statusbar.ui"); auto &statusbar = UI::get_widget(builder, "statusbar"); diff --git a/src/ui/widget/style/paint-order.cpp b/src/ui/widget/style/paint-order.cpp new file mode 100644 index 0000000000..6e0e08a598 --- /dev/null +++ b/src/ui/widget/style/paint-order.cpp @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Widget for paint order styles + *//* + * Copyright (C) 2025 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include + +#include "paint-order.h" + +#include "style-internal.h" + +namespace Inkscape::UI::Widget { + +void PaintOrderWidget::construct() +{ + set_orientation(Gtk::Orientation::VERTICAL); + add_option(_("Marker"), "paint-order-markers", _("Arrows, markers and points"), SP_CSS_PAINT_ORDER_MARKER); + add_option(_("Stroke"), "paint-order-stroke", _("The border line around the shape"), SP_CSS_PAINT_ORDER_STROKE); + add_option(_("Fill"), "paint-order-fill", _("The content of the shape"), SP_CSS_PAINT_ORDER_FILL); +} + +PaintOrderWidget::PaintOrderWidget() + : Glib::ObjectBase("PaintOrderWidget") +{ + construct(); +} + +PaintOrderWidget::PaintOrderWidget(GtkWidget* cobject, const Glib::RefPtr& builder) + : Glib::ObjectBase("PaintOrderWidget") + , ReorderableStack(cobject, builder) +{ + construct(); +} + +void PaintOrderWidget::setValue(SPIPaintOrder &po, bool has_markers) +{ + // array to vector + auto values = po.get_layers(); + // Note: what's painted first is presented at the bottom of the stack. + std::vector vec = {values[2], values[1], values[0]}; + setValues(vec); + + // Hide the markers if the style has no markers + setVisible((int)SP_CSS_PAINT_ORDER_MARKER, has_markers); +} + +SPIPaintOrder PaintOrderWidget::getValue() +{ + SPIPaintOrder po; + auto values = getValues(); + for (auto i = 0; i < 3; i++) { + // Note the reversed order to match setValue() + po.layer[i] = (SPPaintOrderLayer)values[2 - i]; + po.layer_set[i] = true; + } + po.set = true; + return po; +} + +GType PaintOrderWidget::gtype = 0; + +} // namespace Inkscape::UI::Widget + +/* +Local Variables: +mode:c++ +c-file-style:"stroustrup" +c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) +indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim:filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99: diff --git a/src/ui/widget/style/paint-order.h b/src/ui/widget/style/paint-order.h index 6aa551c039..a88fb609bc 100644 --- a/src/ui/widget/style/paint-order.h +++ b/src/ui/widget/style/paint-order.h @@ -9,50 +9,24 @@ #ifndef SEEN_UI_WIDGET_PAINT_ORDER_H #define SEEN_UI_WIDGET_PAINT_ORDER_H -#include - #include "ui/widget/generic/reorderable-stack.h" -#include "style-internal.h" +class SPIPaintOrder; namespace Inkscape::UI::Widget { class PaintOrderWidget : public ReorderableStack { public: - PaintOrderWidget() - : Glib::ObjectBase("PaintOrderWidget") - , ReorderableStack(Gtk::Orientation::VERTICAL) - { - add_option(_("Marker"), "paint-order-markers", _("Arrows, markers and points"), SP_CSS_PAINT_ORDER_MARKER); - add_option(_("Stroke"), "paint-order-stroke", _("The border line around the shape"), SP_CSS_PAINT_ORDER_STROKE); - add_option(_("Fill"), "paint-order-fill", _("The content of the shape"), SP_CSS_PAINT_ORDER_FILL); - } - - void setValue(SPIPaintOrder &po, bool has_markers) - { - // array to vector - auto values = po.get_layers(); - // Note: what's painted first is presented at the bottom of the stack. - std::vector vec = {values[2], values[1], values[0]}; - setValues(vec); + PaintOrderWidget(); + explicit PaintOrderWidget(GtkWidget* cobject, const Glib::RefPtr& builder = {}); - // Hide the markers if the style has no markers - setVisible((int)SP_CSS_PAINT_ORDER_MARKER, has_markers); - } + void setValue(SPIPaintOrder &po, bool has_markers); + SPIPaintOrder getValue(); - SPIPaintOrder getValue() - { - SPIPaintOrder po; - auto values = getValues(); - for (auto i = 0; i < 3; i++) { - // Note the reversed order to match setValue() - po.layer[i] = (SPPaintOrderLayer)values[2 - i]; - po.layer_set[i] = true; - } - po.set = true; - return po; - } + static GType gtype; +private: + void construct(); }; } // namespace Inkscape::UI::Widget -- GitLab From cdb2486bbc5a12e23a71ac5d92623c3ba4ea701b Mon Sep 17 00:00:00 2001 From: mike kowalski Date: Sat, 29 Nov 2025 20:59:12 -0800 Subject: [PATCH 2/2] Add helper class to register Glib type for custom widgets --- src/ui/widget/generic/{README => README.md} | 45 ++++++++++------- src/ui/widget/generic/reorderable-stack.cpp | 4 +- src/ui/widget/generic/reorderable-stack.h | 3 +- src/ui/widget/generic/spin-button.cpp | 4 +- src/ui/widget/generic/spin-button.h | 7 +-- src/ui/widget/generic/tab-strip.cpp | 9 ++-- src/ui/widget/generic/tab-strip.h | 11 +++-- src/ui/widget/gtk-registry.cpp | 9 ++-- src/ui/widget/gtk-registry.h | 53 +++++++++++++-------- src/ui/widget/style/paint-order.cpp | 4 +- src/ui/widget/style/paint-order.h | 3 +- 11 files changed, 85 insertions(+), 67 deletions(-) rename src/ui/widget/generic/{README => README.md} (67%) diff --git a/src/ui/widget/generic/README b/src/ui/widget/generic/README.md similarity index 67% rename from src/ui/widget/generic/README rename to src/ui/widget/generic/README.md index d7197d08df..e3735e5a84 100644 --- a/src/ui/widget/generic/README +++ b/src/ui/widget/generic/README.md @@ -24,15 +24,11 @@ Widgets are expected to be generic and not have specific implementation for data All widgets should be usable via Gtk Builder, and use well understood mechanisms to communicate and be in a Gtkmm style: * Implement GtkBuilder constructors - * Use a initaliser so they can be used in ui xml files + * Use an initaliser so they can be used in ui xml files * Add properties where needed so they can be used * Report changes using signals with non-Gtk/Gdk values * Have setters and getters for their data using non-Gtk/Gdk values -## Final Glib class - -Because of how Gtk works, if you inherit from Glib or CssNameClassInit, or implement an interface like Gtk::Orientable then your class must be marked as `final` to avoid broken widget classes. - ## Mixing Widgets * All sub-widgets should come from this directory OR Gtkmm NOT from parent directories. @@ -46,32 +42,41 @@ each parent of the copypasta is necessary to interact with gtkmm and glibmm. ### Header file ``` -class MyNewWidget : public ParentWidget - // ParentWidget may be any other generic widget, or any widget from the Gtk:: namespace +class MyNewWidget : public BuildableWidget + // BaseWidget may be any other generic widget, or any widget from the Gtk:: namespace public: // Bare constructor is used in registration to initalise the GType MyNewWidget(); - // The Gtk builder constructor allows this widget to be created by ux xml files + // The Gtk builder constructor allows this widget to be created by ui xml files MyNewWidget(GtkWidget *cobject, const Glib::RefPtr& builder = {}); - // We store the new gtype for our registered class here, public so register_type<>() can modify it. - static GType gtype; - private: // Optional constructor method for duplicated constructor logic void construct(); }; ``` +We use `BuildableWidget` to implement registration code that is specific to each custom widget. + +Note 1: If your widget implements an interface, like `Gtk::Orientable`, you will need to add a `using` alias +to disambiguate `BaseObjectType` definition, as it appears in two places in a hierarchy. + +Example: a custom widget that derives from `Gtk::Widget` will need to add `using BaseObjectType = GtkWidget;` line. + +Note 2: Interfaces (like `Gtk::Orientable`) or helper classes (like `CssNameClassInit`) need to be specified *before* `BuildableWidget` to be initialized properly. + +Note 3: To change the name of `MyNewWidget` as seen by CSS, we can use +`CssNameClassInit` to provide a new one. For example `CssNameClassInit("my-new-widget")`. + ### Code file ``` // First the bare constructor, it must be EXACTLY the same as the builder constructor. MyNewWidget::MyNewWidget() // This allows the new widget to be correctly registered and for gobject properties of the parent -// to be accessable to this class too. +// to be accessible to this class too. , Glib::ObjectBase("MyNewWidget") // The parent constructor must omit the cobject entirely because the chain of construction generates // a new cobject only if there is no argument, and not if that argument is nullptr. @@ -85,18 +90,22 @@ MyNewWidget::MyNewWidget() // to us so we can bind it to our C++ code correctly. MyNewWidget::MyNewWidget(GtkWidget *cobject, const Glib::RefPtr& builder) , Glib::ObjectBase("MyNewWidget") -// As above the cobject must be passed into the parent widget. builder can be omitted -// if the parent is a Gtk widget and not a custom widget. - : ParentWidget(cobject, builder) +// As above the cobject must be passed into the base widget. builder can be omitted +// if the base is a Gtk widget and not a custom widget. + : BaseWidget(cobject, builder) // The same construction here, you may want to use a define to reduce duplication { construct(); // optional } -// Ensure that the gtype static is constructed in the object file to avoid linking errors -GType MyNewWidget::gtype = 0; ``` +Note the call to the `Glib::ObjectBase` constructor. It is a virtual base, and as such it needs to be +initialized by the most derived class (here: `MyNewWidget`). +This call is what creates a unique `Glib` `GType` for `MyNewWidget`. + +Reference: [Derived widgets](https://gnome.pages.gitlab.gnome.org/gtkmm-documentation/sec-builder-using-derived-widgets.html) + ### Registration in gtk-registry.cpp ``` @@ -107,7 +116,7 @@ void register_all() { // This makes sure the ctype is ready for the Gtk Builder and adds the required // names to the lookup. Our widget will be available as gtkmm__CustomObject_MyNewWidget - register_type(); + MyNewWidget::register_type(); } ``` diff --git a/src/ui/widget/generic/reorderable-stack.cpp b/src/ui/widget/generic/reorderable-stack.cpp index 4e2c81492b..a3d5877b3a 100644 --- a/src/ui/widget/generic/reorderable-stack.cpp +++ b/src/ui/widget/generic/reorderable-stack.cpp @@ -10,8 +10,6 @@ namespace Inkscape::UI::Widget { -GType ReorderableStack::gtype = 0; - void ReorderableStack::construct() { set_name("ReorderableStack"); @@ -37,7 +35,7 @@ ReorderableStack::ReorderableStack() } ReorderableStack::ReorderableStack(GtkWidget* cobject, const Glib::RefPtr& builder) : Glib::ObjectBase("ReorderableStack") - , TabStrip(cobject, builder) + , BuildableWidget(cobject, builder) { construct(); } diff --git a/src/ui/widget/generic/reorderable-stack.h b/src/ui/widget/generic/reorderable-stack.h index 22c652e027..6ea335e164 100644 --- a/src/ui/widget/generic/reorderable-stack.h +++ b/src/ui/widget/generic/reorderable-stack.h @@ -16,7 +16,7 @@ namespace Inkscape::UI::Widget { -class ReorderableStack : public TabStrip +class ReorderableStack : public BuildableWidget { public: ReorderableStack(); @@ -30,7 +30,6 @@ public: sigc::signal& signal_values_changed() { return _signal_values_changed; } - static GType gtype; private: void construct(); diff --git a/src/ui/widget/generic/spin-button.cpp b/src/ui/widget/generic/spin-button.cpp index b36ab8f965..73b2436895 100644 --- a/src/ui/widget/generic/spin-button.cpp +++ b/src/ui/widget/generic/spin-button.cpp @@ -58,8 +58,6 @@ constexpr int icon_margin = 2; static Glib::RefPtr g_resizing_cursor; static Glib::RefPtr g_text_cursor; -GType InkSpinButton::gtype = 0; - void InkSpinButton::construct() { set_name("InkSpinButton"); @@ -287,7 +285,7 @@ InkSpinButton::InkSpinButton(): InkSpinButton::InkSpinButton(BaseObjectType* cobject, const Glib::RefPtr& builder): CALL_CONSTRUCTORS, - Gtk::Widget(cobject), + BuildableWidget(cobject, builder), INIT_PROPERTIES { construct(); diff --git a/src/ui/widget/generic/spin-button.h b/src/ui/widget/generic/spin-button.h index c457a38d77..aa33905118 100644 --- a/src/ui/widget/generic/spin-button.h +++ b/src/ui/widget/generic/spin-button.h @@ -13,6 +13,7 @@ #include #include "css-name-class-init.h" +#include "ui/widget/gtk-registry.h" namespace Gtk { class EventControllerKey; @@ -27,8 +28,10 @@ namespace Inkscape::UI { class DefocusTarget; } namespace Inkscape::UI::Widget { -class InkSpinButton : public CssNameClassInit, public Gtk::Widget { +class InkSpinButton : public CssNameClassInit, public BuildableWidget { public: + typedef GtkWidget BaseObjectType; + InkSpinButton(); explicit InkSpinButton(BaseObjectType* cobject, const Glib::RefPtr& builder = {}); @@ -221,8 +224,6 @@ public: Glib::PropertyProxy property_show_arrows() { return _show_arrows.get_proxy(); } Glib::PropertyProxy property_enter_exit() { return _enter_exit.get_proxy(); } Glib::PropertyProxy property_wrap_around() { return _wrap_around.get_proxy(); } - - static GType gtype; }; } // namespace Inkscape::UI::Widget diff --git a/src/ui/widget/generic/tab-strip.cpp b/src/ui/widget/generic/tab-strip.cpp index 3b90e78eef..1cb35d245a 100644 --- a/src/ui/widget/generic/tab-strip.cpp +++ b/src/ui/widget/generic/tab-strip.cpp @@ -668,18 +668,17 @@ void TabStrip::construct() _updateVisibility(); } -GType TabStrip::gtype = 0; - -TabStrip::TabStrip() +TabStrip::TabStrip(Gtk::Orientation orientation) : Glib::ObjectBase("TabStrip") , _overlay{Gtk::make_managed()} { + set_orientation(orientation); construct(); } -TabStrip::TabStrip(GtkWidget* cobject, const Glib::RefPtr& /*builder*/) +TabStrip::TabStrip(GtkWidget* cobject, const Glib::RefPtr& builder) : Glib::ObjectBase("TabStrip") - , Gtk::Widget(cobject) + , BuildableWidget(cobject, builder) , _overlay{Gtk::make_managed()} { construct(); diff --git a/src/ui/widget/generic/tab-strip.h b/src/ui/widget/generic/tab-strip.h index b5e7128145..0229e3d3fa 100644 --- a/src/ui/widget/generic/tab-strip.h +++ b/src/ui/widget/generic/tab-strip.h @@ -20,6 +20,8 @@ #include #include +#include "ui/widget/gtk-registry.h" + namespace Gtk { class Popover; } namespace Inkscape::UI::Widget { @@ -28,11 +30,13 @@ struct TabWidget; class TabWidgetDrag; /// Widget that implements strip of tabs -class TabStrip : public Gtk::Orientable, public Gtk::Widget +class TabStrip : public Gtk::Orientable, public BuildableWidget { public: - TabStrip(); - explicit TabStrip(GtkWidget* cobject, const Glib::RefPtr& builder = {}); + using BaseObjectType = GtkWidget; + + TabStrip(Gtk::Orientation orientation = Gtk::Orientation::HORIZONTAL); + explicit TabStrip(BaseObjectType* cobject, const Glib::RefPtr& builder = {}); ~TabStrip() override; // create a new tab @@ -87,7 +91,6 @@ public: // tab d&d has ended; bool argument is true if it was cancelled sigc::signal signal_dnd_end() { return _signal_dnd_end; } - static GType gtype; private: void construct(); diff --git a/src/ui/widget/gtk-registry.cpp b/src/ui/widget/gtk-registry.cpp index 42a22186bc..87eb81e7e1 100644 --- a/src/ui/widget/gtk-registry.cpp +++ b/src/ui/widget/gtk-registry.cpp @@ -10,7 +10,6 @@ #include "generic/reorderable-stack.h" #include "generic/spin-button.h" #include "generic/tab-strip.h" - #include "style/paint-order.h" namespace Inkscape::UI::Widget { @@ -18,12 +17,12 @@ namespace Inkscape::UI::Widget { void register_all() { // Add generic and reusable widgets here - register_type(); - register_type(); - register_type(); + InkSpinButton::register_type(); + TabStrip::register_type(); + ReorderableStack::register_type(); // Specific widgets - register_type(); + PaintOrderWidget::register_type(); } } // namespace Dialog::UI::Widget diff --git a/src/ui/widget/gtk-registry.h b/src/ui/widget/gtk-registry.h index ae5d0b1e26..7205227938 100644 --- a/src/ui/widget/gtk-registry.h +++ b/src/ui/widget/gtk-registry.h @@ -8,8 +8,12 @@ #ifndef SEEN_INKSCAPE_UI_WIDGET_GTK_REGISTRY_H #define SEEN_INKSCAPE_UI_WIDGET_GTK_REGISTRY_H -#include "glibmm/wrap.h" -#include "gtkmm/widget.h" +#include +#include + +namespace Gtk { +class Builder; +} namespace Inkscape::UI::Widget { @@ -17,24 +21,35 @@ namespace Inkscape::UI::Widget { // Used from glade/ui xml files. void register_all(); -// Construct a C++ object from a parent (=base) C class object -template -Glib::ObjectBase* wrap_new(GObject* o) { - auto obj = new T(GTK_WIDGET(o)); - return Gtk::manage(obj); -} +// helper class to handle Glib type registration details for custom widgets +template class BuildableWidget : public Base { + static GType gtype; -/** - * Register a "new" type in Glib and bind it to the C++ wrapper function - */ -template -static void register_type() -{ - if (T::gtype) return; - auto dummy = T(); - T::gtype = G_OBJECT_TYPE(dummy.Gtk::Widget::gobj()); - Glib::wrap_register(T::gtype, wrap_new); -} + static Glib::ObjectBase* wrap_new(GObject* o) { + auto obj = new T(GTK_WIDGET(o)); + return Gtk::manage(obj); + } + +protected: + BuildableWidget() = default; + BuildableWidget(typename Base::BaseObjectType* cobject, const Glib::RefPtr&) : Base(cobject) {} + +public: + static void register_type() { + if (gtype) return; + + auto dummy = T(); + gtype = G_OBJECT_TYPE(dummy.Gtk::Widget::gobj()); + Glib::wrap_register(gtype, wrap_new); + } + + static GType get_gtype() { + return gtype; + } +}; + +template +GType BuildableWidget::gtype = 0; } // namespace Dialog::UI::Widget diff --git a/src/ui/widget/style/paint-order.cpp b/src/ui/widget/style/paint-order.cpp index 6e0e08a598..fd5693ff71 100644 --- a/src/ui/widget/style/paint-order.cpp +++ b/src/ui/widget/style/paint-order.cpp @@ -30,7 +30,7 @@ PaintOrderWidget::PaintOrderWidget() PaintOrderWidget::PaintOrderWidget(GtkWidget* cobject, const Glib::RefPtr& builder) : Glib::ObjectBase("PaintOrderWidget") - , ReorderableStack(cobject, builder) + , BuildableWidget(cobject, builder) { construct(); } @@ -60,8 +60,6 @@ SPIPaintOrder PaintOrderWidget::getValue() return po; } -GType PaintOrderWidget::gtype = 0; - } // namespace Inkscape::UI::Widget /* diff --git a/src/ui/widget/style/paint-order.h b/src/ui/widget/style/paint-order.h index a88fb609bc..a06374762a 100644 --- a/src/ui/widget/style/paint-order.h +++ b/src/ui/widget/style/paint-order.h @@ -15,7 +15,7 @@ class SPIPaintOrder; namespace Inkscape::UI::Widget { -class PaintOrderWidget : public ReorderableStack +class PaintOrderWidget : public BuildableWidget { public: PaintOrderWidget(); @@ -24,7 +24,6 @@ public: void setValue(SPIPaintOrder &po, bool has_markers); SPIPaintOrder getValue(); - static GType gtype; private: void construct(); }; -- GitLab