summaryrefslogtreecommitdiffstats
path: root/src/ui/widget
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:29:01 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:29:01 +0000
commit35a96bde514a8897f6f0fcc41c5833bf63df2e2a (patch)
tree657d15a03cc46bd099fc2c6546a7a4ad43815d9f /src/ui/widget
parentInitial commit. (diff)
downloadinkscape-upstream.tar.xz
inkscape-upstream.zip
Adding upstream version 1.0.2.upstream/1.0.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/ui/widget/alignment-selector.cpp78
-rw-r--r--src/ui/widget/alignment-selector.h53
-rw-r--r--src/ui/widget/anchor-selector.cpp97
-rw-r--r--src/ui/widget/anchor-selector.h62
-rw-r--r--src/ui/widget/attr-widget.h185
-rw-r--r--src/ui/widget/button.cpp273
-rw-r--r--src/ui/widget/button.h89
-rw-r--r--src/ui/widget/clipmaskicon.cpp123
-rw-r--r--src/ui/widget/clipmaskicon.h86
-rw-r--r--src/ui/widget/color-entry.cpp157
-rw-r--r--src/ui/widget/color-entry.h58
-rw-r--r--src/ui/widget/color-icc-selector.cpp1074
-rw-r--r--src/ui/widget/color-icc-selector.h75
-rw-r--r--src/ui/widget/color-notebook.cpp341
-rw-r--r--src/ui/widget/color-notebook.h89
-rw-r--r--src/ui/widget/color-picker.cpp144
-rw-r--r--src/ui/widget/color-picker.h114
-rw-r--r--src/ui/widget/color-preview.cpp171
-rw-r--r--src/ui/widget/color-preview.h57
-rw-r--r--src/ui/widget/color-scales.cpp736
-rw-r--r--src/ui/widget/color-scales.h110
-rw-r--r--src/ui/widget/color-slider.cpp536
-rw-r--r--src/ui/widget/color-slider.h93
-rw-r--r--src/ui/widget/color-wheel-selector.cpp237
-rw-r--r--src/ui/widget/color-wheel-selector.h83
-rw-r--r--src/ui/widget/combo-box-entry-tool-item.cpp691
-rw-r--r--src/ui/widget/combo-box-entry-tool-item.h157
-rw-r--r--src/ui/widget/combo-enums.h224
-rw-r--r--src/ui/widget/combo-tool-item.cpp290
-rw-r--r--src/ui/widget/combo-tool-item.h136
-rw-r--r--src/ui/widget/dash-selector.cpp309
-rw-r--r--src/ui/widget/dash-selector.h112
-rw-r--r--src/ui/widget/dock-item.cpp528
-rw-r--r--src/ui/widget/dock-item.h159
-rw-r--r--src/ui/widget/dock.cpp305
-rw-r--r--src/ui/widget/dock.h108
-rw-r--r--src/ui/widget/entity-entry.cpp207
-rw-r--r--src/ui/widget/entity-entry.h85
-rw-r--r--src/ui/widget/entry.cpp30
-rw-r--r--src/ui/widget/entry.h45
-rw-r--r--src/ui/widget/filter-effect-chooser.cpp194
-rw-r--r--src/ui/widget/filter-effect-chooser.h95
-rw-r--r--src/ui/widget/font-button.cpp58
-rw-r--r--src/ui/widget/font-button.h63
-rw-r--r--src/ui/widget/font-selector-toolbar.cpp301
-rw-r--r--src/ui/widget/font-selector-toolbar.h120
-rw-r--r--src/ui/widget/font-selector.cpp450
-rw-r--r--src/ui/widget/font-selector.h163
-rw-r--r--src/ui/widget/font-variants.cpp1461
-rw-r--r--src/ui/widget/font-variants.h222
-rw-r--r--src/ui/widget/font-variations.cpp179
-rw-r--r--src/ui/widget/font-variations.h126
-rw-r--r--src/ui/widget/frame.cpp80
-rw-r--r--src/ui/widget/frame.h75
-rw-r--r--src/ui/widget/highlight-picker.cpp169
-rw-r--r--src/ui/widget/highlight-picker.h73
-rw-r--r--src/ui/widget/iconrenderer.cpp121
-rw-r--r--src/ui/widget/iconrenderer.h84
-rw-r--r--src/ui/widget/imagetoggler.cpp113
-rw-r--r--src/ui/widget/imagetoggler.h89
-rw-r--r--src/ui/widget/ink-color-wheel.cpp726
-rw-r--r--src/ui/widget/ink-color-wheel.h86
-rw-r--r--src/ui/widget/ink-flow-box.cpp143
-rw-r--r--src/ui/widget/ink-flow-box.h68
-rw-r--r--src/ui/widget/ink-ruler.cpp473
-rw-r--r--src/ui/widget/ink-ruler.h80
-rw-r--r--src/ui/widget/ink-spinscale.cpp285
-rw-r--r--src/ui/widget/ink-spinscale.h96
-rw-r--r--src/ui/widget/insertordericon.cpp118
-rw-r--r--src/ui/widget/insertordericon.h85
-rw-r--r--src/ui/widget/label-tool-item.cpp66
-rw-r--r--src/ui/widget/label-tool-item.h51
-rw-r--r--src/ui/widget/labelled.cpp110
-rw-r--r--src/ui/widget/labelled.h89
-rw-r--r--src/ui/widget/layer-selector.cpp616
-rw-r--r--src/ui/widget/layer-selector.h114
-rw-r--r--src/ui/widget/layertypeicon.cpp113
-rw-r--r--src/ui/widget/layertypeicon.h91
-rw-r--r--src/ui/widget/licensor.cpp156
-rw-r--r--src/ui/widget/licensor.h57
-rw-r--r--src/ui/widget/notebook-page.cpp47
-rw-r--r--src/ui/widget/notebook-page.h59
-rw-r--r--src/ui/widget/object-composite-settings.cpp325
-rw-r--r--src/ui/widget/object-composite-settings.h81
-rw-r--r--src/ui/widget/page-sizer.cpp781
-rw-r--r--src/ui/widget/page-sizer.h307
-rw-r--r--src/ui/widget/pages-skeleton.h154
-rw-r--r--src/ui/widget/panel.cpp176
-rw-r--r--src/ui/widget/panel.h139
-rw-r--r--src/ui/widget/point.cpp180
-rw-r--r--src/ui/widget/point.h196
-rw-r--r--src/ui/widget/preferences-widget.cpp1023
-rw-r--r--src/ui/widget/preferences-widget.h315
-rw-r--r--src/ui/widget/preview.cpp502
-rw-r--r--src/ui/widget/preview.h163
-rw-r--r--src/ui/widget/random.cpp101
-rw-r--r--src/ui/widget/random.h125
-rw-r--r--src/ui/widget/registered-enums.h99
-rw-r--r--src/ui/widget/registered-widget.cpp845
-rw-r--r--src/ui/widget/registered-widget.h458
-rw-r--r--src/ui/widget/registry.cpp53
-rw-r--r--src/ui/widget/registry.h48
-rw-r--r--src/ui/widget/rendering-options.cpp122
-rw-r--r--src/ui/widget/rendering-options.h68
-rw-r--r--src/ui/widget/rotateable.cpp180
-rw-r--r--src/ui/widget/rotateable.h71
-rw-r--r--src/ui/widget/scalar-unit.cpp263
-rw-r--r--src/ui/widget/scalar-unit.h191
-rw-r--r--src/ui/widget/scalar.cpp173
-rw-r--r--src/ui/widget/scalar.h188
-rw-r--r--src/ui/widget/selected-style.cpp1488
-rw-r--r--src/ui/widget/selected-style.h296
-rw-r--r--src/ui/widget/spin-button-tool-item.cpp532
-rw-r--r--src/ui/widget/spin-button-tool-item.h95
-rw-r--r--src/ui/widget/spin-scale.cpp226
-rw-r--r--src/ui/widget/spin-scale.h110
-rw-r--r--src/ui/widget/spin-slider.cpp223
-rw-r--r--src/ui/widget/spin-slider.h106
-rw-r--r--src/ui/widget/spinbutton.cpp137
-rw-r--r--src/ui/widget/spinbutton.h117
-rw-r--r--src/ui/widget/style-subject.cpp189
-rw-r--r--src/ui/widget/style-subject.h133
-rw-r--r--src/ui/widget/style-swatch.cpp373
-rw-r--r--src/ui/widget/style-swatch.h105
-rw-r--r--src/ui/widget/text.cpp60
-rw-r--r--src/ui/widget/text.h81
-rw-r--r--src/ui/widget/tolerance-slider.cpp215
-rw-r--r--src/ui/widget/tolerance-slider.h89
-rw-r--r--src/ui/widget/unit-menu.cpp152
-rw-r--r--src/ui/widget/unit-menu.h146
-rw-r--r--src/ui/widget/unit-tracker.cpp294
-rw-r--r--src/ui/widget/unit-tracker.h87
132 files changed, 29053 insertions, 0 deletions
diff --git a/src/ui/widget/alignment-selector.cpp b/src/ui/widget/alignment-selector.cpp
new file mode 100644
index 0000000..e5ac17a
--- /dev/null
+++ b/src/ui/widget/alignment-selector.cpp
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * anchor-selector.cpp
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "ui/widget/alignment-selector.h"
+#include "ui/icon-loader.h"
+#include "ui/icon-names.h"
+
+#include <gtkmm/image.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+void AlignmentSelector::setupButton(const Glib::ustring& icon, Gtk::Button& button) {
+ Gtk::Image *buttonIcon = Gtk::manage(sp_get_icon_image(icon, Gtk::ICON_SIZE_SMALL_TOOLBAR));
+ buttonIcon->show();
+
+ button.set_relief(Gtk::RELIEF_NONE);
+ button.show();
+ button.add(*buttonIcon);
+ button.set_can_focus(false);
+}
+
+AlignmentSelector::AlignmentSelector()
+ : _container()
+{
+ set_halign(Gtk::ALIGN_CENTER);
+ setupButton(INKSCAPE_ICON("boundingbox_top_left"), _buttons[0]);
+ setupButton(INKSCAPE_ICON("boundingbox_top"), _buttons[1]);
+ setupButton(INKSCAPE_ICON("boundingbox_top_right"), _buttons[2]);
+ setupButton(INKSCAPE_ICON("boundingbox_left"), _buttons[3]);
+ setupButton(INKSCAPE_ICON("boundingbox_center"), _buttons[4]);
+ setupButton(INKSCAPE_ICON("boundingbox_right"), _buttons[5]);
+ setupButton(INKSCAPE_ICON("boundingbox_bottom_left"), _buttons[6]);
+ setupButton(INKSCAPE_ICON("boundingbox_bottom"), _buttons[7]);
+ setupButton(INKSCAPE_ICON("boundingbox_bottom_right"), _buttons[8]);
+
+ _container.set_row_homogeneous();
+ _container.set_column_homogeneous(true);
+
+ for(int i = 0; i < 9; ++i) {
+ _buttons[i].signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(*this, &AlignmentSelector::btn_activated), i));
+
+ _container.attach(_buttons[i], i % 3, i / 3, 1, 1);
+ }
+
+ this->add(_container);
+}
+
+AlignmentSelector::~AlignmentSelector()
+{
+ // TODO Auto-generated destructor stub
+}
+
+void AlignmentSelector::btn_activated(int index)
+{
+ _alignmentClicked.emit(index);
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/ui/widget/alignment-selector.h b/src/ui/widget/alignment-selector.h
new file mode 100644
index 0000000..5bcf0fa
--- /dev/null
+++ b/src/ui/widget/alignment-selector.h
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * anchor-selector.h
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef ANCHOR_SELECTOR_H_
+#define ANCHOR_SELECTOR_H_
+
+#include <gtkmm/bin.h>
+#include <gtkmm/button.h>
+#include <gtkmm/grid.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class AlignmentSelector : public Gtk::Bin
+{
+private:
+ Gtk::Button _buttons[9];
+ Gtk::Grid _container;
+
+ sigc::signal<void, int> _alignmentClicked;
+
+ void setupButton(const Glib::ustring &icon, Gtk::Button &button);
+ void btn_activated(int index);
+
+public:
+
+ sigc::signal<void, int> &on_alignmentClicked() { return _alignmentClicked; }
+
+ AlignmentSelector();
+ ~AlignmentSelector() override;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif /* ANCHOR_SELECTOR_H_ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/ui/widget/anchor-selector.cpp b/src/ui/widget/anchor-selector.cpp
new file mode 100644
index 0000000..b151a81
--- /dev/null
+++ b/src/ui/widget/anchor-selector.cpp
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * anchor-selector.cpp
+ *
+ * Created on: Mar 22, 2012
+ * Author: denis
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "ui/widget/anchor-selector.h"
+#include "ui/icon-loader.h"
+#include "ui/icon-names.h"
+
+#include <gtkmm/image.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+void AnchorSelector::setupButton(const Glib::ustring& icon, Gtk::ToggleButton& button) {
+ Gtk::Image *buttonIcon = Gtk::manage(sp_get_icon_image(icon, Gtk::ICON_SIZE_SMALL_TOOLBAR));
+ buttonIcon->show();
+
+ button.set_relief(Gtk::RELIEF_NONE);
+ button.show();
+ button.add(*buttonIcon);
+ button.set_can_focus(false);
+}
+
+AnchorSelector::AnchorSelector()
+ : _container()
+{
+ set_halign(Gtk::ALIGN_CENTER);
+ setupButton(INKSCAPE_ICON("boundingbox_top_left"), _buttons[0]);
+ setupButton(INKSCAPE_ICON("boundingbox_top"), _buttons[1]);
+ setupButton(INKSCAPE_ICON("boundingbox_top_right"), _buttons[2]);
+ setupButton(INKSCAPE_ICON("boundingbox_left"), _buttons[3]);
+ setupButton(INKSCAPE_ICON("boundingbox_center"), _buttons[4]);
+ setupButton(INKSCAPE_ICON("boundingbox_right"), _buttons[5]);
+ setupButton(INKSCAPE_ICON("boundingbox_bottom_left"), _buttons[6]);
+ setupButton(INKSCAPE_ICON("boundingbox_bottom"), _buttons[7]);
+ setupButton(INKSCAPE_ICON("boundingbox_bottom_right"), _buttons[8]);
+
+ _container.set_row_homogeneous();
+ _container.set_column_homogeneous(true);
+
+ for (int i = 0; i < 9; ++i) {
+ _buttons[i].signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &AnchorSelector::btn_activated), i));
+
+ _container.attach(_buttons[i], i % 3, i / 3, 1, 1);
+ }
+ _selection = 4;
+ _buttons[4].set_active();
+
+ this->add(_container);
+}
+
+AnchorSelector::~AnchorSelector()
+{
+ // TODO Auto-generated destructor stub
+}
+
+void AnchorSelector::btn_activated(int index)
+{
+ if (_selection == index && _buttons[index].get_active() == false) {
+ _buttons[index].set_active(true);
+ }
+ else if (_selection != index && _buttons[index].get_active()) {
+ int old_selection = _selection;
+ _selection = index;
+ _buttons[old_selection].set_active(false);
+ _selectionChanged.emit();
+ }
+}
+
+void AnchorSelector::setAlignment(int horizontal, int vertical)
+{
+ int index = 3 * vertical + horizontal;
+ if (index >= 0 && index < 9) {
+ _buttons[index].set_active(!_buttons[index].get_active());
+ }
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/ui/widget/anchor-selector.h b/src/ui/widget/anchor-selector.h
new file mode 100644
index 0000000..49ce0b2
--- /dev/null
+++ b/src/ui/widget/anchor-selector.h
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * anchor-selector.h
+ *
+ * Created on: Mar 22, 2012
+ * Author: denis
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef ANCHOR_SELECTOR_H_
+#define ANCHOR_SELECTOR_H_
+
+#include <gtkmm/bin.h>
+#include <gtkmm/togglebutton.h>
+#include <gtkmm/grid.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class AnchorSelector : public Gtk::Bin
+{
+private:
+ Gtk::ToggleButton _buttons[9];
+ int _selection;
+ Gtk::Grid _container;
+
+ sigc::signal<void> _selectionChanged;
+
+ void setupButton(const Glib::ustring &icon, Gtk::ToggleButton &button);
+ void btn_activated(int index);
+
+public:
+
+ int getHorizontalAlignment() { return _selection % 3; }
+ int getVerticalAlignment() { return _selection / 3; }
+
+ sigc::signal<void> &on_selectionChanged() { return _selectionChanged; }
+
+ void setAlignment(int horizontal, int vertical);
+
+ AnchorSelector();
+ ~AnchorSelector() override;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif /* ANCHOR_SELECTOR_H_ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/ui/widget/attr-widget.h b/src/ui/widget/attr-widget.h
new file mode 100644
index 0000000..014a540
--- /dev/null
+++ b/src/ui/widget/attr-widget.h
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Nicholas Bishop <nicholasbishop@gmail.com>
+ * Rodrigo Kumpera <kumpera@gmail.com>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_ATTR_WIDGET_H
+#define INKSCAPE_UI_WIDGET_ATTR_WIDGET_H
+
+#include "attributes.h"
+#include "object/sp-object.h"
+#include "xml/node.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+enum DefaultValueType
+{
+ T_NONE,
+ T_DOUBLE,
+ T_VECT_DOUBLE,
+ T_BOOL,
+ T_UINT,
+ T_CHARPTR
+};
+
+/**
+ * Very basic interface for classes that control attributes.
+ */
+class DefaultValueHolder
+{
+ DefaultValueType type;
+ union {
+ double d_val;
+ std::vector<double>* vt_val;
+ bool b_val;
+ unsigned int uint_val;
+ char* cptr_val;
+ } value;
+
+ //FIXME remove copy ctor and assignment operator as private to avoid double free of the vector
+public:
+ DefaultValueHolder () {
+ type = T_NONE;
+ }
+
+ DefaultValueHolder (double d) {
+ type = T_DOUBLE;
+ value.d_val = d;
+ }
+
+ DefaultValueHolder (std::vector<double>* d) {
+ type = T_VECT_DOUBLE;
+ value.vt_val = d;
+ }
+
+ DefaultValueHolder (char* c) {
+ type = T_CHARPTR;
+ value.cptr_val = c;
+ }
+
+ DefaultValueHolder (bool d) {
+ type = T_BOOL;
+ value.b_val = d;
+ }
+
+ DefaultValueHolder (unsigned int ui) {
+ type = T_UINT;
+ value.uint_val = ui;
+ }
+
+ ~DefaultValueHolder() {
+ if (type == T_VECT_DOUBLE)
+ delete value.vt_val;
+ }
+
+ unsigned int as_uint() {
+ g_assert (type == T_UINT);
+ return value.uint_val;
+ }
+
+ bool as_bool() {
+ g_assert (type == T_BOOL);
+ return value.b_val;
+ }
+
+ double as_double() {
+ g_assert (type == T_DOUBLE);
+ return value.d_val;
+ }
+
+ std::vector<double>* as_vector() {
+ g_assert (type == T_VECT_DOUBLE);
+ return value.vt_val;
+ }
+
+ char* as_charptr() {
+ g_assert (type == T_CHARPTR);
+ return value.cptr_val;
+ }
+};
+
+class AttrWidget
+{
+public:
+ AttrWidget(const SPAttributeEnum a, unsigned int value)
+ : _attr(a),
+ _default(value)
+ {}
+
+ AttrWidget(const SPAttributeEnum a, double value)
+ : _attr(a),
+ _default(value)
+ {}
+
+ AttrWidget(const SPAttributeEnum a, bool value)
+ : _attr(a),
+ _default(value)
+ {}
+
+ AttrWidget(const SPAttributeEnum a, char* value)
+ : _attr(a),
+ _default(value)
+ {}
+
+ AttrWidget(const SPAttributeEnum a)
+ : _attr(a),
+ _default()
+ {}
+
+ virtual ~AttrWidget()
+ = default;
+
+ virtual Glib::ustring get_as_attribute() const = 0;
+ virtual void set_from_attribute(SPObject*) = 0;
+
+ SPAttributeEnum get_attribute() const
+ {
+ return _attr;
+ }
+
+ sigc::signal<void>& signal_attr_changed()
+ {
+ return _signal;
+ }
+protected:
+ DefaultValueHolder* get_default() { return &_default; }
+ const gchar* attribute_value(SPObject* o) const
+ {
+ const gchar* name = (const gchar*)sp_attribute_name(_attr);
+ if(name && o) {
+ const gchar* val = o->getRepr()->attribute(name);
+ return val;
+ }
+ return nullptr;
+ }
+
+private:
+ const SPAttributeEnum _attr;
+ DefaultValueHolder _default;
+ sigc::signal<void> _signal;
+};
+
+}
+}
+}
+
+#endif
+
+/*
+ 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/button.cpp b/src/ui/widget/button.cpp
new file mode 100644
index 0000000..f119c06
--- /dev/null
+++ b/src/ui/widget/button.cpp
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Generic button widget
+ *//*
+ * Authors:
+ * see git history
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm.h>
+
+#include "button.h"
+#include "helper/action-context.h"
+#include "helper/action.h"
+#include "shortcuts.h"
+#include "ui/icon-loader.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+Button::~Button()
+{
+ if (_action) {
+ _c_set_active.disconnect();
+ _c_set_sensitive.disconnect();
+ g_object_unref(_action);
+ }
+
+ if (_doubleclick_action) {
+ set_doubleclick_action(nullptr);
+ }
+}
+
+void
+Button::get_preferred_width_vfunc(int &minimal_width, int &natural_width) const
+{
+ auto child = get_child();
+
+ if (child) {
+ child->get_preferred_width(minimal_width, natural_width);
+ } else {
+ minimal_width = 0;
+ natural_width = 0;
+ }
+
+ auto context = get_style_context();
+
+ auto padding = context->get_padding(context->get_state());
+ auto border = context->get_border(context->get_state());
+
+ minimal_width += MAX(2, padding.get_left() + padding.get_right() + border.get_left() + border.get_right());
+ natural_width += MAX(2, padding.get_left() + padding.get_right() + border.get_left() + border.get_right());
+}
+
+void
+Button::get_preferred_height_vfunc(int &minimal_height, int &natural_height) const
+{
+ auto child = get_child();
+
+ if (child) {
+ child->get_preferred_height(minimal_height, natural_height);
+ } else {
+ minimal_height = 0;
+ natural_height = 0;
+ }
+
+ auto context = get_style_context();
+
+ auto padding = context->get_padding(context->get_state());
+ auto border = context->get_border(context->get_state());
+
+ minimal_height += MAX(2, padding.get_top() + padding.get_bottom() + border.get_top() + border.get_bottom());
+ natural_height += MAX(2, padding.get_top() + padding.get_bottom() + border.get_top() + border.get_bottom());
+}
+
+void
+Button::on_clicked()
+{
+ if (_type == BUTTON_TYPE_TOGGLE) {
+ Gtk::Button::on_clicked();
+ }
+}
+
+bool
+Button::process_event(GdkEvent *event)
+{
+ switch (event->type) {
+ case GDK_2BUTTON_PRESS:
+ if (_doubleclick_action) {
+ sp_action_perform(_doubleclick_action, nullptr);
+ }
+ return true;
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+void
+Button::perform_action()
+{
+ if (_action) {
+ sp_action_perform(_action, nullptr);
+ }
+}
+
+Button::Button(GtkIconSize size,
+ ButtonType type,
+ SPAction *action,
+ SPAction *doubleclick_action)
+ :
+ _action(nullptr),
+ _doubleclick_action(nullptr),
+ _type(type),
+ _lsize(CLAMP(size, GTK_ICON_SIZE_MENU, GTK_ICON_SIZE_DIALOG))
+{
+ set_border_width(0);
+
+ set_can_focus(false);
+ set_can_default(false);
+
+ _on_clicked = signal_clicked().connect(sigc::mem_fun(*this, &Button::perform_action));
+
+ signal_event().connect(sigc::mem_fun(*this, &Button::process_event));
+
+ set_action(action);
+
+ if (doubleclick_action) {
+ set_doubleclick_action(doubleclick_action);
+ }
+
+ // The Inkscape style is no-relief buttons
+ set_relief(Gtk::RELIEF_NONE);
+}
+
+void
+Button::toggle_set_down(bool down)
+{
+ _on_clicked.block();
+ set_active(down);
+ _on_clicked.unblock();
+}
+
+void
+Button::set_doubleclick_action(SPAction *action)
+{
+ if (_doubleclick_action) {
+ g_object_unref(_doubleclick_action);
+ }
+ _doubleclick_action = action;
+ if (action) {
+ g_object_ref(action);
+ }
+}
+
+void
+Button::set_action(SPAction *action)
+{
+ Gtk::Widget *child;
+
+ if (_action) {
+ _c_set_active.disconnect();
+ _c_set_sensitive.disconnect();
+ child = get_child();
+ if (child) {
+ remove();
+ }
+ g_object_unref(_action);
+ }
+
+ _action = action;
+ if (action) {
+ g_object_ref(action);
+ _c_set_active = action->signal_set_active.connect(
+ sigc::mem_fun(*this, &Button::action_set_active));
+
+ _c_set_sensitive = action->signal_set_sensitive.connect(
+ sigc::mem_fun(*this, &Gtk::Widget::set_sensitive));
+
+ if (action->image) {
+ child = Glib::wrap(sp_get_icon_image(action->image, _lsize));
+ child->show();
+ add(*child);
+ }
+ }
+
+ set_composed_tooltip(action);
+}
+
+void
+Button::action_set_active(bool active)
+{
+ if (_type != BUTTON_TYPE_TOGGLE) {
+ return;
+ }
+
+ /* temporarily lobotomized until SPActions are per-view */
+ if (false && !active != !get_active()) {
+ toggle_set_down(active);
+ }
+}
+
+void
+Button::set_composed_tooltip(SPAction *action)
+{
+ if (action) {
+ unsigned int shortcut = sp_shortcut_get_primary(action->verb);
+ if (shortcut != GDK_KEY_VoidSymbol) {
+ // there's both action and shortcut
+
+ gchar *key = sp_shortcut_get_label(shortcut);
+
+ gchar *tip = g_strdup_printf("%s (%s)", action->tip, key);
+ set_tooltip_text(tip);
+ g_free(tip);
+ g_free(key);
+ } else {
+ // action has no shortcut
+ set_tooltip_text(action->tip);
+ }
+ } else {
+ // no action
+ set_tooltip_text(nullptr);
+ }
+}
+
+Button::Button(GtkIconSize size,
+ ButtonType type,
+ Inkscape::UI::View::View *view,
+ const gchar *name,
+ const gchar *tip)
+ :
+ _action(nullptr),
+ _doubleclick_action(nullptr),
+ _type(type),
+ _lsize(CLAMP(size, GTK_ICON_SIZE_MENU, GTK_ICON_SIZE_DIALOG))
+{
+ set_border_width(0);
+
+ set_can_focus(false);
+ set_can_default(false);
+
+ _on_clicked = signal_clicked().connect(sigc::mem_fun(*this, &Button::perform_action));
+ signal_event().connect(sigc::mem_fun(*this, &Button::process_event));
+
+ auto action = sp_action_new(Inkscape::ActionContext(view), name, name, tip, name, nullptr);
+ set_action(action);
+ g_object_unref(action);
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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 :
diff --git a/src/ui/widget/button.h b/src/ui/widget/button.h
new file mode 100644
index 0000000..0b1bfc2
--- /dev/null
+++ b/src/ui/widget/button.h
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Generic button widget
+ *//*
+ * Authors:
+ * see git history
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_SP_BUTTON_H
+#define SEEN_SP_BUTTON_H
+
+#include <gtkmm/togglebutton.h>
+#include <sigc++/connection.h>
+
+struct SPAction;
+
+namespace Inkscape {
+namespace UI {
+namespace View {
+class View;
+}
+
+namespace Widget {
+
+enum ButtonType {
+ BUTTON_TYPE_NORMAL,
+ BUTTON_TYPE_TOGGLE
+};
+
+class Button : public Gtk::ToggleButton{
+private:
+ ButtonType _type;
+ GtkIconSize _lsize;
+ unsigned int _psize;
+ SPAction *_action;
+ SPAction *_doubleclick_action;
+
+ sigc::connection _c_set_active;
+ sigc::connection _c_set_sensitive;
+
+ void set_action(SPAction *action);
+ void set_doubleclick_action(SPAction *action);
+ void set_composed_tooltip(SPAction *action);
+ void action_set_active(bool active);
+ void perform_action();
+ bool process_event(GdkEvent *event);
+
+ sigc::connection _on_clicked;
+
+protected:
+ void get_preferred_width_vfunc(int &minimum_width, int &natural_width) const override;
+ void get_preferred_height_vfunc(int &minimum_height, int &natural_height) const override;
+ void on_clicked() override;
+
+public:
+ Button(GtkIconSize size,
+ ButtonType type,
+ SPAction *action,
+ SPAction *doubleclick_action);
+
+ Button(GtkIconSize size,
+ ButtonType type,
+ Inkscape::UI::View::View *view,
+ const gchar *name,
+ const gchar *tip);
+
+ ~Button() override;
+
+ void toggle_set_down(bool down);
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+#endif // !SEEN_SP_BUTTON_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 :
diff --git a/src/ui/widget/clipmaskicon.cpp b/src/ui/widget/clipmaskicon.cpp
new file mode 100644
index 0000000..1093c1f
--- /dev/null
+++ b/src/ui/widget/clipmaskicon.cpp
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Theodore Janeczko
+ *
+ * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "ui/widget/clipmaskicon.h"
+
+#include "layertypeicon.h"
+#include "ui/icon-loader.h"
+#include "ui/icon-names.h"
+#include "widgets/toolbox.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+ClipMaskIcon::ClipMaskIcon() :
+ Glib::ObjectBase(typeid(ClipMaskIcon)),
+ Gtk::CellRendererPixbuf(),
+ _pixClipName(INKSCAPE_ICON("path-cut")),
+ _pixMaskName(INKSCAPE_ICON("path-difference")),
+ _pixBothName(INKSCAPE_ICON("bitmap-trace")),
+ _property_active(*this, "active", 0),
+ _property_pixbuf_clip(*this, "pixbuf_on", Glib::RefPtr<Gdk::Pixbuf>(nullptr)),
+ _property_pixbuf_mask(*this, "pixbuf_off", Glib::RefPtr<Gdk::Pixbuf>(nullptr)),
+ _property_pixbuf_both(*this, "pixbuf_on", Glib::RefPtr<Gdk::Pixbuf>(nullptr))
+{
+
+ property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE;
+
+ _property_pixbuf_clip = sp_get_icon_pixbuf(_pixClipName, GTK_ICON_SIZE_MENU);
+ _property_pixbuf_mask = sp_get_icon_pixbuf(_pixMaskName, GTK_ICON_SIZE_MENU);
+ _property_pixbuf_both = sp_get_icon_pixbuf(_pixBothName, GTK_ICON_SIZE_MENU);
+
+ property_pixbuf() = Glib::RefPtr<Gdk::Pixbuf>(nullptr);
+}
+
+void ClipMaskIcon::get_preferred_height_vfunc(Gtk::Widget& widget,
+ int& min_h,
+ int& nat_h) const
+{
+ Gtk::CellRendererPixbuf::get_preferred_height_vfunc(widget, min_h, nat_h);
+
+ if (min_h) {
+ min_h += (min_h) >> 1;
+ }
+
+ if (nat_h) {
+ nat_h += (nat_h) >> 1;
+ }
+}
+
+void ClipMaskIcon::get_preferred_width_vfunc(Gtk::Widget& widget,
+ int& min_w,
+ int& nat_w) const
+{
+ Gtk::CellRendererPixbuf::get_preferred_width_vfunc(widget, min_w, nat_w);
+
+ if (min_w) {
+ min_w += (min_w) >> 1;
+ }
+
+ if (nat_w) {
+ nat_w += (nat_w) >> 1;
+ }
+}
+
+void ClipMaskIcon::render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ Gtk::CellRendererState flags )
+{
+ switch (_property_active.get_value())
+ {
+ case 1:
+ property_pixbuf() = _property_pixbuf_clip;
+ break;
+ case 2:
+ property_pixbuf() = _property_pixbuf_mask;
+ break;
+ case 3:
+ property_pixbuf() = _property_pixbuf_both;
+ break;
+ default:
+ property_pixbuf() = Glib::RefPtr<Gdk::Pixbuf>(nullptr);
+ break;
+ }
+ Gtk::CellRendererPixbuf::render_vfunc( cr, widget, background_area, cell_area, flags );
+}
+
+bool ClipMaskIcon::activate_vfunc(GdkEvent* /*event*/,
+ Gtk::Widget& /*widget*/,
+ const Glib::ustring& /*path*/,
+ const Gdk::Rectangle& /*background_area*/,
+ const Gdk::Rectangle& /*cell_area*/,
+ Gtk::CellRendererState /*flags*/)
+{
+ return false;
+}
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/clipmaskicon.h b/src/ui/widget/clipmaskicon.h
new file mode 100644
index 0000000..d8bbe52
--- /dev/null
+++ b/src/ui/widget/clipmaskicon.h
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef __UI_DIALOG_CLIPMASKICON_H__
+#define __UI_DIALOG_CLIPMASKICON_H__
+/*
+ * Authors:
+ * Theodore Janeczko
+ *
+ * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/cellrendererpixbuf.h>
+#include <gtkmm/widget.h>
+#include <glibmm/property.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class ClipMaskIcon : public Gtk::CellRendererPixbuf {
+public:
+ ClipMaskIcon();
+ ~ClipMaskIcon() override = default;;
+
+ Glib::PropertyProxy<int> property_active() { return _property_active.get_proxy(); }
+ Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_on();
+ Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_off();
+
+protected:
+
+ void render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ Gtk::CellRendererState flags ) override;
+
+ void get_preferred_width_vfunc(Gtk::Widget& widget,
+ int& min_w,
+ int& nat_w) const override;
+
+ void get_preferred_height_vfunc(Gtk::Widget& widget,
+ int& min_h,
+ int& nat_h) const override;
+
+ bool activate_vfunc(GdkEvent *event,
+ Gtk::Widget &widget,
+ const Glib::ustring &path,
+ const Gdk::Rectangle &background_area,
+ const Gdk::Rectangle &cell_area,
+ Gtk::CellRendererState flags) override;
+
+
+private:
+ int phys;
+
+ Glib::ustring _pixClipName;
+ Glib::ustring _pixMaskName;
+ Glib::ustring _pixBothName;
+
+ Glib::Property<int> _property_active;
+ Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_clip;
+ Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_mask;
+ Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_both;
+
+};
+
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+
+#endif /* __UI_DIALOG_IMAGETOGGLER_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/color-entry.cpp b/src/ui/widget/color-entry.cpp
new file mode 100644
index 0000000..804350c
--- /dev/null
+++ b/src/ui/widget/color-entry.cpp
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Entry widget for typing color value in css form
+ *//*
+ * Authors:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ *
+ * Copyright (C) 2014 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <glibmm.h>
+#include <glibmm/i18n.h>
+#include <iomanip>
+
+#include "color-entry.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+ColorEntry::ColorEntry(SelectedColor &color)
+ : _color(color)
+ , _updating(false)
+ , _updatingrgba(false)
+ , _prevpos(0)
+ , _lastcolor(0)
+{
+ _color_changed_connection = color.signal_changed.connect(sigc::mem_fun(this, &ColorEntry::_onColorChanged));
+ _color_dragged_connection = color.signal_dragged.connect(sigc::mem_fun(this, &ColorEntry::_onColorChanged));
+ signal_activate().connect(sigc::mem_fun(this, &ColorEntry::_onColorChanged));
+ get_buffer()->signal_inserted_text().connect(sigc::mem_fun(this, &ColorEntry::_inputCheck));
+ _onColorChanged();
+
+ // add extra character for pasting a hash, '#11223344'
+ set_max_length(9);
+ set_width_chars(8);
+ set_tooltip_text(_("Hexadecimal RGBA value of the color"));
+}
+
+ColorEntry::~ColorEntry()
+{
+ _color_changed_connection.disconnect();
+ _color_dragged_connection.disconnect();
+}
+
+void ColorEntry::_inputCheck(guint pos, const gchar * /*chars*/, guint n_chars)
+{
+ // remember position of last character, so we can remove it.
+ // we only overflow by 1 character at most.
+ _prevpos = pos + n_chars - 1;
+}
+
+void ColorEntry::on_changed()
+{
+ if (_updating) {
+ return;
+ }
+ if (_updatingrgba) {
+ return; // Typing text into entry box
+ }
+
+ Glib::ustring text = get_text();
+ bool changed = false;
+
+ // Coerce the value format to hexadecimal
+ for (auto it = text.begin(); it != text.end(); /*++it*/) {
+ if (!g_ascii_isxdigit(*it)) {
+ text.erase(it);
+ changed = true;
+ } else {
+ ++it;
+ }
+ }
+
+ if (text.size() > 8) {
+ text.erase(_prevpos, 1);
+ changed = true;
+ }
+
+ // autofill rules
+ gchar *str = g_strdup(text.c_str());
+ gchar *end = nullptr;
+ guint64 rgba = g_ascii_strtoull(str, &end, 16);
+ ptrdiff_t len = end - str;
+ if (len < 8) {
+ if (len == 0) {
+ rgba = _lastcolor;
+ } else if (len <= 2) {
+ if (len == 1) {
+ rgba *= 17;
+ }
+ rgba = (rgba << 24) + (rgba << 16) + (rgba << 8);
+ } else if (len <= 4) {
+ // display as rrggbbaa
+ rgba = rgba << (4 * (4 - len));
+ guint64 r = rgba & 0xf000;
+ guint64 g = rgba & 0x0f00;
+ guint64 b = rgba & 0x00f0;
+ guint64 a = rgba & 0x000f;
+ rgba = 17 * ((r << 12) + (g << 8) + (b << 4) + a);
+ } else {
+ rgba = rgba << (4 * (8 - len));
+ }
+
+ if (len == 7) {
+ rgba = (rgba & 0xfffffff0) + (_lastcolor & 0x00f);
+ } else if (len == 5) {
+ rgba = (rgba & 0xfffff000) + (_lastcolor & 0xfff);
+ } else if (len != 4 && len != 8) {
+ rgba = (rgba & 0xffffff00) + (_lastcolor & 0x0ff);
+ }
+ }
+
+ _updatingrgba = true;
+ if (changed) {
+ set_text(str);
+ }
+ SPColor color(rgba);
+ _color.setColorAlpha(color, SP_RGBA32_A_F(rgba));
+ _updatingrgba = false;
+
+ g_free(str);
+}
+
+
+void ColorEntry::_onColorChanged()
+{
+ if (_updatingrgba) {
+ return;
+ }
+
+ SPColor color = _color.color();
+ gdouble alpha = _color.alpha();
+
+ _lastcolor = color.toRGBA32(alpha);
+ Glib::ustring text = Glib::ustring::format(std::hex, std::setw(8), std::setfill(L'0'), _lastcolor);
+
+ Glib::ustring old_text = get_text();
+ if (old_text != text) {
+ _updating = true;
+ set_text(text);
+ _updating = false;
+ }
+}
+}
+}
+}
+/*
+ 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/color-entry.h b/src/ui/widget/color-entry.h
new file mode 100644
index 0000000..4df80de
--- /dev/null
+++ b/src/ui/widget/color-entry.h
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Entry widget for typing color value in css form
+ *//*
+ * Authors:
+ * Tomasz Boczkowski <penginsbacon@gmail.com>
+ *
+ * Copyright (C) 2014 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_COLOR_ENTRY_H
+#define SEEN_COLOR_ENTRY_H
+
+#include <gtkmm/entry.h>
+#include "ui/selected-color.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class ColorEntry : public Gtk::Entry
+{
+public:
+ ColorEntry(SelectedColor &color);
+ ~ColorEntry() override;
+
+protected:
+ void on_changed() override;
+
+private:
+ void _onColorChanged();
+ void _inputCheck(guint pos, const gchar * /*chars*/, guint /*n_chars*/);
+
+ SelectedColor &_color;
+ sigc::connection _color_changed_connection;
+ sigc::connection _color_dragged_connection;
+ bool _updating;
+ bool _updatingrgba;
+ guint32 _lastcolor;
+ int _prevpos;
+};
+
+}
+}
+}
+
+#endif
+/*
+ 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/color-icc-selector.cpp b/src/ui/widget/color-icc-selector.cpp
new file mode 100644
index 0000000..3100605
--- /dev/null
+++ b/src/ui/widget/color-icc-selector.cpp
@@ -0,0 +1,1074 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#include <set>
+#include <utility>
+
+#include <gtkmm/adjustment.h>
+#include <glibmm/i18n.h>
+
+#include "colorspace.h"
+#include "document.h"
+#include "inkscape.h"
+#include "profile-manager.h"
+
+#include "svg/svg-icc-color.h"
+
+#include "ui/dialog-events.h"
+#include "ui/util.h"
+#include "ui/widget/color-icc-selector.h"
+#include "ui/widget/color-scales.h"
+#include "ui/widget/color-slider.h"
+
+#define noDEBUG_LCMS
+
+#if defined(HAVE_LIBLCMS2)
+#include "object/color-profile.h"
+#include "cms-system.h"
+#include "color-profile-cms-fns.h"
+
+#ifdef DEBUG_LCMS
+#include "preferences.h"
+#endif // DEBUG_LCMS
+#endif // defined(HAVE_LIBLCMS2)
+
+#ifdef DEBUG_LCMS
+extern guint update_in_progress;
+#define DEBUG_MESSAGE(key, ...) \
+ { \
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get(); \
+ bool dump = prefs->getBool("/options/scislac/" #key); \
+ bool dumpD = prefs->getBool("/options/scislac/" #key "D"); \
+ bool dumpD2 = prefs->getBool("/options/scislac/" #key "D2"); \
+ dumpD && = ((update_in_progress == 0) || dumpD2); \
+ if (dump) { \
+ g_message(__VA_ARGS__); \
+ } \
+ if (dumpD) { \
+ GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, \
+ GTK_BUTTONS_OK, __VA_ARGS__); \
+ g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_widget_destroy), dialog); \
+ gtk_widget_show_all(dialog); \
+ } \
+ }
+#endif // DEBUG_LCMS
+
+
+#define XPAD 4
+#define YPAD 1
+
+namespace {
+
+size_t maxColorspaceComponentCount = 0;
+
+#if defined(HAVE_LIBLCMS2)
+
+/**
+ * Internal variable to track all known colorspaces.
+ */
+std::set<cmsUInt32Number> knownColorspaces;
+
+#endif
+
+/**
+ * Helper function to handle GTK2/GTK3 attachment #ifdef code.
+ */
+void attachToGridOrTable(GtkWidget *parent, GtkWidget *child, guint left, guint top, guint width, guint height,
+ bool hexpand = false, bool centered = false, guint xpadding = XPAD, guint ypadding = YPAD)
+{
+ gtk_widget_set_margin_start(child, xpadding);
+ gtk_widget_set_margin_end(child, xpadding);
+ gtk_widget_set_margin_top(child, ypadding);
+ gtk_widget_set_margin_bottom(child, ypadding);
+
+ if (hexpand) {
+ gtk_widget_set_hexpand(child, TRUE);
+ }
+
+ if (centered) {
+ gtk_widget_set_halign(child, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign(child, GTK_ALIGN_CENTER);
+ }
+
+ gtk_grid_attach(GTK_GRID(parent), child, left, top, width, height);
+}
+
+} // namespace
+
+/*
+icSigRgbData
+icSigCmykData
+icSigCmyData
+*/
+#define SPACE_ID_RGB 0
+#define SPACE_ID_CMY 1
+#define SPACE_ID_CMYK 2
+
+
+colorspace::Component::Component()
+ : name()
+ , tip()
+ , scale(1)
+{
+}
+
+colorspace::Component::Component(std::string name, std::string tip, guint scale)
+ : name(std::move(name))
+ , tip(std::move(tip))
+ , scale(scale)
+{
+}
+
+#if defined(HAVE_LIBLCMS2)
+static cmsUInt16Number *getScratch()
+{
+ // bytes per pixel * input channels * width
+ static cmsUInt16Number *scritch = static_cast<cmsUInt16Number *>(g_new(cmsUInt16Number, 4 * 1024));
+
+ return scritch;
+}
+
+std::vector<colorspace::Component> colorspace::getColorSpaceInfo(uint32_t space)
+{
+ static std::map<cmsUInt32Number, std::vector<Component> > sets;
+ if (sets.empty()) {
+ sets[cmsSigXYZData].push_back(Component("_X", "X", 2)); // TYPE_XYZ_16
+ sets[cmsSigXYZData].push_back(Component("_Y", "Y", 1));
+ sets[cmsSigXYZData].push_back(Component("_Z", "Z", 2));
+
+ sets[cmsSigLabData].push_back(Component("_L", "L", 100)); // TYPE_Lab_16
+ sets[cmsSigLabData].push_back(Component("_a", "a", 256));
+ sets[cmsSigLabData].push_back(Component("_b", "b", 256));
+
+ // cmsSigLuvData
+
+ sets[cmsSigYCbCrData].push_back(Component("_Y", "Y", 1)); // TYPE_YCbCr_16
+ sets[cmsSigYCbCrData].push_back(Component("C_b", "Cb", 1));
+ sets[cmsSigYCbCrData].push_back(Component("C_r", "Cr", 1));
+
+ sets[cmsSigYxyData].push_back(Component("_Y", "Y", 1)); // TYPE_Yxy_16
+ sets[cmsSigYxyData].push_back(Component("_x", "x", 1));
+ sets[cmsSigYxyData].push_back(Component("y", "y", 1));
+
+ sets[cmsSigRgbData].push_back(Component(_("_R:"), _("Red"), 1)); // TYPE_RGB_16
+ sets[cmsSigRgbData].push_back(Component(_("_G:"), _("Green"), 1));
+ sets[cmsSigRgbData].push_back(Component(_("_B:"), _("Blue"), 1));
+
+ sets[cmsSigGrayData].push_back(Component(_("G:"), _("Gray"), 1)); // TYPE_GRAY_16
+
+ sets[cmsSigHsvData].push_back(Component(_("_H:"), _("Hue"), 360)); // TYPE_HSV_16
+ sets[cmsSigHsvData].push_back(Component(_("_S:"), _("Saturation"), 1));
+ sets[cmsSigHsvData].push_back(Component("_V:", "Value", 1));
+
+ sets[cmsSigHlsData].push_back(Component(_("_H:"), _("Hue"), 360)); // TYPE_HLS_16
+ sets[cmsSigHlsData].push_back(Component(_("_L:"), _("Lightness"), 1));
+ sets[cmsSigHlsData].push_back(Component(_("_S:"), _("Saturation"), 1));
+
+ sets[cmsSigCmykData].push_back(Component(_("_C:"), _("Cyan"), 1)); // TYPE_CMYK_16
+ sets[cmsSigCmykData].push_back(Component(_("_M:"), _("Magenta"), 1));
+ sets[cmsSigCmykData].push_back(Component(_("_Y:"), _("Yellow"), 1));
+ sets[cmsSigCmykData].push_back(Component(_("_K:"), _("Black"), 1));
+
+ sets[cmsSigCmyData].push_back(Component(_("_C:"), _("Cyan"), 1)); // TYPE_CMY_16
+ sets[cmsSigCmyData].push_back(Component(_("_M:"), _("Magenta"), 1));
+ sets[cmsSigCmyData].push_back(Component(_("_Y:"), _("Yellow"), 1));
+
+ for (auto & set : sets) {
+ knownColorspaces.insert(set.first);
+ maxColorspaceComponentCount = std::max(maxColorspaceComponentCount, set.second.size());
+ }
+ }
+
+ std::vector<Component> target;
+
+ if (sets.find(space) != sets.end()) {
+ target = sets[space];
+ }
+ return target;
+}
+
+
+std::vector<colorspace::Component> colorspace::getColorSpaceInfo(Inkscape::ColorProfile *prof)
+{
+ return getColorSpaceInfo(asICColorSpaceSig(prof->getColorSpace()));
+}
+
+#endif // defined(HAVE_LIBLCMS2)
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * Class containing the parts for a single color component's UI presence.
+ */
+class ComponentUI {
+ public:
+ ComponentUI()
+ : _component()
+ , _adj(nullptr)
+ , _slider(nullptr)
+ , _btn(nullptr)
+ , _label(nullptr)
+ , _map(nullptr)
+ {
+ }
+
+ ComponentUI(colorspace::Component component)
+ : _component(std::move(component))
+ , _adj(nullptr)
+ , _slider(nullptr)
+ , _btn(nullptr)
+ , _label(nullptr)
+ , _map(nullptr)
+ {
+ }
+
+ colorspace::Component _component;
+ GtkAdjustment *_adj; // Component adjustment
+ Inkscape::UI::Widget::ColorSlider *_slider;
+ GtkWidget *_btn; // spinbutton
+ GtkWidget *_label; // Label
+ guchar *_map;
+};
+
+/**
+ * Class that implements the internals of the selector.
+ */
+class ColorICCSelectorImpl {
+ public:
+ ColorICCSelectorImpl(ColorICCSelector *owner, SelectedColor &color);
+
+ ~ColorICCSelectorImpl();
+
+ static void _adjustmentChanged(GtkAdjustment *adjustment, ColorICCSelectorImpl *cs);
+
+ void _sliderGrabbed();
+ void _sliderReleased();
+ void _sliderChanged();
+
+ static void _profileSelected(GtkWidget *src, gpointer data);
+ static void _fixupHit(GtkWidget *src, gpointer data);
+
+#if defined(HAVE_LIBLCMS2)
+ void _setProfile(SVGICCColor *profile);
+ void _switchToProfile(gchar const *name);
+#endif
+ void _updateSliders(gint ignore);
+ void _profilesChanged(std::string const &name);
+
+ ColorICCSelector *_owner;
+ SelectedColor &_color;
+
+ gboolean _updating : 1;
+ gboolean _dragging : 1;
+
+ guint32 _fixupNeeded;
+ GtkWidget *_fixupBtn;
+ GtkWidget *_profileSel;
+
+ std::vector<ComponentUI> _compUI;
+
+ GtkAdjustment *_adj; // Channel adjustment
+ Inkscape::UI::Widget::ColorSlider *_slider;
+ GtkWidget *_sbtn; // Spinbutton
+ GtkWidget *_label; // Label
+
+#if defined(HAVE_LIBLCMS2)
+ std::string _profileName;
+ Inkscape::ColorProfile *_prof;
+ guint _profChannelCount;
+ gulong _profChangedID;
+#endif // defined(HAVE_LIBLCMS2)
+};
+
+
+
+const gchar *ColorICCSelector::MODE_NAME = N_("CMS");
+
+ColorICCSelector::ColorICCSelector(SelectedColor &color)
+ : _impl(nullptr)
+{
+ _impl = new ColorICCSelectorImpl(this, color);
+ init();
+ color.signal_changed.connect(sigc::mem_fun(this, &ColorICCSelector::_colorChanged));
+ // color.signal_dragged.connect(sigc::mem_fun(this, &ColorICCSelector::_colorChanged));
+}
+
+ColorICCSelector::~ColorICCSelector()
+{
+ if (_impl) {
+ delete _impl;
+ _impl = nullptr;
+ }
+}
+
+
+
+ColorICCSelectorImpl::ColorICCSelectorImpl(ColorICCSelector *owner, SelectedColor &color)
+ : _owner(owner)
+ , _color(color)
+ , _updating(FALSE)
+ , _dragging(FALSE)
+ , _fixupNeeded(0)
+ , _fixupBtn(nullptr)
+ , _profileSel(nullptr)
+ , _compUI()
+ , _adj(nullptr)
+ , _slider(nullptr)
+ , _sbtn(nullptr)
+ , _label(nullptr)
+#if defined(HAVE_LIBLCMS2)
+ , _profileName()
+ , _prof(nullptr)
+ , _profChannelCount(0)
+ , _profChangedID(0)
+#endif // defined(HAVE_LIBLCMS2)
+{
+}
+
+ColorICCSelectorImpl::~ColorICCSelectorImpl()
+{
+ _adj = nullptr;
+ _sbtn = nullptr;
+ _label = nullptr;
+}
+
+void ColorICCSelector::init()
+{
+ gint row = 0;
+
+ _impl->_updating = FALSE;
+ _impl->_dragging = FALSE;
+
+ GtkWidget *t = GTK_WIDGET(gobj());
+
+ _impl->_compUI.clear();
+
+ // Create components
+ row = 0;
+
+
+ _impl->_fixupBtn = gtk_button_new_with_label(_("Fix"));
+ g_signal_connect(G_OBJECT(_impl->_fixupBtn), "clicked", G_CALLBACK(ColorICCSelectorImpl::_fixupHit),
+ (gpointer)_impl);
+ gtk_widget_set_sensitive(_impl->_fixupBtn, FALSE);
+ gtk_widget_set_tooltip_text(_impl->_fixupBtn, _("Fix RGB fallback to match icc-color() value."));
+ gtk_widget_show(_impl->_fixupBtn);
+
+ attachToGridOrTable(t, _impl->_fixupBtn, 0, row, 1, 1);
+
+ // Combobox and store with 2 columns : label (0) and full name (1)
+ GtkListStore *store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
+ _impl->_profileSel = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
+
+ GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(_impl->_profileSel), renderer, TRUE);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(_impl->_profileSel), renderer, "text", 0, NULL);
+
+ GtkTreeIter iter;
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, _("<none>"), 1, _("<none>"), -1);
+
+ gtk_widget_show(_impl->_profileSel);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(_impl->_profileSel), 0);
+
+ attachToGridOrTable(t, _impl->_profileSel, 1, row, 1, 1);
+
+#if defined(HAVE_LIBLCMS2)
+ _impl->_profChangedID = g_signal_connect(G_OBJECT(_impl->_profileSel), "changed",
+ G_CALLBACK(ColorICCSelectorImpl::_profileSelected), (gpointer)_impl);
+#else
+ gtk_widget_set_sensitive(_impl->_profileSel, false);
+#endif // defined(HAVE_LIBLCMS2)
+
+
+ row++;
+
+// populate the data for colorspaces and channels:
+#if defined(HAVE_LIBLCMS2)
+ std::vector<colorspace::Component> things = colorspace::getColorSpaceInfo(cmsSigRgbData);
+#endif // defined(HAVE_LIBLCMS2)
+
+ for (size_t i = 0; i < maxColorspaceComponentCount; i++) {
+#if defined(HAVE_LIBLCMS2)
+ if (i < things.size()) {
+ _impl->_compUI.emplace_back(things[i]);
+ }
+ else {
+ _impl->_compUI.emplace_back();
+ }
+
+ std::string labelStr = (i < things.size()) ? things[i].name.c_str() : "";
+#else
+ _impl->_compUI.push_back(ComponentUI());
+
+ std::string labelStr = ".";
+#endif
+
+ _impl->_compUI[i]._label = gtk_label_new_with_mnemonic(labelStr.c_str());
+
+ gtk_widget_set_halign(_impl->_compUI[i]._label, GTK_ALIGN_END);
+ gtk_widget_show(_impl->_compUI[i]._label);
+ gtk_widget_set_no_show_all(_impl->_compUI[i]._label, TRUE);
+
+ attachToGridOrTable(t, _impl->_compUI[i]._label, 0, row, 1, 1);
+
+ // Adjustment
+ guint scaleValue = _impl->_compUI[i]._component.scale;
+ gdouble step = static_cast<gdouble>(scaleValue) / 100.0;
+ gdouble page = static_cast<gdouble>(scaleValue) / 10.0;
+ gint digits = (step > 0.9) ? 0 : 2;
+ _impl->_compUI[i]._adj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, scaleValue, step, page, page));
+
+ // Slider
+ _impl->_compUI[i]._slider =
+ Gtk::manage(new Inkscape::UI::Widget::ColorSlider(Glib::wrap(_impl->_compUI[i]._adj, true)));
+#if defined(HAVE_LIBLCMS2)
+ _impl->_compUI[i]._slider->set_tooltip_text((i < things.size()) ? things[i].tip.c_str() : "");
+#else
+ _impl->_compUI[i]._slider->set_tooltip_text(".");
+#endif // defined(HAVE_LIBLCMS2)
+ _impl->_compUI[i]._slider->show();
+ _impl->_compUI[i]._slider->set_no_show_all();
+
+ attachToGridOrTable(t, _impl->_compUI[i]._slider->gobj(), 1, row, 1, 1, true);
+
+ _impl->_compUI[i]._btn = gtk_spin_button_new(_impl->_compUI[i]._adj, step, digits);
+#if defined(HAVE_LIBLCMS2)
+ gtk_widget_set_tooltip_text(_impl->_compUI[i]._btn, (i < things.size()) ? things[i].tip.c_str() : "");
+#else
+ gtk_widget_set_tooltip_text(_impl->_compUI[i]._btn, ".");
+#endif // defined(HAVE_LIBLCMS2)
+ sp_dialog_defocus_on_enter(_impl->_compUI[i]._btn);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(_impl->_compUI[i]._label), _impl->_compUI[i]._btn);
+ gtk_widget_show(_impl->_compUI[i]._btn);
+ gtk_widget_set_no_show_all(_impl->_compUI[i]._btn, TRUE);
+
+ attachToGridOrTable(t, _impl->_compUI[i]._btn, 2, row, 1, 1, false, true);
+
+ _impl->_compUI[i]._map = g_new(guchar, 4 * 1024);
+ memset(_impl->_compUI[i]._map, 0x0ff, 1024 * 4);
+
+
+ // Signals
+ g_signal_connect(G_OBJECT(_impl->_compUI[i]._adj), "value_changed",
+ G_CALLBACK(ColorICCSelectorImpl::_adjustmentChanged), _impl);
+
+ _impl->_compUI[i]._slider->signal_grabbed.connect(sigc::mem_fun(_impl, &ColorICCSelectorImpl::_sliderGrabbed));
+ _impl->_compUI[i]._slider->signal_released.connect(
+ sigc::mem_fun(_impl, &ColorICCSelectorImpl::_sliderReleased));
+ _impl->_compUI[i]._slider->signal_value_changed.connect(
+ sigc::mem_fun(_impl, &ColorICCSelectorImpl::_sliderChanged));
+
+ row++;
+ }
+
+ // Label
+ _impl->_label = gtk_label_new_with_mnemonic(_("_A:"));
+
+ gtk_widget_set_halign(_impl->_label, GTK_ALIGN_END);
+ gtk_widget_show(_impl->_label);
+
+ attachToGridOrTable(t, _impl->_label, 0, row, 1, 1);
+
+ // Adjustment
+ _impl->_adj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 100.0, 1.0, 10.0, 10.0));
+
+ // Slider
+ _impl->_slider = Gtk::manage(new Inkscape::UI::Widget::ColorSlider(Glib::wrap(_impl->_adj, true)));
+ _impl->_slider->set_tooltip_text(_("Alpha (opacity)"));
+ _impl->_slider->show();
+
+ attachToGridOrTable(t, _impl->_slider->gobj(), 1, row, 1, 1, true);
+
+ _impl->_slider->setColors(SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 0.0), SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 0.5),
+ SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 1.0));
+
+
+ // Spinbutton
+ _impl->_sbtn = gtk_spin_button_new(GTK_ADJUSTMENT(_impl->_adj), 1.0, 0);
+ gtk_widget_set_tooltip_text(_impl->_sbtn, _("Alpha (opacity)"));
+ sp_dialog_defocus_on_enter(_impl->_sbtn);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(_impl->_label), _impl->_sbtn);
+ gtk_widget_show(_impl->_sbtn);
+
+ attachToGridOrTable(t, _impl->_sbtn, 2, row, 1, 1, false, true);
+
+ // Signals
+ g_signal_connect(G_OBJECT(_impl->_adj), "value_changed", G_CALLBACK(ColorICCSelectorImpl::_adjustmentChanged),
+ _impl);
+
+ _impl->_slider->signal_grabbed.connect(sigc::mem_fun(_impl, &ColorICCSelectorImpl::_sliderGrabbed));
+ _impl->_slider->signal_released.connect(sigc::mem_fun(_impl, &ColorICCSelectorImpl::_sliderReleased));
+ _impl->_slider->signal_value_changed.connect(sigc::mem_fun(_impl, &ColorICCSelectorImpl::_sliderChanged));
+
+ gtk_widget_show(t);
+}
+
+void ColorICCSelectorImpl::_fixupHit(GtkWidget * /*src*/, gpointer data)
+{
+ ColorICCSelectorImpl *self = reinterpret_cast<ColorICCSelectorImpl *>(data);
+ gtk_widget_set_sensitive(self->_fixupBtn, FALSE);
+ self->_adjustmentChanged(self->_compUI[0]._adj, self);
+}
+
+#if defined(HAVE_LIBLCMS2)
+void ColorICCSelectorImpl::_profileSelected(GtkWidget * /*src*/, gpointer data)
+{
+ ColorICCSelectorImpl *self = reinterpret_cast<ColorICCSelectorImpl *>(data);
+
+ GtkTreeIter iter;
+ if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(self->_profileSel), &iter)) {
+ GtkTreeModel *store = gtk_combo_box_get_model(GTK_COMBO_BOX(self->_profileSel));
+ gchar *name = nullptr;
+
+ gtk_tree_model_get(store, &iter, 1, &name, -1);
+ self->_switchToProfile(name);
+ gtk_widget_set_tooltip_text(self->_profileSel, name);
+
+ g_free(name);
+ }
+}
+#endif // defined(HAVE_LIBLCMS2)
+
+#if defined(HAVE_LIBLCMS2)
+void ColorICCSelectorImpl::_switchToProfile(gchar const *name)
+{
+ bool dirty = false;
+ SPColor tmp(_color.color());
+
+ if (name) {
+ if (tmp.icc && tmp.icc->colorProfile == name) {
+#ifdef DEBUG_LCMS
+ g_message("Already at name [%s]", name);
+#endif // DEBUG_LCMS
+ }
+ else {
+#ifdef DEBUG_LCMS
+ g_message("Need to switch to profile [%s]", name);
+#endif // DEBUG_LCMS
+ if (tmp.icc) {
+ tmp.icc->colors.clear();
+ }
+ else {
+ tmp.icc = new SVGICCColor();
+ }
+ tmp.icc->colorProfile = name;
+ Inkscape::ColorProfile *newProf = SP_ACTIVE_DOCUMENT->getProfileManager()->find(name);
+ if (newProf) {
+ cmsHTRANSFORM trans = newProf->getTransfFromSRGB8();
+ if (trans) {
+ guint32 val = _color.color().toRGBA32(0);
+ guchar pre[4] = {
+ static_cast<guchar>(SP_RGBA32_R_U(val)),
+ static_cast<guchar>(SP_RGBA32_G_U(val)),
+ static_cast<guchar>(SP_RGBA32_B_U(val)),
+ 255};
+#ifdef DEBUG_LCMS
+ g_message("Shoving in [%02x] [%02x] [%02x]", pre[0], pre[1], pre[2]);
+#endif // DEBUG_LCMS
+ cmsUInt16Number post[4] = { 0, 0, 0, 0 };
+ cmsDoTransform(trans, pre, post, 1);
+#ifdef DEBUG_LCMS
+ g_message("got on out [%04x] [%04x] [%04x] [%04x]", post[0], post[1], post[2], post[3]);
+#endif // DEBUG_LCMS
+#if HAVE_LIBLCMS2
+ guint count = cmsChannelsOf(asICColorSpaceSig(newProf->getColorSpace()));
+#endif
+
+ std::vector<colorspace::Component> things =
+ colorspace::getColorSpaceInfo(asICColorSpaceSig(newProf->getColorSpace()));
+
+ for (guint i = 0; i < count; i++) {
+ gdouble val =
+ (((gdouble)post[i]) / 65535.0) * (gdouble)((i < things.size()) ? things[i].scale : 1);
+#ifdef DEBUG_LCMS
+ g_message(" scaled %d by %d to be %f", i, ((i < things.size()) ? things[i].scale : 1), val);
+#endif // DEBUG_LCMS
+ tmp.icc->colors.push_back(val);
+ }
+ cmsHTRANSFORM retrans = newProf->getTransfToSRGB8();
+ if (retrans) {
+ cmsDoTransform(retrans, post, pre, 1);
+#ifdef DEBUG_LCMS
+ g_message(" back out [%02x] [%02x] [%02x]", pre[0], pre[1], pre[2]);
+#endif // DEBUG_LCMS
+ tmp.set(SP_RGBA32_U_COMPOSE(pre[0], pre[1], pre[2], 0xff));
+ }
+
+ dirty = true;
+ }
+ }
+ }
+ }
+ else {
+#ifdef DEBUG_LCMS
+ g_message("NUKE THE ICC");
+#endif // DEBUG_LCMS
+ if (tmp.icc) {
+ delete tmp.icc;
+ tmp.icc = nullptr;
+ dirty = true;
+ _fixupHit(nullptr, this);
+ }
+ else {
+#ifdef DEBUG_LCMS
+ g_message("No icc to nuke");
+#endif // DEBUG_LCMS
+ }
+ }
+
+ if (dirty) {
+#ifdef DEBUG_LCMS
+ g_message("+----------------");
+ g_message("+ new color is [%s]", tmp.toString().c_str());
+#endif // DEBUG_LCMS
+ _setProfile(tmp.icc);
+ //_adjustmentChanged( _compUI[0]._adj, SP_COLOR_ICC_SELECTOR(_csel) );
+ _color.setColor(tmp);
+#ifdef DEBUG_LCMS
+ g_message("+_________________");
+#endif // DEBUG_LCMS
+ }
+}
+#endif // defined(HAVE_LIBLCMS2)
+
+#if defined(HAVE_LIBLCMS2)
+struct _cmp {
+ bool operator()(const SPObject * const & a, const SPObject * const & b)
+ {
+ const Inkscape::ColorProfile &a_prof = reinterpret_cast<const Inkscape::ColorProfile &>(*a);
+ const Inkscape::ColorProfile &b_prof = reinterpret_cast<const Inkscape::ColorProfile &>(*b);
+ gchar *a_name_casefold = g_utf8_casefold(a_prof.name, -1 );
+ gchar *b_name_casefold = g_utf8_casefold(b_prof.name, -1 );
+ int result = g_strcmp0(a_name_casefold, b_name_casefold);
+ g_free(a_name_casefold);
+ g_free(b_name_casefold);
+ return result < 0;
+ }
+};
+
+template <typename From, typename To>
+struct static_caster { To * operator () (From * value) const { return static_cast<To *>(value); } };
+
+void ColorICCSelectorImpl::_profilesChanged(std::string const &name)
+{
+ GtkComboBox *combo = GTK_COMBO_BOX(_profileSel);
+
+ g_signal_handler_block(G_OBJECT(_profileSel), _profChangedID);
+
+ GtkListStore *store = GTK_LIST_STORE(gtk_combo_box_get_model(combo));
+ gtk_list_store_clear(store);
+
+ GtkTreeIter iter;
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, _("<none>"), 1, _("<none>"), -1);
+
+ gtk_combo_box_set_active(combo, 0);
+
+ int index = 1;
+ std::vector<SPObject *> current = SP_ACTIVE_DOCUMENT->getResourceList("iccprofile");
+
+ std::set<Inkscape::ColorProfile *, Inkscape::ColorProfile::pointerComparator> _current;
+ std::transform(current.begin(),
+ current.end(),
+ std::inserter(_current, _current.begin()),
+ static_caster<SPObject, Inkscape::ColorProfile>());
+
+ for (auto &it: _current) {
+ Inkscape::ColorProfile *prof = it;
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, ink_ellipsize_text(prof->name, 25).c_str(), 1, prof->name, -1);
+
+ if (name == prof->name) {
+ gtk_combo_box_set_active(combo, index);
+ gtk_widget_set_tooltip_text(_profileSel, prof->name);
+ }
+
+ index++;
+ }
+
+ g_signal_handler_unblock(G_OBJECT(_profileSel), _profChangedID);
+}
+#else
+void ColorICCSelectorImpl::_profilesChanged(std::string const & /*name*/) {}
+#endif // defined(HAVE_LIBLCMS2)
+
+void ColorICCSelector::on_show()
+{
+ Gtk::Grid::on_show();
+ _colorChanged();
+}
+
+// Helpers for setting color value
+
+void ColorICCSelector::_colorChanged()
+{
+ _impl->_updating = TRUE;
+// sp_color_icc_set_color( SP_COLOR_ICC( _icc ), &color );
+
+#ifdef DEBUG_LCMS
+ g_message("/^^^^^^^^^ %p::_colorChanged(%08x:%s)", this, _impl->_color.color().toRGBA32(_impl->_color.alpha()),
+ ((_impl->_color.color().icc) ? _impl->_color.color().icc->colorProfile.c_str() : "<null>"));
+#endif // DEBUG_LCMS
+
+#ifdef DEBUG_LCMS
+ g_message("FLIPPIES!!!! %p '%s'", _impl->_color.color().icc,
+ (_impl->_color.color().icc ? _impl->_color.color().icc->colorProfile.c_str() : "<null>"));
+#endif // DEBUG_LCMS
+
+ _impl->_profilesChanged((_impl->_color.color().icc) ? _impl->_color.color().icc->colorProfile : std::string(""));
+ ColorScales::setScaled(_impl->_adj, _impl->_color.alpha());
+
+#if defined(HAVE_LIBLCMS2)
+ _impl->_setProfile(_impl->_color.color().icc);
+ _impl->_fixupNeeded = 0;
+ gtk_widget_set_sensitive(_impl->_fixupBtn, FALSE);
+
+ if (_impl->_prof) {
+ if (_impl->_prof->getTransfToSRGB8()) {
+ cmsUInt16Number tmp[4];
+ for (guint i = 0; i < _impl->_profChannelCount; i++) {
+ gdouble val = 0.0;
+ if (_impl->_color.color().icc->colors.size() > i) {
+ if (_impl->_compUI[i]._component.scale == 256) {
+ val = (_impl->_color.color().icc->colors[i] + 128.0) /
+ static_cast<gdouble>(_impl->_compUI[i]._component.scale);
+ }
+ else {
+ val = _impl->_color.color().icc->colors[i] /
+ static_cast<gdouble>(_impl->_compUI[i]._component.scale);
+ }
+ }
+ tmp[i] = val * 0x0ffff;
+ }
+ guchar post[4] = { 0, 0, 0, 0 };
+ cmsHTRANSFORM trans = _impl->_prof->getTransfToSRGB8();
+ if (trans) {
+ cmsDoTransform(trans, tmp, post, 1);
+ guint32 other = SP_RGBA32_U_COMPOSE(post[0], post[1], post[2], 255);
+ if (other != _impl->_color.color().toRGBA32(255)) {
+ _impl->_fixupNeeded = other;
+ gtk_widget_set_sensitive(_impl->_fixupBtn, TRUE);
+#ifdef DEBUG_LCMS
+ g_message("Color needs to change 0x%06x to 0x%06x", _color.toRGBA32(255) >> 8, other >> 8);
+#endif // DEBUG_LCMS
+ }
+ }
+ }
+ }
+#else
+//(void)color;
+#endif // defined(HAVE_LIBLCMS2)
+ _impl->_updateSliders(-1);
+
+
+ _impl->_updating = FALSE;
+#ifdef DEBUG_LCMS
+ g_message("\\_________ %p::_colorChanged()", this);
+#endif // DEBUG_LCMS
+}
+
+#if defined(HAVE_LIBLCMS2)
+void ColorICCSelectorImpl::_setProfile(SVGICCColor *profile)
+{
+#ifdef DEBUG_LCMS
+ g_message("/^^^^^^^^^ %p::_setProfile(%s)", this, ((profile) ? profile->colorProfile.c_str() : "<null>"));
+#endif // DEBUG_LCMS
+ bool profChanged = false;
+ if (_prof && (!profile || (_profileName != profile->colorProfile))) {
+ // Need to clear out the prior one
+ profChanged = true;
+ _profileName.clear();
+ _prof = nullptr;
+ _profChannelCount = 0;
+ }
+ else if (profile && !_prof) {
+ profChanged = true;
+ }
+
+ for (auto & i : _compUI) {
+ gtk_widget_hide(i._label);
+ i._slider->hide();
+ gtk_widget_hide(i._btn);
+ }
+
+ if (profile) {
+ _prof = SP_ACTIVE_DOCUMENT->getProfileManager()->find(profile->colorProfile.c_str());
+ if (_prof && (asICColorProfileClassSig(_prof->getProfileClass()) != cmsSigNamedColorClass)) {
+#if HAVE_LIBLCMS2
+ _profChannelCount = cmsChannelsOf(asICColorSpaceSig(_prof->getColorSpace()));
+#endif
+
+ if (profChanged) {
+ std::vector<colorspace::Component> things =
+ colorspace::getColorSpaceInfo(asICColorSpaceSig(_prof->getColorSpace()));
+ for (size_t i = 0; (i < things.size()) && (i < _profChannelCount); ++i) {
+ _compUI[i]._component = things[i];
+ }
+
+ for (guint i = 0; i < _profChannelCount; i++) {
+ gtk_label_set_text_with_mnemonic(GTK_LABEL(_compUI[i]._label),
+ (i < things.size()) ? things[i].name.c_str() : "");
+
+ _compUI[i]._slider->set_tooltip_text((i < things.size()) ? things[i].tip.c_str() : "");
+ gtk_widget_set_tooltip_text(_compUI[i]._btn, (i < things.size()) ? things[i].tip.c_str() : "");
+
+ _compUI[i]._slider->setColors(SPColor(0.0, 0.0, 0.0).toRGBA32(0xff),
+ SPColor(0.5, 0.5, 0.5).toRGBA32(0xff),
+ SPColor(1.0, 1.0, 1.0).toRGBA32(0xff));
+ /*
+ _compUI[i]._adj = GTK_ADJUSTMENT( gtk_adjustment_new( val, 0.0, _fooScales[i],
+ step, page, page ) );
+ g_signal_connect( G_OBJECT( _compUI[i]._adj ), "value_changed", G_CALLBACK(
+ _adjustmentChanged ), _csel );
+
+ sp_color_slider_set_adjustment( SP_COLOR_SLIDER(_compUI[i]._slider),
+ _compUI[i]._adj );
+ gtk_spin_button_set_adjustment( GTK_SPIN_BUTTON(_compUI[i]._btn),
+ _compUI[i]._adj );
+ gtk_spin_button_set_digits( GTK_SPIN_BUTTON(_compUI[i]._btn), digits );
+ */
+ gtk_widget_show(_compUI[i]._label);
+ _compUI[i]._slider->show();
+ gtk_widget_show(_compUI[i]._btn);
+ // gtk_adjustment_set_value( _compUI[i]._adj, 0.0 );
+ // gtk_adjustment_set_value( _compUI[i]._adj, val );
+ }
+ for (size_t i = _profChannelCount; i < _compUI.size(); i++) {
+ gtk_widget_hide(_compUI[i]._label);
+ _compUI[i]._slider->hide();
+ gtk_widget_hide(_compUI[i]._btn);
+ }
+ }
+ }
+ else {
+ // Give up for now on named colors
+ _prof = nullptr;
+ }
+ }
+
+#ifdef DEBUG_LCMS
+ g_message("\\_________ %p::_setProfile()", this);
+#endif // DEBUG_LCMS
+}
+#endif // defined(HAVE_LIBLCMS2)
+
+void ColorICCSelectorImpl::_updateSliders(gint ignore)
+{
+#if defined(HAVE_LIBLCMS2)
+ if (_color.color().icc) {
+ for (guint i = 0; i < _profChannelCount; i++) {
+ gdouble val = 0.0;
+ if (_color.color().icc->colors.size() > i) {
+ if (_compUI[i]._component.scale == 256) {
+ val = (_color.color().icc->colors[i] + 128.0) / static_cast<gdouble>(_compUI[i]._component.scale);
+ }
+ else {
+ val = _color.color().icc->colors[i] / static_cast<gdouble>(_compUI[i]._component.scale);
+ }
+ }
+ gtk_adjustment_set_value(_compUI[i]._adj, val);
+ }
+
+ if (_prof) {
+ if (_prof->getTransfToSRGB8()) {
+ for (guint i = 0; i < _profChannelCount; i++) {
+ if (static_cast<gint>(i) != ignore) {
+ cmsUInt16Number *scratch = getScratch();
+ cmsUInt16Number filler[4] = { 0, 0, 0, 0 };
+ for (guint j = 0; j < _profChannelCount; j++) {
+ filler[j] = 0x0ffff * ColorScales::getScaled(_compUI[j]._adj);
+ }
+
+ cmsUInt16Number *p = scratch;
+ for (guint x = 0; x < 1024; x++) {
+ for (guint j = 0; j < _profChannelCount; j++) {
+ if (j == i) {
+ *p++ = x * 0x0ffff / 1024;
+ }
+ else {
+ *p++ = filler[j];
+ }
+ }
+ }
+
+ cmsHTRANSFORM trans = _prof->getTransfToSRGB8();
+ if (trans) {
+ cmsDoTransform(trans, scratch, _compUI[i]._map, 1024);
+ if (_compUI[i]._slider)
+ {
+ _compUI[i]._slider->setMap(_compUI[i]._map);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+#else
+ (void)ignore;
+#endif // defined(HAVE_LIBLCMS2)
+
+ guint32 start = _color.color().toRGBA32(0x00);
+ guint32 mid = _color.color().toRGBA32(0x7f);
+ guint32 end = _color.color().toRGBA32(0xff);
+
+ _slider->setColors(start, mid, end);
+}
+
+
+void ColorICCSelectorImpl::_adjustmentChanged(GtkAdjustment *adjustment, ColorICCSelectorImpl *cs)
+{
+#ifdef DEBUG_LCMS
+ g_message("/^^^^^^^^^ %p::_adjustmentChanged()", cs);
+#endif // DEBUG_LCMS
+
+ ColorICCSelector *iccSelector = cs->_owner;
+ if (iccSelector->_impl->_updating) {
+ return;
+ }
+
+ iccSelector->_impl->_updating = TRUE;
+
+ gint match = -1;
+
+ SPColor newColor(iccSelector->_impl->_color.color());
+ gfloat scaled = ColorScales::getScaled(iccSelector->_impl->_adj);
+ if (iccSelector->_impl->_adj == adjustment) {
+#ifdef DEBUG_LCMS
+ g_message("ALPHA");
+#endif // DEBUG_LCMS
+ }
+ else {
+#if defined(HAVE_LIBLCMS2)
+ for (size_t i = 0; i < iccSelector->_impl->_compUI.size(); i++) {
+ if (iccSelector->_impl->_compUI[i]._adj == adjustment) {
+ match = i;
+ break;
+ }
+ }
+ if (match >= 0) {
+#ifdef DEBUG_LCMS
+ g_message(" channel %d", match);
+#endif // DEBUG_LCMS
+ }
+
+
+ cmsUInt16Number tmp[4];
+ for (guint i = 0; i < 4; i++) {
+ tmp[i] = ColorScales::getScaled(iccSelector->_impl->_compUI[i]._adj) * 0x0ffff;
+ }
+ guchar post[4] = { 0, 0, 0, 0 };
+
+ cmsHTRANSFORM trans = iccSelector->_impl->_prof->getTransfToSRGB8();
+ if (trans) {
+ cmsDoTransform(trans, tmp, post, 1);
+ }
+
+ SPColor other(SP_RGBA32_U_COMPOSE(post[0], post[1], post[2], 255));
+ other.icc = new SVGICCColor();
+ if (iccSelector->_impl->_color.color().icc) {
+ other.icc->colorProfile = iccSelector->_impl->_color.color().icc->colorProfile;
+ }
+
+ guint32 prior = iccSelector->_impl->_color.color().toRGBA32(255);
+ guint32 newer = other.toRGBA32(255);
+
+ if (prior != newer) {
+#ifdef DEBUG_LCMS
+ g_message("Transformed color from 0x%08x to 0x%08x", prior, newer);
+ g_message(" ~~~~ FLIP");
+#endif // DEBUG_LCMS
+ newColor = other;
+ newColor.icc->colors.clear();
+ for (guint i = 0; i < iccSelector->_impl->_profChannelCount; i++) {
+ gdouble val = ColorScales::getScaled(iccSelector->_impl->_compUI[i]._adj);
+ val *= iccSelector->_impl->_compUI[i]._component.scale;
+ if (iccSelector->_impl->_compUI[i]._component.scale == 256) {
+ val -= 128;
+ }
+ newColor.icc->colors.push_back(val);
+ }
+ }
+#endif // defined(HAVE_LIBLCMS2)
+ }
+ iccSelector->_impl->_color.setColorAlpha(newColor, scaled);
+ // iccSelector->_updateInternals( newColor, scaled, iccSelector->_impl->_dragging );
+ iccSelector->_impl->_updateSliders(match);
+
+ iccSelector->_impl->_updating = FALSE;
+#ifdef DEBUG_LCMS
+ g_message("\\_________ %p::_adjustmentChanged()", cs);
+#endif // DEBUG_LCMS
+}
+
+void ColorICCSelectorImpl::_sliderGrabbed()
+{
+ // ColorICCSelector* iccSelector = dynamic_cast<ColorICCSelector*>(SP_COLOR_SELECTOR(cs)->base);
+ // if (!iccSelector->_dragging) {
+ // iccSelector->_dragging = TRUE;
+ // iccSelector->_grabbed();
+ // iccSelector->_updateInternals( iccSelector->_color, ColorScales::getScaled( iccSelector->_impl->_adj ),
+ // iccSelector->_dragging );
+ // }
+}
+
+void ColorICCSelectorImpl::_sliderReleased()
+{
+ // ColorICCSelector* iccSelector = dynamic_cast<ColorICCSelector*>(SP_COLOR_SELECTOR(cs)->base);
+ // if (iccSelector->_dragging) {
+ // iccSelector->_dragging = FALSE;
+ // iccSelector->_released();
+ // iccSelector->_updateInternals( iccSelector->_color, ColorScales::getScaled( iccSelector->_adj ),
+ // iccSelector->_dragging );
+ // }
+}
+
+#ifdef DEBUG_LCMS
+void ColorICCSelectorImpl::_sliderChanged(SPColorSlider *slider, SPColorICCSelector *cs)
+#else
+void ColorICCSelectorImpl::_sliderChanged()
+#endif // DEBUG_LCMS
+{
+#ifdef DEBUG_LCMS
+ g_message("Changed %p and %p", slider, cs);
+#endif // DEBUG_LCMS
+ // ColorICCSelector* iccSelector = dynamic_cast<ColorICCSelector*>(SP_COLOR_SELECTOR(cs)->base);
+
+ // iccSelector->_updateInternals( iccSelector->_color, ColorScales::getScaled( iccSelector->_adj ),
+ // iccSelector->_dragging );
+}
+
+Gtk::Widget *ColorICCSelectorFactory::createWidget(Inkscape::UI::SelectedColor &color) const
+{
+ Gtk::Widget *w = Gtk::manage(new ColorICCSelector(color));
+ return w;
+}
+
+Glib::ustring ColorICCSelectorFactory::modeName() const { return gettext(ColorICCSelector::MODE_NAME); }
+}
+}
+}
+/*
+ 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/color-icc-selector.h b/src/ui/widget/color-icc-selector.h
new file mode 100644
index 0000000..2c5ec41
--- /dev/null
+++ b/src/ui/widget/color-icc-selector.h
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_SP_COLOR_ICC_SELECTOR_H
+#define SEEN_SP_COLOR_ICC_SELECTOR_H
+
+#include <gtkmm/widget.h>
+#include <gtkmm/grid.h>
+
+#include "ui/selected-color.h"
+
+namespace Inkscape {
+
+class ColorProfile;
+
+namespace UI {
+namespace Widget {
+
+class ColorICCSelectorImpl;
+
+class ColorICCSelector
+ : public Gtk::Grid
+ {
+ public:
+ static const gchar *MODE_NAME;
+
+ ColorICCSelector(SelectedColor &color);
+ ~ColorICCSelector() override;
+
+ virtual void init();
+
+ protected:
+ void on_show() override;
+
+ virtual void _colorChanged();
+
+ void _recalcColor(gboolean changing);
+
+ private:
+ friend class ColorICCSelectorImpl;
+
+ // By default, disallow copy constructor and assignment operator
+ ColorICCSelector(const ColorICCSelector &obj);
+ ColorICCSelector &operator=(const ColorICCSelector &obj);
+
+ ColorICCSelectorImpl *_impl;
+};
+
+
+class ColorICCSelectorFactory : public ColorSelectorFactory {
+ public:
+ Gtk::Widget *createWidget(SelectedColor &color) const override;
+ Glib::ustring modeName() const override;
+};
+}
+}
+}
+#endif // SEEN_SP_COLOR_ICC_SELECTOR_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/color-notebook.cpp b/src/ui/widget/color-notebook.cpp
new file mode 100644
index 0000000..474b4d2
--- /dev/null
+++ b/src/ui/widget/color-notebook.cpp
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * A notebook with RGB, CMYK, CMS, HSL, and Wheel pages
+ *//*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Tomasz Boczkowski <penginsbacon@gmail.com> (c++-sification)
+ *
+ * Copyright (C) 2001-2014 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#undef SPCS_PREVIEW
+#define noDUMP_CHANGE_INFO
+
+#include <glibmm/i18n.h>
+#include <gtkmm/label.h>
+#include <gtkmm/notebook.h>
+#include <gtkmm/radiobutton.h>
+
+#include "cms-system.h"
+#include "document.h"
+#include "inkscape.h"
+#include "preferences.h"
+#include "profile-manager.h"
+
+#include "object/color-profile.h"
+#include "ui/icon-loader.h"
+
+#include "svg/svg-icc-color.h"
+
+#include "ui/dialog-events.h"
+#include "ui/tools-switch.h"
+#include "ui/tools/tool-base.h"
+#include "ui/widget/color-entry.h"
+#include "ui/widget/color-icc-selector.h"
+#include "ui/widget/color-notebook.h"
+#include "ui/widget/color-scales.h"
+#include "ui/widget/color-wheel-selector.h"
+
+#include "widgets/spw-utilities.h"
+
+using Inkscape::CMSSystem;
+
+#define XPAD 4
+#define YPAD 1
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+
+ColorNotebook::ColorNotebook(SelectedColor &color)
+ : Gtk::Grid()
+ , _selected_color(color)
+{
+ set_name("ColorNotebook");
+
+ Page *page;
+
+ page = new Page(new ColorScalesFactory(SP_COLOR_SCALES_MODE_RGB), true);
+ _available_pages.push_back(page);
+ page = new Page(new ColorScalesFactory(SP_COLOR_SCALES_MODE_HSL), true);
+ _available_pages.push_back(page);
+ page = new Page(new ColorScalesFactory(SP_COLOR_SCALES_MODE_HSV), true);
+ _available_pages.push_back(page);
+ page = new Page(new ColorScalesFactory(SP_COLOR_SCALES_MODE_CMYK), true);
+ _available_pages.push_back(page);
+ page = new Page(new ColorWheelSelectorFactory, true);
+ _available_pages.push_back(page);
+#if defined(HAVE_LIBLCMS2)
+ page = new Page(new ColorICCSelectorFactory, true);
+ _available_pages.push_back(page);
+#endif
+
+ _initUI();
+
+ _selected_color.signal_changed.connect(sigc::mem_fun(this, &ColorNotebook::_onSelectedColorChanged));
+ _selected_color.signal_dragged.connect(sigc::mem_fun(this, &ColorNotebook::_onSelectedColorChanged));
+}
+
+ColorNotebook::~ColorNotebook()
+{
+ if (_buttons) {
+ delete[] _buttons;
+ _buttons = nullptr;
+ }
+}
+
+ColorNotebook::Page::Page(Inkscape::UI::ColorSelectorFactory *selector_factory, bool enabled_full)
+ : selector_factory(selector_factory)
+ , enabled_full(enabled_full)
+{
+}
+
+
+void ColorNotebook::_initUI()
+{
+ guint row = 0;
+
+ Gtk::Notebook *notebook = Gtk::manage(new Gtk::Notebook);
+ notebook->show();
+ notebook->set_show_border(false);
+ notebook->set_show_tabs(false);
+ _book = GTK_WIDGET(notebook->gobj());
+
+ _buttonbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_box_set_homogeneous(GTK_BOX(_buttonbox), TRUE);
+
+ gtk_widget_show(_buttonbox);
+ _buttons = new GtkWidget *[_available_pages.size()];
+
+ for (int i = 0; static_cast<size_t>(i) < _available_pages.size(); i++) {
+ _addPage(_available_pages[i]);
+ }
+
+ gtk_widget_set_margin_start(_buttonbox, XPAD);
+ gtk_widget_set_margin_end(_buttonbox, XPAD);
+ gtk_widget_set_margin_top(_buttonbox, YPAD);
+ gtk_widget_set_margin_bottom(_buttonbox, YPAD);
+ gtk_widget_set_hexpand(_buttonbox, TRUE);
+ gtk_widget_set_valign(_buttonbox, GTK_ALIGN_CENTER);
+ attach(*Glib::wrap(_buttonbox), 0, row, 2, 1);
+
+ row++;
+
+ gtk_widget_set_margin_start(_book, XPAD * 2);
+ gtk_widget_set_margin_end(_book, XPAD * 2);
+ gtk_widget_set_margin_top(_book, YPAD);
+ gtk_widget_set_margin_bottom(_book, YPAD);
+ gtk_widget_set_hexpand(_book, TRUE);
+ gtk_widget_set_vexpand(_book, TRUE);
+ attach(*notebook, 0, row, 2, 1);
+
+ // restore the last active page
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _setCurrentPage(prefs->getInt("/colorselector/page", 0));
+ row++;
+
+ GtkWidget *rgbabox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+
+#if defined(HAVE_LIBLCMS2)
+ /* Create color management icons */
+ _box_colormanaged = gtk_event_box_new();
+ GtkWidget *colormanaged = sp_get_icon_image("color-management", GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gtk_container_add(GTK_CONTAINER(_box_colormanaged), colormanaged);
+ gtk_widget_set_tooltip_text(_box_colormanaged, _("Color Managed"));
+ gtk_widget_set_sensitive(_box_colormanaged, false);
+ gtk_box_pack_start(GTK_BOX(rgbabox), _box_colormanaged, FALSE, FALSE, 2);
+
+ _box_outofgamut = gtk_event_box_new();
+ GtkWidget *outofgamut = sp_get_icon_image("out-of-gamut-icon", GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gtk_container_add(GTK_CONTAINER(_box_outofgamut), outofgamut);
+ gtk_widget_set_tooltip_text(_box_outofgamut, _("Out of gamut!"));
+ gtk_widget_set_sensitive(_box_outofgamut, false);
+ gtk_box_pack_start(GTK_BOX(rgbabox), _box_outofgamut, FALSE, FALSE, 2);
+
+ _box_toomuchink = gtk_event_box_new();
+ GtkWidget *toomuchink = sp_get_icon_image("too-much-ink-icon", GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gtk_container_add(GTK_CONTAINER(_box_toomuchink), toomuchink);
+ gtk_widget_set_tooltip_text(_box_toomuchink, _("Too much ink!"));
+ gtk_widget_set_sensitive(_box_toomuchink, false);
+ gtk_box_pack_start(GTK_BOX(rgbabox), _box_toomuchink, FALSE, FALSE, 2);
+#endif // defined(HAVE_LIBLCMS2)
+
+
+ /* Color picker */
+ GtkWidget *picker = sp_get_icon_image("color-picker", GTK_ICON_SIZE_SMALL_TOOLBAR);
+ _btn_picker = gtk_button_new();
+ gtk_button_set_relief(GTK_BUTTON(_btn_picker), GTK_RELIEF_NONE);
+ gtk_container_add(GTK_CONTAINER(_btn_picker), picker);
+ gtk_widget_set_tooltip_text(_btn_picker, _("Pick colors from image"));
+ gtk_box_pack_start(GTK_BOX(rgbabox), _btn_picker, FALSE, FALSE, 2);
+ g_signal_connect(G_OBJECT(_btn_picker), "clicked", G_CALLBACK(ColorNotebook::_onPickerClicked), this);
+
+ /* Create RGBA entry and color preview */
+ _rgbal = gtk_label_new_with_mnemonic(_("RGBA_:"));
+ gtk_widget_set_halign(_rgbal, GTK_ALIGN_END);
+ gtk_box_pack_start(GTK_BOX(rgbabox), _rgbal, TRUE, TRUE, 2);
+
+ ColorEntry *rgba_entry = Gtk::manage(new ColorEntry(_selected_color));
+ sp_dialog_defocus_on_enter(GTK_WIDGET(rgba_entry->gobj()));
+ gtk_box_pack_start(GTK_BOX(rgbabox), GTK_WIDGET(rgba_entry->gobj()), FALSE, FALSE, 0);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(_rgbal), GTK_WIDGET(rgba_entry->gobj()));
+
+ gtk_widget_show_all(rgbabox);
+
+#if defined(HAVE_LIBLCMS2)
+ // the "too much ink" icon is initially hidden
+ gtk_widget_hide(GTK_WIDGET(_box_toomuchink));
+#endif // defined(HAVE_LIBLCMS2)
+
+ gtk_widget_set_margin_start(rgbabox, XPAD);
+ gtk_widget_set_margin_end(rgbabox, XPAD);
+ gtk_widget_set_margin_top(rgbabox, YPAD);
+ gtk_widget_set_margin_bottom(rgbabox, YPAD);
+ attach(*Glib::wrap(rgbabox), 0, row, 2, 1);
+
+#ifdef SPCS_PREVIEW
+ _p = sp_color_preview_new(0xffffffff);
+ gtk_widget_show(_p);
+ attach(*Glib::wrap(_p), 2, 3, row, row + 1, Gtk::FILL, Gtk::FILL, XPAD, YPAD);
+#endif
+
+ g_signal_connect(G_OBJECT(_book), "switch-page", G_CALLBACK(ColorNotebook::_onPageSwitched), this);
+}
+
+void ColorNotebook::_onPickerClicked(GtkWidget * /*widget*/, ColorNotebook * /*colorbook*/)
+{
+ // Set the dropper into a "one click" mode, so it reverts to the previous tool after a click
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/dropper/onetimepick", true);
+ Inkscape::UI::Tools::sp_toggle_dropper(SP_ACTIVE_DESKTOP);
+}
+
+void ColorNotebook::_onButtonClicked(GtkWidget *widget, ColorNotebook *nb)
+{
+ if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
+ return;
+ }
+
+ for (gint i = 0; i < gtk_notebook_get_n_pages(GTK_NOTEBOOK(nb->_book)); i++) {
+ if (nb->_buttons[i] == widget) {
+ gtk_notebook_set_current_page(GTK_NOTEBOOK(nb->_book), i);
+ }
+ }
+}
+
+void ColorNotebook::_onSelectedColorChanged() { _updateICCButtons(); }
+
+void ColorNotebook::_onPageSwitched(GtkNotebook *notebook, GtkWidget *page, guint page_num, ColorNotebook *colorbook)
+{
+ if (colorbook->get_visible()) {
+ // remember the page we switched to
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/colorselector/page", page_num);
+ }
+}
+
+
+// TODO pass in param so as to avoid the need for SP_ACTIVE_DOCUMENT
+void ColorNotebook::_updateICCButtons()
+{
+ SPColor color = _selected_color.color();
+ gfloat alpha = _selected_color.alpha();
+
+ g_return_if_fail((0.0 <= alpha) && (alpha <= 1.0));
+
+#if defined(HAVE_LIBLCMS2)
+ /* update color management icon*/
+ gtk_widget_set_sensitive(_box_colormanaged, color.icc != nullptr);
+
+ /* update out-of-gamut icon */
+ gtk_widget_set_sensitive(_box_outofgamut, false);
+ if (color.icc) {
+ Inkscape::ColorProfile *target_profile =
+ SP_ACTIVE_DOCUMENT->getProfileManager()->find(color.icc->colorProfile.c_str());
+ if (target_profile)
+ gtk_widget_set_sensitive(_box_outofgamut, target_profile->GamutCheck(color));
+ }
+
+ /* update too-much-ink icon */
+ gtk_widget_set_sensitive(_box_toomuchink, false);
+ if (color.icc) {
+ Inkscape::ColorProfile *prof = SP_ACTIVE_DOCUMENT->getProfileManager()->find(color.icc->colorProfile.c_str());
+ if (prof && CMSSystem::isPrintColorSpace(prof)) {
+ gtk_widget_show(GTK_WIDGET(_box_toomuchink));
+ double ink_sum = 0;
+ for (double i : color.icc->colors) {
+ ink_sum += i;
+ }
+
+ /* Some literature states that when the sum of paint values exceed 320%, it is considered to be a satured
+ color,
+ which means the paper can get too wet due to an excessive amount of ink. This may lead to several
+ issues
+ such as misalignment and poor quality of printing in general.*/
+ if (ink_sum > 3.2)
+ gtk_widget_set_sensitive(_box_toomuchink, true);
+ }
+ else {
+ gtk_widget_hide(GTK_WIDGET(_box_toomuchink));
+ }
+ }
+#endif // defined(HAVE_LIBLCMS2)
+}
+
+void ColorNotebook::_setCurrentPage(int i)
+{
+ gtk_notebook_set_current_page(GTK_NOTEBOOK(_book), i);
+
+ if (_buttons && (static_cast<size_t>(i) < _available_pages.size())) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_buttons[i]), TRUE);
+ }
+}
+
+void ColorNotebook::_addPage(Page &page)
+{
+ Gtk::Widget *selector_widget;
+
+ selector_widget = page.selector_factory->createWidget(_selected_color);
+ if (selector_widget) {
+ selector_widget->show();
+
+ Glib::ustring mode_name = page.selector_factory->modeName();
+ Gtk::Widget *tab_label = Gtk::manage(new Gtk::Label(mode_name));
+ tab_label->set_name("ColorModeLabel");
+ gint page_num = gtk_notebook_append_page(GTK_NOTEBOOK(_book), selector_widget->gobj(), tab_label->gobj());
+
+ _buttons[page_num] = gtk_radio_button_new_with_label(nullptr, mode_name.c_str());
+ gtk_widget_set_name(_buttons[page_num], "ColorModeButton");
+ gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(_buttons[page_num]), FALSE);
+ if (page_num > 0) {
+ auto g = Glib::wrap(GTK_RADIO_BUTTON(_buttons[0]))->get_group();
+ Glib::wrap(GTK_RADIO_BUTTON(_buttons[page_num]))->set_group(g);
+ }
+ gtk_widget_show(_buttons[page_num]);
+ gtk_box_pack_start(GTK_BOX(_buttonbox), _buttons[page_num], TRUE, TRUE, 0);
+
+ g_signal_connect(G_OBJECT(_buttons[page_num]), "clicked", G_CALLBACK(_onButtonClicked), this);
+ }
+}
+}
+}
+}
+
+/*
+ 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/color-notebook.h b/src/ui/widget/color-notebook.h
new file mode 100644
index 0000000..c7bc7b5
--- /dev/null
+++ b/src/ui/widget/color-notebook.h
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * A notebook with RGB, CMYK, CMS, HSL, and Wheel pages
+ *//*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Tomasz Boczkowski <penginsbacon@gmail.com> (c++-sification)
+ *
+ * Copyright (C) 2001-2014 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_SP_COLOR_NOTEBOOK_H
+#define SEEN_SP_COLOR_NOTEBOOK_H
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#include <boost/ptr_container/ptr_vector.hpp>
+#include <gtkmm/grid.h>
+#include <glib.h>
+
+#include "color.h"
+#include "ui/selected-color.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class ColorNotebook
+ : public Gtk::Grid
+{
+public:
+ ColorNotebook(SelectedColor &color);
+ ~ColorNotebook() override;
+
+protected:
+ struct Page {
+ Page(Inkscape::UI::ColorSelectorFactory *selector_factory, bool enabled_full);
+
+ Inkscape::UI::ColorSelectorFactory *selector_factory;
+ bool enabled_full;
+ };
+
+ virtual void _initUI();
+ void _addPage(Page &page);
+
+ static void _onButtonClicked(GtkWidget *widget, ColorNotebook *colorbook);
+ static void _onPickerClicked(GtkWidget *widget, ColorNotebook *colorbook);
+ static void _onPageSwitched(GtkNotebook *notebook, GtkWidget *page, guint page_num, ColorNotebook *colorbook);
+ virtual void _onSelectedColorChanged();
+
+ void _updateICCButtons();
+ void _setCurrentPage(int i);
+
+ Inkscape::UI::SelectedColor &_selected_color;
+ gulong _entryId;
+ GtkWidget *_book;
+ GtkWidget *_buttonbox;
+ GtkWidget **_buttons;
+ GtkWidget *_rgbal; /* RGBA entry */
+#if defined(HAVE_LIBLCMS2)
+ GtkWidget *_box_outofgamut, *_box_colormanaged, *_box_toomuchink;
+#endif // defined(HAVE_LIBLCMS2)
+ GtkWidget *_btn_picker;
+ GtkWidget *_p; /* Color preview */
+ boost::ptr_vector<Page> _available_pages;
+
+private:
+ // By default, disallow copy constructor and assignment operator
+ ColorNotebook(const ColorNotebook &obj) = delete;
+ ColorNotebook &operator=(const ColorNotebook &obj) = delete;
+};
+}
+}
+}
+#endif // SEEN_SP_COLOR_NOTEBOOK_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/color-picker.cpp b/src/ui/widget/color-picker.cpp
new file mode 100644
index 0000000..5beb090
--- /dev/null
+++ b/src/ui/widget/color-picker.cpp
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Ralf Stephan <ralf@ark.in-berlin.de>
+ * Abhishek Sharma
+ *
+ * Copyright (C) Authors 2000-2005
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "color-picker.h"
+#include "inkscape.h"
+#include "desktop.h"
+#include "document.h"
+#include "document-undo.h"
+#include "ui/dialog-events.h"
+
+#include "ui/widget/color-notebook.h"
+#include "verbs.h"
+
+
+static bool _in_use = false;
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+ColorPicker::ColorPicker (const Glib::ustring& title, const Glib::ustring& tip,
+ guint32 rgba, bool undo)
+ : _preview(rgba), _title(title), _rgba(rgba), _undo(undo),
+ _colorSelectorDialog("dialogs.colorpickerwindow")
+{
+ setupDialog(title);
+ _preview.show();
+ add (_preview);
+ set_tooltip_text (tip);
+ _selected_color.signal_changed.connect(sigc::mem_fun(this, &ColorPicker::_onSelectedColorChanged));
+ _selected_color.signal_dragged.connect(sigc::mem_fun(this, &ColorPicker::_onSelectedColorChanged));
+ _selected_color.signal_released.connect(sigc::mem_fun(this, &ColorPicker::_onSelectedColorChanged));
+}
+
+ColorPicker::~ColorPicker()
+{
+ closeWindow();
+}
+
+void ColorPicker::setupDialog(const Glib::ustring &title)
+{
+ GtkWidget *dlg = GTK_WIDGET(_colorSelectorDialog.gobj());
+ sp_transientize(dlg);
+
+ _colorSelectorDialog.hide();
+ _colorSelectorDialog.set_title (title);
+ _colorSelectorDialog.set_border_width (4);
+
+ _color_selector = Gtk::manage(new ColorNotebook(_selected_color));
+ _colorSelectorDialog.get_content_area()->pack_start (
+ *_color_selector, true, true, 0);
+ _color_selector->show();
+}
+
+void ColorPicker::setSensitive(bool sensitive) { set_sensitive(sensitive); }
+
+void ColorPicker::setRgba32 (guint32 rgba)
+{
+ if (_in_use) return;
+
+ _preview.setRgba32 (rgba);
+ _rgba = rgba;
+ if (_color_selector)
+ {
+ _updating = true;
+ _selected_color.setValue(rgba);
+ _updating = false;
+ }
+}
+
+void ColorPicker::closeWindow()
+{
+ _colorSelectorDialog.hide();
+}
+
+void ColorPicker::on_clicked()
+{
+ if (_color_selector)
+ {
+ _updating = true;
+ _selected_color.setValue(_rgba);
+ _updating = false;
+ }
+ _colorSelectorDialog.show();
+ Glib::RefPtr<Gdk::Window> window = _colorSelectorDialog.get_parent_window();
+ if (window) {
+ window->focus(1);
+ }
+}
+
+void ColorPicker::on_changed (guint32)
+{
+}
+
+void ColorPicker::_onSelectedColorChanged() {
+ if (_updating) {
+ return;
+ }
+
+ if (_in_use) {
+ return;
+ } else {
+ _in_use = true;
+ }
+
+ guint32 rgba = _selected_color.value();
+ _preview.setRgba32(rgba);
+
+ if (_undo && SP_ACTIVE_DESKTOP) {
+ DocumentUndo::done(SP_ACTIVE_DESKTOP->getDocument(), SP_VERB_NONE,
+ /* TODO: annotate */ "color-picker.cpp:130");
+ }
+
+ on_changed(rgba);
+ _in_use = false;
+ _changed_signal.emit(rgba);
+ _rgba = rgba;
+}
+
+}//namespace Widget
+}//namespace UI
+}//namespace Inkscape
+
+
+/*
+ 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/color-picker.h b/src/ui/widget/color-picker.h
new file mode 100644
index 0000000..b98f832
--- /dev/null
+++ b/src/ui/widget/color-picker.h
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Color picker button and window.
+ */
+/* Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Ralf Stephan <ralf@ark.in-berlin.de>
+ *
+ * Copyright (C) Authors 2000-2005
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef __COLOR_PICKER_H__
+#define __COLOR_PICKER_H__
+
+#include "labelled.h"
+
+#include <cstddef>
+
+#include "ui/selected-color.h"
+#include "ui/widget/color-preview.h"
+#include <gtkmm/button.h>
+#include <gtkmm/dialog.h>
+#include <gtkmm/window.h>
+#include <sigc++/sigc++.h>
+
+struct SPColorSelector;
+
+namespace Inkscape
+{
+namespace UI
+{
+namespace Widget
+{
+
+
+class ColorPicker : public Gtk::Button {
+public:
+
+ ColorPicker (const Glib::ustring& title,
+ const Glib::ustring& tip,
+ const guint32 rgba,
+ bool undo);
+
+ ~ColorPicker() override;
+
+ void setRgba32 (guint32 rgba);
+ void setSensitive(bool sensitive);
+ void closeWindow();
+ sigc::connection connectChanged (const sigc::slot<void,guint>& slot)
+ { return _changed_signal.connect (slot); }
+
+protected:
+
+ void _onSelectedColorChanged();
+ void on_clicked() override;
+ virtual void on_changed (guint32);
+
+ ColorPreview _preview;
+
+ /*const*/ Glib::ustring _title;
+ sigc::signal<void,guint32> _changed_signal;
+ guint32 _rgba;
+ bool _undo;
+ bool _updating;
+
+ //Dialog
+ void setupDialog(const Glib::ustring &title);
+ //Inkscape::UI::Dialog::Dialog _colorSelectorDialog;
+ Gtk::Dialog _colorSelectorDialog;
+ SelectedColor _selected_color;
+ Gtk::Widget *_color_selector;
+};
+
+
+class LabelledColorPicker : public Labelled {
+public:
+
+ LabelledColorPicker (const Glib::ustring& label,
+ const Glib::ustring& title,
+ const Glib::ustring& tip,
+ const guint32 rgba,
+ bool undo) : Labelled(label, tip, new ColorPicker(title, tip, rgba, undo)) {}
+
+ ~LabelledColorPicker() override
+ { static_cast<ColorPicker*>(_widget)->~ColorPicker(); }
+
+ void setRgba32 (guint32 rgba)
+ { static_cast<ColorPicker*>(_widget)->setRgba32 (rgba); }
+
+ void closeWindow()
+ { static_cast<ColorPicker*>(_widget)->closeWindow (); }
+
+ sigc::connection connectChanged (const sigc::slot<void,guint>& slot)
+ { return static_cast<ColorPicker*>(_widget)->connectChanged(slot); }
+};
+
+}//namespace Widget
+}//namespace UI
+}//namespace Inkscape
+
+#endif /* !__COLOR_PICKER_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/color-preview.cpp b/src/ui/widget/color-preview.cpp
new file mode 100644
index 0000000..ac8fc57
--- /dev/null
+++ b/src/ui/widget/color-preview.cpp
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Ralf Stephan <ralf@ark.in-berlin.de>
+ *
+ * Copyright (C) 2001-2005 Authors
+ * Copyright (C) 2001 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "ui/widget/color-preview.h"
+#include "display/cairo-utils.h"
+#include <cairo.h>
+
+#define SPCP_DEFAULT_WIDTH 32
+#define SPCP_DEFAULT_HEIGHT 12
+
+namespace Inkscape {
+ namespace UI {
+ namespace Widget {
+
+ColorPreview::ColorPreview (guint32 rgba)
+{
+ _rgba = rgba;
+ set_has_window(false);
+ set_name("ColorPreview");
+}
+
+void
+ColorPreview::on_size_allocate (Gtk::Allocation &all)
+{
+ set_allocation (all);
+ if (get_is_drawable())
+ queue_draw();
+}
+
+void
+ColorPreview::get_preferred_height_vfunc(int& minimum_height, int& natural_height) const
+{
+ minimum_height = natural_height = SPCP_DEFAULT_HEIGHT;
+}
+
+void
+ColorPreview::get_preferred_height_for_width_vfunc(int /* width */, int& minimum_height, int& natural_height) const
+{
+ minimum_height = natural_height = SPCP_DEFAULT_HEIGHT;
+}
+
+void
+ColorPreview::get_preferred_width_vfunc(int& minimum_width, int& natural_width) const
+{
+ minimum_width = natural_width = SPCP_DEFAULT_WIDTH;
+}
+
+void
+ColorPreview::get_preferred_width_for_height_vfunc(int /* height */, int& minimum_width, int& natural_width) const
+{
+ minimum_width = natural_width = SPCP_DEFAULT_WIDTH;
+}
+
+void
+ColorPreview::setRgba32 (guint32 rgba)
+{
+ _rgba = rgba;
+
+ if (get_is_drawable())
+ queue_draw();
+}
+
+bool
+ColorPreview::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
+{
+ double x, y, width, height;
+ const Gtk::Allocation& allocation = get_allocation();
+ x = 0;
+ y = 0;
+ width = allocation.get_width()/2.0;
+ height = allocation.get_height();
+
+ double radius = height / 7.5;
+ double degrees = M_PI / 180.0;
+ cairo_new_sub_path (cr->cobj());
+ cairo_line_to(cr->cobj(), width, 0);
+ cairo_line_to(cr->cobj(), width, height);
+ cairo_arc (cr->cobj(), x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees);
+ cairo_arc (cr->cobj(), x + radius, y + radius, radius, 180 * degrees, 270 * degrees);
+ cairo_close_path (cr->cobj());
+
+ /* Transparent area */
+
+ cairo_pattern_t *checkers = ink_cairo_pattern_create_checkerboard();
+
+ cairo_set_source(cr->cobj(), checkers);
+ cr->fill_preserve();
+ ink_cairo_set_source_rgba32(cr->cobj(), _rgba);
+ cr->fill();
+ cairo_pattern_destroy(checkers);
+
+ /* Solid area */
+
+ x = width;
+
+ cairo_new_sub_path (cr->cobj());
+ cairo_arc (cr->cobj(), x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees);
+ cairo_arc (cr->cobj(), x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees);
+ cairo_line_to(cr->cobj(), x, height);
+ cairo_line_to(cr->cobj(), x, y);
+ cairo_close_path (cr->cobj());
+ ink_cairo_set_source_rgba32(cr->cobj(), _rgba | 0xff);
+ cr->fill();
+
+ return true;
+}
+
+GdkPixbuf*
+ColorPreview::toPixbuf (int width, int height)
+{
+ GdkRectangle carea;
+ gint w2;
+ w2 = width / 2;
+
+ cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
+ cairo_t *ct = cairo_create(s);
+
+ /* Transparent area */
+ carea.x = 0;
+ carea.y = 0;
+ carea.width = w2;
+ carea.height = height;
+
+ cairo_pattern_t *checkers = ink_cairo_pattern_create_checkerboard();
+
+ cairo_rectangle(ct, carea.x, carea.y, carea.width, carea.height);
+ cairo_set_source(ct, checkers);
+ cairo_fill_preserve(ct);
+ ink_cairo_set_source_rgba32(ct, _rgba);
+ cairo_fill(ct);
+
+ cairo_pattern_destroy(checkers);
+
+ /* Solid area */
+ carea.x = w2;
+ carea.y = 0;
+ carea.width = width - w2;
+ carea.height = height;
+
+ cairo_rectangle(ct, carea.x, carea.y, carea.width, carea.height);
+ ink_cairo_set_source_rgba32(ct, _rgba | 0xff);
+ cairo_fill(ct);
+
+ cairo_destroy(ct);
+ cairo_surface_flush(s);
+
+ GdkPixbuf* pixbuf = ink_pixbuf_create_from_cairo_surface(s);
+ return pixbuf;
+}
+
+}}}
+
+/*
+ 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 :
diff --git a/src/ui/widget/color-preview.h b/src/ui/widget/color-preview.h
new file mode 100644
index 0000000..b789579
--- /dev/null
+++ b/src/ui/widget/color-preview.h
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_COLOR_PREVIEW_H
+#define SEEN_COLOR_PREVIEW_H
+/*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Ralf Stephan <ralf@ark.in-berlin.de>
+ *
+ * Copyright (C) 2001-2005 Authors
+ * Copyright (C) 2001 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/widget.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * A simple color preview widget, mainly used within a picker button.
+ */
+class ColorPreview : public Gtk::Widget {
+public:
+ ColorPreview (guint32 rgba);
+ void setRgba32 (guint32 rgba);
+ GdkPixbuf* toPixbuf (int width, int height);
+
+protected:
+ void on_size_allocate (Gtk::Allocation &all) override;
+
+ void get_preferred_height_vfunc(int& minimum_height, int& natural_height) const override;
+ void get_preferred_height_for_width_vfunc(int width, int& minimum_height, int& natural_height) const override;
+ void get_preferred_width_vfunc(int& minimum_width, int& natural_width) const override;
+ void get_preferred_width_for_height_vfunc(int height, int& minimum_width, int& natural_width) const override;
+ bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;
+
+ guint32 _rgba;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // SEEN_COLOR_PREVIEW_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 :
diff --git a/src/ui/widget/color-scales.cpp b/src/ui/widget/color-scales.cpp
new file mode 100644
index 0000000..a93ab2a
--- /dev/null
+++ b/src/ui/widget/color-scales.cpp
@@ -0,0 +1,736 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/adjustment.h>
+#include <glibmm/i18n.h>
+
+#include "ui/dialog-events.h"
+#include "ui/widget/color-scales.h"
+#include "ui/widget/color-slider.h"
+
+#define CSC_CHANNEL_R (1 << 0)
+#define CSC_CHANNEL_G (1 << 1)
+#define CSC_CHANNEL_B (1 << 2)
+#define CSC_CHANNEL_A (1 << 3)
+#define CSC_CHANNEL_H (1 << 0)
+#define CSC_CHANNEL_S (1 << 1)
+#define CSC_CHANNEL_V (1 << 2)
+#define CSC_CHANNEL_C (1 << 0)
+#define CSC_CHANNEL_M (1 << 1)
+#define CSC_CHANNEL_Y (1 << 2)
+#define CSC_CHANNEL_K (1 << 3)
+#define CSC_CHANNEL_CMYKA (1 << 4)
+
+#define CSC_CHANNELS_ALL 0
+
+#define XPAD 4
+#define YPAD 1
+
+#define noDUMP_CHANGE_INFO 1
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+
+static const gchar *sp_color_scales_hue_map();
+
+const gchar *ColorScales::SUBMODE_NAMES[] = { N_("None"), N_("RGB"), N_("HSL"), N_("CMYK"), N_("HSV") };
+
+ColorScales::ColorScales(SelectedColor &color, SPColorScalesMode mode)
+ : Gtk::Grid()
+ , _color(color)
+ , _rangeLimit(255.0)
+ , _updating(FALSE)
+ , _dragging(FALSE)
+ , _mode(SP_COLOR_SCALES_MODE_NONE)
+{
+ for (gint i = 0; i < 5; i++) {
+ _l[i] = nullptr;
+ _a[i] = nullptr;
+ _s[i] = nullptr;
+ _b[i] = nullptr;
+ }
+
+ _initUI(mode);
+
+ _color.signal_changed.connect(sigc::mem_fun(this, &ColorScales::_onColorChanged));
+ _color.signal_dragged.connect(sigc::mem_fun(this, &ColorScales::_onColorChanged));
+}
+
+ColorScales::~ColorScales()
+{
+ for (gint i = 0; i < 5; i++) {
+ _l[i] = nullptr;
+ _a[i] = nullptr;
+ _s[i] = nullptr;
+ _b[i] = nullptr;
+ }
+}
+
+void ColorScales::_initUI(SPColorScalesMode mode)
+{
+ gint i;
+
+ _updating = FALSE;
+ _dragging = FALSE;
+
+ GtkWidget *t = GTK_WIDGET(gobj());
+
+ /* Create components */
+ for (i = 0; i < static_cast<gint>(G_N_ELEMENTS(_a)); i++) {
+ /* Label */
+ _l[i] = gtk_label_new("");
+
+ gtk_widget_set_halign(_l[i], GTK_ALIGN_START);
+ gtk_widget_show(_l[i]);
+
+ gtk_widget_set_margin_start(_l[i], XPAD);
+ gtk_widget_set_margin_end(_l[i], XPAD);
+ gtk_widget_set_margin_top(_l[i], YPAD);
+ gtk_widget_set_margin_bottom(_l[i], YPAD);
+ gtk_grid_attach(GTK_GRID(t), _l[i], 0, i, 1, 1);
+
+ /* Adjustment */
+ _a[i] = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, _rangeLimit, 1.0, 10.0, 10.0));
+ /* Slider */
+ _s[i] = Gtk::manage(new Inkscape::UI::Widget::ColorSlider(Glib::wrap(_a[i], true)));
+ _s[i]->show();
+
+ _s[i]->set_margin_start(XPAD);
+ _s[i]->set_margin_end(XPAD);
+ _s[i]->set_margin_top(YPAD);
+ _s[i]->set_margin_bottom(YPAD);
+ _s[i]->set_hexpand(true);
+ gtk_grid_attach(GTK_GRID(t), _s[i]->gobj(), 1, i, 1, 1);
+
+ /* Spinbutton */
+ _b[i] = gtk_spin_button_new(GTK_ADJUSTMENT(_a[i]), 1.0, 0);
+ sp_dialog_defocus_on_enter(_b[i]);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(_l[i]), _b[i]);
+ gtk_widget_show(_b[i]);
+
+ gtk_widget_set_margin_start(_b[i], XPAD);
+ gtk_widget_set_margin_end(_b[i], XPAD);
+ gtk_widget_set_margin_top(_b[i], YPAD);
+ gtk_widget_set_margin_bottom(_b[i], YPAD);
+ gtk_widget_set_halign(_b[i], GTK_ALIGN_END);
+ gtk_widget_set_valign(_b[i], GTK_ALIGN_CENTER);
+ gtk_grid_attach(GTK_GRID(t), _b[i], 2, i, 1, 1);
+
+ /* Attach channel value to adjustment */
+ g_object_set_data(G_OBJECT(_a[i]), "channel", GINT_TO_POINTER(i));
+ /* Signals */
+ g_signal_connect(G_OBJECT(_a[i]), "value_changed", G_CALLBACK(_adjustmentAnyChanged), this);
+ _s[i]->signal_grabbed.connect(sigc::mem_fun(this, &ColorScales::_sliderAnyGrabbed));
+ _s[i]->signal_released.connect(sigc::mem_fun(this, &ColorScales::_sliderAnyReleased));
+ _s[i]->signal_value_changed.connect(sigc::mem_fun(this, &ColorScales::_sliderAnyChanged));
+ }
+
+ //Prevent 5th bar from being shown by PanelDialog::show_all_children
+ gtk_widget_set_no_show_all(_l[4], TRUE);
+ _s[4]->set_no_show_all(true);
+ gtk_widget_set_no_show_all(_b[4], TRUE);
+
+ /* Initial mode is none, so it works */
+ setMode(mode);
+}
+
+void ColorScales::_recalcColor()
+{
+ SPColor color;
+ gfloat alpha = 1.0;
+ gfloat c[5];
+
+ switch (_mode) {
+ case SP_COLOR_SCALES_MODE_RGB:
+ case SP_COLOR_SCALES_MODE_HSL:
+ case SP_COLOR_SCALES_MODE_HSV:
+ _getRgbaFloatv(c);
+ color.set(c[0], c[1], c[2]);
+ alpha = c[3];
+ break;
+ case SP_COLOR_SCALES_MODE_CMYK: {
+ _getCmykaFloatv(c);
+
+ float rgb[3];
+ SPColor::cmyk_to_rgb_floatv(rgb, c[0], c[1], c[2], c[3]);
+ color.set(rgb[0], rgb[1], rgb[2]);
+ alpha = c[4];
+ break;
+ }
+ default:
+ g_warning("file %s: line %d: Illegal color selector mode %d", __FILE__, __LINE__, _mode);
+ break;
+ }
+
+ _color.preserveICC();
+ _color.setColorAlpha(color, alpha);
+}
+
+void ColorScales::_updateDisplay()
+{
+#ifdef DUMP_CHANGE_INFO
+ g_message("ColorScales::_onColorChanged( this=%p, %f, %f, %f, %f)", this, _color.color().v.c[0],
+ _color.color().v.c[1], _color.color().v.c[2], _color.alpha());
+#endif
+ gfloat tmp[3];
+ gfloat c[5] = { 0.0, 0.0, 0.0, 0.0 };
+
+ SPColor color = _color.color();
+
+ switch (_mode) {
+ case SP_COLOR_SCALES_MODE_RGB:
+ color.get_rgb_floatv(c);
+ c[3] = _color.alpha();
+ c[4] = 0.0;
+ break;
+ case SP_COLOR_SCALES_MODE_HSL:
+ color.get_rgb_floatv(tmp);
+ SPColor::rgb_to_hsl_floatv(c, tmp[0], tmp[1], tmp[2]);
+ c[3] = _color.alpha();
+ c[4] = 0.0;
+ break;
+ case SP_COLOR_SCALES_MODE_HSV:
+ color.get_rgb_floatv(tmp);
+ SPColor::rgb_to_hsv_floatv(c, tmp[0], tmp[1], tmp[2]);
+ c[3] = _color.alpha();
+ c[4] = 0.0;
+ break;
+ case SP_COLOR_SCALES_MODE_CMYK:
+ color.get_cmyk_floatv(c);
+ c[4] = _color.alpha();
+ break;
+ default:
+ g_warning("file %s: line %d: Illegal color selector mode %d", __FILE__, __LINE__, _mode);
+ break;
+ }
+
+ _updating = TRUE;
+ setScaled(_a[0], c[0]);
+ setScaled(_a[1], c[1]);
+ setScaled(_a[2], c[2]);
+ setScaled(_a[3], c[3]);
+ setScaled(_a[4], c[4]);
+ _updateSliders(CSC_CHANNELS_ALL);
+ _updating = FALSE;
+}
+
+/* Helpers for setting color value */
+gfloat ColorScales::getScaled(const GtkAdjustment *a)
+{
+ gfloat val = gtk_adjustment_get_value(const_cast<GtkAdjustment *>(a)) /
+ gtk_adjustment_get_upper(const_cast<GtkAdjustment *>(a));
+ return val;
+}
+
+void ColorScales::setScaled(GtkAdjustment *a, gfloat v, bool constrained)
+{
+ gdouble upper = gtk_adjustment_get_upper(a);
+ gfloat val = v * upper;
+ if (constrained) {
+ // TODO: do we want preferences for these?
+ if (upper == 255) {
+ val = round(val/16) * 16;
+ } else {
+ val = round(val/10) * 10;
+ }
+ }
+ gtk_adjustment_set_value(a, val);
+}
+
+void ColorScales::_setRangeLimit(gdouble upper)
+{
+ _rangeLimit = upper;
+ for (auto & i : _a) {
+ gtk_adjustment_set_upper(i, upper);
+ }
+}
+
+void ColorScales::_onColorChanged()
+{
+ if (!get_visible()) {
+ return;
+ }
+ _updateDisplay();
+}
+
+void ColorScales::on_show()
+{
+ Gtk::Grid::on_show();
+ _updateDisplay();
+}
+
+void ColorScales::_getRgbaFloatv(gfloat *rgba)
+{
+ g_return_if_fail(rgba != nullptr);
+
+ switch (_mode) {
+ case SP_COLOR_SCALES_MODE_RGB:
+ rgba[0] = getScaled(_a[0]);
+ rgba[1] = getScaled(_a[1]);
+ rgba[2] = getScaled(_a[2]);
+ rgba[3] = getScaled(_a[3]);
+ break;
+ case SP_COLOR_SCALES_MODE_HSL:
+ SPColor::hsl_to_rgb_floatv(rgba, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]));
+ rgba[3] = getScaled(_a[3]);
+ break;
+ case SP_COLOR_SCALES_MODE_HSV:
+ SPColor::hsv_to_rgb_floatv(rgba, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]));
+ rgba[3] = getScaled(_a[3]);
+ break;
+ case SP_COLOR_SCALES_MODE_CMYK:
+ SPColor::cmyk_to_rgb_floatv(rgba, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), getScaled(_a[3]));
+ rgba[3] = getScaled(_a[4]);
+ break;
+ default:
+ g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__);
+ break;
+ }
+}
+
+void ColorScales::_getCmykaFloatv(gfloat *cmyka)
+{
+ gfloat rgb[3];
+
+ g_return_if_fail(cmyka != nullptr);
+
+ switch (_mode) {
+ case SP_COLOR_SCALES_MODE_RGB:
+ SPColor::rgb_to_cmyk_floatv(cmyka, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]));
+ cmyka[4] = getScaled(_a[3]);
+ break;
+ case SP_COLOR_SCALES_MODE_HSL:
+ SPColor::hsl_to_rgb_floatv(rgb, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]));
+ SPColor::rgb_to_cmyk_floatv(cmyka, rgb[0], rgb[1], rgb[2]);
+ cmyka[4] = getScaled(_a[3]);
+ break;
+ case SP_COLOR_SCALES_MODE_CMYK:
+ cmyka[0] = getScaled(_a[0]);
+ cmyka[1] = getScaled(_a[1]);
+ cmyka[2] = getScaled(_a[2]);
+ cmyka[3] = getScaled(_a[3]);
+ cmyka[4] = getScaled(_a[4]);
+ break;
+ default:
+ g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__);
+ break;
+ }
+}
+
+guint32 ColorScales::_getRgba32()
+{
+ gfloat c[4];
+ guint32 rgba;
+
+ _getRgbaFloatv(c);
+
+ rgba = SP_RGBA32_F_COMPOSE(c[0], c[1], c[2], c[3]);
+
+ return rgba;
+}
+
+void ColorScales::setMode(SPColorScalesMode mode)
+{
+ gfloat rgba[4];
+ gfloat c[4];
+
+ if (_mode == mode)
+ return;
+
+ if ((_mode == SP_COLOR_SCALES_MODE_RGB) || (_mode == SP_COLOR_SCALES_MODE_HSL) ||
+ (_mode == SP_COLOR_SCALES_MODE_CMYK) || (_mode == SP_COLOR_SCALES_MODE_HSV)) {
+ _getRgbaFloatv(rgba);
+ }
+ else {
+ rgba[0] = rgba[1] = rgba[2] = rgba[3] = 1.0;
+ }
+
+ _mode = mode;
+
+ switch (mode) {
+ case SP_COLOR_SCALES_MODE_RGB:
+ _setRangeLimit(255.0);
+ gtk_adjustment_set_upper(_a[3], 100.0);
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[0]), _("_R:"));
+ _s[0]->set_tooltip_text(_("Red"));
+ gtk_widget_set_tooltip_text(_b[0], _("Red"));
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[1]), _("_G:"));
+ _s[1]->set_tooltip_text(_("Green"));
+ gtk_widget_set_tooltip_text(_b[1], _("Green"));
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[2]), _("_B:"));
+ _s[2]->set_tooltip_text(_("Blue"));
+ gtk_widget_set_tooltip_text(_b[2], _("Blue"));
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[3]), _("_A:"));
+ _s[3]->set_tooltip_text(_("Alpha (opacity)"));
+ gtk_widget_set_tooltip_text(_b[3], _("Alpha (opacity)"));
+ _s[0]->setMap(nullptr);
+ gtk_widget_hide(_l[4]);
+ _s[4]->hide();
+ gtk_widget_hide(_b[4]);
+ _updating = TRUE;
+ setScaled(_a[0], rgba[0]);
+ setScaled(_a[1], rgba[1]);
+ setScaled(_a[2], rgba[2]);
+ setScaled(_a[3], rgba[3]);
+ _updateSliders(CSC_CHANNELS_ALL);
+ _updating = FALSE;
+ break;
+ case SP_COLOR_SCALES_MODE_HSL:
+ _setRangeLimit(100.0);
+
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[0]), _("_H:"));
+ _s[0]->set_tooltip_text(_("Hue"));
+ gtk_widget_set_tooltip_text(_b[0], _("Hue"));
+ gtk_adjustment_set_upper(_a[0], 360.0);
+
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[1]), _("_S:"));
+ _s[1]->set_tooltip_text(_("Saturation"));
+ gtk_widget_set_tooltip_text(_b[1], _("Saturation"));
+
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[2]), _("_L:"));
+ _s[2]->set_tooltip_text(_("Lightness"));
+ gtk_widget_set_tooltip_text(_b[2], _("Lightness"));
+
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[3]), _("_A:"));
+ _s[3]->set_tooltip_text(_("Alpha (opacity)"));
+ gtk_widget_set_tooltip_text(_b[3], _("Alpha (opacity)"));
+ _s[0]->setMap((guchar *)(sp_color_scales_hue_map()));
+ gtk_widget_hide(_l[4]);
+ _s[4]->hide();
+ gtk_widget_hide(_b[4]);
+ _updating = TRUE;
+ c[0] = 0.0;
+
+ SPColor::rgb_to_hsl_floatv(c, rgba[0], rgba[1], rgba[2]);
+
+ setScaled(_a[0], c[0]);
+ setScaled(_a[1], c[1]);
+ setScaled(_a[2], c[2]);
+ setScaled(_a[3], rgba[3]);
+
+ _updateSliders(CSC_CHANNELS_ALL);
+ _updating = FALSE;
+ break;
+ case SP_COLOR_SCALES_MODE_HSV:
+ _setRangeLimit(100.0);
+
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[0]), _("_H:"));
+ _s[0]->set_tooltip_text(_("Hue"));
+ gtk_widget_set_tooltip_text(_b[0], _("Hue"));
+ gtk_adjustment_set_upper(_a[0], 360.0);
+
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[1]), _("_S:"));
+ _s[1]->set_tooltip_text(_("Saturation"));
+ gtk_widget_set_tooltip_text(_b[1], _("Saturation"));
+
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[2]), _("_V:"));
+ _s[2]->set_tooltip_text(_("Value"));
+ gtk_widget_set_tooltip_text(_b[2], _("Value"));
+
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[3]), _("_A:"));
+ _s[3]->set_tooltip_text(_("Alpha (opacity)"));
+ gtk_widget_set_tooltip_text(_b[3], _("Alpha (opacity)"));
+ _s[0]->setMap((guchar *)(sp_color_scales_hue_map()));
+ gtk_widget_hide(_l[4]);
+ _s[4]->hide();
+ gtk_widget_hide(_b[4]);
+ _updating = TRUE;
+ c[0] = 0.0;
+
+ SPColor::rgb_to_hsv_floatv(c, rgba[0], rgba[1], rgba[2]);
+
+ setScaled(_a[0], c[0]);
+ setScaled(_a[1], c[1]);
+ setScaled(_a[2], c[2]);
+ setScaled(_a[3], rgba[3]);
+
+ _updateSliders(CSC_CHANNELS_ALL);
+ _updating = FALSE;
+ break;
+ case SP_COLOR_SCALES_MODE_CMYK:
+ _setRangeLimit(100.0);
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[0]), _("_C:"));
+ _s[0]->set_tooltip_text(_("Cyan"));
+ gtk_widget_set_tooltip_text(_b[0], _("Cyan"));
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[1]), _("_M:"));
+ _s[1]->set_tooltip_text(_("Magenta"));
+ gtk_widget_set_tooltip_text(_b[1], _("Magenta"));
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[2]), _("_Y:"));
+ _s[2]->set_tooltip_text(_("Yellow"));
+ gtk_widget_set_tooltip_text(_b[2], _("Yellow"));
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[3]), _("_K:"));
+ _s[3]->set_tooltip_text(_("Black"));
+ gtk_widget_set_tooltip_text(_b[3], _("Black"));
+ gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[4]), _("_A:"));
+ _s[4]->set_tooltip_text(_("Alpha (opacity)"));
+ gtk_widget_set_tooltip_text(_b[4], _("Alpha (opacity)"));
+ _s[0]->setMap(nullptr);
+ gtk_widget_show(_l[4]);
+ _s[4]->show();
+ gtk_widget_show(_b[4]);
+ _updating = TRUE;
+
+ SPColor::rgb_to_cmyk_floatv(c, rgba[0], rgba[1], rgba[2]);
+ setScaled(_a[0], c[0]);
+ setScaled(_a[1], c[1]);
+ setScaled(_a[2], c[2]);
+ setScaled(_a[3], c[3]);
+
+ setScaled(_a[4], rgba[3]);
+ _updateSliders(CSC_CHANNELS_ALL);
+ _updating = FALSE;
+ break;
+ default:
+ g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__);
+ break;
+ }
+}
+
+SPColorScalesMode ColorScales::getMode() const { return _mode; }
+
+void ColorScales::_adjustmentAnyChanged(GtkAdjustment *adjustment, ColorScales *cs)
+{
+ gint channel = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(adjustment), "channel"));
+
+ _adjustmentChanged(cs, channel);
+}
+
+void ColorScales::_sliderAnyGrabbed()
+{
+ if (_updating) {
+ return;
+ }
+ if (!_dragging) {
+ _dragging = TRUE;
+ _color.setHeld(true);
+ }
+}
+
+void ColorScales::_sliderAnyReleased()
+{
+ if (_updating) {
+ return;
+ }
+ if (_dragging) {
+ _dragging = FALSE;
+ _color.setHeld(false);
+ }
+}
+
+void ColorScales::_sliderAnyChanged()
+{
+ if (_updating) {
+ return;
+ }
+ _recalcColor();
+}
+
+void ColorScales::_adjustmentChanged(ColorScales *scales, guint channel)
+{
+ if (scales->_updating) {
+ return;
+ }
+
+ scales->_updateSliders((1 << channel));
+ scales->_recalcColor();
+}
+
+void ColorScales::_updateSliders(guint channels)
+{
+ gfloat rgb0[3], rgbm[3], rgb1[3];
+#ifdef SPCS_PREVIEW
+ guint32 rgba;
+#endif
+ switch (_mode) {
+ case SP_COLOR_SCALES_MODE_RGB:
+ if ((channels != CSC_CHANNEL_R) && (channels != CSC_CHANNEL_A)) {
+ /* Update red */
+ _s[0]->setColors(SP_RGBA32_F_COMPOSE(0.0, getScaled(_a[1]), getScaled(_a[2]), 1.0),
+ SP_RGBA32_F_COMPOSE(0.5, getScaled(_a[1]), getScaled(_a[2]), 1.0),
+ SP_RGBA32_F_COMPOSE(1.0, getScaled(_a[1]), getScaled(_a[2]), 1.0));
+ }
+ if ((channels != CSC_CHANNEL_G) && (channels != CSC_CHANNEL_A)) {
+ /* Update green */
+ _s[1]->setColors(SP_RGBA32_F_COMPOSE(getScaled(_a[0]), 0.0, getScaled(_a[2]), 1.0),
+ SP_RGBA32_F_COMPOSE(getScaled(_a[0]), 0.5, getScaled(_a[2]), 1.0),
+ SP_RGBA32_F_COMPOSE(getScaled(_a[0]), 1.0, getScaled(_a[2]), 1.0));
+ }
+ if ((channels != CSC_CHANNEL_B) && (channels != CSC_CHANNEL_A)) {
+ /* Update blue */
+ _s[2]->setColors(SP_RGBA32_F_COMPOSE(getScaled(_a[0]), getScaled(_a[1]), 0.0, 1.0),
+ SP_RGBA32_F_COMPOSE(getScaled(_a[0]), getScaled(_a[1]), 0.5, 1.0),
+ SP_RGBA32_F_COMPOSE(getScaled(_a[0]), getScaled(_a[1]), 1.0, 1.0));
+ }
+ if (channels != CSC_CHANNEL_A) {
+ /* Update alpha */
+ _s[3]->setColors(SP_RGBA32_F_COMPOSE(getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), 0.0),
+ SP_RGBA32_F_COMPOSE(getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), 0.5),
+ SP_RGBA32_F_COMPOSE(getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), 1.0));
+ }
+ break;
+ case SP_COLOR_SCALES_MODE_HSL:
+ /* Hue is never updated */
+ if ((channels != CSC_CHANNEL_S) && (channels != CSC_CHANNEL_A)) {
+ /* Update saturation */
+ SPColor::hsl_to_rgb_floatv(rgb0, getScaled(_a[0]), 0.0, getScaled(_a[2]));
+ SPColor::hsl_to_rgb_floatv(rgbm, getScaled(_a[0]), 0.5, getScaled(_a[2]));
+ SPColor::hsl_to_rgb_floatv(rgb1, getScaled(_a[0]), 1.0, getScaled(_a[2]));
+ _s[1]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
+ }
+ if ((channels != CSC_CHANNEL_V) && (channels != CSC_CHANNEL_A)) {
+ /* Update value */
+ SPColor::hsl_to_rgb_floatv(rgb0, getScaled(_a[0]), getScaled(_a[1]), 0.0);
+ SPColor::hsl_to_rgb_floatv(rgbm, getScaled(_a[0]), getScaled(_a[1]), 0.5);
+ SPColor::hsl_to_rgb_floatv(rgb1, getScaled(_a[0]), getScaled(_a[1]), 1.0);
+ _s[2]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
+ }
+ if (channels != CSC_CHANNEL_A) {
+ /* Update alpha */
+ SPColor::hsl_to_rgb_floatv(rgb0, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]));
+ _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.0),
+ SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.5),
+ SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0));
+ }
+ break;
+ case SP_COLOR_SCALES_MODE_HSV:
+ /* Hue is never updated */
+ if ((channels != CSC_CHANNEL_S) && (channels != CSC_CHANNEL_A)) {
+ /* Update saturation */
+ SPColor::hsv_to_rgb_floatv(rgb0, getScaled(_a[0]), 0.0, getScaled(_a[2]));
+ SPColor::hsv_to_rgb_floatv(rgbm, getScaled(_a[0]), 0.5, getScaled(_a[2]));
+ SPColor::hsv_to_rgb_floatv(rgb1, getScaled(_a[0]), 1.0, getScaled(_a[2]));
+ _s[1]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
+ }
+ if ((channels != CSC_CHANNEL_V) && (channels != CSC_CHANNEL_A)) {
+ /* Update value */
+ SPColor::hsv_to_rgb_floatv(rgb0, getScaled(_a[0]), getScaled(_a[1]), 0.0);
+ SPColor::hsv_to_rgb_floatv(rgbm, getScaled(_a[0]), getScaled(_a[1]), 0.5);
+ SPColor::hsv_to_rgb_floatv(rgb1, getScaled(_a[0]), getScaled(_a[1]), 1.0);
+ _s[2]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
+ }
+ if (channels != CSC_CHANNEL_A) {
+ /* Update alpha */
+ SPColor::hsv_to_rgb_floatv(rgb0, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]));
+ _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.0),
+ SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.5),
+ SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0));
+ }
+ break;
+ case SP_COLOR_SCALES_MODE_CMYK:
+ if ((channels != CSC_CHANNEL_C) && (channels != CSC_CHANNEL_CMYKA)) {
+ /* Update C */
+ SPColor::cmyk_to_rgb_floatv(rgb0, 0.0, getScaled(_a[1]), getScaled(_a[2]), getScaled(_a[3]));
+ SPColor::cmyk_to_rgb_floatv(rgbm, 0.5, getScaled(_a[1]), getScaled(_a[2]), getScaled(_a[3]));
+ SPColor::cmyk_to_rgb_floatv(rgb1, 1.0, getScaled(_a[1]), getScaled(_a[2]), getScaled(_a[3]));
+ _s[0]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
+ }
+ if ((channels != CSC_CHANNEL_M) && (channels != CSC_CHANNEL_CMYKA)) {
+ /* Update M */
+ SPColor::cmyk_to_rgb_floatv(rgb0, getScaled(_a[0]), 0.0, getScaled(_a[2]), getScaled(_a[3]));
+ SPColor::cmyk_to_rgb_floatv(rgbm, getScaled(_a[0]), 0.5, getScaled(_a[2]), getScaled(_a[3]));
+ SPColor::cmyk_to_rgb_floatv(rgb1, getScaled(_a[0]), 1.0, getScaled(_a[2]), getScaled(_a[3]));
+ _s[1]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
+ }
+ if ((channels != CSC_CHANNEL_Y) && (channels != CSC_CHANNEL_CMYKA)) {
+ /* Update Y */
+ SPColor::cmyk_to_rgb_floatv(rgb0, getScaled(_a[0]), getScaled(_a[1]), 0.0, getScaled(_a[3]));
+ SPColor::cmyk_to_rgb_floatv(rgbm, getScaled(_a[0]), getScaled(_a[1]), 0.5, getScaled(_a[3]));
+ SPColor::cmyk_to_rgb_floatv(rgb1, getScaled(_a[0]), getScaled(_a[1]), 1.0, getScaled(_a[3]));
+ _s[2]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
+ }
+ if ((channels != CSC_CHANNEL_K) && (channels != CSC_CHANNEL_CMYKA)) {
+ /* Update K */
+ SPColor::cmyk_to_rgb_floatv(rgb0, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), 0.0);
+ SPColor::cmyk_to_rgb_floatv(rgbm, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), 0.5);
+ SPColor::cmyk_to_rgb_floatv(rgb1, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), 1.0);
+ _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
+ SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
+ }
+ if (channels != CSC_CHANNEL_CMYKA) {
+ /* Update alpha */
+ SPColor::cmyk_to_rgb_floatv(rgb0, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]),
+ getScaled(_a[3]));
+ _s[4]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.0),
+ SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.5),
+ SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0));
+ }
+ break;
+ default:
+ g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__);
+ break;
+ }
+
+#ifdef SPCS_PREVIEW
+ rgba = sp_color_scales_get_rgba32(cs);
+ sp_color_preview_set_rgba32(SP_COLOR_PREVIEW(_p), rgba);
+#endif
+}
+
+static const gchar *sp_color_scales_hue_map()
+{
+ static gchar *map = nullptr;
+
+ if (!map) {
+ gchar *p;
+ gint h;
+ map = g_new(gchar, 4 * 1024);
+ p = map;
+ for (h = 0; h < 1024; h++) {
+ gfloat rgb[3];
+ SPColor::hsl_to_rgb_floatv(rgb, h / 1024.0, 1.0, 0.5);
+ *p++ = SP_COLOR_F_TO_U(rgb[0]);
+ *p++ = SP_COLOR_F_TO_U(rgb[1]);
+ *p++ = SP_COLOR_F_TO_U(rgb[2]);
+ *p++ = 0xFF;
+ }
+ }
+
+ return map;
+}
+
+ColorScalesFactory::ColorScalesFactory(SPColorScalesMode submode)
+ : _submode(submode)
+{
+}
+
+ColorScalesFactory::~ColorScalesFactory() = default;
+
+Gtk::Widget *ColorScalesFactory::createWidget(Inkscape::UI::SelectedColor &color) const
+{
+ Gtk::Widget *w = Gtk::manage(new ColorScales(color, _submode));
+ return w;
+}
+
+Glib::ustring ColorScalesFactory::modeName() const {
+ return gettext(ColorScales::SUBMODE_NAMES[_submode]);
+}
+
+}
+}
+}
diff --git a/src/ui/widget/color-scales.h b/src/ui/widget/color-scales.h
new file mode 100644
index 0000000..f3007fd
--- /dev/null
+++ b/src/ui/widget/color-scales.h
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_SP_COLOR_SCALES_H
+#define SEEN_SP_COLOR_SCALES_H
+
+#include <gtkmm/grid.h>
+
+#include "ui/selected-color.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class ColorSlider;
+
+enum SPColorScalesMode {
+ SP_COLOR_SCALES_MODE_NONE = 0,
+ SP_COLOR_SCALES_MODE_RGB = 1,
+ SP_COLOR_SCALES_MODE_HSL = 2,
+ SP_COLOR_SCALES_MODE_CMYK = 3,
+ SP_COLOR_SCALES_MODE_HSV = 4
+};
+
+class ColorScales
+ : public Gtk::Grid
+{
+public:
+ static const gchar *SUBMODE_NAMES[];
+
+ static gfloat getScaled(const GtkAdjustment *a);
+ static void setScaled(GtkAdjustment *a, gfloat v, bool constrained = false);
+
+ ColorScales(SelectedColor &color, SPColorScalesMode mode);
+ ~ColorScales() override;
+
+ virtual void _initUI(SPColorScalesMode mode);
+
+ void setMode(SPColorScalesMode mode);
+ SPColorScalesMode getMode() const;
+
+protected:
+ void _onColorChanged();
+ void on_show() override;
+
+ static void _adjustmentAnyChanged(GtkAdjustment *adjustment, ColorScales *cs);
+ void _sliderAnyGrabbed();
+ void _sliderAnyReleased();
+ void _sliderAnyChanged();
+ static void _adjustmentChanged(ColorScales *cs, guint channel);
+
+ void _getRgbaFloatv(gfloat *rgba);
+ void _getCmykaFloatv(gfloat *cmyka);
+ guint32 _getRgba32();
+ void _updateSliders(guint channels);
+ void _recalcColor();
+ void _updateDisplay();
+
+ void _setRangeLimit(gdouble upper);
+
+ SelectedColor &_color;
+ SPColorScalesMode _mode;
+ gdouble _rangeLimit;
+ gboolean _updating : 1;
+ gboolean _dragging : 1;
+ GtkAdjustment *_a[5]; /* Channel adjustments */
+ Inkscape::UI::Widget::ColorSlider *_s[5]; /* Channel sliders */
+ GtkWidget *_b[5]; /* Spinbuttons */
+ GtkWidget *_l[5]; /* Labels */
+
+private:
+ // By default, disallow copy constructor and assignment operator
+ ColorScales(ColorScales const &obj) = delete;
+ ColorScales &operator=(ColorScales const &obj) = delete;
+};
+
+class ColorScalesFactory : public Inkscape::UI::ColorSelectorFactory
+{
+public:
+ ColorScalesFactory(SPColorScalesMode submode);
+ ~ColorScalesFactory() override;
+
+ Gtk::Widget *createWidget(Inkscape::UI::SelectedColor &color) const override;
+ Glib::ustring modeName() const override;
+
+private:
+ SPColorScalesMode _submode;
+};
+
+}
+}
+}
+
+#endif /* !SEEN_SP_COLOR_SCALES_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 :
diff --git a/src/ui/widget/color-slider.cpp b/src/ui/widget/color-slider.cpp
new file mode 100644
index 0000000..2d19055
--- /dev/null
+++ b/src/ui/widget/color-slider.cpp
@@ -0,0 +1,536 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * A slider with colored background - implementation.
+ *//*
+ * Authors:
+ * see git history
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gdkmm/cursor.h>
+#include <gdkmm/general.h>
+#include <gtkmm/adjustment.h>
+#include <gtkmm/stylecontext.h>
+
+#include "ui/widget/color-scales.h"
+#include "ui/widget/color-slider.h"
+#include "preferences.h"
+
+static const gint SLIDER_WIDTH = 96;
+static const gint SLIDER_HEIGHT = 8;
+static const gint ARROW_SIZE = 7;
+
+static const guchar *sp_color_slider_render_gradient(gint x0, gint y0, gint width, gint height, gint c[], gint dc[],
+ guint b0, guint b1, guint mask);
+static const guchar *sp_color_slider_render_map(gint x0, gint y0, gint width, gint height, guchar *map, gint start,
+ gint step, guint b0, guint b1, guint mask);
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+ColorSlider::ColorSlider(Glib::RefPtr<Gtk::Adjustment> adjustment)
+ : _dragging(false)
+ , _value(0.0)
+ , _oldvalue(0.0)
+ , _mapsize(0)
+ , _map(nullptr)
+{
+ _c0[0] = 0x00;
+ _c0[1] = 0x00;
+ _c0[2] = 0x00;
+ _c0[3] = 0xff;
+
+ _cm[0] = 0xff;
+ _cm[1] = 0x00;
+ _cm[2] = 0x00;
+ _cm[3] = 0xff;
+
+ _c0[0] = 0xff;
+ _c0[1] = 0xff;
+ _c0[2] = 0xff;
+ _c0[3] = 0xff;
+
+ _b0 = 0x5f;
+ _b1 = 0xa0;
+ _bmask = 0x08;
+
+ setAdjustment(adjustment);
+}
+
+ColorSlider::~ColorSlider()
+{
+ if (_adjustment) {
+ _adjustment_changed_connection.disconnect();
+ _adjustment_value_changed_connection.disconnect();
+ _adjustment.reset();
+ }
+}
+
+void ColorSlider::on_realize()
+{
+ set_realized();
+
+ if (!_gdk_window) {
+ GdkWindowAttr attributes;
+ gint attributes_mask;
+ Gtk::Allocation allocation = get_allocation();
+
+ memset(&attributes, 0, sizeof(attributes));
+ attributes.x = allocation.get_x();
+ attributes.y = allocation.get_y();
+ attributes.width = allocation.get_width();
+ attributes.height = allocation.get_height();
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gdk_screen_get_system_visual(gdk_screen_get_default());
+ attributes.event_mask = get_events();
+ attributes.event_mask |= (Gdk::EXPOSURE_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK |
+ Gdk::POINTER_MOTION_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
+
+ _gdk_window = Gdk::Window::create(get_parent_window(), &attributes, attributes_mask);
+ set_window(_gdk_window);
+ _gdk_window->set_user_data(gobj());
+ }
+}
+
+void ColorSlider::on_unrealize()
+{
+ _gdk_window.reset();
+
+ Gtk::Widget::on_unrealize();
+}
+
+void ColorSlider::on_size_allocate(Gtk::Allocation &allocation)
+{
+ set_allocation(allocation);
+
+ if (get_realized()) {
+ _gdk_window->move_resize(allocation.get_x(), allocation.get_y(), allocation.get_width(),
+ allocation.get_height());
+ }
+}
+
+void ColorSlider::get_preferred_width_vfunc(int &minimum_width, int &natural_width) const
+{
+ Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context();
+ Gtk::Border padding = style_context->get_padding(get_state_flags());
+ int width = SLIDER_WIDTH + padding.get_left() + padding.get_right();
+ minimum_width = natural_width = width;
+}
+
+void ColorSlider::get_preferred_width_for_height_vfunc(int /*height*/, int &minimum_width, int &natural_width) const
+{
+ get_preferred_width(minimum_width, natural_width);
+}
+
+void ColorSlider::get_preferred_height_vfunc(int &minimum_height, int &natural_height) const
+{
+ Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context();
+ Gtk::Border padding = style_context->get_padding(get_state_flags());
+ int height = SLIDER_HEIGHT + padding.get_top() + padding.get_bottom();
+ minimum_height = natural_height = height;
+}
+
+void ColorSlider::get_preferred_height_for_width_vfunc(int /*width*/, int &minimum_height, int &natural_height) const
+{
+ get_preferred_height(minimum_height, natural_height);
+}
+
+bool ColorSlider::on_button_press_event(GdkEventButton *event)
+{
+ if (event->button == 1) {
+ Gtk::Allocation allocation = get_allocation();
+ gint cx, cw;
+ cx = get_style_context()->get_padding(get_state_flags()).get_left();
+ cw = allocation.get_width() - 2 * cx;
+ signal_grabbed.emit();
+ _dragging = true;
+ _oldvalue = _value;
+ gfloat value = CLAMP((gfloat)(event->x - cx) / cw, 0.0, 1.0);
+ bool constrained = event->state & GDK_CONTROL_MASK;
+ ColorScales::setScaled(_adjustment->gobj(), value, constrained);
+ signal_dragged.emit();
+
+ auto window = _gdk_window->gobj();
+
+ auto seat = gdk_event_get_seat(reinterpret_cast<GdkEvent *>(event));
+ gdk_seat_grab(seat,
+ window,
+ GDK_SEAT_CAPABILITY_ALL_POINTING,
+ FALSE,
+ nullptr,
+ reinterpret_cast<GdkEvent *>(event),
+ nullptr,
+ nullptr);
+ }
+
+ return false;
+}
+
+bool ColorSlider::on_button_release_event(GdkEventButton *event)
+{
+ if (event->button == 1) {
+ gdk_seat_ungrab(gdk_event_get_seat(reinterpret_cast<GdkEvent *>(event)));
+ _dragging = false;
+ signal_released.emit();
+ if (_value != _oldvalue) {
+ signal_value_changed.emit();
+ }
+ }
+
+ return false;
+}
+
+bool ColorSlider::on_motion_notify_event(GdkEventMotion *event)
+{
+ if (_dragging) {
+ gint cx, cw;
+ Gtk::Allocation allocation = get_allocation();
+ cx = get_style_context()->get_padding(get_state_flags()).get_left();
+ cw = allocation.get_width() - 2 * cx;
+ gfloat value = CLAMP((gfloat)(event->x - cx) / cw, 0.0, 1.0);
+ bool constrained = event->state & GDK_CONTROL_MASK;
+ ColorScales::setScaled(_adjustment->gobj(), value, constrained);
+ signal_dragged.emit();
+ }
+
+ return false;
+}
+
+void ColorSlider::setAdjustment(Glib::RefPtr<Gtk::Adjustment> adjustment)
+{
+ if (!adjustment) {
+ _adjustment = Gtk::Adjustment::create(0.0, 0.0, 1.0, 0.01, 0.0, 0.0);
+ }
+ else {
+ adjustment->set_page_increment(0.0);
+ adjustment->set_page_size(0.0);
+ }
+
+ if (_adjustment != adjustment) {
+ if (_adjustment) {
+ _adjustment_changed_connection.disconnect();
+ _adjustment_value_changed_connection.disconnect();
+ }
+
+ _adjustment = adjustment;
+ _adjustment_changed_connection =
+ _adjustment->signal_changed().connect(sigc::mem_fun(this, &ColorSlider::_onAdjustmentChanged));
+ _adjustment_value_changed_connection =
+ _adjustment->signal_value_changed().connect(sigc::mem_fun(this, &ColorSlider::_onAdjustmentValueChanged));
+
+ _value = ColorScales::getScaled(_adjustment->gobj());
+
+ _onAdjustmentChanged();
+ }
+}
+
+void ColorSlider::_onAdjustmentChanged() { queue_draw(); }
+
+void ColorSlider::_onAdjustmentValueChanged()
+{
+ if (_value != ColorScales::getScaled(_adjustment->gobj())) {
+ gint cx, cy, cw, ch;
+ auto style_context = get_style_context();
+ auto allocation = get_allocation();
+ auto padding = style_context->get_padding(get_state_flags());
+ cx = padding.get_left();
+ cy = padding.get_top();
+ cw = allocation.get_width() - 2 * cx;
+ ch = allocation.get_height() - 2 * cy;
+ if ((gint)(ColorScales::getScaled(_adjustment->gobj()) * cw) != (gint)(_value * cw)) {
+ gint ax, ay;
+ gfloat value;
+ value = _value;
+ _value = ColorScales::getScaled(_adjustment->gobj());
+ ax = (int)(cx + value * cw - ARROW_SIZE / 2 - 2);
+ ay = cy;
+ queue_draw_area(ax, ay, ARROW_SIZE + 4, ch);
+ ax = (int)(cx + _value * cw - ARROW_SIZE / 2 - 2);
+ ay = cy;
+ queue_draw_area(ax, ay, ARROW_SIZE + 4, ch);
+ }
+ else {
+ _value = ColorScales::getScaled(_adjustment->gobj());
+ }
+ }
+}
+
+void ColorSlider::setColors(guint32 start, guint32 mid, guint32 end)
+{
+ // Remove any map, if set
+ _map = nullptr;
+
+ _c0[0] = start >> 24;
+ _c0[1] = (start >> 16) & 0xff;
+ _c0[2] = (start >> 8) & 0xff;
+ _c0[3] = start & 0xff;
+
+ _cm[0] = mid >> 24;
+ _cm[1] = (mid >> 16) & 0xff;
+ _cm[2] = (mid >> 8) & 0xff;
+ _cm[3] = mid & 0xff;
+
+ _c1[0] = end >> 24;
+ _c1[1] = (end >> 16) & 0xff;
+ _c1[2] = (end >> 8) & 0xff;
+ _c1[3] = end & 0xff;
+
+ queue_draw();
+}
+
+void ColorSlider::setMap(const guchar *map)
+{
+ _map = const_cast<guchar *>(map);
+
+ queue_draw();
+}
+
+void ColorSlider::setBackground(guint dark, guint light, guint size)
+{
+ _b0 = dark;
+ _b1 = light;
+ _bmask = size;
+
+ queue_draw();
+}
+
+bool ColorSlider::on_draw(const Cairo::RefPtr<Cairo::Context> &cr)
+{
+ gboolean colorsOnTop = Inkscape::Preferences::get()->getBool("/options/workarounds/colorsontop", false);
+
+ auto allocation = get_allocation();
+ auto style_context = get_style_context();
+
+ // Draw shadow
+ if (colorsOnTop) {
+ style_context->render_frame(cr, 0, 0, allocation.get_width(), allocation.get_height());
+ }
+
+ /* Paintable part of color gradient area */
+ Gdk::Rectangle carea;
+ Gtk::Border padding;
+
+ padding = style_context->get_padding(get_state_flags());
+
+ carea.set_x(padding.get_left());
+ carea.set_y(padding.get_top());
+
+ carea.set_width(allocation.get_width() - 2 * carea.get_x());
+ carea.set_height(allocation.get_height() - 2 * carea.get_y());
+
+ if (_map) {
+ /* Render map pixelstore */
+ gint d = (1024 << 16) / carea.get_width();
+ gint s = 0;
+
+ const guchar *b =
+ sp_color_slider_render_map(0, 0, carea.get_width(), carea.get_height(), _map, s, d, _b0, _b1, _bmask);
+
+ if (b != nullptr && carea.get_width() > 0) {
+ Glib::RefPtr<Gdk::Pixbuf> pb = Gdk::Pixbuf::create_from_data(
+ b, Gdk::COLORSPACE_RGB, false, 8, carea.get_width(), carea.get_height(), carea.get_width() * 3);
+
+ Gdk::Cairo::set_source_pixbuf(cr, pb, carea.get_x(), carea.get_y());
+ cr->paint();
+ }
+ }
+ else {
+ gint c[4], dc[4];
+
+ /* Render gradient */
+
+ // part 1: from c0 to cm
+ if (carea.get_width() > 0) {
+ for (gint i = 0; i < 4; i++) {
+ c[i] = _c0[i] << 16;
+ dc[i] = ((_cm[i] << 16) - c[i]) / (carea.get_width() / 2);
+ }
+ guint wi = carea.get_width() / 2;
+ const guchar *b = sp_color_slider_render_gradient(0, 0, wi, carea.get_height(), c, dc, _b0, _b1, _bmask);
+
+ /* Draw pixelstore 1 */
+ if (b != nullptr && wi > 0) {
+ Glib::RefPtr<Gdk::Pixbuf> pb =
+ Gdk::Pixbuf::create_from_data(b, Gdk::COLORSPACE_RGB, false, 8, wi, carea.get_height(), wi * 3);
+
+ Gdk::Cairo::set_source_pixbuf(cr, pb, carea.get_x(), carea.get_y());
+ cr->paint();
+ }
+ }
+
+ // part 2: from cm to c1
+ if (carea.get_width() > 0) {
+ for (gint i = 0; i < 4; i++) {
+ c[i] = _cm[i] << 16;
+ dc[i] = ((_c1[i] << 16) - c[i]) / (carea.get_width() / 2);
+ }
+ guint wi = carea.get_width() / 2;
+ const guchar *b = sp_color_slider_render_gradient(carea.get_width() / 2, 0, wi, carea.get_height(), c, dc,
+ _b0, _b1, _bmask);
+
+ /* Draw pixelstore 2 */
+ if (b != nullptr && wi > 0) {
+ Glib::RefPtr<Gdk::Pixbuf> pb =
+ Gdk::Pixbuf::create_from_data(b, Gdk::COLORSPACE_RGB, false, 8, wi, carea.get_height(), wi * 3);
+
+ Gdk::Cairo::set_source_pixbuf(cr, pb, carea.get_width() / 2 + carea.get_x(), carea.get_y());
+ cr->paint();
+ }
+ }
+ }
+
+ /* Draw shadow */
+ if (!colorsOnTop) {
+ style_context->render_frame(cr, 0, 0, allocation.get_width(), allocation.get_height());
+ }
+
+ /* Draw arrow */
+ gint x = (int)(_value * (carea.get_width() - 1) - ARROW_SIZE / 2 + carea.get_x());
+ gint y1 = carea.get_y();
+ gint y2 = carea.get_y() + carea.get_height() - 1;
+ cr->set_line_width(1.0);
+
+ // Define top arrow
+ cr->move_to(x - 0.5, y1 + 0.5);
+ cr->line_to(x + ARROW_SIZE - 0.5, y1 + 0.5);
+ cr->line_to(x + (ARROW_SIZE - 1) / 2.0, y1 + ARROW_SIZE / 2.0 + 0.5);
+ cr->line_to(x - 0.5, y1 + 0.5);
+
+ // Define bottom arrow
+ cr->move_to(x - 0.5, y2 + 0.5);
+ cr->line_to(x + ARROW_SIZE - 0.5, y2 + 0.5);
+ cr->line_to(x + (ARROW_SIZE - 1) / 2.0, y2 - ARROW_SIZE / 2.0 + 0.5);
+ cr->line_to(x - 0.5, y2 + 0.5);
+
+ // Render both arrows
+ cr->set_source_rgb(1.0, 1.0, 1.0);
+ cr->stroke_preserve();
+ cr->set_source_rgb(0.0, 0.0, 0.0);
+ cr->fill();
+
+ return false;
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/* Colors are << 16 */
+
+static const guchar *sp_color_slider_render_gradient(gint x0, gint y0, gint width, gint height, gint c[], gint dc[],
+ guint b0, guint b1, guint mask)
+{
+ static guchar *buf = nullptr;
+ static gint bs = 0;
+ guchar *dp;
+ gint x, y;
+ guint r, g, b, a;
+
+ if (buf && (bs < width * height)) {
+ g_free(buf);
+ buf = nullptr;
+ }
+ if (!buf) {
+ buf = g_new(guchar, width * height * 3);
+ bs = width * height;
+ }
+
+ dp = buf;
+ r = c[0];
+ g = c[1];
+ b = c[2];
+ a = c[3];
+ for (x = x0; x < x0 + width; x++) {
+ gint cr, cg, cb, ca;
+ guchar *d;
+ cr = r >> 16;
+ cg = g >> 16;
+ cb = b >> 16;
+ ca = a >> 16;
+ d = dp;
+ for (y = y0; y < y0 + height; y++) {
+ guint bg, fc;
+ /* Background value */
+ bg = ((x & mask) ^ (y & mask)) ? b0 : b1;
+ fc = (cr - bg) * ca;
+ d[0] = bg + ((fc + (fc >> 8) + 0x80) >> 8);
+ fc = (cg - bg) * ca;
+ d[1] = bg + ((fc + (fc >> 8) + 0x80) >> 8);
+ fc = (cb - bg) * ca;
+ d[2] = bg + ((fc + (fc >> 8) + 0x80) >> 8);
+ d += 3 * width;
+ }
+ r += dc[0];
+ g += dc[1];
+ b += dc[2];
+ a += dc[3];
+ dp += 3;
+ }
+
+ return buf;
+}
+
+/* Positions are << 16 */
+
+static const guchar *sp_color_slider_render_map(gint x0, gint y0, gint width, gint height, guchar *map, gint start,
+ gint step, guint b0, guint b1, guint mask)
+{
+ static guchar *buf = nullptr;
+ static gint bs = 0;
+ guchar *dp;
+ gint x, y;
+
+ if (buf && (bs < width * height)) {
+ g_free(buf);
+ buf = nullptr;
+ }
+ if (!buf) {
+ buf = g_new(guchar, width * height * 3);
+ bs = width * height;
+ }
+
+ dp = buf;
+ for (x = x0; x < x0 + width; x++) {
+ gint cr, cg, cb, ca;
+ guchar *d = dp;
+ guchar *sp = map + 4 * (start >> 16);
+ cr = *sp++;
+ cg = *sp++;
+ cb = *sp++;
+ ca = *sp++;
+ for (y = y0; y < y0 + height; y++) {
+ guint bg, fc;
+ /* Background value */
+ bg = ((x & mask) ^ (y & mask)) ? b0 : b1;
+ fc = (cr - bg) * ca;
+ d[0] = bg + ((fc + (fc >> 8) + 0x80) >> 8);
+ fc = (cg - bg) * ca;
+ d[1] = bg + ((fc + (fc >> 8) + 0x80) >> 8);
+ fc = (cb - bg) * ca;
+ d[2] = bg + ((fc + (fc >> 8) + 0x80) >> 8);
+ d += 3 * width;
+ }
+ dp += 3;
+ start += step;
+ }
+
+ return buf;
+}
+/*
+ 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 :
diff --git a/src/ui/widget/color-slider.h b/src/ui/widget/color-slider.h
new file mode 100644
index 0000000..6a0834e
--- /dev/null
+++ b/src/ui/widget/color-slider.h
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+* Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_COLOR_SLIDER_H
+#define SEEN_COLOR_SLIDER_H
+
+#include <gtkmm/widget.h>
+#include <sigc++/signal.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/*
+ * A slider with colored background
+ */
+class ColorSlider : public Gtk::Widget {
+public:
+ ColorSlider(Glib::RefPtr<Gtk::Adjustment> adjustment);
+ ~ColorSlider() override;
+
+ void setAdjustment(Glib::RefPtr<Gtk::Adjustment> adjustment);
+
+ void setColors(guint32 start, guint32 mid, guint32 end);
+
+ void setMap(const guchar *map);
+
+ void setBackground(guint dark, guint light, guint size);
+
+ sigc::signal<void> signal_grabbed;
+ sigc::signal<void> signal_dragged;
+ sigc::signal<void> signal_released;
+ sigc::signal<void> signal_value_changed;
+
+protected:
+ void on_size_allocate(Gtk::Allocation &allocation) override;
+ void on_realize() override;
+ void on_unrealize() override;
+ bool on_button_press_event(GdkEventButton *event) override;
+ bool on_button_release_event(GdkEventButton *event) override;
+ bool on_motion_notify_event(GdkEventMotion *event) override;
+ bool on_draw(const Cairo::RefPtr<Cairo::Context> &cr) override;
+ void get_preferred_width_vfunc(int &minimum_width, int &natural_width) const override;
+ void get_preferred_width_for_height_vfunc(int height, int &minimum_width, int &natural_width) const override;
+ void get_preferred_height_vfunc(int &minimum_height, int &natural_height) const override;
+ void get_preferred_height_for_width_vfunc(int width, int &minimum_height, int &natural_height) const override;
+
+private:
+ void _onAdjustmentChanged();
+ void _onAdjustmentValueChanged();
+
+ bool _dragging;
+
+ Glib::RefPtr<Gtk::Adjustment> _adjustment;
+ sigc::connection _adjustment_changed_connection;
+ sigc::connection _adjustment_value_changed_connection;
+
+ gfloat _value;
+ gfloat _oldvalue;
+ guchar _c0[4], _cm[4], _c1[4];
+ guchar _b0, _b1;
+ guchar _bmask;
+
+ gint _mapsize;
+ guchar *_map;
+
+ Glib::RefPtr<Gdk::Window> _gdk_window;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+/*
+ 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/color-wheel-selector.cpp b/src/ui/widget/color-wheel-selector.cpp
new file mode 100644
index 0000000..9695303
--- /dev/null
+++ b/src/ui/widget/color-wheel-selector.cpp
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "color-wheel-selector.h"
+
+#include <glibmm/i18n.h>
+#include <gtkmm/adjustment.h>
+#include <gtkmm/label.h>
+#include <gtkmm/spinbutton.h>
+#include "ui/dialog-events.h"
+#include "ui/widget/color-scales.h"
+#include "ui/widget/color-slider.h"
+#include "ui/widget/ink-color-wheel.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+
+#define XPAD 4
+#define YPAD 1
+
+
+const gchar *ColorWheelSelector::MODE_NAME = N_("Wheel");
+
+ColorWheelSelector::ColorWheelSelector(SelectedColor &color)
+ : Gtk::Grid()
+ , _color(color)
+ , _updating(false)
+ , _wheel(nullptr)
+ , _slider(nullptr)
+{
+ set_name("ColorWheelSelector");
+
+ _initUI();
+ _color_changed_connection = color.signal_changed.connect(sigc::mem_fun(this, &ColorWheelSelector::_colorChanged));
+ _color_dragged_connection = color.signal_dragged.connect(sigc::mem_fun(this, &ColorWheelSelector::_colorChanged));
+}
+
+ColorWheelSelector::~ColorWheelSelector()
+{
+ _color_changed_connection.disconnect();
+ _color_dragged_connection.disconnect();
+}
+
+void ColorWheelSelector::_initUI()
+{
+ /* Create components */
+ gint row = 0;
+
+ _wheel = Gtk::manage(new Inkscape::UI::Widget::ColorWheel());
+ _wheel->set_halign(Gtk::ALIGN_FILL);
+ _wheel->set_valign(Gtk::ALIGN_FILL);
+ _wheel->set_hexpand(true);
+ _wheel->set_vexpand(true);
+ attach(*_wheel, 0, row, 3, 1);
+
+ row++;
+
+ /* Label */
+ Gtk::Label *label = Gtk::manage(new Gtk::Label(_("_A:"), true));
+ label->set_halign(Gtk::ALIGN_END);
+ label->set_valign(Gtk::ALIGN_CENTER);
+
+ label->set_margin_start(XPAD);
+ label->set_margin_end(XPAD);
+ label->set_margin_top(YPAD);
+ label->set_margin_bottom(YPAD);
+ label->set_halign(Gtk::ALIGN_FILL);
+ label->set_valign(Gtk::ALIGN_FILL);
+ attach(*label, 0, row, 1, 1);
+
+ /* Adjustment */
+ _alpha_adjustment = Gtk::Adjustment::create(0.0, 0.0, 100.0, 1.0, 10.0, 10.0);
+
+ /* Slider */
+ _slider = Gtk::manage(new Inkscape::UI::Widget::ColorSlider(_alpha_adjustment));
+ _slider->set_tooltip_text(_("Alpha (opacity)"));
+
+ _slider->set_margin_start(XPAD);
+ _slider->set_margin_end(XPAD);
+ _slider->set_margin_top(YPAD);
+ _slider->set_margin_bottom(YPAD);
+ _slider->set_hexpand(true);
+ _slider->set_halign(Gtk::ALIGN_FILL);
+ _slider->set_valign(Gtk::ALIGN_FILL);
+ attach(*_slider, 1, row, 1, 1);
+
+ _slider->setColors(SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 0.0), SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 0.5),
+ SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 1.0));
+
+ /* Spinbutton */
+ auto spin_button = Gtk::manage(new Gtk::SpinButton(_alpha_adjustment, 1.0, 0));
+ spin_button->set_tooltip_text(_("Alpha (opacity)"));
+ sp_dialog_defocus_on_enter(GTK_WIDGET(spin_button->gobj()));
+ label->set_mnemonic_widget(*spin_button);
+
+ spin_button->set_margin_start(XPAD);
+ spin_button->set_margin_end(XPAD);
+ spin_button->set_margin_top(YPAD);
+ spin_button->set_margin_bottom(YPAD);
+ spin_button->set_halign(Gtk::ALIGN_CENTER);
+ spin_button->set_valign(Gtk::ALIGN_CENTER);
+ attach(*spin_button, 2, row, 1, 1);
+
+ /* Signals */
+ _alpha_adjustment->signal_value_changed().connect(sigc::mem_fun(this, &ColorWheelSelector::_adjustmentChanged));
+ _slider->signal_grabbed.connect(sigc::mem_fun(*this, &ColorWheelSelector::_sliderGrabbed));
+ _slider->signal_released.connect(sigc::mem_fun(*this, &ColorWheelSelector::_sliderReleased));
+ _slider->signal_value_changed.connect(sigc::mem_fun(*this, &ColorWheelSelector::_sliderChanged));
+ _wheel->signal_color_changed().connect(sigc::mem_fun(*this, &ColorWheelSelector::_wheelChanged));
+
+ show_all();
+}
+
+void ColorWheelSelector::on_show()
+{
+ Gtk::Grid::on_show();
+ _updateDisplay();
+}
+
+void ColorWheelSelector::_colorChanged()
+{
+ _updateDisplay();
+}
+
+void ColorWheelSelector::_adjustmentChanged()
+{
+ if (_updating) {
+ return;
+ }
+
+ _color.preserveICC();
+ _color.setAlpha(ColorScales::getScaled(_alpha_adjustment->gobj()));
+}
+
+void ColorWheelSelector::_sliderGrabbed()
+{
+ _color.preserveICC();
+ _color.setHeld(true);
+}
+
+void ColorWheelSelector::_sliderReleased()
+{
+ _color.preserveICC();
+ _color.setHeld(false);
+}
+
+void ColorWheelSelector::_sliderChanged()
+{
+ if (_updating) {
+ return;
+ }
+
+ _color.preserveICC();
+ _color.setAlpha(ColorScales::getScaled(_alpha_adjustment->gobj()));
+}
+
+void ColorWheelSelector::_wheelChanged()
+{
+ if (_updating) {
+ return;
+ }
+
+ double rgb[3] = { 0, 0, 0 };
+ _wheel->get_rgb(rgb[0], rgb[1], rgb[2]);
+
+ SPColor color(rgb[0], rgb[1], rgb[2]);
+
+ guint32 start = color.toRGBA32(0x00);
+ guint32 mid = color.toRGBA32(0x7f);
+ guint32 end = color.toRGBA32(0xff);
+
+ _updating = true;
+ _slider->setColors(start, mid, end);
+ _color.preserveICC();
+
+ _color.setHeld(_wheel->is_adjusting());
+ _color.setColor(color);
+ _updating = false;
+}
+
+void ColorWheelSelector::_updateDisplay()
+{
+ if(_updating) { return; }
+
+#ifdef DUMP_CHANGE_INFO
+ g_message("ColorWheelSelector::_colorChanged( this=%p, %f, %f, %f, %f)", this, _color.color().v.c[0],
+ _color.color().v.c[1], _color.color().v.c[2], alpha);
+#endif
+
+ _updating = true;
+ {
+ float hsv[3] = { 0, 0, 0 };
+ SPColor::rgb_to_hsv_floatv(hsv, _color.color().v.c[0], _color.color().v.c[1], _color.color().v.c[2]);
+ _wheel->set_rgb(_color.color().v.c[0], _color.color().v.c[1], _color.color().v.c[2]);
+ }
+
+ guint32 start = _color.color().toRGBA32(0x00);
+ guint32 mid = _color.color().toRGBA32(0x7f);
+ guint32 end = _color.color().toRGBA32(0xff);
+
+ _slider->setColors(start, mid, end);
+
+ ColorScales::setScaled(_alpha_adjustment->gobj(), _color.alpha());
+
+ _updating = false;
+}
+
+
+Gtk::Widget *ColorWheelSelectorFactory::createWidget(Inkscape::UI::SelectedColor &color) const
+{
+ Gtk::Widget *w = Gtk::manage(new ColorWheelSelector(color));
+ return w;
+}
+
+Glib::ustring ColorWheelSelectorFactory::modeName() const { return gettext(ColorWheelSelector::MODE_NAME); }
+}
+}
+}
+/*
+ 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/color-wheel-selector.h b/src/ui/widget/color-wheel-selector.h
new file mode 100644
index 0000000..59cf6b6
--- /dev/null
+++ b/src/ui/widget/color-wheel-selector.h
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Color selector widget containing GIMP color wheel and slider
+ */
+/* Authors:
+ * Tomasz Boczkowski <penginsbacon@gmail.com> (c++-sification)
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_SP_COLOR_WHEEL_SELECTOR_H
+#define SEEN_SP_COLOR_WHEEL_SELECTOR_H
+
+#include <gtkmm/grid.h>
+
+#include "ui/selected-color.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class ColorSlider;
+class ColorWheel;
+class ColorWheelSelector
+ : public Gtk::Grid
+{
+public:
+ static const gchar *MODE_NAME;
+
+ ColorWheelSelector(SelectedColor &color);
+ ~ColorWheelSelector() override;
+
+protected:
+ void _initUI();
+
+ void on_show() override;
+
+ void _colorChanged();
+ void _adjustmentChanged();
+ void _sliderGrabbed();
+ void _sliderReleased();
+ void _sliderChanged();
+ void _wheelChanged();
+
+ void _updateDisplay();
+
+ SelectedColor &_color;
+ bool _updating;
+ Glib::RefPtr<Gtk::Adjustment> _alpha_adjustment;
+ Inkscape::UI::Widget::ColorWheel *_wheel;
+ Inkscape::UI::Widget::ColorSlider *_slider;
+
+private:
+ // By default, disallow copy constructor and assignment operator
+ ColorWheelSelector(const ColorWheelSelector &obj) = delete;
+ ColorWheelSelector &operator=(const ColorWheelSelector &obj) = delete;
+
+ sigc::connection _color_changed_connection;
+ sigc::connection _color_dragged_connection;
+};
+
+class ColorWheelSelectorFactory : public ColorSelectorFactory {
+public:
+ Gtk::Widget *createWidget(SelectedColor &color) const override;
+ Glib::ustring modeName() const override;
+};
+}
+}
+}
+
+#endif // SEEN_SP_COLOR_WHEEL_SELECTOR_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/combo-box-entry-tool-item.cpp b/src/ui/widget/combo-box-entry-tool-item.cpp
new file mode 100644
index 0000000..9adcca1
--- /dev/null
+++ b/src/ui/widget/combo-box-entry-tool-item.cpp
@@ -0,0 +1,691 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A class derived from Gtk::ToolItem that wraps a GtkComboBoxEntry.
+ * Features:
+ * Setting GtkEntryBox width in characters.
+ * Passing a function for formatting cells.
+ * Displaying a warning if entry text isn't in list.
+ * Check comma separated values in text against list. (Useful for font-family fallbacks.)
+ * Setting names for GtkComboBoxEntry and GtkEntry (actionName_combobox, actionName_entry)
+ * to allow setting resources.
+ *
+ * Author(s):
+ * Tavmjong Bah
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2010 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+/*
+ * We must provide for both a toolbar item and a menu item.
+ * As we don't know which widgets are used (or even constructed),
+ * we must keep track of things like active entry ourselves.
+ */
+
+#include "combo-box-entry-tool-item.h"
+
+#include <iostream>
+#include <cstring>
+#include <glibmm/ustring.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdkmm/display.h>
+
+#include "ui/icon-names.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+ComboBoxEntryToolItem::ComboBoxEntryToolItem(Glib::ustring name,
+ Glib::ustring label,
+ Glib::ustring tooltip,
+ GtkTreeModel *model,
+ gint entry_width,
+ gint extra_width,
+ void *cell_data_func,
+ void *separator_func,
+ GtkWidget *focusWidget)
+ : _label(std::move(label)),
+ _tooltip(std::move(tooltip)),
+ _model(model),
+ _entry_width(entry_width),
+ _extra_width(extra_width),
+ _cell_data_func(cell_data_func),
+ _separator_func(separator_func),
+ _focusWidget(focusWidget),
+ _active(-1),
+ _text(strdup("")),
+ _entry_completion(nullptr),
+ _indicator(nullptr),
+ _popup(false),
+ _info(nullptr),
+ _info_cb(nullptr),
+ _info_cb_id(0),
+ _info_cb_blocked(false),
+ _warning(nullptr),
+ _warning_cb(nullptr),
+ _warning_cb_id(0),
+ _warning_cb_blocked(false),
+ _altx_name(nullptr)
+{
+ set_name(name);
+
+ gchar *action_name = g_strdup( get_name().c_str() );
+ gchar *combobox_name = g_strjoin( nullptr, action_name, "_combobox", NULL );
+ gchar *entry_name = g_strjoin( nullptr, action_name, "_entry", NULL );
+ g_free( action_name );
+
+ GtkWidget* comboBoxEntry = gtk_combo_box_new_with_model_and_entry (_model);
+ gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (comboBoxEntry), 0);
+
+ // Name it so we can muck with it using an RC file
+ gtk_widget_set_name( comboBoxEntry, combobox_name );
+ g_free( combobox_name );
+
+ {
+ gtk_widget_set_halign(comboBoxEntry, GTK_ALIGN_START);
+ gtk_widget_set_hexpand(comboBoxEntry, FALSE);
+ gtk_widget_set_vexpand(comboBoxEntry, FALSE);
+ add(*Glib::wrap(comboBoxEntry));
+ }
+
+ _combobox = GTK_COMBO_BOX (comboBoxEntry);
+
+ //gtk_combo_box_set_active( GTK_COMBO_BOX( comboBoxEntry ), ink_comboboxentry_action->active );
+ gtk_combo_box_set_active( GTK_COMBO_BOX( comboBoxEntry ), 0 );
+
+ g_signal_connect( G_OBJECT(comboBoxEntry), "changed", G_CALLBACK(combo_box_changed_cb), this );
+
+ // Optionally add separator function...
+ if( _separator_func != nullptr ) {
+ gtk_combo_box_set_row_separator_func( _combobox,
+ GtkTreeViewRowSeparatorFunc (_separator_func),
+ nullptr, nullptr );
+ }
+
+ // FIXME: once gtk3 migration is done this can be removed
+ // https://bugzilla.gnome.org/show_bug.cgi?id=734915
+ gtk_widget_show_all (comboBoxEntry);
+
+ // Optionally add formatting...
+ if( _cell_data_func != nullptr ) {
+ GtkCellRenderer *cell = gtk_cell_renderer_text_new();
+ gtk_cell_layout_clear( GTK_CELL_LAYOUT( comboBoxEntry ) );
+ gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( comboBoxEntry ), cell, true );
+ gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT( comboBoxEntry ), cell,
+ GtkCellLayoutDataFunc (_cell_data_func),
+ nullptr, nullptr );
+ }
+
+ // Optionally widen the combobox width... which widens the drop-down list in list mode.
+ if( _extra_width > 0 ) {
+ GtkRequisition req;
+ gtk_widget_get_preferred_size(GTK_WIDGET(_combobox), &req, nullptr);
+ gtk_widget_set_size_request( GTK_WIDGET( _combobox ),
+ req.width + _extra_width, -1 );
+ }
+
+ // Get reference to GtkEntry and fiddle a bit with it.
+ GtkWidget *child = gtk_bin_get_child( GTK_BIN(comboBoxEntry) );
+
+ // Name it so we can muck with it using an RC file
+ gtk_widget_set_name( child, entry_name );
+ g_free( entry_name );
+
+ if( child && GTK_IS_ENTRY( child ) ) {
+
+ _entry = GTK_ENTRY(child);
+
+ // Change width
+ if( _entry_width > 0 ) {
+ gtk_entry_set_width_chars (GTK_ENTRY (child), _entry_width );
+ }
+
+ // Add pop-up entry completion if required
+ if( _popup ) {
+ popup_enable();
+ }
+
+ // Add altx_name if required
+ if( _altx_name ) {
+ g_object_set_data( G_OBJECT( child ), _altx_name, _entry );
+ }
+
+ // Add signal for GtkEntry to check if finished typing.
+ g_signal_connect( G_OBJECT(child), "activate", G_CALLBACK(entry_activate_cb), this );
+ g_signal_connect( G_OBJECT(child), "key-press-event", G_CALLBACK(keypress_cb), this );
+ }
+
+ set_tooltip(_tooltip.c_str());
+
+ show_all();
+}
+
+// Setters/Getters ---------------------------------------------------
+
+gchar*
+ComboBoxEntryToolItem::get_active_text()
+{
+ gchar* text = g_strdup( _text );
+ return text;
+}
+
+/*
+ * For the font-family list we need to handle two cases:
+ * Text is in list store:
+ * In this case we use row number as the font-family list can have duplicate
+ * entries, one in the document font part and one in the system font part. In
+ * order that scrolling through the list works properly we must distinguish
+ * between the two.
+ * Text is not in the list store (i.e. default font-family is not on system):
+ * In this case we have a row number of -1, and the text must be set by hand.
+ */
+gboolean
+ComboBoxEntryToolItem::set_active_text(const gchar* text, int row)
+{
+ if( strcmp( _text, text ) != 0 ) {
+ g_free( _text );
+ _text = g_strdup( text );
+ }
+
+ // Get active row or -1 if none
+ if( row < 0 ) {
+ row = get_active_row_from_text(this, _text);
+ }
+ _active = row;
+
+ // Set active row, check that combobox has been created.
+ if( _combobox ) {
+ gtk_combo_box_set_active( GTK_COMBO_BOX( _combobox ), _active );
+ }
+
+ // Fiddle with entry
+ if( _entry ) {
+
+ // Explicitly set text in GtkEntry box (won't be set if text not in list).
+ gtk_entry_set_text( _entry, text );
+
+ // Show or hide warning -- this might be better moved to text-toolbox.cpp
+ if( _info_cb_id != 0 &&
+ !_info_cb_blocked ) {
+ g_signal_handler_block (G_OBJECT(_entry),
+ _info_cb_id );
+ _info_cb_blocked = true;
+ }
+ if( _warning_cb_id != 0 &&
+ !_warning_cb_blocked ) {
+ g_signal_handler_block (G_OBJECT(_entry),
+ _warning_cb_id );
+ _warning_cb_blocked = true;
+ }
+
+ bool set = false;
+ if( _warning != nullptr ) {
+ Glib::ustring missing = check_comma_separated_text();
+ if( !missing.empty() ) {
+ gtk_entry_set_icon_from_icon_name( _entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ INKSCAPE_ICON("dialog-warning") );
+ // Can't add tooltip until icon set
+ Glib::ustring warning = _warning;
+ warning += ": ";
+ warning += missing;
+ gtk_entry_set_icon_tooltip_text( _entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ warning.c_str() );
+
+ if( _warning_cb ) {
+
+ // Add callback if we haven't already
+ if( _warning_cb_id == 0 ) {
+ _warning_cb_id =
+ g_signal_connect( G_OBJECT(_entry),
+ "icon-press",
+ G_CALLBACK(_warning_cb),
+ this);
+ }
+ // Unblock signal
+ if( _warning_cb_blocked ) {
+ g_signal_handler_unblock (G_OBJECT(_entry),
+ _warning_cb_id );
+ _warning_cb_blocked = false;
+ }
+ }
+ set = true;
+ }
+ }
+
+ if( !set && _info != nullptr ) {
+ gtk_entry_set_icon_from_icon_name( GTK_ENTRY(_entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ INKSCAPE_ICON("edit-select-all") );
+ gtk_entry_set_icon_tooltip_text( _entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ _info );
+
+ if( _info_cb ) {
+ // Add callback if we haven't already
+ if( _info_cb_id == 0 ) {
+ _info_cb_id =
+ g_signal_connect( G_OBJECT(_entry),
+ "icon-press",
+ G_CALLBACK(_info_cb),
+ this);
+ }
+ // Unblock signal
+ if( _info_cb_blocked ) {
+ g_signal_handler_unblock (G_OBJECT(_entry),
+ _info_cb_id );
+ _info_cb_blocked = false;
+ }
+ }
+ set = true;
+ }
+
+ if( !set ) {
+ gtk_entry_set_icon_from_icon_name( GTK_ENTRY(_entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ nullptr );
+ }
+ }
+
+ // Return if active text in list
+ gboolean found = ( _active != -1 );
+ return found;
+}
+
+void
+ComboBoxEntryToolItem::set_entry_width(gint entry_width)
+{
+ _entry_width = entry_width;
+
+ // Clamp to limits
+ if(entry_width < -1) entry_width = -1;
+ if(entry_width > 100) entry_width = 100;
+
+ // Widget may not have been created....
+ if( _entry ) {
+ gtk_entry_set_width_chars( GTK_ENTRY(_entry), entry_width );
+ }
+}
+
+void
+ComboBoxEntryToolItem::set_extra_width( gint extra_width )
+{
+ _extra_width = extra_width;
+
+ // Clamp to limits
+ if(extra_width < -1) extra_width = -1;
+ if(extra_width > 500) extra_width = 500;
+
+ // Widget may not have been created....
+ if( _combobox ) {
+ GtkRequisition req;
+ gtk_widget_get_preferred_size(GTK_WIDGET(_combobox), &req, nullptr);
+ gtk_widget_set_size_request( GTK_WIDGET( _combobox ), req.width + _extra_width, -1 );
+ }
+}
+
+void
+ComboBoxEntryToolItem::focus_on_click( bool focus_on_click )
+{
+ if (_combobox) {
+ gtk_widget_set_focus_on_click(GTK_WIDGET(_combobox), focus_on_click);
+ }
+}
+
+void
+ComboBoxEntryToolItem::popup_enable()
+{
+ _popup = true;
+
+ // Widget may not have been created....
+ if( _entry ) {
+
+ // Check we don't already have a GtkEntryCompletion
+ if( _entry_completion ) return;
+
+ _entry_completion = gtk_entry_completion_new();
+
+ gtk_entry_set_completion( _entry, _entry_completion );
+ gtk_entry_completion_set_model( _entry_completion, _model );
+ gtk_entry_completion_set_text_column( _entry_completion, 0 );
+ gtk_entry_completion_set_popup_completion( _entry_completion, true );
+ gtk_entry_completion_set_inline_completion( _entry_completion, false );
+ gtk_entry_completion_set_inline_selection( _entry_completion, true );
+
+ g_signal_connect (G_OBJECT (_entry_completion), "match-selected", G_CALLBACK (match_selected_cb), this);
+ }
+}
+
+void
+ComboBoxEntryToolItem::popup_disable()
+{
+ _popup = false;
+
+ if( _entry_completion ) {
+ gtk_widget_destroy(GTK_WIDGET(_entry_completion));
+ _entry_completion = nullptr;
+ }
+}
+
+void
+ComboBoxEntryToolItem::set_tooltip(const gchar* tooltip)
+{
+ set_tooltip_text(tooltip);
+ gtk_widget_set_tooltip_text ( GTK_WIDGET(_combobox), tooltip);
+
+ // Widget may not have been created....
+ if( _entry ) {
+ gtk_widget_set_tooltip_text ( GTK_WIDGET(_entry), tooltip);
+ }
+}
+
+void
+ComboBoxEntryToolItem::set_info(const gchar* info)
+{
+ g_free( _info );
+ _info = g_strdup( info );
+
+ // Widget may not have been created....
+ if( _entry ) {
+ gtk_entry_set_icon_tooltip_text( GTK_ENTRY(_entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ _info );
+ }
+}
+
+void
+ComboBoxEntryToolItem::set_info_cb(gpointer info_cb)
+{
+ _info_cb = info_cb;
+}
+
+void
+ComboBoxEntryToolItem::set_warning(const gchar* warning)
+{
+ g_free( _warning );
+ _warning = g_strdup( warning );
+
+ // Widget may not have been created....
+ if( _entry ) {
+ gtk_entry_set_icon_tooltip_text( GTK_ENTRY(_entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ _warning );
+ }
+}
+
+void
+ComboBoxEntryToolItem::set_warning_cb(gpointer warning_cb)
+{
+ _warning_cb = warning_cb;
+}
+
+void
+ComboBoxEntryToolItem::set_altx_name(const gchar* altx_name)
+{
+ g_free(_altx_name);
+ _altx_name = g_strdup( altx_name );
+
+ // Widget may not have been created....
+ if(_entry) {
+ g_object_set_data( G_OBJECT(_entry), _altx_name, _entry );
+ }
+}
+
+// Internal ---------------------------------------------------
+
+// Return row of active text or -1 if not found. If exclude is true,
+// use 3d column if available to exclude row from checking (useful to
+// skip rows added for font-families included in doc and not on
+// system)
+gint
+ComboBoxEntryToolItem::get_active_row_from_text(ComboBoxEntryToolItem *action,
+ const gchar *target_text,
+ gboolean exclude,
+ gboolean ignore_case )
+{
+ // Check if text in list
+ gint row = 0;
+ gboolean found = false;
+ GtkTreeIter iter;
+ gboolean valid = gtk_tree_model_get_iter_first( action->_model, &iter );
+ while ( valid ) {
+
+ // See if we should exclude a row
+ gboolean check = true; // If true, font-family is on system.
+ if( exclude && gtk_tree_model_get_n_columns( action->_model ) > 2 ) {
+ gtk_tree_model_get( action->_model, &iter, 2, &check, -1 );
+ }
+
+ if( check ) {
+ // Get text from list entry
+ gchar* text = nullptr;
+ gtk_tree_model_get( action->_model, &iter, 0, &text, -1 ); // Column 0
+
+ if( !ignore_case ) {
+ // Case sensitive compare
+ if( strcmp( target_text, text ) == 0 ){
+ found = true;
+ g_free(text);
+ break;
+ }
+ } else {
+ // Case insensitive compare
+ gchar* target_text_casefolded = g_utf8_casefold( target_text, -1 );
+ gchar* text_casefolded = g_utf8_casefold( text, -1 );
+ gboolean equal = (strcmp( target_text_casefolded, text_casefolded ) == 0 );
+ g_free( text_casefolded );
+ g_free( target_text_casefolded );
+ if( equal ) {
+ found = true;
+ g_free(text);
+ break;
+ }
+ }
+ g_free(text);
+ }
+
+ ++row;
+ valid = gtk_tree_model_iter_next( action->_model, &iter );
+ }
+
+ if( !found ) row = -1;
+
+ return row;
+}
+
+// Checks if all comma separated text fragments are in the list and
+// returns a ustring with a list of missing fragments.
+// This is useful for checking if all fonts in a font-family fallback
+// list are available on the system.
+//
+// This routine could also create a Pango Markup string to show which
+// fragments are invalid in the entry box itself. See:
+// http://developer.gnome.org/pango/stable/PangoMarkupFormat.html
+// However... it appears that while one can retrieve the PangoLayout
+// for a GtkEntry box, it is only a copy and changing it has no effect.
+// PangoLayout * pl = gtk_entry_get_layout( entry );
+// pango_layout_set_markup( pl, "NEW STRING", -1 ); // DOESN'T WORK
+Glib::ustring
+ComboBoxEntryToolItem::check_comma_separated_text()
+{
+ Glib::ustring missing;
+
+ // Parse fallback_list using a comma as deliminator
+ gchar** tokens = g_strsplit( _text, ",", 0 );
+
+ gint i = 0;
+ while( tokens[i] != nullptr ) {
+
+ // Remove any surrounding white space.
+ g_strstrip( tokens[i] );
+
+ if( get_active_row_from_text( this, tokens[i], true, true ) == -1 ) {
+ missing += tokens[i];
+ missing += ", ";
+ }
+ ++i;
+ }
+ g_strfreev( tokens );
+
+ // Remove extra comma and space from end.
+ if( missing.size() >= 2 ) {
+ missing.resize( missing.size()-2 );
+ }
+ return missing;
+}
+
+// Callbacks ---------------------------------------------------
+
+void
+ComboBoxEntryToolItem::combo_box_changed_cb( GtkComboBox* widget, gpointer data )
+{
+ // Two things can happen to get here:
+ // An item is selected in the drop-down menu.
+ // Text is typed.
+ // We only react here if an item is selected.
+
+ // Get action
+ auto action = reinterpret_cast<ComboBoxEntryToolItem *>( data );
+
+ // Check if item selected:
+ gint newActive = gtk_combo_box_get_active(widget);
+ if( newActive >= 0 && newActive != action->_active ) {
+
+ action->_active = newActive;
+
+ GtkTreeIter iter;
+ if( gtk_combo_box_get_active_iter( GTK_COMBO_BOX( action->_combobox ), &iter ) ) {
+
+ gchar* text = nullptr;
+ gtk_tree_model_get( action->_model, &iter, 0, &text, -1 );
+ gtk_entry_set_text( action->_entry, text );
+
+ g_free( action->_text );
+ action->_text = text;
+ }
+
+ // Now let the world know
+ action->_signal_changed.emit();
+ }
+}
+
+void
+ComboBoxEntryToolItem::entry_activate_cb( GtkEntry *widget,
+ gpointer data )
+{
+ // Get text from entry box.. check if it matches a menu entry.
+
+ // Get action
+ auto action = reinterpret_cast<ComboBoxEntryToolItem*>( data );
+
+ // Get text
+ g_free( action->_text );
+ action->_text = g_strdup( gtk_entry_get_text( widget ) );
+
+ // Get row
+ action->_active =
+ get_active_row_from_text( action, action->_text );
+
+ // Set active row
+ gtk_combo_box_set_active( GTK_COMBO_BOX( action->_combobox), action->_active );
+
+ // Now let the world know
+ action->_signal_changed.emit();
+}
+
+gboolean
+ComboBoxEntryToolItem::match_selected_cb( GtkEntryCompletion* /*widget*/, GtkTreeModel* model, GtkTreeIter* iter, gpointer data )
+{
+ // Get action
+ auto action = reinterpret_cast<ComboBoxEntryToolItem*>(data);
+ GtkEntry *entry = action->_entry;
+
+ if( entry) {
+ gchar *family = nullptr;
+ gtk_tree_model_get(model, iter, 0, &family, -1);
+
+ // Set text in GtkEntry
+ gtk_entry_set_text (GTK_ENTRY (entry), family );
+
+ // Set text in ToolItem
+ g_free( action->_text );
+ action->_text = family;
+
+ // Get row
+ action->_active =
+ get_active_row_from_text( action, action->_text );
+
+ // Set active row
+ gtk_combo_box_set_active( GTK_COMBO_BOX( action->_combobox), action->_active );
+
+ // Now let the world know
+ action->_signal_changed.emit();
+
+ return true;
+ }
+ return false;
+}
+
+void
+ComboBoxEntryToolItem::defocus()
+{
+ if ( _focusWidget ) {
+ gtk_widget_grab_focus( _focusWidget );
+ }
+}
+
+gboolean
+ComboBoxEntryToolItem::keypress_cb( GtkWidget * /*widget*/, GdkEventKey *event, gpointer data )
+{
+ gboolean wasConsumed = FALSE; /* default to report event not consumed */
+ guint key = 0;
+ auto action = reinterpret_cast<ComboBoxEntryToolItem*>(data);
+ gdk_keymap_translate_keyboard_state( Gdk::Display::get_default()->get_keymap(),
+ event->hardware_keycode, (GdkModifierType)event->state,
+ 0, &key, nullptr, nullptr, nullptr );
+
+ switch ( key ) {
+
+ // TODO Add bindings for Tab/LeftTab
+ case GDK_KEY_Escape:
+ {
+ //gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), action->private_data->lastVal );
+ action->defocus();
+ wasConsumed = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ {
+ action->defocus();
+ //wasConsumed = TRUE;
+ }
+ break;
+
+
+ }
+
+ return wasConsumed;
+}
+
+}
+}
+}
+
+/*
+ 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/combo-box-entry-tool-item.h b/src/ui/widget/combo-box-entry-tool-item.h
new file mode 100644
index 0000000..3d6440a
--- /dev/null
+++ b/src/ui/widget/combo-box-entry-tool-item.h
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A class derived from Gtk::ToolItem that wraps a GtkComboBoxEntry.
+ * Features:
+ * Setting GtkEntryBox width in characters.
+ * Passing a function for formatting cells.
+ * Displaying a warning if entry text isn't in list.
+ * Check comma separated values in text against list. (Useful for font-family fallbacks.)
+ * Setting names for GtkComboBoxEntry and GtkEntry (actionName_combobox, actionName_entry)
+ * to allow setting resources.
+ *
+ * Author(s):
+ * Tavmjong Bah
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2010 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_COMBOBOXENTRY_ACTION
+#define SEEN_INK_COMBOBOXENTRY_ACTION
+
+#include <gtkmm/toolitem.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * Creates a Gtk::ToolItem subclass that wraps a Gtk::ComboBox object.
+ */
+class ComboBoxEntryToolItem : public Gtk::ToolItem {
+private:
+ Glib::ustring _tooltip;
+ Glib::ustring _label;
+ GtkTreeModel *_model; ///< Tree Model
+ GtkComboBox *_combobox;
+ GtkEntry *_entry;
+ gint _entry_width;// Width of GtkEntry in characters.
+ gint _extra_width;// Extra Width of GtkComboBox.. to widen drop-down list in list mode.
+ gpointer _cell_data_func; // drop-down menu format
+ gpointer _separator_func;
+ gboolean _popup; // Do we pop-up an entry-completion dialog?
+ GtkEntryCompletion *_entry_completion;
+ GtkWidget *_focusWidget; ///< The widget to return focus to
+
+ GtkWidget *_indicator;
+ gint _active; // Index of active menu item (-1 if not in list).
+ gchar *_text; // Text of active menu item or entry box.
+ gchar *_info; // Text for tooltip info about entry.
+ gpointer _info_cb; // Callback for clicking info icon.
+ gint _info_cb_id;
+ gboolean _info_cb_blocked;
+ gchar *_warning; // Text for tooltip warning that entry isn't in list.
+ gpointer _warning_cb; // Callback for clicking warning icon.
+ gint _warning_cb_id;
+ gboolean _warning_cb_blocked;
+ gchar *_altx_name; // Target for Alt-X keyboard shortcut.
+
+ // Signals
+ sigc::signal<void> _signal_changed;
+
+ void (*changed) (ComboBoxEntryToolItem* action);
+ void (*activated) (ComboBoxEntryToolItem* action);
+
+ static gint get_active_row_from_text(ComboBoxEntryToolItem *action,
+ const gchar *target_text,
+ gboolean exclude = false,
+ gboolean ignore_case = false);
+ void defocus();
+
+ static void combo_box_changed_cb( GtkComboBox* widget, gpointer data );
+ static void entry_activate_cb( GtkEntry *widget,
+ gpointer data );
+ static gboolean match_selected_cb( GtkEntryCompletion *widget,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data);
+ static gboolean keypress_cb( GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer data );
+
+ Glib::ustring check_comma_separated_text();
+
+public:
+ ComboBoxEntryToolItem(const Glib::ustring name,
+ const Glib::ustring label,
+ const Glib::ustring tooltip,
+ GtkTreeModel *model,
+ gint entry_width = -1,
+ gint extra_width = -1,
+ gpointer cell_data_func = nullptr,
+ gpointer separator_func = nullptr,
+ GtkWidget* focusWidget = nullptr);
+
+ gchar* get_active_text();
+ gboolean set_active_text(const gchar* text, int row=-1);
+
+ void set_entry_width(gint entry_width);
+ void set_extra_width(gint extra_width);
+
+ void popup_enable();
+ void popup_disable();
+ void focus_on_click( bool focus_on_click );
+
+ void set_info( const gchar* info );
+ void set_info_cb( gpointer info_cb );
+ void set_warning( const gchar* warning_cb );
+ void set_warning_cb(gpointer warning );
+ void set_tooltip( const gchar* tooltip );
+
+ void set_altx_name( const gchar* altx_name );
+
+ // Accessor methods
+ decltype(_model) get_model() const {return _model;}
+ decltype(_combobox) get_combobox() const {return _combobox;}
+ decltype(_entry) get_entry() const {return _entry;}
+ decltype(_entry_width) get_entry_width() const {return _entry_width;}
+ decltype(_extra_width) get_extra_width() const {return _extra_width;}
+ decltype(_cell_data_func) get_cell_data_func() const {return _cell_data_func;}
+ decltype(_separator_func) get_separator_func() const {return _separator_func;}
+ decltype(_popup) get_popup() const {return _popup;}
+ decltype(_focusWidget) get_focus_widget() const {return _focusWidget;}
+
+ decltype(_active) get_active() const {return _active;}
+
+ decltype(_signal_changed) signal_changed() {return _signal_changed;}
+
+ // Mutator methods
+ void set_model (decltype(_model) model) {_model = model;}
+ void set_combobox (decltype(_combobox) combobox) {_combobox = combobox;}
+ void set_entry (decltype(_entry) entry) {_entry = entry;}
+ void set_cell_data_func(decltype(_cell_data_func) cell_data_func) {_cell_data_func = cell_data_func;}
+ void set_separator_func(decltype(_separator_func) separator_func) {_separator_func = separator_func;}
+ void set_popup (decltype(_popup) popup) {_popup = popup;}
+ void set_focus_widget (decltype(_focusWidget) focus_widget) {_focusWidget = focus_widget;}
+
+ // This doesn't seem right... surely we should set the active row in the Combobox too?
+ void set_active (decltype(_active) active) {_active = active;}
+};
+
+}
+}
+}
+#endif /* SEEN_INK_COMBOBOXENTRY_ACTION */
+
+/*
+ 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/combo-enums.h b/src/ui/widget/combo-enums.h
new file mode 100644
index 0000000..d574106
--- /dev/null
+++ b/src/ui/widget/combo-enums.h
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Nicholas Bishop <nicholasbishop@gmail.com>
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_COMBO_ENUMS_H
+#define INKSCAPE_UI_WIDGET_COMBO_ENUMS_H
+
+#include "ui/widget/labelled.h"
+#include <gtkmm/combobox.h>
+#include <gtkmm/liststore.h>
+#include "attr-widget.h"
+#include "util/enums.h"
+#include <glibmm/i18n.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * Simplified management of enumerations in the UI as combobox.
+ */
+template<typename E> class ComboBoxEnum : public Gtk::ComboBox, public AttrWidget
+{
+private:
+ int on_sort_compare( const Gtk::TreeModel::iterator & a, const Gtk::TreeModel::iterator & b)
+ {
+ Glib::ustring an=(*a)[_columns.label];
+ Glib::ustring bn=(*b)[_columns.label];
+ return an.compare(bn);
+ }
+
+ bool _sort;
+
+public:
+ ComboBoxEnum(E default_value, const Util::EnumDataConverter<E>& c, const SPAttributeEnum a = SP_ATTR_INVALID, bool sort = true)
+ : AttrWidget(a, (unsigned int)default_value), setProgrammatically(false), _converter(c)
+ {
+ _sort = sort;
+
+ signal_changed().connect(signal_attr_changed().make_slot());
+ gtk_widget_add_events(GTK_WIDGET(gobj()), GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK);
+ signal_scroll_event().connect(sigc::mem_fun(*this, &ComboBoxEnum<E>::on_scroll_event));
+ _model = Gtk::ListStore::create(_columns);
+ set_model(_model);
+
+ pack_start(_columns.label);
+
+ // Initialize list
+ for(int i = 0; i < static_cast<int>(_converter._length); ++i) {
+ Gtk::TreeModel::Row row = *_model->append();
+ const Util::EnumData<E>* data = &_converter.data(i);
+ row[_columns.data] = data;
+ row[_columns.label] = _( _converter.get_label(data->id).c_str() );
+ }
+ set_active_by_id(default_value);
+
+ // Sort the list
+ if (sort) {
+ _model->set_default_sort_func(sigc::mem_fun(*this, &ComboBoxEnum<E>::on_sort_compare));
+ _model->set_sort_column(_columns.label, Gtk::SORT_ASCENDING);
+ }
+ }
+
+ ComboBoxEnum(const Util::EnumDataConverter<E>& c, const SPAttributeEnum a = SP_ATTR_INVALID, bool sort = true)
+ : AttrWidget(a, (unsigned int) 0), setProgrammatically(false), _converter(c)
+ {
+ _sort = sort;
+
+ signal_changed().connect(signal_attr_changed().make_slot());
+ gtk_widget_add_events(GTK_WIDGET(gobj()), GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK);
+ signal_scroll_event().connect(sigc::mem_fun(*this, &ComboBoxEnum<E>::on_scroll_event));
+
+ _model = Gtk::ListStore::create(_columns);
+ set_model(_model);
+
+ pack_start(_columns.label);
+
+ // Initialize list
+ for(unsigned int i = 0; i < _converter._length; ++i) {
+ Gtk::TreeModel::Row row = *_model->append();
+ const Util::EnumData<E>* data = &_converter.data(i);
+ row[_columns.data] = data;
+ row[_columns.label] = _( _converter.get_label(data->id).c_str() );
+ }
+ set_active(0);
+
+ // Sort the list
+ if (_sort) {
+ _model->set_default_sort_func(sigc::mem_fun(*this, &ComboBoxEnum<E>::on_sort_compare));
+ _model->set_sort_column(_columns.label, Gtk::SORT_ASCENDING);
+ }
+ }
+
+ Glib::ustring get_as_attribute() const override
+ {
+ return get_active_data()->key;
+ }
+
+ void set_from_attribute(SPObject* o) override
+ {
+ setProgrammatically = true;
+ const gchar* val = attribute_value(o);
+ if(val)
+ set_active_by_id(_converter.get_id_from_key(val));
+ else
+ set_active(get_default()->as_uint());
+ }
+
+ const Util::EnumData<E>* get_active_data() const
+ {
+ Gtk::TreeModel::iterator i = this->get_active();
+ if(i)
+ return (*i)[_columns.data];
+ return nullptr;
+ }
+
+ void add_row(const Glib::ustring& s)
+ {
+ Gtk::TreeModel::Row row = *_model->append();
+ row[_columns.data] = 0;
+ row[_columns.label] = s;
+ }
+
+ void remove_row(E id) {
+ Gtk::TreeModel::iterator i;
+
+ for(i = _model->children().begin(); i != _model->children().end(); ++i) {
+ const Util::EnumData<E>* data = (*i)[_columns.data];
+
+ if(data->id == id)
+ break;
+ }
+
+ if(i != _model->children().end())
+ _model->erase(i);
+ }
+
+ void set_active_by_id(E id) {
+ setProgrammatically = true;
+ for(Gtk::TreeModel::iterator i = _model->children().begin();
+ i != _model->children().end(); ++i)
+ {
+ const Util::EnumData<E>* data = (*i)[_columns.data];
+ if(data->id == id) {
+ set_active(i);
+ break;
+ }
+ }
+ };
+
+ bool on_scroll_event(GdkEventScroll *event) override { return false; }
+
+ void set_active_by_key(const Glib::ustring& key) {
+ setProgrammatically = true;
+ set_active_by_id( _converter.get_id_from_key(key) );
+ };
+
+ bool setProgrammatically;
+
+private:
+ class Columns : public Gtk::TreeModel::ColumnRecord
+ {
+ public:
+ Columns()
+ {
+ add(data);
+ add(label);
+ }
+
+ Gtk::TreeModelColumn<const Util::EnumData<E>*> data;
+ Gtk::TreeModelColumn<Glib::ustring> label;
+ };
+
+ Columns _columns;
+ Glib::RefPtr<Gtk::ListStore> _model;
+ const Util::EnumDataConverter<E>& _converter;
+};
+
+
+/**
+ * Simplified management of enumerations in the UI as combobox.
+ */
+template<typename E> class LabelledComboBoxEnum : public Labelled
+{
+public:
+ LabelledComboBoxEnum( Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ const Util::EnumDataConverter<E>& c,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true,
+ bool sorted = true)
+ : Labelled(label, tooltip, new ComboBoxEnum<E>(c, SP_ATTR_INVALID, sorted), suffix, icon, mnemonic)
+ {
+ }
+
+ ComboBoxEnum<E>* getCombobox() {
+ return static_cast< ComboBoxEnum<E>* > (_widget);
+ }
+};
+
+}
+}
+}
+
+#endif
+
+/*
+ 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/combo-tool-item.cpp b/src/ui/widget/combo-tool-item.cpp
new file mode 100644
index 0000000..ffc7e75
--- /dev/null
+++ b/src/ui/widget/combo-tool-item.cpp
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+/** \file
+ A combobox that can be displayed in a toolbar.
+*/
+
+#include "combo-tool-item.h"
+#include "preferences.h"
+#include <iostream>
+#include <utility>
+#include <gtkmm/toolitem.h>
+#include <gtkmm/menuitem.h>
+#include <gtkmm/radiomenuitem.h>
+#include <gtkmm/combobox.h>
+#include <gtkmm/menu.h>
+#include <gtkmm/box.h>
+#include <gtkmm/label.h>
+#include <gtkmm/image.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+ComboToolItem*
+ComboToolItem::create(const Glib::ustring &group_label,
+ const Glib::ustring &tooltip,
+ const Glib::ustring &stock_id,
+ Glib::RefPtr<Gtk::ListStore> store,
+ bool has_entry)
+{
+ return new ComboToolItem(group_label, tooltip, stock_id, store, has_entry);
+}
+
+ComboToolItem::ComboToolItem(Glib::ustring group_label,
+ Glib::ustring tooltip,
+ Glib::ustring stock_id,
+ Glib::RefPtr<Gtk::ListStore> store,
+ bool has_entry) :
+ _active(-1),
+ _group_label(std::move( group_label )),
+ _tooltip(std::move( tooltip )),
+ _stock_id(std::move( stock_id )),
+ _store (std::move(store)),
+ _use_label (true),
+ _use_icon (false),
+ _use_pixbuf (true),
+ _icon_size ( Gtk::ICON_SIZE_LARGE_TOOLBAR ),
+ _combobox (nullptr),
+ _group_label_widget(nullptr),
+ _container(Gtk::manage(new Gtk::Box())),
+ _menuitem (nullptr)
+{
+ add(*_container);
+ _container->set_spacing(3);
+
+ // ": " is added to the group label later
+ if (!_group_label.empty()) {
+ // we don't expect trailing spaces
+ // g_assert(_group_label.raw()[_group_label.raw().size() - 1] != ' ');
+
+ // strip space (note: raw() indexing is much cheaper on Glib::ustring)
+ if (_group_label.raw()[_group_label.raw().size() - 1] == ' ') {
+ _group_label.resize(_group_label.size() - 1);
+ }
+ }
+ if (!_group_label.empty()) {
+ // we don't expect a trailing colon
+ // g_assert(_group_label.raw()[_group_label.raw().size() - 1] != ':');
+
+ // strip colon (note: raw() indexing is much cheaper on Glib::ustring)
+ if (_group_label.raw()[_group_label.raw().size() - 1] == ':') {
+ _group_label.resize(_group_label.size() - 1);
+ }
+ }
+
+
+ // Create combobox
+ _combobox = Gtk::manage (new Gtk::ComboBox(has_entry));
+ _combobox->set_model(_store);
+
+ populate_combobox();
+
+ _combobox->signal_changed().connect(
+ sigc::mem_fun(*this, &ComboToolItem::on_changed_combobox));
+ _container->pack_start(*_combobox);
+
+ show_all();
+}
+
+void
+ComboToolItem::focus_on_click( bool focus_on_click )
+{
+ _combobox->set_focus_on_click(focus_on_click);
+}
+
+
+void
+ComboToolItem::use_label(bool use_label)
+{
+ _use_label = use_label;
+ populate_combobox();
+}
+
+void
+ComboToolItem::use_icon(bool use_icon)
+{
+ _use_icon = use_icon;
+ populate_combobox();
+}
+
+void
+ComboToolItem::use_pixbuf(bool use_pixbuf)
+{
+ _use_pixbuf = use_pixbuf;
+ populate_combobox();
+}
+
+void
+ComboToolItem::use_group_label(bool use_group_label)
+{
+ if (use_group_label == (_group_label_widget != nullptr)) {
+ return;
+ }
+ if (use_group_label) {
+ _container->remove(*_combobox);
+ _group_label_widget = Gtk::manage(new Gtk::Label(_group_label + ": "));
+ _container->pack_start(*_group_label_widget);
+ _container->pack_start(*_combobox);
+ } else {
+ _container->remove(*_group_label_widget);
+ delete _group_label_widget;
+ _group_label_widget = nullptr;
+ }
+}
+
+void
+ComboToolItem::populate_combobox()
+{
+ _combobox->clear();
+
+ ComboToolItemColumns columns;
+ if (_use_icon) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/theme/symbolicIcons", false)) {
+ auto children = _store->children();
+ for (auto row : children) {
+ Glib::ustring icon = row[columns.col_icon];
+ gint pos = icon.find("-symbolic");
+ if (pos == std::string::npos) {
+ icon += "-symbolic";
+ }
+ row[columns.col_icon] = icon;
+ }
+ }
+ Gtk::CellRendererPixbuf *renderer = new Gtk::CellRendererPixbuf;
+ renderer->set_property ("stock_size", Gtk::ICON_SIZE_LARGE_TOOLBAR);
+ _combobox->pack_start (*Gtk::manage(renderer), false);
+ _combobox->add_attribute (*renderer, "icon_name", columns.col_icon );
+ } else if (_use_pixbuf) {
+ Gtk::CellRendererPixbuf *renderer = new Gtk::CellRendererPixbuf;
+ //renderer->set_property ("stock_size", Gtk::ICON_SIZE_LARGE_TOOLBAR);
+ _combobox->pack_start (*Gtk::manage(renderer), false);
+ _combobox->add_attribute (*renderer, "pixbuf", columns.col_pixbuf );
+ }
+
+ if (_use_label) {
+ _combobox->pack_start(columns.col_label);
+ }
+
+ std::vector<Gtk::CellRenderer*> cells = _combobox->get_cells();
+ for (auto & cell : cells) {
+ _combobox->add_attribute (*cell, "sensitive", columns.col_sensitive);
+ }
+
+ set_tooltip_text(_tooltip);
+ _combobox->set_tooltip_text(_tooltip);
+ _combobox->set_active (_active);
+}
+
+void
+ComboToolItem::set_active (gint active) {
+ if (_active != active) {
+
+ _active = active;
+
+ if (_combobox) {
+ _combobox->set_active (active);
+ }
+
+ if (active < _radiomenuitems.size()) {
+ _radiomenuitems[ active ]->set_active();
+ }
+ }
+}
+
+Glib::ustring
+ComboToolItem::get_active_text () {
+ Gtk::TreeModel::Row row = _store->children()[_active];
+ ComboToolItemColumns columns;
+ Glib::ustring label = row[columns.col_label];
+ return label;
+}
+
+bool
+ComboToolItem::on_create_menu_proxy()
+{
+ if (_menuitem == nullptr) {
+
+ _menuitem = Gtk::manage (new Gtk::MenuItem(_group_label));
+ Gtk::Menu *menu = Gtk::manage (new Gtk::Menu);
+
+ Gtk::RadioButton::Group group;
+ int index = 0;
+ auto children = _store->children();
+ for (auto row : children) {
+ ComboToolItemColumns columns;
+ Glib::ustring label = row[columns.col_label ];
+ Glib::ustring icon = row[columns.col_icon ];
+ Glib::ustring tooltip = row[columns.col_tooltip ];
+ bool sensitive = row[columns.col_sensitive ];
+
+ Gtk::RadioMenuItem* button = Gtk::manage(new Gtk::RadioMenuItem(group));
+ button->set_label (label);
+ button->set_tooltip_text( tooltip );
+ button->set_sensitive( sensitive );
+
+ button->signal_toggled().connect( sigc::bind<0>(
+ sigc::mem_fun(*this, &ComboToolItem::on_toggled_radiomenu), index++)
+ );
+
+ menu->add (*button);
+
+ _radiomenuitems.push_back( button );
+ }
+
+ if ( _active < _radiomenuitems.size()) {
+ _radiomenuitems[ _active ]->set_active();
+ }
+
+ _menuitem->set_submenu (*menu);
+ _menuitem->show_all();
+ }
+
+ set_proxy_menu_item(_group_label, *_menuitem);
+ return true;
+}
+
+void
+ComboToolItem::on_changed_combobox() {
+
+ int row = _combobox->get_active_row_number();
+ set_active( row );
+ _changed.emit (_active);
+ _changed_after.emit (_active);
+}
+
+void
+ComboToolItem::on_toggled_radiomenu(int n) {
+
+ // toggled emitted twice, first for button toggled off, second for button toggled on.
+ // We want to react only to the button turned on.
+ if ( n < _radiomenuitems.size() &&_radiomenuitems[ n ]->get_active()) {
+ set_active ( n );
+ _changed.emit (_active);
+ _changed_after.emit (_active);
+ }
+}
+
+}
+}
+}
+/*
+ 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/combo-tool-item.h b/src/ui/widget/combo-tool-item.h
new file mode 100644
index 0000000..1fc8b00
--- /dev/null
+++ b/src/ui/widget/combo-tool-item.h
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_COMBO_TOOL_ITEM
+#define SEEN_COMBO_TOOL_ITEM
+
+/*
+ * Authors:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+/**
+ A combobox that can be displayed in a toolbar
+*/
+
+#include <gtkmm/toolitem.h>
+#include <gtkmm/liststore.h>
+#include <sigc++/sigc++.h>
+#include <vector>
+
+namespace Gtk {
+class Box;
+class ComboBox;
+class Label;
+class MenuItem;
+class RadioMenuItem;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+class ComboToolItemColumns : public Gtk::TreeModel::ColumnRecord {
+public:
+ ComboToolItemColumns() {
+ add (col_label);
+ add (col_value);
+ add (col_icon);
+ add (col_pixbuf);
+ add (col_data); // Used to store a pointer
+ add (col_tooltip);
+ add (col_sensitive);
+ }
+ Gtk::TreeModelColumn<Glib::ustring> col_label;
+ Gtk::TreeModelColumn<Glib::ustring> col_value;
+ Gtk::TreeModelColumn<Glib::ustring> col_icon;
+ Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > col_pixbuf;
+ Gtk::TreeModelColumn<void *> col_data;
+ Gtk::TreeModelColumn<Glib::ustring> col_tooltip;
+ Gtk::TreeModelColumn<bool> col_sensitive;
+};
+
+
+class ComboToolItem : public Gtk::ToolItem {
+
+public:
+ static ComboToolItem* create(const Glib::ustring &label,
+ const Glib::ustring &tooltip,
+ const Glib::ustring &stock_id,
+ Glib::RefPtr<Gtk::ListStore> store,
+ bool has_entry = false);
+
+ /* Style of combobox */
+ void use_label( bool use_label );
+ void use_icon( bool use_icon );
+ void focus_on_click( bool focus_on_click );
+ void use_pixbuf( bool use_pixbuf );
+ void use_group_label( bool use_group_label ); // Applies to tool item only
+
+ gint get_active() { return _active; }
+ Glib::ustring get_active_text();
+ void set_active( gint active );
+ void set_icon_size( Gtk::BuiltinIconSize size ) { _icon_size = size; }
+
+ Glib::RefPtr<Gtk::ListStore> get_store() { return _store; }
+
+ sigc::signal<void, int> signal_changed() { return _changed; }
+ sigc::signal<void, int> signal_changed_after() { return _changed_after; }
+
+protected:
+ bool on_create_menu_proxy() override;
+ void populate_combobox();
+
+ /* Signals */
+ sigc::signal<void, int> _changed;
+ sigc::signal<void, int> _changed_after; // Needed for unit tracker which eats _changed.
+
+private:
+
+ Glib::ustring _group_label;
+ Glib::ustring _tooltip;
+ Glib::ustring _stock_id;
+ Glib::RefPtr<Gtk::ListStore> _store;
+
+ gint _active; /* Active menu item/button */
+
+ /* Style */
+ bool _use_label;
+ bool _use_icon; // Applies to menu item only
+ bool _use_pixbuf;
+ Gtk::BuiltinIconSize _icon_size;
+
+ /* Combobox in tool */
+ Gtk::ComboBox* _combobox;
+ Gtk::Label* _group_label_widget;
+ Gtk::Box* _container;
+
+ Gtk::MenuItem* _menuitem;
+ std::vector<Gtk::RadioMenuItem*> _radiomenuitems;
+
+ /* Internal Callbacks */
+ void on_changed_combobox();
+ void on_toggled_radiomenu(int n);
+
+ ComboToolItem(Glib::ustring group_label,
+ Glib::ustring tooltip,
+ Glib::ustring stock_id,
+ Glib::RefPtr<Gtk::ListStore> store,
+ bool has_entry = false);
+};
+}
+}
+}
+#endif /* SEEN_COMBO_TOOL_ITEM */
+
+/*
+ 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/dash-selector.cpp b/src/ui/widget/dash-selector.cpp
new file mode 100644
index 0000000..897b964
--- /dev/null
+++ b/src/ui/widget/dash-selector.cpp
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Combobox for selecting dash patterns - implementation.
+ */
+/* Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ *
+ * Copyright (C) 2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "dash-selector.h"
+
+#include <cstring>
+
+#include <glibmm/i18n.h>
+
+#include <2geom/coord.h>
+
+#include "preferences.h"
+
+#include "display/cairo-utils.h"
+
+#include "style.h"
+
+#include "ui/dialog-events.h"
+#include "ui/widget/spinbutton.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+gchar const *const DashSelector::_prefs_path = "/palette/dashes";
+
+static double dash_0[] = {-1.0};
+static double dash_1_1[] = {1.0, 1.0, -1.0};
+static double dash_2_1[] = {2.0, 1.0, -1.0};
+static double dash_4_1[] = {4.0, 1.0, -1.0};
+static double dash_1_2[] = {1.0, 2.0, -1.0};
+static double dash_1_4[] = {1.0, 4.0, -1.0};
+
+static size_t BD_LEN = 7; // must correspond to the number of entries in the next line
+static double *builtin_dashes[] = {dash_0, dash_1_1, dash_2_1, dash_4_1, dash_1_2, dash_1_4, nullptr};
+
+static double **dashes = nullptr;
+
+DashSelector::DashSelector()
+ : preview_width(80),
+ preview_height(16),
+ preview_lineheight(2)
+{
+ set_spacing(4);
+
+ // TODO: find something more sensible here!!
+ init_dashes();
+
+ dash_store = Gtk::ListStore::create(dash_columns);
+ dash_combo.set_model(dash_store);
+ dash_combo.pack_start(image_renderer);
+ dash_combo.set_cell_data_func(image_renderer, sigc::mem_fun(*this, &DashSelector::prepareImageRenderer));
+ dash_combo.set_tooltip_text(_("Dash pattern"));
+ dash_combo.get_style_context()->add_class("combobright");
+ dash_combo.show();
+ dash_combo.signal_changed().connect( sigc::mem_fun(*this, &DashSelector::on_selection) );
+
+ this->pack_start(dash_combo, true, true, 0);
+
+ offset = Gtk::Adjustment::create(0.0, 0.0, 10.0, 0.1, 1.0, 0.0);
+ offset->signal_value_changed().connect(sigc::mem_fun(*this, &DashSelector::offset_value_changed));
+ auto sb = new Inkscape::UI::Widget::SpinButton(offset, 0.1, 2);
+ sb->set_tooltip_text(_("Pattern offset"));
+ sp_dialog_defocus_on_enter_cpp(sb);
+ sb->show();
+
+ this->pack_start(*sb, false, false, 0);
+
+ int np=0;
+ while (dashes[np]){ np++;}
+ for (int i = 0; i<np-1; i++) { // all but the custom one go this way
+ // Add the dashes to the combobox
+ Gtk::TreeModel::Row row = *(dash_store->append());
+ row[dash_columns.dash] = dashes[i];
+ row[dash_columns.pixbuf] = Glib::wrap(sp_dash_to_pixbuf(dashes[i]));
+ }
+ // add the custom one
+ Gtk::TreeModel::Row row = *(dash_store->append());
+ row[dash_columns.dash] = dashes[np-1];
+ row[dash_columns.pixbuf] = Glib::wrap(sp_text_to_pixbuf((char *)"Custom"));
+
+ this->set_data("pattern", dashes[0]);
+}
+
+DashSelector::~DashSelector() {
+ // FIXME: for some reason this doesn't get called; does the call to manage() in
+ // sp_stroke_style_line_widget_new() not processed correctly?
+}
+
+void DashSelector::prepareImageRenderer( Gtk::TreeModel::const_iterator const &row ) {
+
+ Glib::RefPtr<Gdk::Pixbuf> pixbuf = (*row)[dash_columns.pixbuf];
+ image_renderer.property_pixbuf() = pixbuf;
+}
+
+void DashSelector::init_dashes() {
+
+ if (!dashes) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ std::vector<Glib::ustring> dash_prefs = prefs->getAllDirs(_prefs_path);
+
+ int pos = 0;
+ if (!dash_prefs.empty()) {
+ SPStyle style;
+ dashes = g_new (double *, dash_prefs.size() + 2); // +1 for custom slot, +1 for terminator slot
+
+ for (auto & dash_pref : dash_prefs) {
+ style.readFromPrefs( dash_pref );
+
+ if (!style.stroke_dasharray.values.empty()) {
+ dashes[pos] = g_new (double, style.stroke_dasharray.values.size() + 1);
+ double *d = dashes[pos];
+ unsigned i = 0;
+ for (; i < style.stroke_dasharray.values.size(); i++) {
+ d[i] = style.stroke_dasharray.values[i].value;
+ }
+ d[i] = -1;
+ } else {
+ dashes[pos] = dash_0;
+ }
+ pos += 1;
+ }
+ } else { // This code may never execute - a new preferences.xml is created for a new user. Maybe if the user deletes dashes from preferences.xml?
+ dashes = g_new (double *, BD_LEN + 2); // +1 for custom slot, +1 for terminator slot
+ unsigned i;
+ for(i=0;i<BD_LEN;i++) {
+ dashes[i] = builtin_dashes[i];
+ }
+ pos = BD_LEN;
+ }
+ // make a place to hold the custom dashes, up to 15 positions long (+ terminator)
+ dashes[pos] = g_new (double, 16);
+ double *d = dashes[pos];
+ int i=0;
+ for(i=0;i<15;i++){ d[i]=i; } // have to put something in there, this is a pattern hopefully nobody would choose
+ d[15]=-1.0;
+ // final terminator
+ dashes[++pos] = nullptr;
+ }
+}
+
+void DashSelector::set_dash (int ndash, double *dash, double o)
+{
+ int pos = -1; // Allows custom patterns to remain unscathed by this.
+ int count = 0; // will hold the NULL terminator at the end of the dashes list
+ if (ndash > 0) {
+ double delta = 0.0;
+ for (int i = 0; i < ndash; i++)
+ delta += dash[i];
+ delta /= 1000.0;
+
+ for (int i = 0; dashes[i]; i++,count++) {
+ double *pattern = dashes[i];
+ int np = 0;
+ while (pattern[np] >= 0.0)
+ np += 1;
+ if (np == ndash) {
+ int j;
+ for (j = 0; j < ndash; j++) {
+ if (!Geom::are_near(dash[j], pattern[j], delta)) {
+ break;
+ }
+ }
+ if (j == ndash) {
+ pos = i;
+ break;
+ }
+ }
+ }
+ }
+ else if(ndash==0) {
+ pos = 0;
+ }
+ if(pos>=0){
+ this->set_data("pattern", dashes[pos]);
+ this->dash_combo.set_active(pos);
+ this->offset->set_value(o);
+ if(pos == 10) {
+ this->offset->set_value(10.0);
+ }
+ }
+ else { // Hit a custom pattern in the SVG, write it into the combobox.
+ count--; // the one slot for custom patterns
+ double *d = dashes[count];
+ int i=0;
+ for(i=0;i< (ndash > 15 ? 15 : ndash) ;i++) {
+ d[i]=dash[i];
+ } // store the custom pattern
+ d[ndash]=-1.0; //terminate it
+ this->set_data("pattern", dashes[count]);
+ this->dash_combo.set_active(count);
+ this->offset->set_value(o); // what does this do????
+ }
+}
+
+void DashSelector::get_dash(int *ndash, double **dash, double *off)
+{
+ double *pattern = (double*) this->get_data("pattern");
+
+ int nd = 0;
+ while (pattern[nd] >= 0.0)
+ nd += 1;
+
+ if (nd > 0) {
+ if (ndash)
+ *ndash = nd;
+ if (dash) {
+ *dash = g_new (double, nd);
+ memcpy (*dash, pattern, nd * sizeof (double));
+ }
+ if (off)
+ *off = offset->get_value();
+ } else {
+ if (ndash)
+ *ndash = 0;
+ if (dash)
+ *dash = nullptr;
+ if (off)
+ *off = 0.0;
+ }
+}
+
+/**
+ * Fill a pixbuf with the dash pattern using standard cairo drawing
+ */
+GdkPixbuf* DashSelector::sp_dash_to_pixbuf(double *pattern)
+{
+ int n_dashes;
+ for (n_dashes = 0; pattern[n_dashes] >= 0.0; n_dashes ++) ;
+
+ cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, preview_width, preview_height);
+ cairo_t *ct = cairo_create(s);
+
+ cairo_set_line_width (ct, preview_lineheight);
+ cairo_scale (ct, preview_lineheight, 1);
+ //cairo_set_source_rgb (ct, 0, 0, 0);
+ cairo_move_to (ct, 0, preview_height/2);
+ cairo_line_to (ct, preview_width, preview_height/2);
+ cairo_set_dash(ct, pattern, n_dashes, 0);
+ cairo_stroke (ct);
+
+ cairo_destroy(ct);
+ cairo_surface_flush(s);
+
+ GdkPixbuf* pixbuf = ink_pixbuf_create_from_cairo_surface(s);
+ return pixbuf;
+}
+
+/**
+ * Fill a pixbuf with a text label using standard cairo drawing
+ */
+GdkPixbuf* DashSelector::sp_text_to_pixbuf(char *text)
+{
+ cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, preview_width, preview_height);
+ cairo_t *ct = cairo_create(s);
+
+ cairo_select_font_face (ct, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+ cairo_set_font_size (ct, 12.0);
+ cairo_set_source_rgb (ct, 0.0, 0.0, 0.0);
+ cairo_move_to (ct, 16.0, 13.0);
+ cairo_show_text (ct, text);
+
+ cairo_stroke (ct);
+
+ cairo_destroy(ct);
+ cairo_surface_flush(s);
+
+ GdkPixbuf* pixbuf = ink_pixbuf_create_from_cairo_surface(s);
+ return pixbuf;
+}
+
+void DashSelector::on_selection ()
+{
+ double *pattern = dash_combo.get_active()->get_value(dash_columns.dash);
+ this->set_data ("pattern", pattern);
+
+ changed_signal.emit();
+}
+
+void DashSelector::offset_value_changed()
+{
+ changed_signal.emit();
+}
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/dash-selector.h b/src/ui/widget/dash-selector.h
new file mode 100644
index 0000000..449392a
--- /dev/null
+++ b/src/ui/widget/dash-selector.h
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SP_DASH_SELECTOR_NEW_H
+#define SEEN_SP_DASH_SELECTOR_NEW_H
+
+/* Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Maximilian Albert <maximilian.albert> (gtkmm-ification)
+ *
+ * Copyright (C) 2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/box.h>
+#include <gtkmm/combobox.h>
+#include <gtkmm/liststore.h>
+
+#include <sigc++/signal.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * Class that wraps a combobox and spinbutton for selecting dash patterns.
+ */
+class DashSelector : public Gtk::HBox {
+public:
+ DashSelector();
+ ~DashSelector() override;
+
+ /**
+ * Get and set methods for dashes
+ */
+ void set_dash(int ndash, double *dash, double offset);
+ void get_dash(int *ndash, double **dash, double *offset);
+
+ sigc::signal<void> changed_signal;
+
+private:
+
+ /**
+ * Initialize dashes list from preferences
+ */
+ static void init_dashes();
+
+ /**
+ * Fill a pixbuf with the dash pattern using standard cairo drawing
+ */
+ GdkPixbuf* sp_dash_to_pixbuf(double *pattern);
+
+ /**
+ * Fill a pixbuf with text standard cairo drawing
+ */
+ GdkPixbuf* sp_text_to_pixbuf(char *text);
+
+ /**
+ * Callback for combobox image renderer
+ */
+ void prepareImageRenderer( Gtk::TreeModel::const_iterator const &row );
+
+ /**
+ * Callback for offset adjustment changing
+ */
+ void offset_value_changed();
+
+ /**
+ * Callback for combobox selection changing
+ */
+ void on_selection();
+
+ /**
+ * Combobox columns
+ */
+ class DashColumns : public Gtk::TreeModel::ColumnRecord {
+ public:
+ Gtk::TreeModelColumn<double *> dash;
+ Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > pixbuf;
+
+ DashColumns() {
+ add(dash); add(pixbuf);
+ }
+ };
+ DashColumns dash_columns;
+ Glib::RefPtr<Gtk::ListStore> dash_store;
+ Gtk::ComboBox dash_combo;
+ Gtk::CellRendererPixbuf image_renderer;
+ Glib::RefPtr<Gtk::Adjustment> offset;
+
+ static gchar const *const _prefs_path;
+ int preview_width;
+ int preview_height;
+ int preview_lineheight;
+
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // SEEN_SP_DASH_SELECTOR_NEW_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/dock-item.cpp b/src/ui/widget/dock-item.cpp
new file mode 100644
index 0000000..01786d5
--- /dev/null
+++ b/src/ui/widget/dock-item.cpp
@@ -0,0 +1,528 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Gustav Broberg <broberg@kth.se>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "ui/widget/dock.h"
+
+#include "desktop.h"
+#include "inkscape.h"
+#include "ui/icon-loader.h"
+#include "ui/icon-names.h"
+#include <glibmm/exceptionhandler.h>
+#include <gtkmm/icontheme.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+DockItem::DockItem(Dock& dock, const Glib::ustring& name, const Glib::ustring& long_name,
+ const Glib::ustring& icon_name, State state, GdlDockPlacement placement) :
+ _dock(dock),
+ _prev_state(state),
+ _prev_position(0),
+ _window(nullptr),
+ _x(0),
+ _y(0),
+ _grab_focus_on_realize(false),
+ _gdl_dock_item(nullptr)
+{
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ GdlDockItemBehavior gdl_dock_behavior =
+ (prefs->getBool("/options/dock/cancenterdock", true) ?
+ GDL_DOCK_ITEM_BEH_NORMAL :
+ GDL_DOCK_ITEM_BEH_CANT_DOCK_CENTER);
+
+
+ if (!icon_name.empty()) {
+ _icon_pixbuf = sp_get_icon_pixbuf(icon_name, "/toolbox/secondary");
+ }
+
+ if ( _icon_pixbuf ) {
+ _gdl_dock_item = gdl_dock_item_new_with_pixbuf_icon( name.c_str(), long_name.c_str(),
+ _icon_pixbuf->gobj(), gdl_dock_behavior );
+ } else {
+ _gdl_dock_item = gdl_dock_item_new(name.c_str(), long_name.c_str(), gdl_dock_behavior);
+ }
+
+ _frame.set_shadow_type(Gtk::SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (_gdl_dock_item), GTK_WIDGET (_frame.gobj()));
+ _frame.add(_dock_item_box);
+ _dock_item_box.set_border_width(3);
+
+ signal_drag_begin().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onDragBegin));
+ signal_drag_end().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onDragEnd));
+ signal_hide().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onHide), false);
+ signal_show().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onShow), false);
+ signal_state_changed().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onStateChanged));
+ signal_delete_event().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onDeleteEvent));
+ signal_realize().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onRealize));
+
+ _dock.addItem(*this, ( _prev_state == FLOATING_STATE || _prev_state == ICONIFIED_FLOATING_STATE ) ? GDL_DOCK_FLOATING : placement);
+
+ if (_prev_state == ICONIFIED_FLOATING_STATE || _prev_state == ICONIFIED_DOCKED_STATE) {
+ iconify();
+ }
+
+ show_all();
+
+}
+
+DockItem::~DockItem()
+{
+ g_free(_gdl_dock_item);
+}
+
+Gtk::Widget&
+DockItem::getWidget()
+{
+ return *Glib::wrap(GTK_WIDGET(_gdl_dock_item));
+}
+
+GtkWidget *
+DockItem::gobj()
+{
+ return _gdl_dock_item;
+}
+
+Gtk::VBox *
+DockItem::get_vbox()
+{
+ return &_dock_item_box;
+}
+
+
+void
+DockItem::get_position(int& x, int& y)
+{
+ if (getWindow()) {
+ getWindow()->get_position(x, y);
+ } else {
+ x = _x;
+ y = _y;
+ }
+}
+
+void
+DockItem::get_size(int& width, int& height)
+{
+ if (getWindow()) {
+ getWindow()->get_size(width, height);
+ } else {
+ width = get_vbox()->get_width();
+ height = get_vbox()->get_height();
+ }
+}
+
+
+void
+DockItem::resize(int width, int height)
+{
+ if (_window)
+ _window->resize(width, height);
+}
+
+
+void
+DockItem::move(int x, int y)
+{
+ if (_window)
+ _window->move(x, y);
+}
+
+void
+DockItem::set_position(Gtk::WindowPosition position)
+{
+ if (_window)
+ _window->set_position(position);
+}
+
+void
+DockItem::set_size_request(int width, int height)
+{
+ getWidget().set_size_request(width, height);
+}
+
+void DockItem::size_request(Gtk::Requisition& requisition)
+{
+ Gtk::Requisition req_natural;
+ getWidget().get_preferred_size(req_natural, requisition);
+}
+
+void
+DockItem::set_title(Glib::ustring title)
+{
+ g_object_set (_gdl_dock_item,
+ "long-name", title.c_str(),
+ NULL);
+
+ gdl_dock_item_set_tablabel(GDL_DOCK_ITEM(_gdl_dock_item),
+ gtk_label_new (title.c_str()));
+}
+
+bool
+DockItem::isAttached() const
+{
+ return GDL_DOCK_OBJECT_ATTACHED (_gdl_dock_item);
+}
+
+
+bool
+DockItem::isFloating() const
+{
+ return (GTK_WIDGET(gdl_dock_object_get_toplevel(GDL_DOCK_OBJECT (_gdl_dock_item))) !=
+ _dock.getGdlWidget());
+}
+
+bool
+DockItem::isIconified() const
+{
+ return GDL_DOCK_ITEM_ICONIFIED (_gdl_dock_item);
+}
+
+DockItem::State
+DockItem::getState() const
+{
+ if (isIconified() && _prev_state == FLOATING_STATE) {
+ return ICONIFIED_FLOATING_STATE;
+ } else if (isIconified()) {
+ return ICONIFIED_DOCKED_STATE;
+ } else if (isFloating() && isAttached()) {
+ return FLOATING_STATE;
+ } else if (isAttached()) {
+ return DOCKED_STATE;
+ }
+
+ return UNATTACHED;
+}
+
+DockItem::State
+DockItem::getPrevState() const
+{
+ return _prev_state;
+}
+
+GdlDockPlacement
+DockItem::getPlacement() const
+{
+ GdlDockPlacement placement = GDL_DOCK_TOP;
+ GdlDockObject *parent = gdl_dock_object_get_parent_object (GDL_DOCK_OBJECT(_gdl_dock_item));
+ if (parent) {
+ gdl_dock_object_child_placement(parent, GDL_DOCK_OBJECT(_gdl_dock_item), &placement);
+ }
+
+ return placement;
+}
+
+void
+DockItem::hide()
+{
+ gdl_dock_item_hide_item (GDL_DOCK_ITEM(_gdl_dock_item));
+}
+
+void
+DockItem::show()
+{
+ gdl_dock_item_show_item (GDL_DOCK_ITEM(_gdl_dock_item));
+}
+
+void
+DockItem::iconify()
+{
+ gdl_dock_item_iconify_item (GDL_DOCK_ITEM(_gdl_dock_item));
+}
+
+void
+DockItem::show_all()
+{
+ gtk_widget_show_all(_gdl_dock_item);
+}
+
+void
+DockItem::present()
+{
+ gdl_dock_object_present(GDL_DOCK_OBJECT(_gdl_dock_item), nullptr);
+
+ // always grab focus, even if we're already present
+ grab_focus();
+
+ if (!isFloating() && getWidget().get_realized())
+ _dock.scrollToItem(*this);
+}
+
+
+void
+DockItem::grab_focus()
+{
+ if (gtk_widget_get_realized (_gdl_dock_item)) {
+
+ // make sure the window we're in is present
+ Gtk::Widget *toplevel = getWidget().get_toplevel();
+ if (Gtk::Window *window = dynamic_cast<Gtk::Window *>(toplevel)) {
+ window->present();
+ }
+
+ gtk_widget_grab_focus (_gdl_dock_item);
+
+ } else {
+ _grab_focus_on_realize = true;
+ }
+}
+
+
+/* Signal wrappers */
+
+Glib::SignalProxy0<void>
+DockItem::signal_show()
+{
+ return Glib::SignalProxy0<void>(Glib::wrap(GTK_WIDGET(_gdl_dock_item)),
+ &_signal_show_proxy);
+}
+
+Glib::SignalProxy0<void>
+DockItem::signal_hide()
+{
+ return Glib::SignalProxy0<void>(Glib::wrap(GTK_WIDGET(_gdl_dock_item)),
+ &_signal_hide_proxy);
+}
+
+Glib::SignalProxy1<bool, GdkEventAny *>
+DockItem::signal_delete_event()
+{
+ return Glib::SignalProxy1<bool, GdkEventAny *>(Glib::wrap(GTK_WIDGET(_gdl_dock_item)),
+ &_signal_delete_event_proxy);
+}
+
+Glib::SignalProxy0<void>
+DockItem::signal_drag_begin()
+{
+ return Glib::SignalProxy0<void>(Glib::wrap(GTK_WIDGET(_gdl_dock_item)),
+ &_signal_drag_begin_proxy);
+}
+
+Glib::SignalProxy1<void, bool>
+DockItem::signal_drag_end()
+{
+ return Glib::SignalProxy1<void, bool>(Glib::wrap(GTK_WIDGET(_gdl_dock_item)),
+ &_signal_drag_end_proxy);
+}
+
+Glib::SignalProxy0<void>
+DockItem::signal_realize()
+{
+ return Glib::SignalProxy0<void>(Glib::wrap(GTK_WIDGET(_gdl_dock_item)),
+ &_signal_realize_proxy);
+}
+
+sigc::signal<void, DockItem::State, DockItem::State>
+DockItem::signal_state_changed()
+{
+ return _signal_state_changed;
+}
+
+void
+DockItem::_onHideWindow()
+{
+ if (_window)
+ _window->get_position(_x, _y);
+}
+
+void
+DockItem::_onHide()
+{
+ if (_prev_state == ICONIFIED_DOCKED_STATE)
+ _prev_state = DOCKED_STATE;
+ else if (_prev_state == ICONIFIED_FLOATING_STATE)
+ _prev_state = FLOATING_STATE;
+
+ _signal_state_changed.emit(UNATTACHED, getState());
+}
+
+void
+DockItem::_onShow()
+{
+ _signal_state_changed.emit(UNATTACHED, getState());
+}
+
+void
+DockItem::_onDragBegin()
+{
+ _prev_state = getState();
+ if (_prev_state == FLOATING_STATE)
+ _dock.toggleDockable(getWidget().get_width(), getWidget().get_height());
+}
+
+void
+DockItem::_onDragEnd(bool)
+{
+ State state = getState();
+
+ if (state != _prev_state)
+ _signal_state_changed.emit(_prev_state, state);
+
+ if (state == FLOATING_STATE) {
+ if (_prev_state == FLOATING_STATE)
+ _dock.toggleDockable();
+ }
+
+ _prev_state = state;
+}
+
+void
+DockItem::_onRealize()
+{
+ if (_grab_focus_on_realize) {
+ _grab_focus_on_realize = false;
+ grab_focus();
+ }
+}
+
+bool
+DockItem::_onKeyPress(GdkEventKey *event)
+{
+ gboolean return_value;
+ g_signal_emit_by_name (_gdl_dock_item, "key_press_event", event, &return_value);
+ return return_value;
+}
+
+void
+DockItem::_onStateChanged(State /*prev_state*/, State new_state)
+{
+ _window = getWindow();
+ if(_window)
+ _window->set_type_hint(Gdk::WINDOW_TYPE_HINT_NORMAL);
+
+ if (new_state == FLOATING_STATE && _window) {
+ _window->signal_hide().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onHideWindow));
+ _signal_key_press_event_connection =
+ _window->signal_key_press_event().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onKeyPress));
+ }
+}
+
+
+bool
+DockItem::_onDeleteEvent(GdkEventAny */*event*/)
+{
+ hide();
+ return false;
+}
+
+
+Gtk::Window *
+DockItem::getWindow()
+{
+ g_return_val_if_fail(_gdl_dock_item, 0);
+ Gtk::Container *parent = getWidget().get_parent();
+ parent = (parent ? parent->get_parent() : nullptr);
+ return (parent ? dynamic_cast<Gtk::Window *>(parent) : nullptr);
+}
+
+const Glib::SignalProxyInfo
+DockItem::_signal_show_proxy =
+{
+ "show",
+ (GCallback) &Glib::SignalProxyNormal::slot0_void_callback,
+ (GCallback) &Glib::SignalProxyNormal::slot0_void_callback
+};
+
+const Glib::SignalProxyInfo
+DockItem::_signal_hide_proxy =
+{
+ "hide",
+ (GCallback) &Glib::SignalProxyNormal::slot0_void_callback,
+ (GCallback) &Glib::SignalProxyNormal::slot0_void_callback
+};
+
+
+const Glib::SignalProxyInfo
+DockItem::_signal_delete_event_proxy =
+{
+ "delete_event",
+ (GCallback) &_signal_delete_event_callback,
+ (GCallback) &_signal_delete_event_callback
+};
+
+
+const Glib::SignalProxyInfo
+DockItem::_signal_drag_begin_proxy =
+{
+ "dock-drag-begin",
+ (GCallback) &Glib::SignalProxyNormal::slot0_void_callback,
+ (GCallback) &Glib::SignalProxyNormal::slot0_void_callback
+};
+
+
+const Glib::SignalProxyInfo
+DockItem::_signal_drag_end_proxy =
+{
+ "dock_drag_end",
+ (GCallback) &_signal_drag_end_callback,
+ (GCallback) &_signal_drag_end_callback
+};
+
+
+const Glib::SignalProxyInfo
+DockItem::_signal_realize_proxy =
+{
+ "realize",
+ (GCallback) &Glib::SignalProxyNormal::slot0_void_callback,
+ (GCallback) &Glib::SignalProxyNormal::slot0_void_callback
+};
+
+
+gboolean
+DockItem::_signal_delete_event_callback(GtkWidget *self, GdkEventAny *event, void *data)
+{
+ using namespace Gtk;
+ typedef sigc::slot<bool, GdkEventAny *> SlotType;
+
+ if (Glib::ObjectBase::_get_current_wrapper((GObject *) self)) {
+ try {
+ if(sigc::slot_base *const slot = Glib::SignalProxyNormal::data_to_slot(data))
+ return static_cast<int>( (*static_cast<SlotType*>(slot))(event) );
+ } catch(...) {
+ Glib::exception_handlers_invoke();
+ }
+ }
+
+ typedef gboolean RType;
+ return RType();
+}
+
+void
+DockItem::_signal_drag_end_callback(GtkWidget *self, gboolean cancelled, void *data)
+{
+ using namespace Gtk;
+ typedef sigc::slot<void, bool> SlotType;
+
+ if (Glib::ObjectBase::_get_current_wrapper((GObject *) self)) {
+ try {
+ if(sigc::slot_base *const slot = Glib::SignalProxyNormal::data_to_slot(data))
+ (*static_cast<SlotType *>(slot))(cancelled);
+ } catch(...) {
+ Glib::exception_handlers_invoke();
+ }
+ }
+}
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/dock-item.h b/src/ui/widget/dock-item.h
new file mode 100644
index 0000000..be7ac77
--- /dev/null
+++ b/src/ui/widget/dock-item.h
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Gustav Broberg <broberg@kth.se>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#ifndef INKSCAPE_UI_WIGET_DOCK_ITEM_H
+#define INKSCAPE_UI_WIGET_DOCK_ITEM_H
+
+#include <gtkmm/box.h>
+#include <gtkmm/frame.h>
+#include <gtkmm/window.h>
+
+#include <gdl/gdl.h>
+
+namespace Gtk {
+ class HButtonBox;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class Dock;
+
+/**
+ * A custom wrapper around gdl-dock-item.
+ */
+class DockItem {
+
+public:
+
+ enum State { UNATTACHED, // item not bound to the dock (a temporary state)
+ FLOATING_STATE, // item not in its dock (but can be docked in other,
+ // e.g. floating, docks)
+ DOCKED_STATE, // item in its assigned dock
+ ICONIFIED_DOCKED_STATE, // item iconified in its assigned dock from dock
+ ICONIFIED_FLOATING_STATE}; // item iconified in its assigned dock from float
+
+ DockItem(Dock& dock, const Glib::ustring& name, const Glib::ustring& long_name,
+ const Glib::ustring& icon_name, State state, GdlDockPlacement placement);
+
+ ~DockItem();
+
+ Gtk::Widget& getWidget();
+ GtkWidget *gobj();
+
+ Gtk::VBox *get_vbox();
+
+ void get_position(int& x, int& y);
+ void get_size(int& width, int& height);
+
+ void resize(int width, int height);
+ void move(int x, int y);
+ void set_position(Gtk::WindowPosition);
+ void set_size_request(int width, int height);
+ void size_request(Gtk::Requisition& requisition);
+ void set_title(Glib::ustring title);
+
+ bool isAttached() const;
+ bool isFloating() const;
+ bool isIconified() const;
+ State getState() const;
+ State getPrevState() const;
+ GdlDockPlacement getPlacement() const;
+
+ Gtk::Window *getWindow(); //< gives the parent window, if the dock item has one (i.e. it's floating)
+
+ void hide();
+ void show();
+ void iconify();
+ void show_all();
+
+ void present();
+
+ void grab_focus();
+
+ Glib::SignalProxy0<void> signal_show();
+ Glib::SignalProxy0<void> signal_hide();
+ Glib::SignalProxy1<bool, GdkEventAny *> signal_delete_event();
+ Glib::SignalProxy0<void> signal_drag_begin();
+ Glib::SignalProxy1<void, bool> signal_drag_end();
+ Glib::SignalProxy0<void> signal_realize();
+
+ sigc::signal<void, State, State> signal_state_changed();
+
+private:
+ Dock &_dock; //< parent dock
+
+ State _prev_state; //< last known state
+
+ int _prev_position;
+
+ Gtk::Window *_window; //< reference to floating window, if any
+ int _x, _y; //< last known position of window, if floating
+
+ bool _grab_focus_on_realize; //< if the dock item should grab focus on the next realize
+
+ GtkWidget *_gdl_dock_item;
+ Glib::RefPtr<Gdk::Pixbuf> _icon_pixbuf;
+
+ /** Interface widgets, will be packed like
+ * gdl_dock_item -> _frame -> _dock_item_box
+ */
+ Gtk::Frame _frame;
+ Gtk::VBox _dock_item_box;
+
+ /** Internal signal handlers */
+ void _onHide();
+ void _onHideWindow();
+ void _onShow();
+ void _onDragBegin();
+ void _onDragEnd(bool cancelled);
+ void _onRealize();
+
+ bool _onKeyPress(GdkEventKey *event);
+ void _onStateChanged(State prev_state, State new_state);
+ bool _onDeleteEvent(GdkEventAny *event);
+
+ sigc::connection _signal_key_press_event_connection;
+
+ /** GdlDockItem signal proxy structures */
+ static const Glib::SignalProxyInfo _signal_show_proxy;
+ static const Glib::SignalProxyInfo _signal_hide_proxy;
+ static const Glib::SignalProxyInfo _signal_delete_event_proxy;
+
+ static const Glib::SignalProxyInfo _signal_drag_begin_proxy;
+ static const Glib::SignalProxyInfo _signal_drag_end_proxy;
+ static const Glib::SignalProxyInfo _signal_realize_proxy;
+
+ static gboolean _signal_delete_event_callback(GtkWidget *self, GdkEventAny *event, void *data);
+ static void _signal_drag_end_callback(GtkWidget* self, gboolean p0, void* data);
+
+ sigc::signal<void, State, State> _signal_state_changed;
+
+ DockItem() = delete;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIGET_DOCK_ITEM_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/dock.cpp b/src/ui/widget/dock.cpp
new file mode 100644
index 0000000..9720296
--- /dev/null
+++ b/src/ui/widget/dock.cpp
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * A desktop dock pane to dock dialogs.
+ */
+/* Author:
+ * Gustav Broberg <broberg@kth.se>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#include "dock.h"
+#include "inkscape.h"
+#include "preferences.h"
+#include "desktop.h"
+
+#include <gtkmm/adjustment.h>
+#include <gtkmm/paned.h>
+#include <gtkmm/scrolledwindow.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+namespace {
+
+void hideCallback(GObject * /*object*/, gpointer dock_ptr)
+{
+ g_return_if_fail( dock_ptr != nullptr );
+
+ Dock *dock = static_cast<Dock *>(dock_ptr);
+ dock->hide();
+}
+
+void unhideCallback(GObject * /*object*/, gpointer dock_ptr)
+{
+ g_return_if_fail( dock_ptr != nullptr );
+
+ Dock *dock = static_cast<Dock *>(dock_ptr);
+ dock->show();
+}
+
+}
+
+const int Dock::_default_empty_width = 0;
+const int Dock::_default_dock_bar_width = 36;
+
+
+Dock::Dock(Gtk::Orientation orientation)
+ : _gdl_dock(gdl_dock_new()),
+#if WITH_GDL_3_6
+ _gdl_dock_bar(GDL_DOCK_BAR(gdl_dock_bar_new(G_OBJECT(_gdl_dock)))),
+#else
+ _gdl_dock_bar(GDL_DOCK_BAR(gdl_dock_bar_new(GDL_DOCK(_gdl_dock)))),
+#endif
+ _scrolled_window (Gtk::manage(new Gtk::ScrolledWindow))
+{
+ gtk_widget_set_name(_gdl_dock, "GdlDock");
+
+#if WITH_GDL_3_6
+ gtk_orientable_set_orientation(GTK_ORIENTABLE(_gdl_dock_bar),
+ static_cast<GtkOrientation>(orientation));
+#else
+ gdl_dock_bar_set_orientation(_gdl_dock_bar,
+ static_cast<GtkOrientation>(orientation));
+#endif
+
+ _filler.set_name("DockBoxFiller");
+
+ _paned = Gtk::manage(new Gtk::Paned(orientation));
+ _paned->set_name("DockBoxPane");
+ _paned->pack1(*Glib::wrap(GTK_WIDGET(_gdl_dock)), false, false);
+ _paned->pack2(_filler, true, false);
+ // resize, shrink
+
+ _dock_box = Gtk::manage(new Gtk::Box(orientation == Gtk::ORIENTATION_HORIZONTAL ?
+ Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL));
+ _dock_box->set_name("DockBox");
+ _dock_box->pack_start(*_paned, Gtk::PACK_EXPAND_WIDGET);
+ _dock_box->pack_end(*Gtk::manage(Glib::wrap(GTK_WIDGET(_gdl_dock_bar))), Gtk::PACK_SHRINK);
+
+ _scrolled_window->set_name("DockScrolledWindow");
+ _scrolled_window->add(*_dock_box);
+ _scrolled_window->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
+ _scrolled_window->set_size_request(0);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ GdlSwitcherStyle gdl_switcher_style =
+ static_cast<GdlSwitcherStyle>(prefs->getIntLimited("/options/dock/switcherstyle",
+ GDL_SWITCHER_STYLE_BOTH, 0, 4));
+
+ GdlDockMaster* master = nullptr;
+
+ g_object_get(GDL_DOCK_OBJECT(_gdl_dock),
+ "master", &master,
+ NULL);
+
+ g_object_set(master,
+ "switcher-style", gdl_switcher_style,
+ NULL);
+
+ GdlDockBarStyle gdl_dock_bar_style =
+ static_cast<GdlDockBarStyle>(prefs->getIntLimited("/options/dock/dockbarstyle",
+ GDL_DOCK_BAR_BOTH, 0, 3));
+
+ gdl_dock_bar_set_style(_gdl_dock_bar, gdl_dock_bar_style);
+
+
+ INKSCAPE.signal_dialogs_hide.connect(sigc::mem_fun(*this, &Dock::hide));
+ INKSCAPE.signal_dialogs_unhide.connect(sigc::mem_fun(*this, &Dock::show));
+
+ g_signal_connect(_paned->gobj(), "button-press-event", G_CALLBACK(_on_paned_button_event), (void *)this);
+ g_signal_connect(_paned->gobj(), "button-release-event", G_CALLBACK(_on_paned_button_event), (void *)this);
+
+ signal_layout_changed().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::Dock::_onLayoutChanged));
+}
+
+Dock::~Dock()
+{
+ g_free(_gdl_dock);
+ g_free(_gdl_dock_bar);
+}
+
+void Dock::addItem(DockItem& item, GdlDockPlacement placement)
+{
+ _dock_items.push_back(&item);
+
+ gdl_dock_add_item(GDL_DOCK(_gdl_dock),
+ GDL_DOCK_ITEM(item.gobj()),
+ placement);
+}
+
+Gtk::Widget &Dock::getWidget()
+{
+ return *_scrolled_window;
+}
+
+Gtk::Paned *Dock::getParentPaned()
+{
+ g_return_val_if_fail(_dock_box, 0);
+ Gtk::Container *parent = getWidget().get_parent();
+ return (parent != nullptr ? dynamic_cast<Gtk::Paned *>(parent) : nullptr);
+}
+
+
+Gtk::Paned *Dock::getPaned()
+{
+ return _paned;
+}
+
+GtkWidget *Dock::getGdlWidget()
+{
+ return GTK_WIDGET(_gdl_dock);
+}
+
+bool Dock::isEmpty() const
+{
+ std::list<const DockItem *>::const_iterator
+ i = _dock_items.begin(),
+ e = _dock_items.end();
+
+ for (; i != e; ++i) {
+ if ((*i)->getState() == DockItem::DOCKED_STATE) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool Dock::hasIconifiedItems() const
+{
+ std::list<const DockItem *>::const_iterator
+ i = _dock_items.begin(),
+ e = _dock_items.end();
+
+ for (; i != e; ++i) {
+ if ((*i)->isIconified()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void Dock::hide()
+{
+ getWidget().hide();
+}
+
+void Dock::show()
+{
+ getWidget().show();
+}
+
+void Dock::toggleDockable(int width, int height)
+{
+ static int prev_horizontal_position, prev_vertical_position;
+
+ Gtk::Paned *parent_paned = getParentPaned();
+
+ if (width > 0 && height > 0) {
+ prev_horizontal_position = parent_paned->get_position();
+ prev_vertical_position = _paned->get_position();
+
+ if (getWidget().get_width() < width)
+ parent_paned->set_position(parent_paned->get_width() - width);
+
+ if (_paned->get_position() < height)
+ _paned->set_position(height);
+
+ } else {
+ parent_paned->set_position(prev_horizontal_position);
+ _paned->set_position(prev_vertical_position);
+ }
+}
+
+void Dock::scrollToItem(DockItem& item)
+{
+ int item_x, item_y;
+ item.getWidget().translate_coordinates(getWidget(), 0, 0, item_x, item_y);
+
+ int dock_height = getWidget().get_height(), item_height = item.getWidget().get_height();
+ double vadjustment = _scrolled_window->get_vadjustment()->get_value();
+
+ if (item_y < 0)
+ _scrolled_window->get_vadjustment()->set_value(vadjustment + item_y);
+ else if (item_y + item_height > dock_height)
+ _scrolled_window->get_vadjustment()->set_value(
+ vadjustment + ((item_y + item_height) - dock_height));
+}
+
+Glib::SignalProxy0<void>
+Dock::signal_layout_changed()
+{
+ return Glib::SignalProxy0<void>(Glib::wrap(GTK_WIDGET(_gdl_dock)),
+ &_signal_layout_changed_proxy);
+}
+
+void Dock::_onLayoutChanged()
+{
+ if (isEmpty()) {
+ if (hasIconifiedItems()) {
+ _paned->get_child1()->set_size_request(-1, -1);
+ _scrolled_window->set_size_request(_default_dock_bar_width);
+ } else {
+ _paned->get_child1()->set_size_request(-1, -1);
+ _scrolled_window->set_size_request(_default_empty_width);
+ }
+ getParentPaned()->set_position(10000);
+
+ } else {
+ // unset any forced size requests
+ _paned->get_child1()->set_size_request(-1, -1);
+ _scrolled_window->set_size_request(-1);
+ }
+}
+
+void
+Dock::_onPanedButtonEvent(GdkEventButton *event)
+{
+ if (event->button == 1 && event->type == GDK_BUTTON_PRESS)
+ /* unset size request when starting a drag */
+ _paned->get_child1()->set_size_request(-1, -1);
+}
+
+gboolean
+Dock::_on_paned_button_event(GtkWidget */*widget*/, GdkEventButton *event, gpointer user_data)
+{
+ if (Dock *dock = static_cast<Dock *>(user_data))
+ dock->_onPanedButtonEvent(event);
+
+ return FALSE;
+}
+
+const Glib::SignalProxyInfo
+Dock::_signal_layout_changed_proxy =
+{
+ "layout-changed",
+ (GCallback) &Glib::SignalProxyNormal::slot0_void_callback,
+ (GCallback) &Glib::SignalProxyNormal::slot0_void_callback
+};
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+
+/*
+ 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/dock.h b/src/ui/widget/dock.h
new file mode 100644
index 0000000..f061f59
--- /dev/null
+++ b/src/ui/widget/dock.h
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief A desktop dock pane to dock dialogs, a custom wrapper around gdl-dock.
+ */
+/* Author:
+ * Gustav Broberg <broberg@kth.se>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_DOCK_H
+#define INKSCAPE_UI_WIDGET_DOCK_H
+
+#include <gtkmm/box.h>
+#include <list>
+#include "ui/widget/dock-item.h"
+
+struct _GdlDock;
+typedef _GdlDock GdlDock;
+struct _GdlDockBar;
+typedef _GdlDockBar GdlDockBar;
+
+namespace Gtk {
+class Paned;
+class ScrolledWindow;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class Dock {
+
+public:
+
+ Dock(Gtk::Orientation orientation=Gtk::ORIENTATION_VERTICAL);
+ ~Dock();
+
+ void addItem(DockItem& item, GdlDockPlacement placement);
+
+ Gtk::Widget& getWidget(); //< return the top widget
+ Gtk::Paned *getParentPaned();
+ Gtk::Paned *getPaned();
+
+ GtkWidget* getGdlWidget(); //< return the top gdl widget
+
+ bool isEmpty() const; //< true iff none of the dock's items are in a docked state
+ bool hasIconifiedItems() const;
+
+ Glib::SignalProxy0<void> signal_layout_changed();
+
+ void hide();
+ void show();
+
+ /** Toggle size of dock between the previous dimensions and the ones sent as parameters */
+ void toggleDockable(int width=0, int height=0);
+
+ /** Scrolls the scrolled window container to make the provided dock item visible, if needed */
+ void scrollToItem(DockItem& item);
+
+protected:
+
+ std::list<const DockItem *> _dock_items; //< added dock items
+
+ /** Interface widgets, will be packed like
+ * _scrolled_window -> (_dock_box -> (_paned -> (_dock -> _filler) | _dock_bar))
+ */
+ Gtk::Box *_dock_box;
+ Gtk::Paned *_paned;
+ GtkWidget *_gdl_dock;
+ GdlDockBar *_gdl_dock_bar;
+ Gtk::Box _filler;
+ Gtk::ScrolledWindow *_scrolled_window;
+
+ /** Internal signal handlers */
+ void _onLayoutChanged();
+ void _onPanedButtonEvent(GdkEventButton *event);
+
+ static gboolean _on_paned_button_event(GtkWidget *widget, GdkEventButton *event,
+ gpointer user_data);
+
+ /** GdlDock signal proxy structures */
+ static const Glib::SignalProxyInfo _signal_layout_changed_proxy;
+
+ /** Standard widths */
+ static const int _default_empty_width;
+ static const int _default_dock_bar_width;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif //INKSCAPE_UI_DIALOG_BEHAVIOUR_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/entity-entry.cpp b/src/ui/widget/entity-entry.cpp
new file mode 100644
index 0000000..6e87459
--- /dev/null
+++ b/src/ui/widget/entity-entry.cpp
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * bulia byak <buliabyak@users.sf.net>
+ * Bryce W. Harrington <bryce@bryceharrington.org>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Jon Phillips <jon@rejon.org>
+ * Ralf Stephan <ralf@ark.in-berlin.de> (Gtkmm)
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2000 - 2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "entity-entry.h"
+
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/entry.h>
+
+#include "document-undo.h"
+#include "inkscape.h"
+#include "preferences.h"
+#include "rdf.h"
+#include "verbs.h"
+
+#include "object/sp-root.h"
+
+#include "ui/widget/registry.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+//===================================================
+
+//---------------------------------------------------
+
+EntityEntry*
+EntityEntry::create (rdf_work_entity_t* ent, Registry& wr)
+{
+ g_assert (ent);
+ EntityEntry* obj = nullptr;
+ switch (ent->format)
+ {
+ case RDF_FORMAT_LINE:
+ obj = new EntityLineEntry (ent, wr);
+ break;
+ case RDF_FORMAT_MULTILINE:
+ obj = new EntityMultiLineEntry (ent, wr);
+ break;
+ default:
+ g_warning ("An unknown RDF format was requested.");
+ }
+
+ g_assert (obj);
+ obj->_label.show();
+ return obj;
+}
+
+EntityEntry::EntityEntry (rdf_work_entity_t* ent, Registry& wr)
+ : _label(Glib::ustring(_(ent->title)), Gtk::ALIGN_END),
+ _packable(nullptr),
+ _entity(ent), _wr(&wr)
+{
+}
+
+EntityEntry::~EntityEntry()
+{
+ _changed_connection.disconnect();
+}
+
+void EntityEntry::save_to_preferences(SPDocument *doc)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ const gchar *text = rdf_get_work_entity (doc, _entity);
+ prefs->setString(PREFS_METADATA + Glib::ustring(_entity->name), Glib::ustring(text ? text : ""));
+}
+
+EntityLineEntry::EntityLineEntry (rdf_work_entity_t* ent, Registry& wr)
+: EntityEntry (ent, wr)
+{
+ Gtk::Entry *e = new Gtk::Entry;
+ e->set_tooltip_text (_(ent->tip));
+ _packable = e;
+ _changed_connection = e->signal_changed().connect (sigc::mem_fun (*this, &EntityLineEntry::on_changed));
+}
+
+EntityLineEntry::~EntityLineEntry()
+{
+ delete static_cast<Gtk::Entry*>(_packable);
+}
+
+void EntityLineEntry::update(SPDocument *doc)
+{
+ const char *text = rdf_get_work_entity (doc, _entity);
+ // If RDF title is not set, get the document's <title> and set the RDF:
+ if ( !text && !strcmp(_entity->name, "title") && doc->getRoot() ) {
+ text = doc->getRoot()->title();
+ rdf_set_work_entity(doc, _entity, text);
+ }
+ static_cast<Gtk::Entry*>(_packable)->set_text (text ? text : "");
+}
+
+
+void EntityLineEntry::load_from_preferences()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ Glib::ustring text = prefs->getString(PREFS_METADATA + Glib::ustring(_entity->name));
+ if (text.length() > 0) {
+ static_cast<Gtk::Entry*>(_packable)->set_text (text.c_str());
+ }
+}
+
+void
+EntityLineEntry::on_changed()
+{
+ if (_wr->isUpdating()) return;
+
+ _wr->setUpdating (true);
+ SPDocument *doc = SP_ACTIVE_DOCUMENT;
+ Glib::ustring text = static_cast<Gtk::Entry*>(_packable)->get_text();
+ if (rdf_set_work_entity (doc, _entity, text.c_str())) {
+ if (doc->isSensitive()) {
+ DocumentUndo::done(doc, SP_VERB_NONE, "Document metadata updated");
+ }
+ }
+ _wr->setUpdating (false);
+}
+
+EntityMultiLineEntry::EntityMultiLineEntry (rdf_work_entity_t* ent, Registry& wr)
+: EntityEntry (ent, wr)
+{
+ Gtk::ScrolledWindow *s = new Gtk::ScrolledWindow;
+ s->set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+ s->set_shadow_type (Gtk::SHADOW_IN);
+ _packable = s;
+ _v.set_size_request (-1, 35);
+ _v.set_wrap_mode (Gtk::WRAP_WORD);
+ _v.set_accepts_tab (false);
+ s->add (_v);
+ _v.set_tooltip_text (_(ent->tip));
+ _changed_connection = _v.get_buffer()->signal_changed().connect (sigc::mem_fun (*this, &EntityMultiLineEntry::on_changed));
+}
+
+EntityMultiLineEntry::~EntityMultiLineEntry()
+{
+ delete static_cast<Gtk::ScrolledWindow*>(_packable);
+}
+
+void EntityMultiLineEntry::update(SPDocument *doc)
+{
+ const char *text = rdf_get_work_entity (doc, _entity);
+ // If RDF title is not set, get the document's <title> and set the RDF:
+ if ( !text && !strcmp(_entity->name, "title") && doc->getRoot() ) {
+ text = doc->getRoot()->title();
+ rdf_set_work_entity(doc, _entity, text);
+ }
+ Gtk::ScrolledWindow *s = static_cast<Gtk::ScrolledWindow*>(_packable);
+ Gtk::TextView *tv = static_cast<Gtk::TextView*>(s->get_child());
+ tv->get_buffer()->set_text (text ? text : "");
+}
+
+
+void EntityMultiLineEntry::load_from_preferences()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ Glib::ustring text = prefs->getString(PREFS_METADATA + Glib::ustring(_entity->name));
+ if (text.length() > 0) {
+ Gtk::ScrolledWindow *s = static_cast<Gtk::ScrolledWindow*>(_packable);
+ Gtk::TextView *tv = static_cast<Gtk::TextView*>(s->get_child());
+ tv->get_buffer()->set_text (text.c_str());
+ }
+}
+
+
+void
+EntityMultiLineEntry::on_changed()
+{
+ if (_wr->isUpdating()) return;
+
+ _wr->setUpdating (true);
+ SPDocument *doc = SP_ACTIVE_DOCUMENT;
+ Gtk::ScrolledWindow *s = static_cast<Gtk::ScrolledWindow*>(_packable);
+ Gtk::TextView *tv = static_cast<Gtk::TextView*>(s->get_child());
+ Glib::ustring text = tv->get_buffer()->get_text();
+ if (rdf_set_work_entity (doc, _entity, text.c_str())) {
+ DocumentUndo::done(doc, SP_VERB_NONE, "Document metadata updated");
+ }
+ _wr->setUpdating (false);
+}
+
+} // namespace Dialog
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/entity-entry.h b/src/ui/widget/entity-entry.h
new file mode 100644
index 0000000..3168e4c
--- /dev/null
+++ b/src/ui/widget/entity-entry.h
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ralf Stephan <ralf@ark.in-berlin.de>
+ *
+ * Copyright (C) 2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_ENTITY_ENTRY__H
+#define INKSCAPE_UI_WIDGET_ENTITY_ENTRY__H
+
+#include <gtkmm/textview.h>
+
+struct rdf_work_entity_t;
+class SPDocument;
+
+namespace Gtk {
+class TextBuffer;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class Registry;
+
+class EntityEntry {
+public:
+ static EntityEntry* create (rdf_work_entity_t* ent, Registry& wr);
+ virtual ~EntityEntry() = 0;
+ virtual void update (SPDocument *doc) = 0;
+ virtual void on_changed() = 0;
+ virtual void load_from_preferences() = 0;
+ void save_to_preferences(SPDocument *doc);
+ Gtk::Label _label;
+ Gtk::Widget *_packable;
+
+protected:
+ EntityEntry (rdf_work_entity_t* ent, Registry& wr);
+ sigc::connection _changed_connection;
+ rdf_work_entity_t *_entity;
+ Registry *_wr;
+};
+
+class EntityLineEntry : public EntityEntry {
+public:
+ EntityLineEntry (rdf_work_entity_t* ent, Registry& wr);
+ ~EntityLineEntry() override;
+ void update (SPDocument *doc) override;
+ void load_from_preferences() override;
+
+protected:
+ void on_changed() override;
+};
+
+class EntityMultiLineEntry : public EntityEntry {
+public:
+ EntityMultiLineEntry (rdf_work_entity_t* ent, Registry& wr);
+ ~EntityMultiLineEntry() override;
+ void update (SPDocument *doc) override;
+ void load_from_preferences() override;
+
+protected:
+ void on_changed() override;
+ Gtk::TextView _v;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_ENTITY_ENTRY__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/entry.cpp b/src/ui/widget/entry.cpp
new file mode 100644
index 0000000..e9a63c5
--- /dev/null
+++ b/src/ui/widget/entry.cpp
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ *
+ * Copyright (C) 2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "entry.h"
+
+#include <gtkmm/entry.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+Entry::Entry( Glib::ustring const &label, Glib::ustring const &tooltip,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Labelled(label, tooltip, new Gtk::Entry(), suffix, icon, mnemonic)
+{
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
diff --git a/src/ui/widget/entry.h b/src/ui/widget/entry.h
new file mode 100644
index 0000000..3674d51
--- /dev/null
+++ b/src/ui/widget/entry.h
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ *
+ * Copyright (C) 2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_ENTRY__H
+#define INKSCAPE_UI_WIDGET_ENTRY__H
+
+#include "labelled.h"
+
+namespace Gtk {
+class Entry;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * Helperclass for Gtk::Entry widgets.
+ */
+class Entry : public Labelled
+{
+public:
+ Entry( Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ // TO DO: add methods to access Gtk::Entry widget
+
+ Gtk::Entry* getEntry() {return (Gtk::Entry*)(_widget);};
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_ENTRY__H
diff --git a/src/ui/widget/filter-effect-chooser.cpp b/src/ui/widget/filter-effect-chooser.cpp
new file mode 100644
index 0000000..d933408
--- /dev/null
+++ b/src/ui/widget/filter-effect-chooser.cpp
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Filter effect selection selection widget
+ *
+ * Author:
+ * Nicholas Bishop <nicholasbishop@gmail.com>
+ * Tavmjong Bah
+ *
+ * Copyright (C) 2007, 2017 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "filter-effect-chooser.h"
+
+#include "document.h"
+
+namespace Inkscape {
+
+const EnumData<SPBlendMode> SPBlendModeData[SP_CSS_BLEND_ENDMODE] = {
+ { SP_CSS_BLEND_NORMAL, _("Normal"), "normal" },
+ { SP_CSS_BLEND_MULTIPLY, _("Multiply"), "multiply" },
+ { SP_CSS_BLEND_SCREEN, _("Screen"), "screen" },
+ { SP_CSS_BLEND_DARKEN, _("Darken"), "darken" },
+ { SP_CSS_BLEND_LIGHTEN, _("Lighten"), "lighten" },
+ // New in Compositing and Blending Level 1
+ { SP_CSS_BLEND_OVERLAY, _("Overlay"), "overlay" },
+ { SP_CSS_BLEND_COLORDODGE, _("Color Dodge"), "color-dodge" },
+ { SP_CSS_BLEND_COLORBURN, _("Color Burn"), "color-burn" },
+ { SP_CSS_BLEND_HARDLIGHT, _("Hard Light"), "hard-light" },
+ { SP_CSS_BLEND_SOFTLIGHT, _("Soft Light"), "soft-light" },
+ { SP_CSS_BLEND_DIFFERENCE, _("Difference"), "difference" },
+ { SP_CSS_BLEND_EXCLUSION, _("Exclusion"), "exclusion" },
+ { SP_CSS_BLEND_HUE, _("Hue"), "hue" },
+ { SP_CSS_BLEND_SATURATION, _("Saturation"), "saturation" },
+ { SP_CSS_BLEND_COLOR, _("Color"), "color" },
+ { SP_CSS_BLEND_LUMINOSITY, _("Luminosity"), "luminosity" }
+};
+const EnumDataConverter<SPBlendMode> SPBlendModeConverter(SPBlendModeData, SP_CSS_BLEND_ENDMODE);
+
+
+namespace UI {
+namespace Widget {
+
+SimpleFilterModifier::SimpleFilterModifier(int flags)
+ : _flags(flags)
+ , _lb_blend(_("Blend mode:"))
+ , _lb_isolation("Isolate") // Translate for 1.1
+ , _blend(SPBlendModeConverter, SP_ATTR_INVALID, false)
+ , _blur(_("Blur (%)"), 0, 0, 100, 1, 0.1, 1)
+ , _opacity(_("Opacity (%)"), 0, 0, 100, 1, 0.1, 1)
+ , _notify(true)
+{
+ set_name("SimpleFilterModifier");
+
+ _flags = flags;
+
+ if (flags & BLEND) {
+ add(_hb_blend);
+ _lb_blend.set_use_underline();
+ _hb_blend.set_halign(Gtk::ALIGN_END);
+ _hb_blend.set_valign(Gtk::ALIGN_CENTER);
+ _hb_blend.set_margin_top(3);
+ _hb_blend.set_margin_end(5);
+ _lb_blend.set_mnemonic_widget(_blend);
+ _hb_blend.pack_start(_lb_blend, false, false, 5);
+ _hb_blend.pack_start(_blend, false, false, 5);
+ /*
+ * For best fit inkscape-browsers with no GUI to isolation we need all groups,
+ * clones and symbols with isolation == isolate to not show to the user of
+ * Inkscape a "strange" behabiour from the designer point of view.
+ * Is strange because only happends when object not has clip, mask,
+ * filter, blending or opacity .
+ * Anyway the feature is a no-gui feature and render as spected.
+ */
+ /* if (flags & ISOLATION) {
+ _isolation.property_active() = false;
+ _hb_blend.pack_start(_isolation, false, false, 5);
+ _hb_blend.pack_start(_lb_isolation, false, false, 5);
+ _isolation.set_tooltip_text("Don't blend childrens with objects behind");
+ _lb_isolation.set_tooltip_text("Don't blend childrens with objects behind");
+ } */
+ Gtk::Separator *separator = Gtk::manage(new Gtk::Separator());
+ separator->set_margin_top(8);
+ separator->set_margin_bottom(8);
+ add(*separator);
+ }
+
+ if (flags & BLUR) {
+ add(_blur);
+ }
+
+ if (flags & OPACITY) {
+ add(_opacity);
+ }
+ show_all_children();
+
+ _blend.signal_changed().connect(signal_blend_changed());
+ _blur.signal_value_changed().connect(signal_blur_changed());
+ _opacity.signal_value_changed().connect(signal_opacity_changed());
+ _isolation.signal_toggled().connect(signal_isolation_changed());
+}
+
+sigc::signal<void> &SimpleFilterModifier::signal_isolation_changed()
+{
+ if (_notify) {
+ return _signal_isolation_changed;
+ }
+ _notify = true;
+ return _signal_null;
+}
+
+sigc::signal<void>& SimpleFilterModifier::signal_blend_changed()
+{
+ if (_notify) {
+ return _signal_blend_changed;
+ }
+ _notify = true;
+ return _signal_null;
+}
+
+sigc::signal<void>& SimpleFilterModifier::signal_blur_changed()
+{
+ // we dont use notifi to block use aberaje for multiple
+ return _signal_blur_changed;
+}
+
+sigc::signal<void>& SimpleFilterModifier::signal_opacity_changed()
+{
+ // we dont use notifi to block use averaje for multiple
+ return _signal_opacity_changed;
+}
+
+SPIsolation SimpleFilterModifier::get_isolation_mode()
+{
+ return _isolation.get_active() ? SP_CSS_ISOLATION_ISOLATE : SP_CSS_ISOLATION_AUTO;
+}
+
+void SimpleFilterModifier::set_isolation_mode(const SPIsolation val, bool notify)
+{
+ _notify = notify;
+ _isolation.set_active(val == SP_CSS_ISOLATION_ISOLATE);
+}
+
+SPBlendMode SimpleFilterModifier::get_blend_mode()
+{
+ const Util::EnumData<SPBlendMode> *d = _blend.get_active_data();
+ if (d) {
+ return _blend.get_active_data()->id;
+ } else {
+ return SP_CSS_BLEND_NORMAL;
+ }
+}
+
+void SimpleFilterModifier::set_blend_mode(const SPBlendMode val, bool notify)
+{
+ _notify = notify;
+ _blend.set_active(val);
+}
+
+double SimpleFilterModifier::get_blur_value() const
+{
+ return _blur.get_value();
+}
+
+void SimpleFilterModifier::set_blur_value(const double val)
+{
+ _blur.set_value(val);
+}
+
+double SimpleFilterModifier::get_opacity_value() const
+{
+ return _opacity.get_value();
+}
+
+void SimpleFilterModifier::set_opacity_value(const double val)
+{
+ _opacity.set_value(val);
+}
+
+}
+}
+}
+
+/*
+ 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/filter-effect-chooser.h b/src/ui/widget/filter-effect-chooser.h
new file mode 100644
index 0000000..cbbe2b5
--- /dev/null
+++ b/src/ui/widget/filter-effect-chooser.h
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef __FILTER_EFFECT_CHOOSER_H__
+#define __FILTER_EFFECT_CHOOSER_H__
+
+/*
+ * Filter effect selection selection widget
+ *
+ * Author:
+ * Nicholas Bishop <nicholasbishop@gmail.com>
+ * Tavmjong Bah
+ *
+ * Copyright (C) 2007, 2017 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/box.h>
+#include <gtkmm/checkbutton.h>
+#include <gtkmm/combobox.h>
+#include <gtkmm/separator.h>
+
+#include "combo-enums.h"
+#include "spin-scale.h"
+#include "style-enums.h"
+
+using Inkscape::Util::EnumData;
+using Inkscape::Util::EnumDataConverter;
+
+namespace Inkscape {
+extern const Util::EnumDataConverter<SPBlendMode> SPBlendModeConverter;
+namespace UI {
+namespace Widget {
+
+/* Allows basic control over feBlend and feGaussianBlur effects as well as opacity.
+ * Common for Object, Layers, and Fill and Stroke dialogs.
+*/
+class SimpleFilterModifier : public Gtk::VBox
+{
+public:
+ enum Flags { NONE = 0, BLUR = 1, OPACITY = 2, BLEND = 4, ISOLATION = 16 };
+
+ SimpleFilterModifier(int flags);
+
+ sigc::signal<void> &signal_blend_changed();
+ sigc::signal<void> &signal_blur_changed();
+ sigc::signal<void> &signal_opacity_changed();
+ sigc::signal<void> &signal_isolation_changed();
+
+ SPIsolation get_isolation_mode();
+ void set_isolation_mode(const SPIsolation, bool notify);
+
+ SPBlendMode get_blend_mode();
+ void set_blend_mode(const SPBlendMode, bool notify);
+
+ double get_blur_value() const;
+ void set_blur_value(const double);
+
+ double get_opacity_value() const;
+ void set_opacity_value(const double);
+
+private:
+ int _flags;
+ bool _notify;
+
+ Gtk::HBox _hb_blend;
+ Gtk::Label _lb_blend;
+ Gtk::Label _lb_isolation;
+ ComboBoxEnum<SPBlendMode> _blend;
+ SpinScale _blur;
+ SpinScale _opacity;
+ Gtk::CheckButton _isolation;
+
+ sigc::signal<void> _signal_null;
+ sigc::signal<void> _signal_blend_changed;
+ sigc::signal<void> _signal_blur_changed;
+ sigc::signal<void> _signal_opacity_changed;
+ sigc::signal<void> _signal_isolation_changed;
+};
+
+}
+}
+}
+
+#endif
+
+/*
+ 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/font-button.cpp b/src/ui/widget/font-button.cpp
new file mode 100644
index 0000000..e0a140a
--- /dev/null
+++ b/src/ui/widget/font-button.cpp
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "font-button.h"
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/fontbutton.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+FontButton::FontButton(Glib::ustring const &label, Glib::ustring const &tooltip,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Labelled(label, tooltip, new Gtk::FontButton("Sans 10"), suffix, icon, mnemonic)
+{
+}
+
+Glib::ustring FontButton::getValue() const
+{
+ g_assert(_widget != nullptr);
+ return static_cast<Gtk::FontButton*>(_widget)->get_font_name();
+}
+
+
+void FontButton::setValue (Glib::ustring fontspec)
+{
+ g_assert(_widget != nullptr);
+ static_cast<Gtk::FontButton*>(_widget)->set_font_name(fontspec);
+}
+
+Glib::SignalProxy0<void> FontButton::signal_font_value_changed()
+{
+ g_assert(_widget != nullptr);
+ return static_cast<Gtk::FontButton*>(_widget)->signal_font_set();
+}
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/font-button.h b/src/ui/widget/font-button.h
new file mode 100644
index 0000000..a53b7d6
--- /dev/null
+++ b/src/ui/widget/font-button.h
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * Copyright (C) 2007 Author
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_FONT_BUTTON_H
+#define INKSCAPE_UI_WIDGET_FONT_BUTTON_H
+
+#include "labelled.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * A labelled font button for entering font values
+ */
+class FontButton : public Labelled
+{
+public:
+ /**
+ * Construct a FontButton Widget.
+ *
+ * @param label Label.
+ * @param suffix Suffix, placed after the widget (defaults to "").
+ * @param icon Icon filename, placed before the label (defaults to "").
+ * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to false).
+ */
+ FontButton( Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ Glib::ustring getValue() const;
+ void setValue (Glib::ustring fontspec);
+ /**
+ * Signal raised when the font button's value changes.
+ */
+ Glib::SignalProxy0<void> signal_font_value_changed();
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_RANDOM_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/font-selector-toolbar.cpp b/src/ui/widget/font-selector-toolbar.cpp
new file mode 100644
index 0000000..ea53d90
--- /dev/null
+++ b/src/ui/widget/font-selector-toolbar.cpp
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2018 Tavmong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/i18n.h>
+#include <glibmm/regex.h>
+#include <gdkmm/display.h>
+
+#include "font-selector-toolbar.h"
+
+#include "libnrtype/font-lister.h"
+#include "libnrtype/font-instance.h"
+
+#include "ui/icon-names.h"
+
+// For updating from selection
+#include "inkscape.h"
+#include "desktop.h"
+#include "object/sp-text.h"
+
+// TEMP TEMP TEMP
+#include "ui/toolbar/text-toolbar.h"
+
+/* To do:
+ * Fix altx. Need to store
+ */
+
+void family_cell_data_func(const Gtk::TreeModel::const_iterator iter, Gtk::CellRendererText* cell ) {
+
+ Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
+ Glib::ustring markup = font_lister->get_font_family_markup(iter);
+ // std::cout << "Markup: " << markup << std::endl;
+
+ cell->set_property ("markup", markup);
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+FontSelectorToolbar::FontSelectorToolbar ()
+ : Gtk::Grid ()
+ , family_combo (true) // true => with text entry.
+ , style_combo (true)
+ , signal_block (false)
+{
+
+ Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
+
+ // Font family
+ family_combo.set_model (font_lister->get_font_list());
+ family_combo.set_entry_text_column (0);
+ family_combo.set_name ("FontSelectorToolBar: Family");
+ family_combo.set_row_separator_func (&font_lister_separator_func);
+
+ family_combo.clear(); // Clears all CellRenderer mappings.
+ family_combo.set_cell_data_func (family_cell,
+ sigc::bind(sigc::ptr_fun(family_cell_data_func), &family_cell));
+ family_combo.pack_start (family_cell);
+
+
+ Gtk::Entry* entry = family_combo.get_entry();
+ entry->signal_icon_press().connect (sigc::mem_fun(*this, &FontSelectorToolbar::on_icon_pressed));
+ entry->signal_key_press_event().connect (sigc::mem_fun(*this, &FontSelectorToolbar::on_key_press_event), false); // false => connect first
+ entry->set_data (Glib::Quark("altx-text"), entry); // Desktop will set focus to entry with Alt-x.
+
+
+ Glib::RefPtr<Gtk::EntryCompletion> completion = Gtk::EntryCompletion::create();
+ completion->set_model (font_lister->get_font_list());
+ completion->set_text_column (0);
+ completion->set_popup_completion ();
+ completion->set_inline_completion (false);
+ completion->set_inline_selection ();
+ // completion->signal_match_selected().connect(sigc::mem_fun(*this, &FontSelectorToolbar::on_match_selected), false); // false => connect before default handler.
+ entry->set_completion (completion);
+
+ // Style
+ style_combo.set_model (font_lister->get_style_list());
+ style_combo.set_name ("FontSelectorToolbar: Style");
+
+ // Grid
+ set_name ("FontSelectorToolbar: Grid");
+ attach (family_combo, 0, 0, 1, 1);
+ attach (style_combo, 1, 0, 1, 1);
+
+ // Add signals
+ family_combo.signal_changed().connect (sigc::mem_fun(*this, &FontSelectorToolbar::on_family_changed));
+ style_combo.signal_changed().connect (sigc::mem_fun(*this, &FontSelectorToolbar::on_style_changed));
+
+ show_all_children();
+
+ // Initialize font family lists. (May already be done.) Should be done on document change.
+ font_lister->update_font_list(SP_ACTIVE_DESKTOP->getDocument());
+
+ // When FontLister is changed, update family and style shown in GUI.
+ font_lister->connectUpdate(sigc::mem_fun(*this, &FontSelectorToolbar::update_font));
+}
+
+
+// Update GUI based on font-selector values.
+void
+FontSelectorToolbar::update_font ()
+{
+ if (signal_block) return;
+
+ signal_block = true;
+
+ Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
+ Gtk::TreeModel::Row row;
+
+ // Set font family.
+ try {
+ row = font_lister->get_row_for_font ();
+ family_combo.set_active (row);
+ } catch (...) {
+ std::cerr << "FontSelectorToolbar::update_font: Couldn't find row for family: "
+ << font_lister->get_font_family() << std::endl;
+ }
+
+ // Set style.
+ try {
+ row = font_lister->get_row_for_style ();
+ style_combo.set_active (row);
+ } catch (...) {
+ std::cerr << "FontSelectorToolbar::update_font: Couldn't find row for style: "
+ << font_lister->get_font_style() << std::endl;
+ }
+
+ // Check for missing fonts.
+ Glib::ustring missing_fonts = get_missing_fonts();
+
+ // Add an icon to end of entry.
+ Gtk::Entry* entry = family_combo.get_entry();
+ if (missing_fonts.empty()) {
+ // If no missing fonts, add icon for selecting all objects with this font-family.
+ entry->set_icon_from_icon_name (INKSCAPE_ICON("edit-select-all"), Gtk::ENTRY_ICON_SECONDARY);
+ entry->set_icon_tooltip_text (_("Select all text with this text family"), Gtk::ENTRY_ICON_SECONDARY);
+ } else {
+ // If missing fonts, add warning icon.
+ Glib::ustring warning = _("Font not found on system: ") + missing_fonts;
+ entry->set_icon_from_icon_name (INKSCAPE_ICON("dialog-warning"), Gtk::ENTRY_ICON_SECONDARY);
+ entry->set_icon_tooltip_text (warning, Gtk::ENTRY_ICON_SECONDARY);
+ }
+
+ signal_block = false;
+}
+
+// Get comma separated list of fonts in font-family that are not on system.
+// To do, move to font-lister.
+Glib::ustring
+FontSelectorToolbar::get_missing_fonts ()
+{
+ // Get font list in text entry which may be a font stack (with fallbacks).
+ Glib::ustring font_list = family_combo.get_entry_text();
+ Glib::ustring missing_font_list;
+ Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
+
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s*,\\s*", font_list);
+
+ for (auto token: tokens) {
+ bool found = false;
+ Gtk::TreeModel::Children children = font_lister->get_font_list()->children();
+ for (auto iter2: children) {
+ Gtk::TreeModel::Row row2 = *iter2;
+ Glib::ustring family2 = row2[font_lister->FontList.family];
+ bool onSystem2 = row2[font_lister->FontList.onSystem];
+ // CSS dictates that font family names are case insensitive.
+ // This should really implement full Unicode case unfolding.
+ if (onSystem2 && token.casefold().compare(family2.casefold()) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ missing_font_list += token;
+ missing_font_list += ", ";
+ }
+ }
+
+ // Remove extra comma and space from end.
+ if (missing_font_list.size() >= 2) {
+ missing_font_list.resize(missing_font_list.size() - 2);
+ }
+
+ return missing_font_list;
+}
+
+
+// Callbacks
+
+// Need to update style list
+void
+FontSelectorToolbar::on_family_changed() {
+
+ if (signal_block) return;
+ signal_block = true;
+
+ Glib::ustring family = family_combo.get_entry_text();
+
+ Inkscape::FontLister *fontlister = Inkscape::FontLister::get_instance();
+ fontlister->set_font_family (family);
+
+ signal_block = false;
+
+ // Let world know
+ changed_emit();
+}
+
+void
+FontSelectorToolbar::on_style_changed() {
+
+ if (signal_block) return;
+ signal_block = true;
+
+ Glib::ustring style = style_combo.get_entry_text();
+
+ Inkscape::FontLister *fontlister = Inkscape::FontLister::get_instance();
+ fontlister->set_font_style (style);
+
+ signal_block = false;
+
+ // Let world know
+ changed_emit();
+}
+
+void
+FontSelectorToolbar::on_icon_pressed (Gtk::EntryIconPosition icon_position, const GdkEventButton* event) {
+ std::cout << "FontSelectorToolbar::on_entry_icon_pressed" << std::endl;
+ std::cout << " .... Should select all items with same font-family. FIXME" << std::endl;
+ // Call equivalent of sp_text_toolbox_select_cb() in text-toolbar.cpp
+ // Should be action! (Maybe: select_all_fontfamily( Glib::ustring font_family );).
+ // Check how Find dialog works.
+}
+
+// bool
+// FontSelectorToolbar::on_match_selected (const Gtk::TreeModel::iterator& iter)
+// {
+// std::cout << "on_match_selected" << std::endl;
+// std::cout << " FIXME" << std::endl;
+// Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
+// Glib::ustring family = (*iter)[font_lister->FontList.family];
+// std::cout << " family: " << family << std::endl;
+// return false; // Leave it to default handler to set entry text.
+// }
+
+// Return focus to canvas.
+bool
+FontSelectorToolbar::on_key_press_event (GdkEventKey* key_event)
+{
+ bool consumed = false;
+
+ unsigned int key = 0;
+ gdk_keymap_translate_keyboard_state( Gdk::Display::get_default()->get_keymap(),
+ key_event->hardware_keycode,
+ (GdkModifierType)key_event->state,
+ 0, &key, nullptr, nullptr, nullptr );
+
+ switch ( key ) {
+
+ case GDK_KEY_Escape:
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ {
+ // Defocus
+ std::cerr << "FontSelectorToolbar::on_key_press_event: Defocus: FIXME" << std::endl;
+ consumed = true;
+ }
+ break;
+ }
+
+ return consumed; // Leave it to default handler if false.
+}
+
+void
+FontSelectorToolbar::changed_emit() {
+ signal_block = true;
+ changed_signal.emit ();
+ signal_block = false;
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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 :
diff --git a/src/ui/widget/font-selector-toolbar.h b/src/ui/widget/font-selector-toolbar.h
new file mode 100644
index 0000000..53cdcea
--- /dev/null
+++ b/src/ui/widget/font-selector-toolbar.h
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2018 Tavmong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ *
+ * The routines here create and manage a font selector widget with two parts,
+ * one each for font-family and font-style.
+ *
+ * This is essentially a toolbar version of the 'FontSelector' widget. Someday
+ * this may be merged with it.
+ *
+ * The main functions are:
+ * Create the font-selector toolbar widget.
+ * Update the lists when a new text selection is made.
+ * Update the Style list when a new font-family is selected, highlighting the
+ * best match to the original font style (as not all fonts have the same style options).
+ * Update the on-screen text.
+ * Provide the currently selected values.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_FONT_SELECTOR_TOOLBAR_H
+#define INKSCAPE_UI_WIDGET_FONT_SELECTOR_TOOLBAR_H
+
+#include <gtkmm/grid.h>
+#include <gtkmm/treeview.h>
+#include <gtkmm/comboboxtext.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * A container of widgets for selecting font faces.
+ *
+ * It is used by Text tool toolbar. The FontSelectorToolbar class utilizes the
+ * FontLister class to obtain a list of font-families and their associated styles for fonts either
+ * on the system or in the document. The FontLister class is also used by the Text toolbar. Fonts
+ * are kept track of by their "fontspecs" which are the same as the strings that Pango generates.
+ *
+ * The main functions are:
+ * Create the font-selector widget.
+ * Update the child widgets when a new text selection is made.
+ * Update the Style list when a new font-family is selected, highlighting the
+ * best match to the original font style (as not all fonts have the same style options).
+ * Emit a signal when any change is made to a child widget.
+ */
+class FontSelectorToolbar : public Gtk::Grid
+{
+
+public:
+
+ /**
+ * Constructor
+ */
+ FontSelectorToolbar ();
+
+protected:
+
+ // Font family
+ Gtk::ComboBox family_combo;
+ Gtk::CellRendererText family_cell;
+
+ // Font style
+ Gtk::ComboBoxText style_combo;
+ Gtk::CellRendererText style_cell;
+
+private:
+
+ // Make a list of missing fonts for tooltip and for warning icon.
+ Glib::ustring get_missing_fonts ();
+
+ // Signal handlers
+ void on_family_changed();
+ void on_style_changed();
+ void on_icon_pressed (Gtk::EntryIconPosition icon_position, const GdkEventButton* event);
+ // bool on_match_selected (const Gtk::TreeModel::iterator& iter);
+ bool on_key_press_event (GdkEventKey* key_event) override;
+
+ // Signals
+ sigc::signal<void> changed_signal;
+ void changed_emit();
+ bool signal_block;
+
+public:
+
+ /**
+ * Update GUI based on font-selector values.
+ */
+ void update_font ();
+
+ /**
+ * Let others know that user has changed GUI settings.
+ */
+ sigc::connection connectChanged(sigc::slot<void> slot) {
+ return changed_signal.connect(slot);
+ }
+};
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_FONT_SETTINGS_TOOLBAR_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 :
diff --git a/src/ui/widget/font-selector.cpp b/src/ui/widget/font-selector.cpp
new file mode 100644
index 0000000..df13fa3
--- /dev/null
+++ b/src/ui/widget/font-selector.cpp
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2018 Tavmong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/i18n.h>
+#include <glibmm/markup.h>
+
+#include "font-selector.h"
+
+#include "libnrtype/font-lister.h"
+#include "libnrtype/font-instance.h"
+
+// For updating from selection
+#include "inkscape.h"
+#include "desktop.h"
+#include "object/sp-text.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+FontSelector::FontSelector (bool with_size, bool with_variations)
+ : Gtk::Grid ()
+ , family_frame (_("Font family"))
+ , style_frame (C_("Font selector", "Style"))
+ , size_label (_("Font size"))
+ , size_combobox (true) // With entry
+ , signal_block (false)
+ , font_size (18)
+{
+
+ Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
+
+ // Font family
+ family_treecolumn.pack_start (family_cell, false);
+ family_treecolumn.set_fixed_width (200);
+ family_treecolumn.add_attribute (family_cell, "text", 0);
+ family_treecolumn.set_cell_data_func (family_cell, &font_lister_cell_data_func);
+
+ family_treeview.set_row_separator_func (&font_lister_separator_func);
+ family_treeview.set_model (font_lister->get_font_list());
+ family_treeview.set_name ("FontSelector: Family");
+ family_treeview.set_headers_visible (false);
+ family_treeview.append_column (family_treecolumn);
+
+ family_scroll.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
+ family_scroll.add (family_treeview);
+
+ family_frame.set_hexpand (true);
+ family_frame.set_vexpand (true);
+ family_frame.add (family_scroll);
+
+ // Style
+ style_treecolumn.pack_start (style_cell, false);
+ style_treecolumn.add_attribute (style_cell, "text", 0);
+ style_treecolumn.set_cell_data_func (style_cell, sigc::mem_fun(*this, &FontSelector::style_cell_data_func));
+ style_treecolumn.set_title ("Face");
+ style_treecolumn.set_resizable (true);
+
+ style_treeview.set_model (font_lister->get_style_list());
+ style_treeview.set_name ("FontSelectorStyle");
+ style_treeview.append_column ("CSS", font_lister->FontStyleList.cssStyle);
+ style_treeview.append_column (style_treecolumn);
+
+ style_treeview.get_column(0)->set_resizable (true);
+
+ style_scroll.set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+ style_scroll.add (style_treeview);
+
+ style_frame.set_hexpand (true);
+ style_frame.set_vexpand (true);
+ style_frame.add (style_scroll);
+
+ // Size
+ size_combobox.set_name ("FontSelectorSize");
+ set_sizes();
+ size_combobox.set_active_text( "18" );
+
+ // Font Variations
+ font_variations.set_vexpand (true);
+ font_variations_scroll.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
+ font_variations_scroll.add (font_variations);
+
+ // Grid
+ set_name ("FontSelectorGrid");
+ set_row_spacing(4);
+ set_column_spacing(4);
+ // Add extra columns to the "family frame" to change space distribution
+ // by prioritizing font family over styles
+ const int extra = 4;
+ attach (family_frame, 0, 0, 1 + extra, 2);
+ attach (style_frame, 1 + extra, 0, 2, 1);
+ if (with_size) { // Glyph panel does not use size.
+ attach (size_label, 1 + extra, 1, 1, 1);
+ attach (size_combobox, 2 + extra, 1, 1, 1);
+ }
+ if (with_variations) { // Glyphs panel does not use variations.
+ attach (font_variations_scroll, 0, 2, 3 + extra, 1);
+ }
+
+ // Add signals
+ family_treeview.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_family_changed));
+ style_treeview.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_style_changed));
+ size_combobox.signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_size_changed));
+ font_variations.connectChanged(sigc::mem_fun(*this, &FontSelector::on_variations_changed));
+
+ show_all_children();
+
+ // Initialize font family lists. (May already be done.) Should be done on document change.
+ font_lister->update_font_list(SP_ACTIVE_DESKTOP->getDocument());
+}
+
+void
+FontSelector::set_sizes ()
+{
+ size_combobox.remove_all();
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
+
+ int sizes[] = {
+ 4, 6, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 28,
+ 32, 36, 40, 48, 56, 64, 72, 144
+ };
+
+ // Array must be same length as SPCSSUnit in style-internal.h
+ // PX PT PC MM CM IN EM EX %
+ double ratios[] = {1, 1, 1, 10, 4, 40, 100, 16, 8, 0.16};
+
+ for (int i : sizes)
+ {
+ double size = i/ratios[unit];
+ size_combobox.append( Glib::ustring::format(size) );
+ }
+}
+
+void
+FontSelector::set_fontsize_tooltip()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
+ Glib::ustring tooltip = Glib::ustring::format(_("Font size"), " (", sp_style_get_css_unit_string(unit), ")");
+ size_combobox.set_tooltip_text (tooltip);
+}
+
+// Update GUI.
+// We keep a private copy of the style list as the font-family in widget is only temporary
+// until the "Apply" button is set so the style list can be different from that in
+// FontLister.
+void
+FontSelector::update_font ()
+{
+ signal_block = true;
+
+ Inkscape::FontLister *font_lister = Inkscape::FontLister::get_instance();
+ Gtk::TreePath path;
+ Glib::ustring family = font_lister->get_font_family();
+ Glib::ustring style = font_lister->get_font_style();
+
+ // Set font family
+ try {
+ path = font_lister->get_row_for_font (family);
+ } catch (...) {
+ std::cerr << "FontSelector::update_font: Couldn't find row for font-family: "
+ << family << std::endl;
+ path.clear();
+ path.push_back(0);
+ }
+
+ Gtk::TreePath currentPath;
+ Gtk::TreeViewColumn *currentColumn;
+ family_treeview.get_cursor(currentPath, currentColumn);
+ if (currentPath.empty() || !font_lister->is_path_for_font(currentPath, family)) {
+ family_treeview.set_cursor (path);
+ family_treeview.scroll_to_row (path);
+ }
+
+ // Get font-lister style list for selected family
+ Gtk::TreeModel::Row row = *(family_treeview.get_model()->get_iter (path));
+ GList *styles;
+ row.get_value(1, styles);
+
+ // Copy font-lister style list to private list store, searching for match.
+ Gtk::TreeModel::iterator match;
+ FontLister::FontStyleListClass FontStyleList;
+ Glib::RefPtr<Gtk::ListStore> local_style_list_store = Gtk::ListStore::create(FontStyleList);
+ for ( ; styles; styles = styles->next ) {
+ Gtk::TreeModel::iterator treeModelIter = local_style_list_store->append();
+ (*treeModelIter)[FontStyleList.cssStyle] = ((StyleNames *)styles->data)->CssName;
+ (*treeModelIter)[FontStyleList.displayStyle] = ((StyleNames *)styles->data)->DisplayName;
+ if (style == ((StyleNames*)styles->data)->CssName) {
+ match = treeModelIter;
+ }
+ }
+
+ // Attach store to tree view and select row.
+ style_treeview.set_model (local_style_list_store);
+ if (match) {
+ style_treeview.get_selection()->select (match);
+ }
+
+ Glib::ustring fontspec = font_lister->get_fontspec();
+ update_variations(fontspec);
+
+ signal_block = false;
+}
+
+void
+FontSelector::update_size (double size)
+{
+ signal_block = true;
+
+ // Set font size
+ std::stringstream ss;
+ ss << size;
+ size_combobox.get_entry()->set_text( ss.str() );
+ font_size = size; // Store value
+ set_fontsize_tooltip();
+
+ signal_block = false;
+}
+
+
+// If use_variations is true (default), we get variation values from variations widget otherwise we
+// get values from CSS widget (we need to be able to keep the two widgets synchronized both ways).
+Glib::ustring
+FontSelector::get_fontspec(bool use_variations) {
+
+ // Build new fontspec from GUI settings
+ Glib::ustring family = "Sans"; // Default...family list may not have been constructed.
+ Gtk::TreeModel::iterator iter = family_treeview.get_selection()->get_selected();
+ if (iter) {
+ (*iter).get_value(0, family);
+ }
+
+ Glib::ustring style = "Normal";
+ iter = style_treeview.get_selection()->get_selected();
+ if (iter) {
+ (*iter).get_value(0, style);
+ }
+
+ if (family.empty()) {
+ std::cerr << "FontSelector::get_fontspec: empty family!" << std::endl;
+ }
+
+ if (style.empty()) {
+ std::cerr << "FontSelector::get_fontspec: empty style!" << std::endl;
+ }
+
+ Glib::ustring fontspec = family + ", ";
+
+ if (use_variations) {
+ // Clip any font_variation data in 'style' as we'll replace it.
+ auto pos = style.find('@');
+ if (pos != Glib::ustring::npos) {
+ style.erase (pos, style.length()-1);
+ }
+
+ Glib::ustring variations = font_variations.get_pango_string();
+
+ if (variations.empty()) {
+ fontspec += style;
+ } else {
+ fontspec += variations;
+ }
+ } else {
+ fontspec += style;
+ }
+
+ return fontspec;
+}
+
+void
+FontSelector::style_cell_data_func (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter)
+{
+ Glib::ustring family = "Sans"; // Default...family list may not have been constructed.
+ Gtk::TreeModel::iterator iter_family = family_treeview.get_selection()->get_selected();
+ if (iter_family) {
+ (*iter_family).get_value(0, family);
+ }
+
+ Glib::ustring style = "Normal";
+ (*iter).get_value(1, style);
+
+ Glib::ustring style_escaped = Glib::Markup::escape_text( style );
+ Glib::ustring font_desc = Glib::Markup::escape_text( family + ", " + style );
+ Glib::ustring markup;
+
+ markup = "<span font='" + font_desc + "'>" + style_escaped + "</span>";
+
+ // std::cout << " markup: " << markup << " (" << name << ")" << std::endl;
+
+ renderer->set_property("markup", markup);
+}
+
+
+// Callbacks
+
+// Need to update style list
+void
+FontSelector::on_family_changed() {
+
+ if (signal_block) return;
+ signal_block = true;
+
+ Glib::RefPtr<Gtk::TreeModel> model;
+ Gtk::TreeModel::iterator iter = family_treeview.get_selection()->get_selected(model);
+
+ if (!iter) {
+ // This can happen just after the family list is recreated.
+ signal_block = false;
+ return;
+ }
+
+ Inkscape::FontLister *fontlister = Inkscape::FontLister::get_instance();
+ fontlister->ensureRowStyles(model, iter);
+
+ Gtk::TreeModel::Row row = *iter;
+
+ // Get family name
+ Glib::ustring family;
+ row.get_value(0, family);
+
+ // Get style list (TO DO: Get rid of GList)
+ GList *styles;
+ row.get_value(1, styles);
+
+ // Find best style match for selected family with current style (e.g. of selected text).
+ Glib::ustring style = fontlister->get_font_style();
+ Glib::ustring best = fontlister->get_best_style_match (family, style);
+
+ // Create are own store of styles for selected font-family (the font-family selected
+ // in the dialog may not be the same as stored in the font-lister class until the
+ // "Apply" button is triggered).
+ Gtk::TreeModel::iterator it_best;
+ FontLister::FontStyleListClass FontStyleList;
+ Glib::RefPtr<Gtk::ListStore> local_style_list_store = Gtk::ListStore::create(FontStyleList);
+
+ // Build list and find best match.
+ for ( ; styles; styles = styles->next ) {
+ Gtk::TreeModel::iterator treeModelIter = local_style_list_store->append();
+ (*treeModelIter)[FontStyleList.cssStyle] = ((StyleNames *)styles->data)->CssName;
+ (*treeModelIter)[FontStyleList.displayStyle] = ((StyleNames *)styles->data)->DisplayName;
+ if (best == ((StyleNames*)styles->data)->CssName) {
+ it_best = treeModelIter;
+ }
+ }
+
+ // Attach store to tree view and select row.
+ style_treeview.set_model (local_style_list_store);
+ if (it_best) {
+ style_treeview.get_selection()->select (it_best);
+ }
+
+ signal_block = false;
+
+ // Let world know
+ changed_emit();
+}
+
+void
+FontSelector::on_style_changed() {
+ if (signal_block) return;
+
+ // Update variations widget if new style selected from style widget.
+ signal_block = true;
+ Glib::ustring fontspec = get_fontspec( false );
+ update_variations(fontspec);
+ signal_block = false;
+
+ // Let world know
+ changed_emit();
+}
+
+void
+FontSelector::on_size_changed() {
+
+ if (signal_block) return;
+
+ double size;
+ Glib::ustring input = size_combobox.get_active_text();
+ try {
+ size = std::stod (input);
+ }
+ catch (std::invalid_argument) {
+ std::cerr << "FontSelector::on_size_changed: Invalid input: " << input << std::endl;
+ size = -1;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ // Arbitrary: Text and Font preview freezes with huge font sizes.
+ int max_size = prefs->getInt("/dialogs/textandfont/maxFontSize", 10000);
+
+ if (size <= 0) {
+ return;
+ }
+ if (size > max_size)
+ size = max_size;
+
+ if (fabs(font_size - size) > 0.001) {
+ font_size = size;
+ // Let world know
+ changed_emit();
+ }
+}
+
+void
+FontSelector::on_variations_changed() {
+
+ if (signal_block) return;
+
+ // Let world know
+ changed_emit();
+}
+
+void
+FontSelector::changed_emit() {
+ signal_block = true;
+ signal_changed.emit (get_fontspec());
+ signal_block = false;
+}
+
+void FontSelector::update_variations(const Glib::ustring& fontspec) {
+ font_variations.update(fontspec);
+
+ // Check if there are any variations available; if not, don't expand font_variations_scroll
+ bool hasContent = font_variations.variations_present();
+ font_variations_scroll.set_vexpand(hasContent);
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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 :
diff --git a/src/ui/widget/font-selector.h b/src/ui/widget/font-selector.h
new file mode 100644
index 0000000..137d411
--- /dev/null
+++ b/src/ui/widget/font-selector.h
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2018 Tavmong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ *
+ * The routines here create and manage a font selector widget with three parts,
+ * one each for font-family, font-style, and font-size.
+ *
+ * It is used by the TextEdit and Glyphs panel dialogs. The FontLister class is used
+ * to access the list of font-families and their associated styles for fonts either
+ * on the system or in the document. The FontLister class is also used by the Text
+ * toolbar. Fonts are kept track of by their "fontspecs" which are the same as the
+ * strings that Pango generates.
+ *
+ * The main functions are:
+ * Create the font-seletor widget.
+ * Update the lists when a new text selection is made.
+ * Update the Style list when a new font-family is selected, highlighting the
+ * best match to the original font style (as not all fonts have the same style options).
+ * Emit a signal when any change is made so that the Text Preview can be updated.
+ * Provide the currently selected values.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_FONT_SELECTOR_H
+#define INKSCAPE_UI_WIDGET_FONT_SELECTOR_H
+
+#include <gtkmm/grid.h>
+#include <gtkmm/frame.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/treeview.h>
+#include <gtkmm/label.h>
+#include <gtkmm/comboboxtext.h>
+
+#include "ui/widget/font-variations.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * A container of widgets for selecting font faces.
+ *
+ * It is used by the TextEdit and Glyphs panel dialogs. The FontSelector class utilizes the
+ * FontLister class to obtain a list of font-families and their associated styles for fonts either
+ * on the system or in the document. The FontLister class is also used by the Text toolbar. Fonts
+ * are kept track of by their "fontspecs" which are the same as the strings that Pango generates.
+ *
+ * The main functions are:
+ * Create the font-selector widget.
+ * Update the child widgets when a new text selection is made.
+ * Update the Style list when a new font-family is selected, highlighting the
+ * best match to the original font style (as not all fonts have the same style options).
+ * Emit a signal when any change is made to a child widget.
+ */
+class FontSelector : public Gtk::Grid
+{
+
+public:
+
+ /**
+ * Constructor
+ */
+ FontSelector (bool with_size = true, bool with_variations = true);
+
+protected:
+
+ // Font family
+ Gtk::Frame family_frame;
+ Gtk::ScrolledWindow family_scroll;
+ Gtk::TreeView family_treeview;
+ Gtk::TreeViewColumn family_treecolumn;
+ Gtk::CellRendererText family_cell;
+
+ // Font style
+ Gtk::Frame style_frame;
+ Gtk::ScrolledWindow style_scroll;
+ Gtk::TreeView style_treeview;
+ Gtk::TreeViewColumn style_treecolumn;
+ Gtk::CellRendererText style_cell;
+
+ // Font size
+ Gtk::Label size_label;
+ Gtk::ComboBoxText size_combobox;
+
+ // Font variations
+ Gtk::ScrolledWindow font_variations_scroll;
+ FontVariations font_variations;
+
+private:
+
+ // Set sizes in font size combobox.
+ void set_sizes();
+ void set_fontsize_tooltip();
+
+ // Use font style when listing style names.
+ void style_cell_data_func (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter);
+
+ // Signal handlers
+ void on_family_changed();
+ void on_style_changed();
+ void on_size_changed();
+ void on_variations_changed();
+
+ // Signals
+ sigc::signal<void, Glib::ustring> signal_changed;
+ void changed_emit();
+ bool signal_block;
+
+ // Variables
+ double font_size;
+
+ // control font variations update and UI element size
+ void update_variations(const Glib::ustring& fontspec);
+
+public:
+
+ /**
+ * Update GUI based on fontspec
+ */
+ void update_font ();
+ void update_size (double size);
+
+ /**
+ * Get fontspec based on current settings. (Does not handle size, yet.)
+ */
+ Glib::ustring get_fontspec(bool use_variations = true);
+
+ /**
+ * Get font size. Could be merged with fontspec.
+ */
+ double get_fontsize() { return font_size; };
+
+ /**
+ * Let others know that user has changed GUI settings.
+ * (Used to enable 'Apply' and 'Default' buttons.)
+ */
+ sigc::connection connectChanged(sigc::slot<void, Glib::ustring> slot) {
+ return signal_changed.connect(slot);
+ }
+};
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_FONT_SETTINGS_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 :
diff --git a/src/ui/widget/font-variants.cpp b/src/ui/widget/font-variants.cpp
new file mode 100644
index 0000000..1087431
--- /dev/null
+++ b/src/ui/widget/font-variants.cpp
@@ -0,0 +1,1461 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2015, 2018 Tavmong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm.h>
+#include <glibmm/i18n.h>
+
+#include <libnrtype/font-instance.h>
+
+#include "font-variants.h"
+
+// For updating from selection
+#include "desktop.h"
+#include "object/sp-text.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+ // A simple class to handle UI for one feature. We could of derived this from Gtk::HBox but by
+ // attaching widgets directly to Gtk::Grid, we keep columns lined up (which may or may not be a
+ // good thing).
+ class Feature
+ {
+ public:
+ Feature( const Glib::ustring& name, OTSubstitution& glyphs, int options, Glib::ustring family, Gtk::Grid& grid, int &row, FontVariants* parent)
+ : _name (name)
+ , _options (options)
+ {
+ Gtk::Label* table_name = Gtk::manage (new Gtk::Label());
+ table_name->set_markup ("\"" + name + "\" ");
+
+ grid.attach (*table_name, 0, row, 1, 1);
+
+ Gtk::FlowBox* flow_box = nullptr;
+ Gtk::ScrolledWindow* scrolled_window = nullptr;
+ if (options > 2) {
+ // If there are more than 2 option, pack them into a flowbox instead of directly putting them in the grid.
+ // Some fonts might have a table with many options (Bungee Hairline table 'ornm' has 113 entries).
+ flow_box = Gtk::manage (new Gtk::FlowBox());
+ flow_box->set_selection_mode(); // Turn off selection
+ flow_box->set_homogeneous();
+ flow_box->set_max_children_per_line (100); // Override default value
+ flow_box->set_min_children_per_line (10); // Override default value
+
+ // We pack this into a scrollbar... otherwise the minimum height is set to what is required to fit all
+ // flow box children into the flow box when the flow box has minimum width. (Crazy if you ask me!)
+ scrolled_window = Gtk::manage (new Gtk::ScrolledWindow());
+ scrolled_window->set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
+ scrolled_window->add(*flow_box);
+ }
+
+ Gtk::RadioButton::Group group;
+ for (int i = 0; i < options; ++i) {
+
+ // Create radio button and create or add to button group.
+ Gtk::RadioButton* button = Gtk::manage (new Gtk::RadioButton());
+ if (i == 0) {
+ group = button->get_group();
+ } else {
+ button->set_group (group);
+ }
+ button->signal_clicked().connect ( sigc::mem_fun(*parent, &FontVariants::feature_callback) );
+ buttons.push_back (button);
+
+ // Create label.
+ Gtk::Label* label = Gtk::manage (new Gtk::Label());
+
+ // Restrict label width (some fonts have lots of alternatives).
+ label->set_line_wrap( true );
+ label->set_line_wrap_mode( Pango::WRAP_WORD_CHAR );
+ label->set_ellipsize( Pango::ELLIPSIZE_END );
+ label->set_lines(3);
+ label->set_hexpand();
+
+ Glib::ustring markup;
+ markup += "<span font_family='";
+ markup += family;
+ markup += "' font_features='";
+ markup += name;
+ markup += " ";
+ markup += std::to_string (i);
+ markup += "'>";
+ markup += Glib::Markup::escape_text (glyphs.input);
+ markup += "</span>";
+ label->set_markup (markup);
+
+ // Add button and label to widget
+ if (!flow_box) {
+ // Attach directly to grid (keeps things aligned row-to-row).
+ grid.attach (*button, 2*i+1, row, 1, 1);
+ grid.attach (*label, 2*i+2, row, 1, 1);
+ } else {
+ // Pack into FlowBox
+
+ // Pack button and label into a box so they stay together.
+ Gtk::Box* box = Gtk::manage (new Gtk::Box());
+ box->add(*button);
+ box->add(*label);
+
+ flow_box->add(*box);
+ }
+ }
+
+ if (scrolled_window) {
+ grid.attach (*scrolled_window, 1, row, 4, 1);
+ }
+ }
+
+ Glib::ustring
+ get_css()
+ {
+ int i = 0;
+ for (auto b: buttons) {
+ if (b->get_active()) {
+ if (i == 0) {
+ // Features are always off by default (for those handled here).
+ return "";
+ } else if (i == 1) {
+ // Feature without value has implied value of 1.
+ return ("\"" + _name + "\", ");
+ } else {
+ // Feature with value greater than 1 must be explicitly set.
+ return ("\"" + _name + "\" " + std::to_string (i) + ", ");
+ }
+ }
+ ++i;
+ }
+ return "";
+ }
+
+ void
+ set_active(int i)
+ {
+ if (i < buttons.size()) {
+ buttons[i]->set_active();
+ }
+ }
+
+ private:
+ Glib::ustring _name;
+ int _options;
+ std::vector <Gtk::RadioButton*> buttons;
+ };
+
+ FontVariants::FontVariants () :
+ Gtk::VBox (),
+ _ligatures_frame ( Glib::ustring(C_("Font feature", "Ligatures" )) ),
+ _ligatures_common ( Glib::ustring(C_("Font feature", "Common" )) ),
+ _ligatures_discretionary ( Glib::ustring(C_("Font feature", "Discretionary")) ),
+ _ligatures_historical ( Glib::ustring(C_("Font feature", "Historical" )) ),
+ _ligatures_contextual ( Glib::ustring(C_("Font feature", "Contextual" )) ),
+
+ _position_frame ( Glib::ustring(C_("Font feature", "Position" )) ),
+ _position_normal ( Glib::ustring(C_("Font feature", "Normal" )) ),
+ _position_sub ( Glib::ustring(C_("Font feature", "Subscript" )) ),
+ _position_super ( Glib::ustring(C_("Font feature", "Superscript" )) ),
+
+ _caps_frame ( Glib::ustring(C_("Font feature", "Capitals" )) ),
+ _caps_normal ( Glib::ustring(C_("Font feature", "Normal" )) ),
+ _caps_small ( Glib::ustring(C_("Font feature", "Small" )) ),
+ _caps_all_small ( Glib::ustring(C_("Font feature", "All small" )) ),
+ _caps_petite ( Glib::ustring(C_("Font feature", "Petite" )) ),
+ _caps_all_petite ( Glib::ustring(C_("Font feature", "All petite" )) ),
+ _caps_unicase ( Glib::ustring(C_("Font feature", "Unicase" )) ),
+ _caps_titling ( Glib::ustring(C_("Font feature", "Titling" )) ),
+
+ _numeric_frame ( Glib::ustring(C_("Font feature", "Numeric" )) ),
+ _numeric_lining ( Glib::ustring(C_("Font feature", "Lining" )) ),
+ _numeric_old_style ( Glib::ustring(C_("Font feature", "Old Style" )) ),
+ _numeric_default_style ( Glib::ustring(C_("Font feature", "Default Style")) ),
+ _numeric_proportional ( Glib::ustring(C_("Font feature", "Proportional" )) ),
+ _numeric_tabular ( Glib::ustring(C_("Font feature", "Tabular" )) ),
+ _numeric_default_width ( Glib::ustring(C_("Font feature", "Default Width")) ),
+ _numeric_diagonal ( Glib::ustring(C_("Font feature", "Diagonal" )) ),
+ _numeric_stacked ( Glib::ustring(C_("Font feature", "Stacked" )) ),
+ _numeric_default_fractions( Glib::ustring(C_("Font feature", "Default Fractions")) ),
+ _numeric_ordinal ( Glib::ustring(C_("Font feature", "Ordinal" )) ),
+ _numeric_slashed_zero ( Glib::ustring(C_("Font feature", "Slashed Zero" )) ),
+
+ _asian_frame ( Glib::ustring(C_("Font feature", "East Asian" )) ),
+ _asian_default_variant ( Glib::ustring(C_("Font feature", "Default" )) ),
+ _asian_jis78 ( Glib::ustring(C_("Font feature", "JIS78" )) ),
+ _asian_jis83 ( Glib::ustring(C_("Font feature", "JIS83" )) ),
+ _asian_jis90 ( Glib::ustring(C_("Font feature", "JIS90" )) ),
+ _asian_jis04 ( Glib::ustring(C_("Font feature", "JIS04" )) ),
+ _asian_simplified ( Glib::ustring(C_("Font feature", "Simplified" )) ),
+ _asian_traditional ( Glib::ustring(C_("Font feature", "Traditional" )) ),
+ _asian_default_width ( Glib::ustring(C_("Font feature", "Default" )) ),
+ _asian_full_width ( Glib::ustring(C_("Font feature", "Full Width" )) ),
+ _asian_proportional_width ( Glib::ustring(C_("Font feature", "Proportional" )) ),
+ _asian_ruby ( Glib::ustring(C_("Font feature", "Ruby" )) ),
+
+ _feature_frame ( Glib::ustring(C_("Font feature", "Feature Settings")) ),
+ _feature_label ( Glib::ustring(C_("Font feature", "Selection has different Feature Settings!")) ),
+
+ _ligatures_changed( false ),
+ _position_changed( false ),
+ _caps_changed( false ),
+ _numeric_changed( false ),
+ _asian_changed( false )
+
+ {
+
+ set_name ( "FontVariants" );
+
+ // Ligatures --------------------------
+
+ // Add tooltips
+ _ligatures_common.set_tooltip_text(
+ _("Common ligatures. On by default. OpenType tables: 'liga', 'clig'"));
+ _ligatures_discretionary.set_tooltip_text(
+ _("Discretionary ligatures. Off by default. OpenType table: 'dlig'"));
+ _ligatures_historical.set_tooltip_text(
+ _("Historical ligatures. Off by default. OpenType table: 'hlig'"));
+ _ligatures_contextual.set_tooltip_text(
+ _("Contextual forms. On by default. OpenType table: 'calt'"));
+
+ // Add signals
+ _ligatures_common.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) );
+ _ligatures_discretionary.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) );
+ _ligatures_historical.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) );
+ _ligatures_contextual.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) );
+
+ // Restrict label widths (some fonts have lots of ligatures). Must also set ellipsize mode.
+ _ligatures_label_common.set_max_width_chars( 60 );
+ _ligatures_label_discretionary.set_max_width_chars( 60 );
+ _ligatures_label_historical.set_max_width_chars( 60 );
+ _ligatures_label_contextual.set_max_width_chars( 60 );
+
+ _ligatures_label_common.set_ellipsize( Pango::ELLIPSIZE_END );
+ _ligatures_label_discretionary.set_ellipsize( Pango::ELLIPSIZE_END );
+ _ligatures_label_historical.set_ellipsize( Pango::ELLIPSIZE_END );
+ _ligatures_label_contextual.set_ellipsize( Pango::ELLIPSIZE_END );
+
+ _ligatures_label_common.set_lines( 5 );
+ _ligatures_label_discretionary.set_lines( 5 );
+ _ligatures_label_historical.set_lines( 5 );
+ _ligatures_label_contextual.set_lines( 5 );
+
+ // Allow user to select characters. Not useful as this selects the ligatures.
+ // _ligatures_label_common.set_selectable( true );
+ // _ligatures_label_discretionary.set_selectable( true );
+ // _ligatures_label_historical.set_selectable( true );
+ // _ligatures_label_contextual.set_selectable( true );
+
+ // Add to frame
+ _ligatures_grid.attach( _ligatures_common, 0, 0, 1, 1);
+ _ligatures_grid.attach( _ligatures_discretionary, 0, 1, 1, 1);
+ _ligatures_grid.attach( _ligatures_historical, 0, 2, 1, 1);
+ _ligatures_grid.attach( _ligatures_contextual, 0, 3, 1, 1);
+ _ligatures_grid.attach( _ligatures_label_common, 1, 0, 1, 1);
+ _ligatures_grid.attach( _ligatures_label_discretionary, 1, 1, 1, 1);
+ _ligatures_grid.attach( _ligatures_label_historical, 1, 2, 1, 1);
+ _ligatures_grid.attach( _ligatures_label_contextual, 1, 3, 1, 1);
+
+ _ligatures_grid.set_margin_start(15);
+ _ligatures_grid.set_margin_end(15);
+
+ _ligatures_frame.add( _ligatures_grid );
+ pack_start( _ligatures_frame, Gtk::PACK_SHRINK );
+
+ ligatures_init();
+
+ // Position ----------------------------------
+
+ // Add tooltips
+ _position_normal.set_tooltip_text( _("Normal position."));
+ _position_sub.set_tooltip_text( _("Subscript. OpenType table: 'subs'") );
+ _position_super.set_tooltip_text( _("Superscript. OpenType table: 'sups'") );
+
+ // Group buttons
+ Gtk::RadioButton::Group position_group = _position_normal.get_group();
+ _position_sub.set_group(position_group);
+ _position_super.set_group(position_group);
+
+ // Add signals
+ _position_normal.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::position_callback) );
+ _position_sub.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::position_callback) );
+ _position_super.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::position_callback) );
+
+ // Add to frame
+ _position_grid.attach( _position_normal, 0, 0, 1, 1);
+ _position_grid.attach( _position_sub, 1, 0, 1, 1);
+ _position_grid.attach( _position_super, 2, 0, 1, 1);
+
+ _position_grid.set_margin_start(15);
+ _position_grid.set_margin_end(15);
+
+ _position_frame.add( _position_grid );
+ pack_start( _position_frame, Gtk::PACK_SHRINK );
+
+ position_init();
+
+ // Caps ----------------------------------
+
+ // Add tooltips
+ _caps_normal.set_tooltip_text( _("Normal capitalization."));
+ _caps_small.set_tooltip_text( _("Small-caps (lowercase). OpenType table: 'smcp'"));
+ _caps_all_small.set_tooltip_text( _("All small-caps (uppercase and lowercase). OpenType tables: 'c2sc' and 'smcp'"));
+ _caps_petite.set_tooltip_text( _("Petite-caps (lowercase). OpenType table: 'pcap'"));
+ _caps_all_petite.set_tooltip_text( _("All petite-caps (uppercase and lowercase). OpenType tables: 'c2sc' and 'pcap'"));
+ _caps_unicase.set_tooltip_text( _("Unicase (small caps for uppercase, normal for lowercase). OpenType table: 'unic'"));
+ _caps_titling.set_tooltip_text( _("Titling caps (lighter-weight uppercase for use in titles). OpenType table: 'titl'"));
+
+ // Group buttons
+ Gtk::RadioButton::Group caps_group = _caps_normal.get_group();
+ _caps_small.set_group(caps_group);
+ _caps_all_small.set_group(caps_group);
+ _caps_petite.set_group(caps_group);
+ _caps_all_petite.set_group(caps_group);
+ _caps_unicase.set_group(caps_group);
+ _caps_titling.set_group(caps_group);
+
+ // Add signals
+ _caps_normal.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
+ _caps_small.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
+ _caps_all_small.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
+ _caps_petite.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
+ _caps_all_petite.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
+ _caps_unicase.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
+ _caps_titling.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) );
+
+ // Add to frame
+ _caps_grid.attach( _caps_normal, 0, 0, 1, 1);
+ _caps_grid.attach( _caps_unicase, 1, 0, 1, 1);
+ _caps_grid.attach( _caps_titling, 2, 0, 1, 1);
+ _caps_grid.attach( _caps_small, 0, 1, 1, 1);
+ _caps_grid.attach( _caps_all_small, 1, 1, 1, 1);
+ _caps_grid.attach( _caps_petite, 2, 1, 1, 1);
+ _caps_grid.attach( _caps_all_petite, 3, 1, 1, 1);
+
+ _caps_grid.set_margin_start(15);
+ _caps_grid.set_margin_end(15);
+
+ _caps_frame.add( _caps_grid );
+ pack_start( _caps_frame, Gtk::PACK_SHRINK );
+
+ caps_init();
+
+ // Numeric ------------------------------
+
+ // Add tooltips
+ _numeric_default_style.set_tooltip_text( _("Normal style."));
+ _numeric_lining.set_tooltip_text( _("Lining numerals. OpenType table: 'lnum'"));
+ _numeric_old_style.set_tooltip_text( _("Old style numerals. OpenType table: 'onum'"));
+ _numeric_default_width.set_tooltip_text( _("Normal widths."));
+ _numeric_proportional.set_tooltip_text( _("Proportional width numerals. OpenType table: 'pnum'"));
+ _numeric_tabular.set_tooltip_text( _("Same width numerals. OpenType table: 'tnum'"));
+ _numeric_default_fractions.set_tooltip_text( _("Normal fractions."));
+ _numeric_diagonal.set_tooltip_text( _("Diagonal fractions. OpenType table: 'frac'"));
+ _numeric_stacked.set_tooltip_text( _("Stacked fractions. OpenType table: 'afrc'"));
+ _numeric_ordinal.set_tooltip_text( _("Ordinals (raised 'th', etc.). OpenType table: 'ordn'"));
+ _numeric_slashed_zero.set_tooltip_text( _("Slashed zeros. OpenType table: 'zero'"));
+
+ // Group buttons
+ Gtk::RadioButton::Group style_group = _numeric_default_style.get_group();
+ _numeric_lining.set_group(style_group);
+ _numeric_old_style.set_group(style_group);
+
+ Gtk::RadioButton::Group width_group = _numeric_default_width.get_group();
+ _numeric_proportional.set_group(width_group);
+ _numeric_tabular.set_group(width_group);
+
+ Gtk::RadioButton::Group fraction_group = _numeric_default_fractions.get_group();
+ _numeric_diagonal.set_group(fraction_group);
+ _numeric_stacked.set_group(fraction_group);
+
+ // Add signals
+ _numeric_default_style.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
+ _numeric_lining.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
+ _numeric_old_style.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
+ _numeric_default_width.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
+ _numeric_proportional.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
+ _numeric_tabular.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
+ _numeric_default_fractions.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
+ _numeric_diagonal.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
+ _numeric_stacked.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
+ _numeric_ordinal.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
+ _numeric_slashed_zero.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) );
+
+ // Add to frame
+ _numeric_grid.attach (_numeric_default_style, 0, 0, 1, 1);
+ _numeric_grid.attach (_numeric_lining, 1, 0, 1, 1);
+ _numeric_grid.attach (_numeric_lining_label, 2, 0, 1, 1);
+ _numeric_grid.attach (_numeric_old_style, 3, 0, 1, 1);
+ _numeric_grid.attach (_numeric_old_style_label, 4, 0, 1, 1);
+
+ _numeric_grid.attach (_numeric_default_width, 0, 1, 1, 1);
+ _numeric_grid.attach (_numeric_proportional, 1, 1, 1, 1);
+ _numeric_grid.attach (_numeric_proportional_label, 2, 1, 1, 1);
+ _numeric_grid.attach (_numeric_tabular, 3, 1, 1, 1);
+ _numeric_grid.attach (_numeric_tabular_label, 4, 1, 1, 1);
+
+ _numeric_grid.attach (_numeric_default_fractions, 0, 2, 1, 1);
+ _numeric_grid.attach (_numeric_diagonal, 1, 2, 1, 1);
+ _numeric_grid.attach (_numeric_diagonal_label, 2, 2, 1, 1);
+ _numeric_grid.attach (_numeric_stacked, 3, 2, 1, 1);
+ _numeric_grid.attach (_numeric_stacked_label, 4, 2, 1, 1);
+
+ _numeric_grid.attach (_numeric_ordinal, 0, 3, 1, 1);
+ _numeric_grid.attach (_numeric_ordinal_label, 1, 3, 4, 1);
+
+ _numeric_grid.attach (_numeric_slashed_zero, 0, 4, 1, 1);
+ _numeric_grid.attach (_numeric_slashed_zero_label, 1, 4, 1, 1);
+
+ _numeric_grid.set_margin_start(15);
+ _numeric_grid.set_margin_end(15);
+
+ _numeric_frame.add( _numeric_grid );
+ pack_start( _numeric_frame, Gtk::PACK_SHRINK );
+
+ // East Asian
+
+ // Add tooltips
+ _asian_default_variant.set_tooltip_text ( _("Default variant."));
+ _asian_jis78.set_tooltip_text( _("JIS78 forms. OpenType table: 'jp78'."));
+ _asian_jis83.set_tooltip_text( _("JIS83 forms. OpenType table: 'jp83'."));
+ _asian_jis90.set_tooltip_text( _("JIS90 forms. OpenType table: 'jp90'."));
+ _asian_jis04.set_tooltip_text( _("JIS2004 forms. OpenType table: 'jp04'."));
+ _asian_simplified.set_tooltip_text( _("Simplified forms. OpenType table: 'smpl'."));
+ _asian_traditional.set_tooltip_text( _("Traditional forms. OpenType table: 'trad'."));
+ _asian_default_width.set_tooltip_text ( _("Default width."));
+ _asian_full_width.set_tooltip_text( _("Full width variants. OpenType table: 'fwid'."));
+ _asian_proportional_width.set_tooltip_text(_("Proportional width variants. OpenType table: 'pwid'."));
+ _asian_ruby.set_tooltip_text( _("Ruby variants. OpenType table: 'ruby'."));
+
+ // Add signals
+ _asian_default_variant.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) );
+ _asian_jis78.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) );
+ _asian_jis83.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) );
+ _asian_jis90.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) );
+ _asian_jis04.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) );
+ _asian_simplified.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) );
+ _asian_traditional.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) );
+ _asian_default_width.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) );
+ _asian_full_width.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) );
+ _asian_proportional_width.signal_clicked().connect (sigc::mem_fun(*this, &FontVariants::asian_callback) );
+ _asian_ruby.signal_clicked().connect( sigc::mem_fun(*this, &FontVariants::asian_callback) );
+
+ // Add to frame
+ _asian_grid.attach (_asian_default_variant, 0, 0, 1, 1);
+ _asian_grid.attach (_asian_jis78, 1, 0, 1, 1);
+ _asian_grid.attach (_asian_jis83, 2, 0, 1, 1);
+ _asian_grid.attach (_asian_jis90, 1, 1, 1, 1);
+ _asian_grid.attach (_asian_jis04, 2, 1, 1, 1);
+ _asian_grid.attach (_asian_simplified, 1, 2, 1, 1);
+ _asian_grid.attach (_asian_traditional, 2, 2, 1, 1);
+ _asian_grid.attach (_asian_default_width, 0, 3, 1, 1);
+ _asian_grid.attach (_asian_full_width, 1, 3, 1, 1);
+ _asian_grid.attach (_asian_proportional_width, 2, 3, 1, 1);
+ _asian_grid.attach (_asian_ruby, 0, 4, 1, 1);
+
+ _asian_grid.set_margin_start(15);
+ _asian_grid.set_margin_end(15);
+
+ _asian_frame.add( _asian_grid );
+ pack_start( _asian_frame, Gtk::PACK_SHRINK );
+
+ // Group Buttons
+ Gtk::RadioButton::Group asian_variant_group = _asian_default_variant.get_group();
+ _asian_jis78.set_group(asian_variant_group);
+ _asian_jis83.set_group(asian_variant_group);
+ _asian_jis90.set_group(asian_variant_group);
+ _asian_jis04.set_group(asian_variant_group);
+ _asian_simplified.set_group(asian_variant_group);
+ _asian_traditional.set_group(asian_variant_group);
+
+ Gtk::RadioButton::Group asian_width_group = _asian_default_width.get_group();
+ _asian_full_width.set_group (asian_width_group);
+ _asian_proportional_width.set_group (asian_width_group);
+
+ // Feature settings ---------------------
+
+ // Add tooltips
+ _feature_entry.set_tooltip_text( _("Feature settings in CSS form (e.g. \"wxyz\" or \"wxyz\" 3)."));
+
+ _feature_substitutions.set_justify( Gtk::JUSTIFY_LEFT );
+ _feature_substitutions.set_line_wrap( true );
+ _feature_substitutions.set_line_wrap_mode( Pango::WRAP_WORD_CHAR );
+
+ _feature_list.set_justify( Gtk::JUSTIFY_LEFT );
+ _feature_list.set_line_wrap( true );
+
+ // Add to frame
+ _feature_vbox.pack_start( _feature_grid, Gtk::PACK_SHRINK );
+ _feature_vbox.pack_start( _feature_entry, Gtk::PACK_SHRINK );
+ _feature_vbox.pack_start( _feature_label, Gtk::PACK_SHRINK );
+ _feature_vbox.pack_start( _feature_substitutions, Gtk::PACK_SHRINK );
+ _feature_vbox.pack_start( _feature_list, Gtk::PACK_SHRINK );
+
+ _feature_vbox.set_margin_start(15);
+ _feature_vbox.set_margin_end(15);
+
+ _feature_frame.add( _feature_vbox );
+ pack_start( _feature_frame, Gtk::PACK_SHRINK );
+
+ // Add signals
+ //_feature_entry.signal_key_press_event().connect ( sigc::mem_fun(*this, &FontVariants::feature_callback) );
+ _feature_entry.signal_changed().connect( sigc::mem_fun(*this, &FontVariants::feature_callback) );
+
+ show_all_children();
+
+ }
+
+ void
+ FontVariants::ligatures_init() {
+ // std::cout << "FontVariants::ligatures_init()" << std::endl;
+ }
+
+ void
+ FontVariants::ligatures_callback() {
+ // std::cout << "FontVariants::ligatures_callback()" << std::endl;
+ _ligatures_changed = true;
+ _changed_signal.emit();
+ }
+
+ void
+ FontVariants::position_init() {
+ // std::cout << "FontVariants::position_init()" << std::endl;
+ }
+
+ void
+ FontVariants::position_callback() {
+ // std::cout << "FontVariants::position_callback()" << std::endl;
+ _position_changed = true;
+ _changed_signal.emit();
+ }
+
+ void
+ FontVariants::caps_init() {
+ // std::cout << "FontVariants::caps_init()" << std::endl;
+ }
+
+ void
+ FontVariants::caps_callback() {
+ // std::cout << "FontVariants::caps_callback()" << std::endl;
+ _caps_changed = true;
+ _changed_signal.emit();
+ }
+
+ void
+ FontVariants::numeric_init() {
+ // std::cout << "FontVariants::numeric_init()" << std::endl;
+ }
+
+ void
+ FontVariants::numeric_callback() {
+ // std::cout << "FontVariants::numeric_callback()" << std::endl;
+ _numeric_changed = true;
+ _changed_signal.emit();
+ }
+
+ void
+ FontVariants::asian_init() {
+ // std::cout << "FontVariants::asian_init()" << std::endl;
+ }
+
+ void
+ FontVariants::asian_callback() {
+ // std::cout << "FontVariants::asian_callback()" << std::endl;
+ _asian_changed = true;
+ _changed_signal.emit();
+ }
+
+ void
+ FontVariants::feature_init() {
+ // std::cout << "FontVariants::feature_init()" << std::endl;
+ }
+
+ void
+ FontVariants::feature_callback() {
+ // std::cout << "FontVariants::feature_callback()" << std::endl;
+ _feature_changed = true;
+ _changed_signal.emit();
+ }
+
+ // Update GUI based on query.
+ void
+ FontVariants::update( SPStyle const *query, bool different_features, Glib::ustring& font_spec ) {
+
+ update_opentype( font_spec );
+
+ _ligatures_all = query->font_variant_ligatures.computed;
+ _ligatures_mix = query->font_variant_ligatures.value;
+
+ _ligatures_common.set_active( _ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_COMMON );
+ _ligatures_discretionary.set_active(_ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_DISCRETIONARY );
+ _ligatures_historical.set_active( _ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_HISTORICAL );
+ _ligatures_contextual.set_active( _ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_CONTEXTUAL );
+
+ _ligatures_common.set_inconsistent( _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_COMMON );
+ _ligatures_discretionary.set_inconsistent( _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_DISCRETIONARY );
+ _ligatures_historical.set_inconsistent( _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_HISTORICAL );
+ _ligatures_contextual.set_inconsistent( _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_CONTEXTUAL );
+
+ _position_all = query->font_variant_position.computed;
+ _position_mix = query->font_variant_position.value;
+
+ _position_normal.set_active( _position_all & SP_CSS_FONT_VARIANT_POSITION_NORMAL );
+ _position_sub.set_active( _position_all & SP_CSS_FONT_VARIANT_POSITION_SUB );
+ _position_super.set_active( _position_all & SP_CSS_FONT_VARIANT_POSITION_SUPER );
+
+ _position_normal.set_inconsistent( _position_mix & SP_CSS_FONT_VARIANT_POSITION_NORMAL );
+ _position_sub.set_inconsistent( _position_mix & SP_CSS_FONT_VARIANT_POSITION_SUB );
+ _position_super.set_inconsistent( _position_mix & SP_CSS_FONT_VARIANT_POSITION_SUPER );
+
+ _caps_all = query->font_variant_caps.computed;
+ _caps_mix = query->font_variant_caps.value;
+
+ _caps_normal.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_NORMAL );
+ _caps_small.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_SMALL );
+ _caps_all_small.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL );
+ _caps_petite.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_PETITE );
+ _caps_all_petite.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE );
+ _caps_unicase.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_UNICASE );
+ _caps_titling.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_TITLING );
+
+ _caps_normal.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_NORMAL );
+ _caps_small.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_SMALL );
+ _caps_all_small.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL );
+ _caps_petite.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_PETITE );
+ _caps_all_petite.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE );
+ _caps_unicase.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_UNICASE );
+ _caps_titling.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_TITLING );
+
+ _numeric_all = query->font_variant_numeric.computed;
+ _numeric_mix = query->font_variant_numeric.value;
+
+ if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_LINING_NUMS) {
+ _numeric_lining.set_active();
+ } else if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_OLDSTYLE_NUMS) {
+ _numeric_old_style.set_active();
+ } else {
+ _numeric_default_style.set_active();
+ }
+
+ if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_PROPORTIONAL_NUMS) {
+ _numeric_proportional.set_active();
+ } else if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_TABULAR_NUMS) {
+ _numeric_tabular.set_active();
+ } else {
+ _numeric_default_width.set_active();
+ }
+
+ if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS) {
+ _numeric_diagonal.set_active();
+ } else if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS) {
+ _numeric_stacked.set_active();
+ } else {
+ _numeric_default_fractions.set_active();
+ }
+
+ _numeric_ordinal.set_active( _numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_ORDINAL );
+ _numeric_slashed_zero.set_active( _numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_SLASHED_ZERO );
+
+
+ _numeric_lining.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_LINING_NUMS );
+ _numeric_old_style.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_OLDSTYLE_NUMS );
+ _numeric_proportional.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_PROPORTIONAL_NUMS );
+ _numeric_tabular.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_TABULAR_NUMS );
+ _numeric_diagonal.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS );
+ _numeric_stacked.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS );
+ _numeric_ordinal.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_ORDINAL );
+ _numeric_slashed_zero.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_SLASHED_ZERO );
+
+ _asian_all = query->font_variant_east_asian.computed;
+ _asian_mix = query->font_variant_east_asian.value;
+
+ if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78) {
+ _asian_jis78.set_active();
+ } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83) {
+ _asian_jis83.set_active();
+ } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90) {
+ _asian_jis90.set_active();
+ } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04) {
+ _asian_jis04.set_active();
+ } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED) {
+ _asian_simplified.set_active();
+ } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL) {
+ _asian_traditional.set_active();
+ } else {
+ _asian_default_variant.set_active();
+ }
+
+ if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH) {
+ _asian_full_width.set_active();
+ } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH) {
+ _asian_proportional_width.set_active();
+ } else {
+ _asian_default_width.set_active();
+ }
+
+ _asian_ruby.set_active ( _asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_RUBY );
+
+ _asian_jis78.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78);
+ _asian_jis83.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83);
+ _asian_jis90.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90);
+ _asian_jis04.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04);
+ _asian_simplified.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED);
+ _asian_traditional.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL);
+ _asian_full_width.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH);
+ _asian_proportional_width.set_inconsistent(_asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH);
+ _asian_ruby.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_RUBY);
+
+ // Fix me: Should match a space if second part matches. ---,
+ // : Add boundary to 'on' and 'off'. v
+ Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("\"(\\w{4})\"\\s*([0-9]+|on|off|)");
+ Glib::MatchInfo matchInfo;
+ std::string setting;
+
+ // Set feature radiobutton (if it exists) or add to _feature_entry string.
+ char const *val = query->font_feature_settings.value();
+ if (val) {
+
+ std::vector<Glib::ustring> tokens =
+ Glib::Regex::split_simple("\\s*,\\s*", val);
+
+ for (auto token: tokens) {
+ regex->match(token, matchInfo);
+ if (matchInfo.matches()) {
+ Glib::ustring table = matchInfo.fetch(1);
+ Glib::ustring value = matchInfo.fetch(2);
+
+ if (_features.find(table) != _features.end()) {
+ int v = 0;
+ if (value == "0" || value == "off") v = 0;
+ else if (value == "1" || value == "on" || value.empty() ) v = 1;
+ else v = std::stoi(value);
+ _features[table]->set_active(v);
+ } else {
+ setting += token + ", ";
+ }
+ }
+ }
+ }
+
+ // Remove final ", "
+ if (setting.length() > 1) {
+ setting.pop_back();
+ setting.pop_back();
+ }
+
+ // Tables without radiobuttons.
+ _feature_entry.set_text( setting );
+
+ if( different_features ) {
+ _feature_label.show();
+ } else {
+ _feature_label.hide();
+ }
+ }
+
+ // Update GUI based on OpenType tables of selected font (which may be changed in font selector tab).
+ void
+ FontVariants::update_opentype (Glib::ustring& font_spec) {
+
+ // Disable/Enable based on available OpenType tables.
+ font_instance* res = font_factory::Default()->FaceFromFontSpecification( font_spec.c_str() );
+ if( res ) {
+
+ std::map<Glib::ustring, OTSubstitution>::iterator it;
+
+ if((it = res->openTypeTables.find("liga"))!= res->openTypeTables.end() ||
+ (it = res->openTypeTables.find("clig"))!= res->openTypeTables.end()) {
+ _ligatures_common.set_sensitive();
+ } else {
+ _ligatures_common.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("dlig"))!= res->openTypeTables.end()) {
+ _ligatures_discretionary.set_sensitive();
+ } else {
+ _ligatures_discretionary.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("hlig"))!= res->openTypeTables.end()) {
+ _ligatures_historical.set_sensitive();
+ } else {
+ _ligatures_historical.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("calt"))!= res->openTypeTables.end()) {
+ _ligatures_contextual.set_sensitive();
+ } else {
+ _ligatures_contextual.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("subs"))!= res->openTypeTables.end()) {
+ _position_sub.set_sensitive();
+ } else {
+ _position_sub.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("sups"))!= res->openTypeTables.end()) {
+ _position_super.set_sensitive();
+ } else {
+ _position_super.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("smcp"))!= res->openTypeTables.end()) {
+ _caps_small.set_sensitive();
+ } else {
+ _caps_small.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("c2sc"))!= res->openTypeTables.end() &&
+ (it = res->openTypeTables.find("smcp"))!= res->openTypeTables.end()) {
+ _caps_all_small.set_sensitive();
+ } else {
+ _caps_all_small.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("pcap"))!= res->openTypeTables.end()) {
+ _caps_petite.set_sensitive();
+ } else {
+ _caps_petite.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("c2sc"))!= res->openTypeTables.end() &&
+ (it = res->openTypeTables.find("pcap"))!= res->openTypeTables.end()) {
+ _caps_all_petite.set_sensitive();
+ } else {
+ _caps_all_petite.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("unic"))!= res->openTypeTables.end()) {
+ _caps_unicase.set_sensitive();
+ } else {
+ _caps_unicase.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("titl"))!= res->openTypeTables.end()) {
+ _caps_titling.set_sensitive();
+ } else {
+ _caps_titling.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("lnum"))!= res->openTypeTables.end()) {
+ _numeric_lining.set_sensitive();
+ } else {
+ _numeric_lining.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("onum"))!= res->openTypeTables.end()) {
+ _numeric_old_style.set_sensitive();
+ } else {
+ _numeric_old_style.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("pnum"))!= res->openTypeTables.end()) {
+ _numeric_proportional.set_sensitive();
+ } else {
+ _numeric_proportional.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("tnum"))!= res->openTypeTables.end()) {
+ _numeric_tabular.set_sensitive();
+ } else {
+ _numeric_tabular.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("frac"))!= res->openTypeTables.end()) {
+ _numeric_diagonal.set_sensitive();
+ } else {
+ _numeric_diagonal.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("afrac"))!= res->openTypeTables.end()) {
+ _numeric_stacked.set_sensitive();
+ } else {
+ _numeric_stacked.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("ordn"))!= res->openTypeTables.end()) {
+ _numeric_ordinal.set_sensitive();
+ } else {
+ _numeric_ordinal.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("zero"))!= res->openTypeTables.end()) {
+ _numeric_slashed_zero.set_sensitive();
+ } else {
+ _numeric_slashed_zero.set_sensitive( false );
+ }
+
+ // East-Asian
+ if((it = res->openTypeTables.find("jp78"))!= res->openTypeTables.end()) {
+ _asian_jis78.set_sensitive();
+ } else {
+ _asian_jis78.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("jp83"))!= res->openTypeTables.end()) {
+ _asian_jis83.set_sensitive();
+ } else {
+ _asian_jis83.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("jp90"))!= res->openTypeTables.end()) {
+ _asian_jis90.set_sensitive();
+ } else {
+ _asian_jis90.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("jp04"))!= res->openTypeTables.end()) {
+ _asian_jis04.set_sensitive();
+ } else {
+ _asian_jis04.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("smpl"))!= res->openTypeTables.end()) {
+ _asian_simplified.set_sensitive();
+ } else {
+ _asian_simplified.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("trad"))!= res->openTypeTables.end()) {
+ _asian_traditional.set_sensitive();
+ } else {
+ _asian_traditional.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("fwid"))!= res->openTypeTables.end()) {
+ _asian_full_width.set_sensitive();
+ } else {
+ _asian_full_width.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("pwid"))!= res->openTypeTables.end()) {
+ _asian_proportional_width.set_sensitive();
+ } else {
+ _asian_proportional_width.set_sensitive( false );
+ }
+
+ if((it = res->openTypeTables.find("ruby"))!= res->openTypeTables.end()) {
+ _asian_ruby.set_sensitive();
+ } else {
+ _asian_ruby.set_sensitive( false );
+ }
+
+ // List available ligatures
+ Glib::ustring markup_liga;
+ Glib::ustring markup_dlig;
+ Glib::ustring markup_hlig;
+ Glib::ustring markup_calt;
+
+ for (auto table: res->openTypeTables) {
+
+ if (table.first == "liga" ||
+ table.first == "clig" ||
+ table.first == "dlig" ||
+ table.first == "hgli" ||
+ table.first == "calt") {
+
+ Glib::ustring markup;
+ markup += "<span font_family='";
+ markup += sp_font_description_get_family(res->descr);
+ markup += "'>";
+ markup += Glib::Markup::escape_text(table.second.output);
+ markup += "</span>";
+
+ if (table.first == "liga") markup_liga += markup;
+ if (table.first == "clig") markup_liga += markup;
+ if (table.first == "dlig") markup_dlig += markup;
+ if (table.first == "hlig") markup_hlig += markup;
+ if (table.first == "calt") markup_calt += markup;
+ }
+ }
+
+ _ligatures_label_common.set_markup ( markup_liga.c_str() );
+ _ligatures_label_discretionary.set_markup ( markup_dlig.c_str() );
+ _ligatures_label_historical.set_markup ( markup_hlig.c_str() );
+ _ligatures_label_contextual.set_markup ( markup_calt.c_str() );
+
+ // List available numeric variants
+ Glib::ustring markup_lnum;
+ Glib::ustring markup_onum;
+ Glib::ustring markup_pnum;
+ Glib::ustring markup_tnum;
+ Glib::ustring markup_frac;
+ Glib::ustring markup_afrc;
+ Glib::ustring markup_ordn;
+ Glib::ustring markup_zero;
+
+ for (auto table: res->openTypeTables) {
+
+ Glib::ustring markup;
+ markup += "<span font_family='";
+ markup += sp_font_description_get_family(res->descr);
+ markup += "' font_features='";
+ markup += table.first;
+ markup += "'>";
+ if (table.first == "lnum" ||
+ table.first == "onum" ||
+ table.first == "pnum" ||
+ table.first == "tnum") markup += "0123456789";
+ if (table.first == "zero") markup += "0";
+ if (table.first == "ordn") markup += "[" + table.second.before + "]" + table.second.output;
+ if (table.first == "frac" ||
+ table.first == "afrc" ) markup += "1/2 2/3 3/4 4/5 5/6"; // Can we do better?
+ markup += "</span>";
+
+ if (table.first == "lnum") markup_lnum += markup;
+ if (table.first == "onum") markup_onum += markup;
+ if (table.first == "pnum") markup_pnum += markup;
+ if (table.first == "tnum") markup_tnum += markup;
+ if (table.first == "frac") markup_frac += markup;
+ if (table.first == "afrc") markup_afrc += markup;
+ if (table.first == "ordn") markup_ordn += markup;
+ if (table.first == "zero") markup_zero += markup;
+ }
+
+ _numeric_lining_label.set_markup ( markup_lnum.c_str() );
+ _numeric_old_style_label.set_markup ( markup_onum.c_str() );
+ _numeric_proportional_label.set_markup ( markup_pnum.c_str() );
+ _numeric_tabular_label.set_markup ( markup_tnum.c_str() );
+ _numeric_diagonal_label.set_markup ( markup_frac.c_str() );
+ _numeric_stacked_label.set_markup ( markup_afrc.c_str() );
+ _numeric_ordinal_label.set_markup ( markup_ordn.c_str() );
+ _numeric_slashed_zero_label.set_markup ( markup_zero.c_str() );
+
+ // Make list of tables not handled above.
+ std::map<Glib::ustring, OTSubstitution> table_copy = res->openTypeTables;
+ if( (it = table_copy.find("liga")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("clig")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("dlig")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("hlig")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("calt")) != table_copy.end() ) table_copy.erase( it );
+
+ if( (it = table_copy.find("subs")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("sups")) != table_copy.end() ) table_copy.erase( it );
+
+ if( (it = table_copy.find("smcp")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("c2sc")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("pcap")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("c2pc")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("unic")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("titl")) != table_copy.end() ) table_copy.erase( it );
+
+ if( (it = table_copy.find("lnum")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("onum")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("pnum")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("tnum")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("frac")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("afrc")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("ordn")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("zero")) != table_copy.end() ) table_copy.erase( it );
+
+ if( (it = table_copy.find("jp78")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("jp83")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("jp90")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("jp04")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("smpl")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("trad")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("fwid")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("pwid")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("ruby")) != table_copy.end() ) table_copy.erase( it );
+
+ // An incomplete list of tables that should not be exposed to the user:
+ if( (it = table_copy.find("abvf")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("abvs")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("akhn")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("blwf")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("blws")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("ccmp")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("cjct")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("dnom")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("dtls")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("fina")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("half")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("haln")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("init")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("isol")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("locl")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("medi")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("nukt")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("numr")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("pref")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("pres")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("pstf")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("psts")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("rlig")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("rkrf")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("rphf")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("rtlm")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("ssty")) != table_copy.end() ) table_copy.erase( it );
+ if( (it = table_copy.find("vatu")) != table_copy.end() ) table_copy.erase( it );
+
+ // Clear out old features
+ auto children = _feature_grid.get_children();
+ for (auto child: children) {
+ _feature_grid.remove (*child);
+ }
+ _features.clear();
+
+ std::string markup;
+ int grid_row = 0;
+
+ // GSUB lookup type 1 (1 to 1 mapping).
+ for (auto table: res->openTypeTables) {
+ if (table.first == "case" ||
+ table.first == "hist" ||
+ (table.first[0] == 's' && table.first[1] == 's' && !(table.first[2] == 't'))) {
+
+ if( (it = table_copy.find(table.first)) != table_copy.end() ) table_copy.erase( it );
+
+ _features[table.first] = new Feature (table.first, table.second, 2,
+ sp_font_description_get_family(res->descr),
+ _feature_grid, grid_row, this);
+ grid_row++;
+ }
+ }
+
+ // GSUB lookup type 3 (1 to many mapping). Optionally type 1.
+ for (auto table: res->openTypeTables) {
+ if (table.first == "salt" ||
+ table.first == "swsh" ||
+ table.first == "cwsh" ||
+ table.first == "ornm" ||
+ table.first == "nalt" ||
+ (table.first[0] == 'c' && table.first[1] == 'v')) {
+
+ if (table.second.input.length() == 0) {
+ // This can happen if a table is not in the 'DFLT' script and 'dflt' language.
+ // We should be using the 'lang' attribute to find the correct tables.
+ // std::cerr << "FontVariants::open_type_update: "
+ // << table.first << " has no entries!" << std::endl;
+ continue;
+ }
+
+ if( (it = table_copy.find(table.first)) != table_copy.end() ) table_copy.erase( it );
+
+ // Our lame attempt at determining number of alternative glyphs for one glyph:
+ int number = table.second.output.length() / table.second.input.length();
+ if (number < 1) {
+ number = 1; // Must have at least on/off, see comment above about 'lang' attribute.
+ // std::cout << table.first << " "
+ // << table.second.output.length() << "/"
+ // << table.second.input.length() << "="
+ // << number << std::endl;
+ }
+
+ _features[table.first] = new Feature (table.first, table.second, number+1,
+ sp_font_description_get_family(res->descr),
+ _feature_grid, grid_row, this);
+ grid_row++;
+ }
+ }
+
+ _feature_grid.show_all();
+
+ _feature_substitutions.set_markup ( markup.c_str() );
+
+ std::string ott_list = "OpenType tables not included above: ";
+ for(it = table_copy.begin(); it != table_copy.end(); ++it) {
+ ott_list += it->first;
+ ott_list += ", ";
+ }
+
+ if (table_copy.size() > 0) {
+ ott_list.pop_back();
+ ott_list.pop_back();
+ _feature_list.set_text( ott_list.c_str() );
+ } else {
+ _feature_list.set_text( "" );
+ }
+
+ } else {
+ std::cerr << "FontVariants::update(): Couldn't find font_instance for: "
+ << font_spec << std::endl;
+ }
+
+ _ligatures_changed = false;
+ _position_changed = false;
+ _caps_changed = false;
+ _numeric_changed = false;
+ _feature_changed = false;
+ }
+
+ void
+ FontVariants::fill_css( SPCSSAttr *css ) {
+
+ // Ligatures
+ bool common = _ligatures_common.get_active();
+ bool discretionary = _ligatures_discretionary.get_active();
+ bool historical = _ligatures_historical.get_active();
+ bool contextual = _ligatures_contextual.get_active();
+
+ if( !common && !discretionary && !historical && !contextual ) {
+ sp_repr_css_set_property(css, "font-variant-ligatures", "none" );
+ } else if ( common && !discretionary && !historical && contextual ) {
+ sp_repr_css_set_property(css, "font-variant-ligatures", "normal" );
+ } else {
+ Glib::ustring css_string;
+ if ( !common )
+ css_string += "no-common-ligatures ";
+ if ( discretionary )
+ css_string += "discretionary-ligatures ";
+ if ( historical )
+ css_string += "historical-ligatures ";
+ if ( !contextual )
+ css_string += "no-contextual ";
+ sp_repr_css_set_property(css, "font-variant-ligatures", css_string.c_str() );
+ }
+
+ // Position
+ {
+ unsigned position_new = SP_CSS_FONT_VARIANT_POSITION_NORMAL;
+ Glib::ustring css_string;
+ if( _position_normal.get_active() ) {
+ css_string = "normal";
+ } else if( _position_sub.get_active() ) {
+ css_string = "sub";
+ position_new = SP_CSS_FONT_VARIANT_POSITION_SUB;
+ } else if( _position_super.get_active() ) {
+ css_string = "super";
+ position_new = SP_CSS_FONT_VARIANT_POSITION_SUPER;
+ }
+
+ // 'if' may not be necessary... need to test.
+ if( (_position_all != position_new) || ((_position_mix != 0) && _position_changed) ) {
+ sp_repr_css_set_property(css, "font-variant-position", css_string.c_str() );
+ }
+ }
+
+ // Caps
+ {
+ //unsigned caps_new;
+ Glib::ustring css_string;
+ if( _caps_normal.get_active() ) {
+ css_string = "normal";
+ // caps_new = SP_CSS_FONT_VARIANT_CAPS_NORMAL;
+ } else if( _caps_small.get_active() ) {
+ css_string = "small-caps";
+ // caps_new = SP_CSS_FONT_VARIANT_CAPS_SMALL;
+ } else if( _caps_all_small.get_active() ) {
+ css_string = "all-small-caps";
+ // caps_new = SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL;
+ } else if( _caps_petite.get_active() ) {
+ css_string = "petite";
+ // caps_new = SP_CSS_FONT_VARIANT_CAPS_PETITE;
+ } else if( _caps_all_petite.get_active() ) {
+ css_string = "all-petite";
+ // caps_new = SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE;
+ } else if( _caps_unicase.get_active() ) {
+ css_string = "unicase";
+ // caps_new = SP_CSS_FONT_VARIANT_CAPS_UNICASE;
+ } else if( _caps_titling.get_active() ) {
+ css_string = "titling";
+ // caps_new = SP_CSS_FONT_VARIANT_CAPS_TITLING;
+ //} else {
+ // caps_new = SP_CSS_FONT_VARIANT_CAPS_NORMAL;
+ }
+
+ // May not be necessary... need to test.
+ //if( (_caps_all != caps_new) || ((_caps_mix != 0) && _caps_changed) ) {
+ sp_repr_css_set_property(css, "font-variant-caps", css_string.c_str() );
+ //}
+ }
+
+ // Numeric
+ bool default_style = _numeric_default_style.get_active();
+ bool lining = _numeric_lining.get_active();
+ bool old_style = _numeric_old_style.get_active();
+
+ bool default_width = _numeric_default_width.get_active();
+ bool proportional = _numeric_proportional.get_active();
+ bool tabular = _numeric_tabular.get_active();
+
+ bool default_fractions = _numeric_default_fractions.get_active();
+ bool diagonal = _numeric_diagonal.get_active();
+ bool stacked = _numeric_stacked.get_active();
+
+ bool ordinal = _numeric_ordinal.get_active();
+ bool slashed_zero = _numeric_slashed_zero.get_active();
+
+ if (default_style & default_width & default_fractions & !ordinal & !slashed_zero) {
+ sp_repr_css_set_property(css, "font-variant-numeric", "normal");
+ } else {
+ Glib::ustring css_string;
+ if ( lining )
+ css_string += "lining-nums ";
+ if ( old_style )
+ css_string += "oldstyle-nums ";
+ if ( proportional )
+ css_string += "proportional-nums ";
+ if ( tabular )
+ css_string += "tabular-nums ";
+ if ( diagonal )
+ css_string += "diagonal-fractions ";
+ if ( stacked )
+ css_string += "stacked-fractions ";
+ if ( ordinal )
+ css_string += "ordinal ";
+ if ( slashed_zero )
+ css_string += "slashed-zero ";
+ sp_repr_css_set_property(css, "font-variant-numeric", css_string.c_str() );
+ }
+
+ // East Asian
+ bool default_variant = _asian_default_variant.get_active();
+ bool jis78 = _asian_jis78.get_active();
+ bool jis83 = _asian_jis83.get_active();
+ bool jis90 = _asian_jis90.get_active();
+ bool jis04 = _asian_jis04.get_active();
+ bool simplified = _asian_simplified.get_active();
+ bool traditional = _asian_traditional.get_active();
+ bool asian_width = _asian_default_width.get_active();
+ bool fwid = _asian_full_width.get_active();
+ bool pwid = _asian_proportional_width.get_active();
+ bool ruby = _asian_ruby.get_active();
+
+ if (default_style & asian_width & !ruby) {
+ sp_repr_css_set_property(css, "font-variant-east-asian", "normal");
+ } else {
+ Glib::ustring css_string;
+ if (jis78) css_string += "jis78 ";
+ if (jis83) css_string += "jis83 ";
+ if (jis90) css_string += "jis90 ";
+ if (jis04) css_string += "jis04 ";
+ if (simplified) css_string += "simplfied ";
+ if (traditional) css_string += "traditional ";
+
+ if (fwid) css_string += "fwid ";
+ if (pwid) css_string += "pwid ";
+
+ if (ruby) css_string += "ruby ";
+
+ sp_repr_css_set_property(css, "font-variant-east-asian", css_string.c_str() );
+ }
+
+ // Feature settings
+ Glib::ustring feature_string;
+ for (auto i: _features) {
+ feature_string += i.second->get_css();
+ }
+
+ feature_string += _feature_entry.get_text();
+ // std::cout << "feature_string: " << feature_string << std::endl;
+
+ if (!feature_string.empty()) {
+ sp_repr_css_set_property(css, "font-feature-settings", feature_string.c_str());
+ } else {
+ sp_repr_css_unset_property(css, "font-feature-settings");
+ }
+ }
+
+ Glib::ustring
+ FontVariants::get_markup() {
+
+ Glib::ustring markup;
+
+ // Ligatures
+ bool common = _ligatures_common.get_active();
+ bool discretionary = _ligatures_discretionary.get_active();
+ bool historical = _ligatures_historical.get_active();
+ bool contextual = _ligatures_contextual.get_active();
+
+ if (!common) markup += "liga=0,clig=0,"; // On by default.
+ if (discretionary) markup += "dlig=1,";
+ if (historical) markup += "hlig=1,";
+ if (contextual) markup += "calt=1,";
+
+ // Position
+ if ( _position_sub.get_active() ) markup += "subs=1,";
+ else if ( _position_super.get_active() ) markup += "sups=1,";
+
+ // Caps
+ if ( _caps_small.get_active() ) markup += "smcp=1,";
+ else if ( _caps_all_small.get_active() ) markup += "c2sc=1,smcp=1,";
+ else if ( _caps_petite.get_active() ) markup += "pcap=1,";
+ else if ( _caps_all_petite.get_active() ) markup += "c2pc=1,pcap=1,";
+ else if ( _caps_unicase.get_active() ) markup += "unic=1,";
+ else if ( _caps_titling.get_active() ) markup += "titl=1,";
+
+ // Numeric
+ bool default_style = _numeric_default_style.get_active();
+ bool lining = _numeric_lining.get_active();
+ bool old_style = _numeric_old_style.get_active();
+
+ bool default_width = _numeric_default_width.get_active();
+ bool proportional = _numeric_proportional.get_active();
+ bool tabular = _numeric_tabular.get_active();
+
+ bool default_fractions = _numeric_default_fractions.get_active();
+ bool diagonal = _numeric_diagonal.get_active();
+ bool stacked = _numeric_stacked.get_active();
+
+ bool ordinal = _numeric_ordinal.get_active();
+ bool slashed_zero = _numeric_slashed_zero.get_active();
+
+ if (lining) markup += "lnum=1,";
+ if (old_style) markup += "onum=1,";
+ if (proportional) markup += "pnum=1,";
+ if (tabular) markup += "tnum=1,";
+ if (diagonal) markup += "frac=1,";
+ if (stacked) markup += "afrc=1,";
+ if (ordinal) markup += "ordn=1,";
+ if (slashed_zero) markup += "zero=1,";
+
+ // East Asian
+ bool default_variant = _asian_default_variant.get_active();
+ bool jis78 = _asian_jis78.get_active();
+ bool jis83 = _asian_jis83.get_active();
+ bool jis90 = _asian_jis90.get_active();
+ bool jis04 = _asian_jis04.get_active();
+ bool simplified = _asian_simplified.get_active();
+ bool traditional = _asian_traditional.get_active();
+ bool asian_width = _asian_default_width.get_active();
+ bool fwid = _asian_full_width.get_active();
+ bool pwid = _asian_proportional_width.get_active();
+ bool ruby = _asian_ruby.get_active();
+
+ if (jis78 ) markup += "jp78=1,";
+ if (jis83 ) markup += "jp83=1,";
+ if (jis90 ) markup += "jp90=1,";
+ if (jis04 ) markup += "jp04=1,";
+ if (simplified ) markup += "smpl=1,";
+ if (traditional ) markup += "trad=1,";
+
+ if (fwid ) markup += "fwid=1,";
+ if (pwid ) markup += "pwid=1,";
+
+ if (ruby ) markup += "ruby=1,";
+
+ // Feature settings
+ Glib::ustring feature_string;
+ for (auto i: _features) {
+ feature_string += i.second->get_css();
+ }
+
+ feature_string += _feature_entry.get_text();
+ if (!feature_string.empty()) {
+ markup += feature_string;
+ }
+
+ // std::cout << "|" << markup << "|" << std::endl;
+ return markup;
+ }
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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 :
diff --git a/src/ui/widget/font-variants.h b/src/ui/widget/font-variants.h
new file mode 100644
index 0000000..83c9fa9
--- /dev/null
+++ b/src/ui/widget/font-variants.h
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2015, 2018 Tavmong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_FONT_VARIANT_H
+#define INKSCAPE_UI_WIDGET_FONT_VARIANT_H
+
+#include <gtkmm/expander.h>
+#include <gtkmm/checkbutton.h>
+#include <gtkmm/radiobutton.h>
+#include <gtkmm/entry.h>
+#include <gtkmm/grid.h>
+
+class SPDesktop;
+class SPObject;
+class SPStyle;
+class SPCSSAttr;
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class Feature;
+
+/**
+ * A container for selecting font variants (OpenType Features).
+ */
+class FontVariants : public Gtk::VBox
+{
+
+public:
+
+ /**
+ * Constructor
+ */
+ FontVariants();
+
+protected:
+ // Ligatures: To start, use four check buttons.
+ Gtk::Expander _ligatures_frame;
+ Gtk::Grid _ligatures_grid;
+ Gtk::CheckButton _ligatures_common;
+ Gtk::CheckButton _ligatures_discretionary;
+ Gtk::CheckButton _ligatures_historical;
+ Gtk::CheckButton _ligatures_contextual;
+ Gtk::Label _ligatures_label_common;
+ Gtk::Label _ligatures_label_discretionary;
+ Gtk::Label _ligatures_label_historical;
+ Gtk::Label _ligatures_label_contextual;
+
+ // Position: Exclusive options
+ Gtk::Expander _position_frame;
+ Gtk::Grid _position_grid;
+ Gtk::RadioButton _position_normal;
+ Gtk::RadioButton _position_sub;
+ Gtk::RadioButton _position_super;
+
+ // Caps: Exclusive options (maybe a dropdown menu to save space?)
+ Gtk::Expander _caps_frame;
+ Gtk::Grid _caps_grid;
+ Gtk::RadioButton _caps_normal;
+ Gtk::RadioButton _caps_small;
+ Gtk::RadioButton _caps_all_small;
+ Gtk::RadioButton _caps_petite;
+ Gtk::RadioButton _caps_all_petite;
+ Gtk::RadioButton _caps_unicase;
+ Gtk::RadioButton _caps_titling;
+
+ // Numeric: Complicated!
+ Gtk::Expander _numeric_frame;
+ Gtk::Grid _numeric_grid;
+
+ Gtk::RadioButton _numeric_default_style;
+ Gtk::RadioButton _numeric_lining;
+ Gtk::Label _numeric_lining_label;
+ Gtk::RadioButton _numeric_old_style;
+ Gtk::Label _numeric_old_style_label;
+
+ Gtk::RadioButton _numeric_default_width;
+ Gtk::RadioButton _numeric_proportional;
+ Gtk::Label _numeric_proportional_label;
+ Gtk::RadioButton _numeric_tabular;
+ Gtk::Label _numeric_tabular_label;
+
+ Gtk::RadioButton _numeric_default_fractions;
+ Gtk::RadioButton _numeric_diagonal;
+ Gtk::Label _numeric_diagonal_label;
+ Gtk::RadioButton _numeric_stacked;
+ Gtk::Label _numeric_stacked_label;
+
+ Gtk::CheckButton _numeric_ordinal;
+ Gtk::Label _numeric_ordinal_label;
+
+ Gtk::CheckButton _numeric_slashed_zero;
+ Gtk::Label _numeric_slashed_zero_label;
+
+ // East Asian: Complicated!
+ Gtk::Expander _asian_frame;
+ Gtk::Grid _asian_grid;
+
+ Gtk::RadioButton _asian_default_variant;
+ Gtk::RadioButton _asian_jis78;
+ Gtk::RadioButton _asian_jis83;
+ Gtk::RadioButton _asian_jis90;
+ Gtk::RadioButton _asian_jis04;
+ Gtk::RadioButton _asian_simplified;
+ Gtk::RadioButton _asian_traditional;
+
+ Gtk::RadioButton _asian_default_width;
+ Gtk::RadioButton _asian_full_width;
+ Gtk::RadioButton _asian_proportional_width;
+
+ Gtk::CheckButton _asian_ruby;
+
+ // -----
+ Gtk::Expander _feature_frame;
+ Gtk::Grid _feature_grid;
+ Gtk::VBox _feature_vbox;
+ Gtk::Entry _feature_entry;
+ Gtk::Label _feature_label;
+ Gtk::Label _feature_list;
+ Gtk::Label _feature_substitutions;
+
+private:
+ void ligatures_init();
+ void ligatures_callback();
+
+ void position_init();
+ void position_callback();
+
+ void caps_init();
+ void caps_callback();
+
+ void numeric_init();
+ void numeric_callback();
+
+ void asian_init();
+ void asian_callback();
+
+ void feature_init();
+public:
+ void feature_callback();
+
+private:
+ // To determine if we need to write out property (may not be necessary)
+ unsigned _ligatures_all;
+ unsigned _position_all;
+ unsigned _caps_all;
+ unsigned _numeric_all;
+ unsigned _asian_all;
+
+ unsigned _ligatures_mix;
+ unsigned _position_mix;
+ unsigned _caps_mix;
+ unsigned _numeric_mix;
+ unsigned _asian_mix;
+
+ bool _ligatures_changed;
+ bool _position_changed;
+ bool _caps_changed;
+ bool _numeric_changed;
+ bool _feature_changed;
+ bool _asian_changed;
+
+ std::map<std::string, Feature*> _features;
+
+ sigc::signal<void> _changed_signal;
+
+public:
+
+ /**
+ * Update GUI based on query results.
+ */
+ void update( SPStyle const *query, bool different_features, Glib::ustring& font_spec );
+
+ /**
+ * Update GUI based on OpenType features of selected font.
+ */
+ void update_opentype( Glib::ustring& font_spec );
+
+ /**
+ * Fill SPCSSAttr based on settings of buttons.
+ */
+ void fill_css( SPCSSAttr* css );
+
+ /**
+ * Get CSS string for markup.
+ */
+ Glib::ustring get_markup();
+
+ /**
+ * Let others know that user has changed GUI settings.
+ * (Used to enable 'Apply' and 'Default' buttons.)
+ */
+ sigc::connection connectChanged(sigc::slot<void> slot) {
+ return _changed_signal.connect(slot);
+ }
+};
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_FONT_VARIANT_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 :
diff --git a/src/ui/widget/font-variations.cpp b/src/ui/widget/font-variations.cpp
new file mode 100644
index 0000000..fae5cc3
--- /dev/null
+++ b/src/ui/widget/font-variations.cpp
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2018 Felipe CorrĂȘa da Silva Sanches, Tavmong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <iostream>
+#include <iomanip>
+
+#include <gtkmm.h>
+#include <glibmm/i18n.h>
+
+#include <libnrtype/font-instance.h>
+
+#include "font-variations.h"
+
+// For updating from selection
+#include "desktop.h"
+#include "object/sp-text.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+FontVariationAxis::FontVariationAxis (Glib::ustring name, OTVarAxis& axis)
+ : name (name)
+{
+
+ // std::cout << "FontVariationAxis::FontVariationAxis:: "
+ // << " name: " << name
+ // << " min: " << axis.minimum
+ // << " max: " << axis.maximum
+ // << " val: " << axis.set_val << std::endl;
+
+ label = Gtk::manage( new Gtk::Label( name ) );
+ add( *label );
+
+ precision = 2 - int( log10(axis.maximum - axis.minimum));
+ if (precision < 0) precision = 0;
+
+ scale = Gtk::manage( new Gtk::Scale() );
+ scale->set_range (axis.minimum, axis.maximum);
+ scale->set_value (axis.set_val);
+ scale->set_digits (precision);
+ scale->set_hexpand(true);
+ add( *scale );
+}
+
+
+// ------------------------------------------------------------- //
+
+FontVariations::FontVariations () :
+ Gtk::Grid ()
+{
+ // std::cout << "FontVariations::FontVariations" << std::endl;
+ set_orientation( Gtk::ORIENTATION_VERTICAL );
+ set_name ("FontVariations");
+ size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
+ show_all_children();
+}
+
+
+// Update GUI based on query.
+void
+FontVariations::update (const Glib::ustring& font_spec) {
+
+ font_instance* res = font_factory::Default()->FaceFromFontSpecification (font_spec.c_str());
+
+ auto children = get_children();
+ for (auto child: children) {
+ remove ( *child );
+ }
+ axes.clear();
+
+ for (auto a: res->openTypeVarAxes) {
+ // std::cout << "Creating axis: " << a.first << std::endl;
+ FontVariationAxis* axis = Gtk::manage( new FontVariationAxis( a.first, a.second ));
+ axes.push_back( axis );
+ add( *axis );
+ size_group->add_widget( *(axis->get_label()) ); // Keep labels the same width
+ axis->get_scale()->signal_value_changed().connect(
+ sigc::mem_fun(*this, &FontVariations::on_variations_change)
+ );
+ }
+
+ show_all_children();
+}
+
+void
+FontVariations::fill_css( SPCSSAttr *css ) {
+
+ // Eventually will want to favor using 'font-weight', etc. but at the moment these
+ // can't handle "fractional" values. See CSS Fonts Module Level 4.
+ sp_repr_css_set_property(css, "font-variation-settings", get_css_string().c_str());
+}
+
+Glib::ustring
+FontVariations::get_css_string() {
+
+ Glib::ustring css_string;
+
+ for (auto axis: axes) {
+ Glib::ustring name = axis->get_name();
+
+ // Translate the "named" axes. (Additional names in 'stat' table, may need to handle them.)
+ if (name == "Width") name = "wdth"; // 'font-stretch'
+ if (name == "Weight") name = "wght"; // 'font-weight'
+ if (name == "Optical size") name = "opsz"; // 'font-optical-sizing' Can trigger glyph substitution.
+ if (name == "Slant") name = "slnt"; // 'font-style'
+ if (name == "Italic") name = "ital"; // 'font-style' Toggles from Roman to Italic.
+
+ std::stringstream value;
+ value << std::fixed << std::setprecision(axis->get_precision()) << axis->get_value();
+ css_string += "'" + name + "' " + value.str() + "', ";
+ }
+
+ return css_string;
+}
+
+Glib::ustring
+FontVariations::get_pango_string() {
+
+ Glib::ustring pango_string;
+
+ if (!axes.empty()) {
+
+ pango_string += "@";
+
+ for (auto axis: axes) {
+ if (axis->get_value() == 0) continue; // TEMP: Should check against default value.
+ Glib::ustring name = axis->get_name();
+
+ // Translate the "named" axes. (Additional names in 'stat' table, may need to handle them.)
+ if (name == "Width") name = "wdth"; // 'font-stretch'
+ if (name == "Weight") name = "wght"; // 'font-weight'
+ if (name == "Optical size") name = "opsz"; // 'font-optical-sizing' Can trigger glyph substitution.
+ if (name == "Slant") name = "slnt"; // 'font-style'
+ if (name == "Italic") name = "ital"; // 'font-style' Toggles from Roman to Italic.
+
+ std::stringstream value;
+ value << std::fixed << std::setprecision(axis->get_precision()) << axis->get_value();
+ pango_string += name + "=" + value.str() + ",";
+ }
+
+ pango_string.erase (pango_string.size() - 1); // Erase last ','
+ }
+
+ return pango_string;
+}
+
+void
+FontVariations::on_variations_change() {
+ // std::cout << "FontVariations::on_variations_change: " << get_css_string() << std::endl;;
+ signal_changed.emit ();
+}
+
+bool FontVariations::variations_present() const {
+ return !axes.empty();
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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 :
diff --git a/src/ui/widget/font-variations.h b/src/ui/widget/font-variations.h
new file mode 100644
index 0000000..a3d3896
--- /dev/null
+++ b/src/ui/widget/font-variations.h
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2018 Felipe CorrĂȘa da Silva Sanches, Tavmong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_FONT_VARIATIONS_H
+#define INKSCAPE_UI_WIDGET_FONT_VARIATIONS_H
+
+#include <gtkmm/grid.h>
+#include <gtkmm/sizegroup.h>
+#include <gtkmm/label.h>
+#include <gtkmm/scale.h>
+
+#include "libnrtype/OpenTypeUtil.h"
+
+#include "style.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+
+/**
+ * A widget for a single axis: Label and Slider
+ */
+class FontVariationAxis : public Gtk::Grid
+{
+public:
+ FontVariationAxis(Glib::ustring name, OTVarAxis& axis);
+ Glib::ustring get_name() { return name; }
+ Gtk::Label* get_label() { return label; }
+ double get_value() { return scale->get_value(); }
+ int get_precision() { return precision; }
+ Gtk::Scale* get_scale() { return scale; }
+
+private:
+
+ // Widgets
+ Glib::ustring name;
+ Gtk::Label* label;
+ Gtk::Scale* scale;
+
+ int precision;
+
+ // Signals
+ sigc::signal<void> signal_changed;
+};
+
+/**
+ * A widget for selecting font variations (OpenType Variations).
+ */
+class FontVariations : public Gtk::Grid
+{
+
+public:
+
+ /**
+ * Constructor
+ */
+ FontVariations();
+
+protected:
+
+public:
+
+ /**
+ * Update GUI.
+ */
+ void update(const Glib::ustring& font_spec);
+
+ /**
+ * Fill SPCSSAttr based on settings of buttons.
+ */
+ void fill_css( SPCSSAttr* css );
+
+ /**
+ * Get CSS String
+ */
+ Glib::ustring get_css_string();
+
+ Glib::ustring get_pango_string();
+
+ void on_variations_change();
+
+ /**
+ * Let others know that user has changed GUI settings.
+ * (Used to enable 'Apply' and 'Default' buttons.)
+ */
+ sigc::connection connectChanged(sigc::slot<void> slot) {
+ return signal_changed.connect(slot);
+ }
+
+ // return true if there are some variations present
+ bool variations_present() const;
+
+private:
+
+ std::vector<FontVariationAxis*> axes;
+ Glib::RefPtr<Gtk::SizeGroup> size_group;
+
+ sigc::signal<void> signal_changed;
+};
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_FONT_VARIATIONS_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 :
diff --git a/src/ui/widget/frame.cpp b/src/ui/widget/frame.cpp
new file mode 100644
index 0000000..eac4e22
--- /dev/null
+++ b/src/ui/widget/frame.cpp
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Murray C
+ *
+ * Copyright (C) 2012 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "frame.h"
+
+
+// Inkscape::UI::Widget::Frame
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+Frame::Frame(Glib::ustring const &label_text /*= ""*/, gboolean label_bold /*= TRUE*/ )
+ : _label(label_text, Gtk::ALIGN_END, Gtk::ALIGN_CENTER, true)
+{
+ set_shadow_type(Gtk::SHADOW_NONE);
+
+ set_label_widget(_label);
+ set_label(label_text, label_bold);
+}
+
+void
+Frame::add(Widget& widget)
+{
+ Gtk::Frame::add(widget);
+ set_padding(4, 0, 8, 0);
+ show_all_children();
+}
+
+void
+Frame::set_label(const Glib::ustring &label_text, gboolean label_bold /*= TRUE*/)
+{
+ if (label_bold) {
+ _label.set_markup(Glib::ustring("<b>") + label_text + "</b>");
+ } else {
+ _label.set_text(label_text);
+ }
+}
+
+void
+Frame::set_padding (guint padding_top, guint padding_bottom, guint padding_left, guint padding_right)
+{
+ auto child = get_child();
+
+ if(child)
+ {
+ child->set_margin_top(padding_top);
+ child->set_margin_bottom(padding_bottom);
+ child->set_margin_start(padding_left);
+ child->set_margin_end(padding_right);
+ }
+}
+
+Gtk::Label const *
+Frame::get_label_widget() const
+{
+ return &_label;
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/frame.h b/src/ui/widget/frame.h
new file mode 100644
index 0000000..b2934b6
--- /dev/null
+++ b/src/ui/widget/frame.h
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Murray C
+ *
+ * Copyright (C) 2012 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_FRAME_H
+#define INKSCAPE_UI_WIDGET_FRAME_H
+
+#include <gtkmm/frame.h>
+#include <gtkmm/label.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * Creates a Gnome HIG style indented frame with bold label
+ * See http://developer.gnome.org/hig-book/stable/controls-frames.html.en
+ */
+class Frame : public Gtk::Frame
+{
+public:
+
+ /**
+ * Construct a Frame Widget.
+ *
+ * @param label The frame text.
+ */
+ Frame(Glib::ustring const &label = "", gboolean label_bold = TRUE);
+
+ /**
+ * Return the label widget
+ */
+ Gtk::Label const *get_label_widget() const;
+
+ /**
+ * Add a widget to this frame
+ */
+ void add(Widget& widget) override;
+
+ /**
+ * Set the frame label text and if bold or not
+ */
+ void set_label(const Glib::ustring &label, gboolean label_bold = TRUE);
+
+ /**
+ * Set the frame padding
+ */
+ void set_padding (guint padding_top, guint padding_bottom, guint padding_left, guint padding_right);
+
+protected:
+ Gtk::Label _label;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_FRAME_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/highlight-picker.cpp b/src/ui/widget/highlight-picker.cpp
new file mode 100644
index 0000000..f35e405
--- /dev/null
+++ b/src/ui/widget/highlight-picker.cpp
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Theodore Janeczko
+ *
+ * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm.h>
+#include <gtkmm/icontheme.h>
+
+#include "display/cairo-utils.h"
+
+#include "highlight-picker.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+HighlightPicker::HighlightPicker() :
+ Glib::ObjectBase(typeid(HighlightPicker)),
+ Gtk::CellRendererPixbuf(),
+ _property_active(*this, "active", 0)
+{
+
+ property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE;
+}
+
+HighlightPicker::~HighlightPicker()
+= default;
+
+void HighlightPicker::get_preferred_height_vfunc(Gtk::Widget& widget,
+ int& min_h,
+ int& nat_h) const
+{
+ Gtk::CellRendererPixbuf::get_preferred_height_vfunc(widget, min_h, nat_h);
+
+ if (min_h) {
+ min_h += (min_h) >> 1;
+ }
+
+ if (nat_h) {
+ nat_h += (nat_h) >> 1;
+ }
+}
+
+void HighlightPicker::get_preferred_width_vfunc(Gtk::Widget& widget,
+ int& min_w,
+ int& nat_w) const
+{
+ Gtk::CellRendererPixbuf::get_preferred_width_vfunc(widget, min_w, nat_w);
+
+ if (min_w) {
+ min_w += (min_w) >> 1;
+ }
+
+ if (nat_w) {
+ nat_w += (nat_w) >> 1;
+ }
+}
+
+void HighlightPicker::render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ Gtk::CellRendererState flags )
+{
+ GdkRectangle carea;
+
+ cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 20);
+ cairo_t *ct = cairo_create(s);
+
+ /* Transparent area */
+ carea.x = 0;
+ carea.y = 0;
+ carea.width = 10;
+ carea.height = 20;
+
+ cairo_pattern_t *checkers = ink_cairo_pattern_create_checkerboard();
+
+ cairo_rectangle(ct, carea.x, carea.y, carea.width, carea.height / 2);
+ cairo_set_source(ct, checkers);
+ cairo_fill_preserve(ct);
+ ink_cairo_set_source_rgba32(ct, _property_active.get_value());
+ cairo_fill(ct);
+
+ cairo_pattern_destroy(checkers);
+
+ cairo_rectangle(ct, carea.x, carea.y + carea.height / 2, carea.width, carea.height / 2);
+ ink_cairo_set_source_rgba32(ct, _property_active.get_value() | 0x000000ff);
+ cairo_fill(ct);
+
+ cairo_rectangle(ct, carea.x, carea.y, carea.width, carea.height);
+ ink_cairo_set_source_rgba32(ct, 0x333333ff);
+ cairo_set_line_width(ct, 2);
+ cairo_stroke(ct);
+
+ cairo_destroy(ct);
+ cairo_surface_flush(s);
+
+ GdkPixbuf* pixbuf = gdk_pixbuf_new_from_data( cairo_image_surface_get_data(s),
+ GDK_COLORSPACE_RGB, TRUE, 8,
+ 10, 20, cairo_image_surface_get_stride(s),
+ ink_cairo_pixbuf_cleanup, s);
+ convert_pixbuf_argb32_to_normal(pixbuf);
+
+ property_pixbuf() = Glib::wrap(pixbuf);
+ Gtk::CellRendererPixbuf::render_vfunc( cr, widget, background_area, cell_area, flags );
+}
+
+bool HighlightPicker::activate_vfunc(GdkEvent* /*event*/,
+ Gtk::Widget& /*widget*/,
+ const Glib::ustring& /*path*/,
+ const Gdk::Rectangle& /*background_area*/,
+ const Gdk::Rectangle& /*cell_area*/,
+ Gtk::CellRendererState /*flags*/)
+{
+ return false;
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+//should be okay to put this here
+/**
+ * Converts GdkPixbuf's data to premultiplied ARGB.
+ * This function will convert a GdkPixbuf in place into Cairo's native pixel format.
+ * Note that this is a hack intended to save memory. When the pixbuf is in Cairo's format,
+ * using it with GTK will result in corrupted drawings.
+ */
+void
+convert_pixbuf_normal_to_argb32(GdkPixbuf *pb)
+{
+ convert_pixels_pixbuf_to_argb32(
+ gdk_pixbuf_get_pixels(pb),
+ gdk_pixbuf_get_width(pb),
+ gdk_pixbuf_get_height(pb),
+ gdk_pixbuf_get_rowstride(pb));
+}
+
+/**
+ * Converts GdkPixbuf's data back to its native format.
+ * Once this is done, the pixbuf can be used with GTK again.
+ */
+void
+convert_pixbuf_argb32_to_normal(GdkPixbuf *pb)
+{
+ convert_pixels_argb32_to_pixbuf(
+ gdk_pixbuf_get_pixels(pb),
+ gdk_pixbuf_get_width(pb),
+ gdk_pixbuf_get_height(pb),
+ gdk_pixbuf_get_rowstride(pb));
+}
+
+/*
+ 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/highlight-picker.h b/src/ui/widget/highlight-picker.h
new file mode 100644
index 0000000..0b77a23
--- /dev/null
+++ b/src/ui/widget/highlight-picker.h
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef __UI_DIALOG_HIGHLIGHT_PICKER_H__
+#define __UI_DIALOG_HIGHLIGHT_PICKER_H__
+/*
+ * Authors:
+ * Theodore Janeczko
+ *
+ * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/cellrendererpixbuf.h>
+#include <gtkmm/widget.h>
+#include <glibmm/property.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class HighlightPicker : public Gtk::CellRendererPixbuf {
+public:
+ HighlightPicker();
+ ~HighlightPicker() override;
+
+ Glib::PropertyProxy<guint32> property_active() { return _property_active.get_proxy(); }
+
+protected:
+ void render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ Gtk::CellRendererState flags ) override;
+
+ void get_preferred_width_vfunc(Gtk::Widget& widget,
+ int& min_w,
+ int& nat_w) const override;
+
+ void get_preferred_height_vfunc(Gtk::Widget& widget,
+ int& min_h,
+ int& nat_h) const override;
+
+ bool activate_vfunc(GdkEvent *event,
+ Gtk::Widget &widget,
+ const Glib::ustring &path,
+ const Gdk::Rectangle &background_area,
+ const Gdk::Rectangle &cell_area,
+ Gtk::CellRendererState flags) override;
+
+private:
+
+ Glib::Property<guint32> _property_active;
+};
+
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+
+#endif /* __UI_DIALOG_IMAGETOGGLER_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/iconrenderer.cpp b/src/ui/widget/iconrenderer.cpp
new file mode 100644
index 0000000..4ca250e
--- /dev/null
+++ b/src/ui/widget/iconrenderer.cpp
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Theodore Janeczko
+ *
+ * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com>
+ * Martin Owens 2018 <doctormo@gmail.com>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "ui/widget/iconrenderer.h"
+
+#include "layertypeicon.h"
+#include "ui/icon-loader.h"
+#include "ui/icon-names.h"
+#include "widgets/toolbox.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+IconRenderer::IconRenderer() :
+ Glib::ObjectBase(typeid(IconRenderer)),
+ Gtk::CellRendererPixbuf(),
+ _property_icon(*this, "icon", 0)
+{
+ property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE;
+ set_pixbuf();
+}
+
+/*
+ * Called when an icon is clicked.
+ */
+IconRenderer::type_signal_activated IconRenderer::signal_activated()
+{
+ return m_signal_activated;
+}
+
+void IconRenderer::get_preferred_height_vfunc(Gtk::Widget& widget,
+ int& min_h,
+ int& nat_h) const
+{
+ Gtk::CellRendererPixbuf::get_preferred_height_vfunc(widget, min_h, nat_h);
+
+ if (min_h) {
+ min_h += (min_h) >> 1;
+ }
+
+ if (nat_h) {
+ nat_h += (nat_h) >> 1;
+ }
+}
+
+void IconRenderer::get_preferred_width_vfunc(Gtk::Widget& widget,
+ int& min_w,
+ int& nat_w) const
+{
+ Gtk::CellRendererPixbuf::get_preferred_width_vfunc(widget, min_w, nat_w);
+
+ if (min_w) {
+ min_w += (min_w) >> 1;
+ }
+
+ if (nat_w) {
+ nat_w += (nat_w) >> 1;
+ }
+}
+
+void IconRenderer::render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ Gtk::CellRendererState flags )
+{
+ set_pixbuf();
+
+ Gtk::CellRendererPixbuf::render_vfunc( cr, widget, background_area, cell_area, flags );
+}
+
+bool IconRenderer::activate_vfunc(GdkEvent* /*event*/,
+ Gtk::Widget& /*widget*/,
+ const Glib::ustring& path,
+ const Gdk::Rectangle& /*background_area*/,
+ const Gdk::Rectangle& /*cell_area*/,
+ Gtk::CellRendererState /*flags*/)
+{
+ m_signal_activated.emit(path);
+ return true;
+}
+
+void IconRenderer::add_icon(Glib::ustring name)
+{
+ _icons.push_back(sp_get_icon_pixbuf(name.c_str(), GTK_ICON_SIZE_BUTTON));
+}
+
+void IconRenderer::set_pixbuf()
+{
+ int icon_index = property_icon().get_value();
+ if(icon_index >= 0 && icon_index < _icons.size()) {
+ property_pixbuf() = _icons[icon_index];
+ } else {
+ property_pixbuf() = sp_get_icon_pixbuf("image-missing", GTK_ICON_SIZE_BUTTON);
+ }
+}
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/iconrenderer.h b/src/ui/widget/iconrenderer.h
new file mode 100644
index 0000000..662ce5b
--- /dev/null
+++ b/src/ui/widget/iconrenderer.h
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef __UI_DIALOG_ADDTOICON_H__
+#define __UI_DIALOG_ADDTOICON_H__
+/*
+ * Authors:
+ * Theodore Janeczko
+ *
+ * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com>
+ * Martin Owens 2018 <doctormo@gmail.com>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/cellrendererpixbuf.h>
+#include <gtkmm/widget.h>
+#include <glibmm/property.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class IconRenderer : public Gtk::CellRendererPixbuf {
+public:
+ IconRenderer();
+ ~IconRenderer() override = default;;
+
+ Glib::PropertyProxy<int> property_icon() { return _property_icon.get_proxy(); }
+ Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_on();
+ Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_off();
+
+ void add_icon(Glib::ustring name);
+
+ typedef sigc::signal<void, Glib::ustring> type_signal_activated;
+ type_signal_activated signal_activated();
+protected:
+ type_signal_activated m_signal_activated;
+
+ void render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ Gtk::CellRendererState flags ) override;
+
+ void get_preferred_width_vfunc(Gtk::Widget& widget,
+ int& min_w,
+ int& nat_w) const override;
+
+ void get_preferred_height_vfunc(Gtk::Widget& widget,
+ int& min_h,
+ int& nat_h) const override;
+
+ bool activate_vfunc(GdkEvent *event,
+ Gtk::Widget &widget,
+ const Glib::ustring &path,
+ const Gdk::Rectangle &background_area,
+ const Gdk::Rectangle &cell_area,
+ Gtk::CellRendererState flags) override;
+
+private:
+
+ Glib::Property<int> _property_icon;
+ std::vector<Glib::RefPtr<Gdk::Pixbuf>> _icons;
+ void set_pixbuf();
+};
+
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+
+#endif /* __UI_DIALOG_IMAGETOGGLER_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/imagetoggler.cpp b/src/ui/widget/imagetoggler.cpp
new file mode 100644
index 0000000..a1d258e
--- /dev/null
+++ b/src/ui/widget/imagetoggler.cpp
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Jon A. Cruz
+ * Johan B. C. Engelen
+ *
+ * Copyright (C) 2006-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include "ui/widget/imagetoggler.h"
+
+#include "ui/icon-loader.h"
+#include "ui/icon-names.h"
+#include "widgets/toolbox.h"
+
+#include <iostream>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+ImageToggler::ImageToggler( char const* on, char const* off) :
+ Glib::ObjectBase(typeid(ImageToggler)),
+ Gtk::CellRendererPixbuf(),
+ _pixOnName(on),
+ _pixOffName(off),
+ _property_active(*this, "active", false),
+ _property_activatable(*this, "activatable", true),
+ _property_pixbuf_on(*this, "pixbuf_on", Glib::RefPtr<Gdk::Pixbuf>(nullptr)),
+ _property_pixbuf_off(*this, "pixbuf_off", Glib::RefPtr<Gdk::Pixbuf>(nullptr))
+{
+ property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE;
+
+ _property_pixbuf_on = sp_get_icon_pixbuf(_pixOnName, GTK_ICON_SIZE_MENU);
+ _property_pixbuf_off = sp_get_icon_pixbuf(_pixOffName, GTK_ICON_SIZE_MENU);
+
+ property_pixbuf() = _property_pixbuf_off.get_value();
+}
+
+void ImageToggler::get_preferred_height_vfunc(Gtk::Widget& widget,
+ int& min_h,
+ int& nat_h) const
+{
+ Gtk::CellRendererPixbuf::get_preferred_height_vfunc(widget, min_h, nat_h);
+
+ if (min_h) {
+ min_h += (min_h) >> 1;
+ }
+
+ if (nat_h) {
+ nat_h += (nat_h) >> 1;
+ }
+}
+
+void ImageToggler::get_preferred_width_vfunc(Gtk::Widget& widget,
+ int& min_w,
+ int& nat_w) const
+{
+ Gtk::CellRendererPixbuf::get_preferred_width_vfunc(widget, min_w, nat_w);
+
+ if (min_w) {
+ min_w += (min_w) >> 1;
+ }
+
+ if (nat_w) {
+ nat_w += (nat_w) >> 1;
+ }
+}
+
+void ImageToggler::render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ Gtk::CellRendererState flags )
+{
+ property_pixbuf() = _property_active.get_value() ? _property_pixbuf_on : _property_pixbuf_off;
+ Gtk::CellRendererPixbuf::render_vfunc( cr, widget, background_area, cell_area, flags );
+}
+
+bool
+ImageToggler::activate_vfunc(GdkEvent* event,
+ Gtk::Widget& /*widget*/,
+ const Glib::ustring& path,
+ const Gdk::Rectangle& /*background_area*/,
+ const Gdk::Rectangle& /*cell_area*/,
+ Gtk::CellRendererState /*flags*/)
+{
+ _signal_pre_toggle.emit(event);
+ _signal_toggled.emit(path);
+
+ return false;
+}
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/imagetoggler.h b/src/ui/widget/imagetoggler.h
new file mode 100644
index 0000000..a03ee37
--- /dev/null
+++ b/src/ui/widget/imagetoggler.h
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef __UI_DIALOG_IMAGETOGGLER_H__
+#define __UI_DIALOG_IMAGETOGGLER_H__
+/*
+ * Authors:
+ * Jon A. Cruz
+ * Johan B. C. Engelen
+ *
+ * Copyright (C) 2006-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/cellrendererpixbuf.h>
+#include <gtkmm/widget.h>
+#include <glibmm/property.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class ImageToggler : public Gtk::CellRendererPixbuf {
+public:
+ ImageToggler( char const *on, char const *off);
+ ~ImageToggler() override = default;;
+
+ sigc::signal<void, const Glib::ustring&> signal_toggled() { return _signal_toggled;}
+ sigc::signal<void, GdkEvent const *> signal_pre_toggle() { return _signal_pre_toggle; }
+
+ Glib::PropertyProxy<bool> property_active() { return _property_active.get_proxy(); }
+ Glib::PropertyProxy<bool> property_activatable() { return _property_activatable.get_proxy(); }
+ Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_on();
+ Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_off();
+
+protected:
+ void render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ Gtk::CellRendererState flags ) override;
+
+ void get_preferred_width_vfunc(Gtk::Widget& widget,
+ int& min_w,
+ int& nat_w) const override;
+
+ void get_preferred_height_vfunc(Gtk::Widget& widget,
+ int& min_h,
+ int& nat_h) const override;
+
+ bool activate_vfunc(GdkEvent *event,
+ Gtk::Widget &widget,
+ const Glib::ustring &path,
+ const Gdk::Rectangle &background_area,
+ const Gdk::Rectangle &cell_area,
+ Gtk::CellRendererState flags) override;
+
+
+private:
+ Glib::ustring _pixOnName;
+ Glib::ustring _pixOffName;
+
+ Glib::Property<bool> _property_active;
+ Glib::Property<bool> _property_activatable;
+ Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_on;
+ Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_off;
+
+ sigc::signal<void, const Glib::ustring&> _signal_toggled;
+ sigc::signal<void, GdkEvent const *> _signal_pre_toggle;
+};
+
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+
+#endif /* __UI_DIALOG_IMAGETOGGLER_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/ink-color-wheel.cpp b/src/ui/widget/ink-color-wheel.cpp
new file mode 100644
index 0000000..7e56ee5
--- /dev/null
+++ b/src/ui/widget/ink-color-wheel.cpp
@@ -0,0 +1,726 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Color wheel widget. Outer ring for Hue. Inner triangle for Saturation and Value.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include "ink-color-wheel.h"
+
+// A point with a color value.
+class color_point {
+public:
+ color_point() : x(0), y(0), r(0), g(0), b(0) {};
+ color_point(double x, double y, double r, double g, double b) : x(x), y(y), r(r), g(g), b(b) {};
+ color_point(double x, double y, guint32 color) : x(x), y(y),
+ r(((color & 0xff0000) >> 16)/255.0),
+ g(((color & 0xff00) >> 8)/255.0),
+ b(((color & 0xff) )/255.0) {};
+ guint32 get_color() { return (int(r*255) << 16 | int(g*255) << 8 | int(b*255)); };
+ double x;
+ double y;
+ double r;
+ double g;
+ double b;
+};
+
+inline double lerp(const double& v0, const double& v1, const double& t0, const double&t1, const double& t) {
+ double s = 0;
+ if (t0 != t1) {
+ s = (t - t0)/(t1 - t0);
+ }
+ return (1.0 - s) * v0 + s * v1;
+}
+
+inline color_point lerp(const color_point& v0, const color_point& v1, const double &t0, const double &t1, const double& t) {
+ double x = lerp(v0.x, v1.x, t0, t1, t);
+ double y = lerp(v0.y, v1.y, t0, t1, t);
+ double r = lerp(v0.r, v1.r, t0, t1, t);
+ double g = lerp(v0.g, v1.g, t0, t1, t);
+ double b = lerp(v0.b, v1.b, t0, t1, t);
+ return (color_point(x, y, r, g, b));
+}
+
+inline double clamp(const double& value, const double& min, const double& max) {
+ if (value < min) return min;
+ if (value > max) return max;
+ return value;
+}
+
+// h, s, and v in range 0 to 1. Returns rgb value useful for use in Cairo.
+guint32 hsv_to_rgb(double h, double s, double v) {
+
+ if (h < 0.0 || h > 1.0 ||
+ s < 0.0 || s > 1.0 ||
+ v < 0.0 || v > 1.0) {
+ std::cerr << "ColorWheel: hsv_to_rgb: input out of bounds: (0-1)"
+ << " h: " << h << " s: " << s << " v: " << v << std::endl;
+ return 0x0;
+ }
+
+ double r = v;
+ double g = v;
+ double b = v;
+
+ if (s != 0.0) {
+ double c = s * v;
+ if (h == 1.0) h = 0.0;
+ h *= 6.0;
+
+ double f = h - (int)h;
+ double p = v * (1.0 - s);
+ double q = v * (1.0 - s * f);
+ double t = v * (1.0 - s * (1.0 - f));
+
+ switch ((int)h) {
+ case 0: r = v; g = t; b = p; break;
+ case 1: r = q; g = v; b = p; break;
+ case 2: r = p; g = v; b = t; break;
+ case 3: r = p; g = q; b = v; break;
+ case 4: r = t; g = p; b = v; break;
+ case 5: r = v; g = p; b = q; break;
+ default: g_assert_not_reached();
+ }
+ }
+ guint32 rgb = (((int)floor (r*255 + 0.5) << 16) |
+ ((int)floor (g*255 + 0.5) << 8) |
+ ((int)floor (b*255 + 0.5) ));
+ return rgb;
+}
+
+double luminance(guint32 color)
+{
+ double r(((color & 0xff0000) >> 16)/255.0);
+ double g(((color & 0xff00) >> 8)/255.0);
+ double b(((color & 0xff) )/255.0);
+ return (r * 0.2125 + g * 0.7154 + b * 0.0721);
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+ColorWheel::ColorWheel()
+ : _hue(0.0)
+ , _saturation(1.0)
+ , _value(1.0)
+ , _ring_width(0.2)
+ , _mode(DRAG_NONE)
+ , _focus_on_ring(true)
+{
+ set_name("ColorWheel");
+ add_events(Gdk::BUTTON_PRESS_MASK |
+ Gdk::BUTTON_RELEASE_MASK |
+ Gdk::BUTTON_MOTION_MASK |
+ Gdk::KEY_PRESS_MASK );
+ set_can_focus();
+}
+
+void
+ColorWheel::set_rgb(const double& r, const double&g, const double&b, bool override_hue)
+{
+ double Min = std::min({r, g, b});
+ double Max = std::max({r, g, b});
+ _value = Max;
+ if (Min == Max) {
+ if (override_hue) {
+ _hue = 0.0;
+ }
+ } else {
+ if (Max == r) {
+ _hue = ((g-b)/(Max-Min) )/6.0;
+ } else if (Max == g) {
+ _hue = ((b-r)/(Max-Min) + 2)/6.0;
+ } else {
+ _hue = ((r-g)/(Max-Min) + 4)/6.0;
+ }
+ if (_hue < 0.0) {
+ _hue += 1.0;
+ }
+ }
+ if (Max == 0) {
+ _saturation = 0;
+ } else {
+ _saturation = (Max - Min)/Max;
+ }
+}
+
+void
+ColorWheel::get_rgb(double& r, double& g, double& b)
+{
+ guint32 color = get_rgb();
+ r = ((color & 0xff0000) >> 16)/255.0;
+ g = ((color & 0x00ff00) >> 8)/255.0;
+ b = ((color & 0x0000ff) )/255.0;
+}
+
+guint32
+ColorWheel::get_rgb()
+{
+ return hsv_to_rgb(_hue, _saturation, _value);
+}
+
+/* Pad triangle vertically if necessary */
+void
+draw_vertical_padding(color_point p0, color_point p1, int padding, bool pad_upwards,
+ guint32 *buffer, int height, int stride);
+
+bool
+ColorWheel::on_draw(const::Cairo::RefPtr<::Cairo::Context>& cr) {
+ Gtk::Allocation allocation = get_allocation();
+ const int width = allocation.get_width();
+ const int height = allocation.get_height();
+
+ const int stride = Cairo::ImageSurface::format_stride_for_width(Cairo::FORMAT_RGB24, width);
+
+ int cx = width/2;
+ int cy = height/2;
+
+ int focus_line_width;
+ int focus_padding;
+ get_style_property("focus-line-width", focus_line_width);
+ get_style_property("focus-padding", focus_padding);
+
+ // Paint ring
+ guint32* buffer_ring = g_new (guint32, height * stride / 4);
+ double r_max = std::min( width, height)/2.0 - 2 * (focus_line_width + focus_padding);
+ double r_min = r_max * (1.0 - _ring_width);
+ double r2_max = (r_max+1) * (r_max+1); // Must expand a bit to avoid edge effects.
+ double r2_min = (r_min-1) * (r_min-1); // Must shrink a bit to avoid edge effects.
+
+ for (int i = 0; i < height; ++i) {
+ guint32* p = buffer_ring + i * width;
+ double dy = (cy - i);
+ for (int j = 0; j < width; ++j) {
+ double dx = (j - cx);
+ double r2 = dx * dx + dy * dy;
+ if (r2 < r2_min || r2 > r2_max) {
+ *p++ = 0; // Save calculation time.
+ } else {
+ double angle = atan2 (dy, dx);
+ if (angle < 0.0) {
+ angle += 2.0 * M_PI;
+ }
+ double hue = angle/(2.0 * M_PI);
+
+ *p++ = hsv_to_rgb(hue, 1.0, 1.0);
+ }
+ }
+ }
+
+ Cairo::RefPtr<::Cairo::ImageSurface> source_ring =
+ ::Cairo::ImageSurface::create((unsigned char *)buffer_ring,
+ Cairo::FORMAT_RGB24,
+ width, height, stride);
+
+ cr->set_antialias(Cairo::ANTIALIAS_SUBPIXEL);
+
+ // Paint line on ring in source (so it gets clipped by stroke).
+ double l = 0.0;
+ guint32 color_on_ring = hsv_to_rgb(_hue, 1.0, 1.0);
+ if (luminance(color_on_ring) < 0.5) l = 1.0;
+
+ Cairo::RefPtr<::Cairo::Context> cr_source_ring = ::Cairo::Context::create(source_ring);
+ cr_source_ring->set_source_rgb(l, l, l);
+
+ cr_source_ring->move_to (cx, cy);
+ cr_source_ring->line_to (cx + cos(_hue * M_PI * 2.0) * r_max+1,
+ cy - sin(_hue * M_PI * 2.0) * r_max+1);
+ cr_source_ring->stroke();
+
+ // Paint with ring surface, clipping to ring.
+ cr->save();
+ cr->set_source(source_ring, 0, 0);
+ cr->set_line_width (r_max - r_min);
+ cr->begin_new_path();
+ cr->arc(cx, cy, (r_max + r_min)/2.0, 0, 2.0 * M_PI);
+ cr->stroke();
+ cr->restore();
+
+ g_free(buffer_ring);
+
+ // Draw focus
+ if (has_focus() && _focus_on_ring) {
+ Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context();
+ style_context->render_focus(cr, 0, 0, width, height);
+ }
+
+ // Paint triangle.
+ /* The triangle is painted by first finding color points on the
+ * edges of the triangle at the same y value via linearly
+ * interpolating between corner values, and then interpolating along
+ * x between the those edge points. The interpolation is in sRGB
+ * space which leads to a complicated mapping between x/y and
+ * saturation/value. This was probably done to remove the need to
+ * convert between HSV and RGB for each pixel.
+ * Black corner: v = 0, s = 1
+ * White corner: v = 1, s = 0
+ * Color corner; v = 1, s = 1
+ */
+ const int padding = 3; // Avoid edge artifacts.
+ double x0, y0, x1, y1, x2, y2;
+ triangle_corners(x0, y0, x1, y1, x2, y2);
+ guint32 color0 = hsv_to_rgb(_hue, 1.0, 1.0);
+ guint32 color1 = hsv_to_rgb(_hue, 1.0, 0.0);
+ guint32 color2 = hsv_to_rgb(_hue, 0.0, 1.0);
+
+ color_point p0 (x0, y0, color0);
+ color_point p1 (x1, y1, color1);
+ color_point p2 (x2, y2, color2);
+
+ // Reorder so we paint from top down.
+ if (p1.y > p2.y) {
+ std::swap(p1, p2);
+ }
+
+ if (p0.y > p2.y) {
+ std::swap(p0, p2);
+ }
+
+ if (p0.y > p1.y) {
+ std::swap(p0, p1);
+ }
+
+ guint32* buffer_triangle = g_new (guint32, height * stride / 4);
+
+ for (int y = 0; y < height; ++y) {
+ guint32 *p = buffer_triangle + y * (stride / 4);
+
+ if (p0.y <= y+padding && y-padding < p2.y) {
+
+ // Get values on side at position y.
+ color_point side0;
+ double y_inter = clamp(y, p0.y, p2.y);
+ if (y < p1.y) {
+ side0 = lerp(p0, p1, p0.y, p1.y, y_inter);
+ } else {
+ side0 = lerp(p1, p2, p1.y, p2.y, y_inter);
+ }
+ color_point side1 = lerp(p0, p2, p0.y, p2.y, y_inter);
+
+ // side0 should be on left
+ if (side0.x > side1.x) {
+ std::swap (side0, side1);
+ }
+
+ int x_start = std::max(0, int(side0.x));
+ int x_end = std::min(int(side1.x), width);
+
+ for (int x = 0; x < width; ++x) {
+ if (x <= x_start) {
+ *p++ = side0.get_color();
+ } else if (x < x_end) {
+ *p++ = lerp(side0, side1, side0.x, side1.x, x).get_color();
+ } else {
+ *p++ = side1.get_color();
+ }
+ }
+ }
+ }
+
+ // add vertical padding to each side separately
+ color_point temp_point = lerp(p0, p1, p0.x, p1.x, (p0.x + p1.x) / 2.0);
+ bool pad_upwards = is_in_triangle(temp_point.x, temp_point.y + 1);
+ draw_vertical_padding(p0, p1, padding, pad_upwards, buffer_triangle, height, stride / 4);
+
+ temp_point = lerp(p0, p2, p0.x, p2.x, (p0.x + p2.x) / 2.0);
+ pad_upwards = is_in_triangle(temp_point.x, temp_point.y + 1);
+ draw_vertical_padding(p0, p2, padding, pad_upwards, buffer_triangle, height, stride / 4);
+
+ temp_point = lerp(p1, p2, p1.x, p2.x, (p1.x + p2.x) / 2.0);
+ pad_upwards = is_in_triangle(temp_point.x, temp_point.y + 1);
+ draw_vertical_padding(p1, p2, padding, pad_upwards, buffer_triangle, height, stride / 4);
+
+ Cairo::RefPtr<::Cairo::ImageSurface> source_triangle =
+ ::Cairo::ImageSurface::create((unsigned char *)buffer_triangle,
+ Cairo::FORMAT_RGB24,
+ width, height, stride);
+
+ // Paint with triangle surface, clipping to triangle.
+ cr->save();
+ cr->set_source(source_triangle, 0, 0);
+ cr->move_to(p0.x, p0.y);
+ cr->line_to(p1.x, p1.y);
+ cr->line_to(p2.x, p2.y);
+ cr->close_path();
+ cr->fill();
+ cr->restore();
+
+ g_free(buffer_triangle);
+
+ // Draw marker
+ double mx = x1 + (x2-x1) * _value + (x0-x2) * _saturation * _value;
+ double my = y1 + (y2-y1) * _value + (y0-y2) * _saturation * _value;
+
+ double a = 0.0;
+ guint32 color_at_marker = get_rgb();
+ if (luminance(color_at_marker) < 0.5) a = 1.0;
+
+ cr->set_source_rgb(a, a, a);
+ cr->begin_new_path();
+ cr->arc(mx, my, 4, 0, 2 * M_PI);
+ cr->stroke();
+
+ // Draw focus
+ if (has_focus() && !_focus_on_ring) {
+ Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context();
+ style_context->render_focus(cr, mx-4, my-4, 8, 8); // This doesn't seem to work.
+ cr->set_line_width(0.5);
+ cr->set_source_rgb(1-a, 1-a, 1-a);
+ cr->begin_new_path();
+ cr->arc(mx, my, 7, 0, 2 * M_PI);
+ cr->stroke();
+ }
+
+ return true;
+}
+
+void
+draw_vertical_padding(color_point p0, color_point p1, int padding, bool pad_upwards,
+ guint32 *buffer, int height, int stride)
+{
+ // skip if horizontal padding is more accurate
+ double gradient = (p1.y - p0.y) / (p1.x - p0.x);
+ if (std::abs(gradient) > 1.0) {
+ return;
+ }
+
+ double min_y = std::min(p0.y, p1.y);
+ double max_y = std::max(p0.y, p1.y);
+
+ double min_x = std::min(p0.x, p1.x);
+ double max_x = std::max(p0.x, p1.x);
+
+ for (int y = min_y; y <= max_y; ++y) {
+ double start_x = lerp(p0, p1, p0.y, p1.y, clamp(y, min_y, max_y)).x;
+ double end_x = lerp(p0, p1, p0.y, p1.y, clamp(y + 1, min_y, max_y)).x;
+ if (start_x > end_x) {
+ std::swap(start_x, end_x);
+ }
+
+ guint32 *p = buffer + y * stride;
+ p += static_cast<int>(start_x);
+ for (int x = start_x; x <= end_x; ++x) {
+ color_point point = lerp(p0, p1, p0.x, p1.x, clamp(x, min_x, max_x));
+ for (int offset = 0; offset <= padding; ++offset) {
+ if (pad_upwards && (point.y - offset) >= 0) {
+ *(p - (offset * stride)) = point.get_color();
+ } else if (!pad_upwards && (point.y + offset) < height) {
+ *(p + (offset * stride)) = point.get_color();
+ }
+ }
+ ++p;
+ }
+ }
+}
+
+// Find triangle corners given hue and radius.
+void
+ColorWheel::triangle_corners(double &x0, double &y0,
+ double &x1, double &y1,
+ double &x2, double &y2)
+{
+ Gtk::Allocation allocation = get_allocation();
+ const int width = allocation.get_width();
+ const int height = allocation.get_height();
+
+ int cx = width/2;
+ int cy = height/2;
+
+ int focus_line_width;
+ int focus_padding;
+ get_style_property("focus-line-width", focus_line_width);
+ get_style_property("focus-padding", focus_padding);
+
+ double r_max = std::min( width, height)/2.0 - 2 * (focus_line_width + focus_padding);
+ double r_min = r_max * (1.0 - _ring_width);
+
+ double angle = _hue * 2.0 * M_PI;
+
+ x0 = cx + cos(angle) * r_min;
+ y0 = cy - sin(angle) * r_min;
+ x1 = cx + cos(angle + 2.0 * M_PI / 3.0) * r_min;
+ y1 = cy - sin(angle + 2.0 * M_PI / 3.0) * r_min;
+ x2 = cx + cos(angle + 4.0 * M_PI / 3.0) * r_min;
+ y2 = cy - sin(angle + 4.0 * M_PI / 3.0) * r_min;
+}
+
+void
+ColorWheel::set_from_xy(const double& x, const double& y)
+{
+ Gtk::Allocation allocation = get_allocation();
+ const int width = allocation.get_width();
+ const int height = allocation.get_height();
+ double cx = width/2.0;
+ double cy = height/2.0;
+ double r = std::min(cx, cy) * (1 - _ring_width);
+
+ // We calculate RGB value under the cursor by rotating the cursor
+ // and triangle by the hue value and looking at position in the
+ // now right pointing triangle.
+ double angle = _hue * 2 * M_PI;
+ double Sin = sin(angle);
+ double Cos = cos(angle);
+ double xp = ((x-cx) * Cos - (y-cy) * Sin) / r;
+ double yp = ((x-cx) * Sin + (y-cy) * Cos) / r;
+
+ double xt = lerp(0.0, 1.0, -0.5, 1.0, xp);
+ xt = clamp(xt, 0, 1);
+
+ double dy = (1-xt) * cos(M_PI/6.0);
+ double yt = lerp(0.0, 1.0, -dy, dy, yp);
+ yt = clamp(yt, 0, 1);
+
+ color_point c0(0, 0, yt, yt, yt); // Grey point along base.
+ color_point c1(0, 0, hsv_to_rgb(_hue, 1, 1)); // Hue point at apex
+ color_point c = lerp(c0, c1, 0, 1, xt);
+
+ set_rgb(c.r, c.g, c.b, false); // Don't override previous hue.
+}
+
+bool
+ColorWheel::is_in_ring(const double& x, const double& y)
+{
+ Gtk::Allocation allocation = get_allocation();
+ const int width = allocation.get_width();
+ const int height = allocation.get_height();
+
+ int cx = width/2;
+ int cy = height/2;
+
+ int focus_line_width;
+ int focus_padding;
+ get_style_property("focus-line-width", focus_line_width);
+ get_style_property("focus-padding", focus_padding);
+
+ double r_max = std::min( width, height)/2.0 - 2 * (focus_line_width + focus_padding);
+ double r_min = r_max * (1.0 - _ring_width);
+ double r2_max = r_max * r_max;
+ double r2_min = r_min * r_min;
+
+ double dx = x - cx;
+ double dy = y - cy;
+ double r2 = dx * dx + dy * dy;
+
+ return (r2_min < r2 && r2 < r2_max);
+}
+
+bool
+ColorWheel::is_in_triangle(const double& x, const double& y)
+{
+ double x0, y0, x1, y1, x2, y2;
+ triangle_corners(x0, y0, x1, y1, x2, y2);
+
+ double det = (x2-x1) * (y0-y1) - (y2-y1) * (x0-x1);
+ double s = ((x -x1) * (y0-y1) - (y -y1) * (x0-x1)) / det;
+ double t = ((x2-x1) * (y -y1) - (y2-y1) * (x -x1)) / det;
+
+ return (s >= 0.0 && t >= 0.0 && s+t <= 1.0);
+}
+
+bool
+ColorWheel::on_focus(Gtk::DirectionType direction)
+{
+ // In forward direction, focus passes from no focus to ring focus to triangle focus to no focus.
+ if (!has_focus()) {
+ _focus_on_ring = (direction == Gtk::DIR_TAB_FORWARD);
+ grab_focus();
+ return true;
+ }
+
+ // Already have focus
+ bool keep_focus = false;
+
+ switch (direction) {
+ case Gtk::DIR_UP:
+ case Gtk::DIR_LEFT:
+ case Gtk::DIR_TAB_BACKWARD:
+ if (!_focus_on_ring) {
+ _focus_on_ring = true;
+ keep_focus = true;
+ }
+ break;
+
+ case Gtk::DIR_DOWN:
+ case Gtk::DIR_RIGHT:
+ case Gtk::DIR_TAB_FORWARD:
+ if (_focus_on_ring) {
+ _focus_on_ring = false;
+ keep_focus = true;
+ }
+ break;
+ }
+
+ queue_draw(); // Update focus indicators.
+
+ return keep_focus;
+}
+
+bool
+ColorWheel::on_button_press_event(GdkEventButton* event)
+{
+ // Seat is automatically grabbed.
+ double x = event->x;
+ double y = event->y;
+
+ if (is_in_ring(x, y) ) {
+ _mode = DRAG_H;
+ grab_focus();
+ _focus_on_ring = true;
+ return true;
+ }
+
+ if (is_in_triangle(x, y)) {
+ _mode = DRAG_SV;
+ grab_focus();
+ _focus_on_ring = false;
+ return true;
+ }
+
+ return false;
+}
+
+bool
+ColorWheel::on_button_release_event(GdkEventButton* event)
+{
+ _mode = DRAG_NONE;
+ return true;
+}
+
+bool
+ColorWheel::on_motion_notify_event(GdkEventMotion* event)
+{
+ double x = event->x;
+ double y = event->y;
+
+ Gtk::Allocation allocation = get_allocation();
+ const int width = allocation.get_width();
+ const int height = allocation.get_height();
+ double cx = width/2.0;
+ double cy = height/2.0;
+ double r = std::min(cx, cy) * (1 - _ring_width);
+
+ if (_mode == DRAG_H) {
+
+ double angle = -atan2(y-cy, x-cx);
+ if (angle < 0) angle += 2.0 * M_PI;
+ _hue = angle / (2.0 * M_PI);
+
+ queue_draw();
+ _signal_color_changed.emit();
+ return true;
+ }
+
+ if (_mode == DRAG_SV) {
+
+ set_from_xy(x, y);
+ _signal_color_changed.emit();
+ queue_draw();
+ return true;
+ }
+
+ return false;
+}
+
+bool
+ColorWheel::on_key_press_event(GdkEventKey* key_event)
+{
+ bool consumed = false;
+
+ unsigned int key = 0;
+ gdk_keymap_translate_keyboard_state( Gdk::Display::get_default()->get_keymap(),
+ key_event->hardware_keycode,
+ (GdkModifierType)key_event->state,
+ 0, &key, nullptr, nullptr, nullptr );
+
+ double x0, y0, x1, y1, x2, y2;
+ triangle_corners(x0, y0, x1, y1, x2, y2);
+
+ // Marker position
+ double mx = x1 + (x2-x1) * _value + (x0-x2) * _saturation * _value;
+ double my = y1 + (y2-y1) * _value + (y0-y2) * _saturation * _value;
+
+
+ const double delta_hue = 2.0/360.0;
+
+ switch (key) {
+
+ case GDK_KEY_Up:
+ case GDK_KEY_KP_Up:
+ if (_focus_on_ring) {
+ _hue += delta_hue;
+ } else {
+ my -= 1.0;
+ set_from_xy(mx, my);
+ }
+ consumed = true;
+ break;
+
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Down:
+ if (_focus_on_ring) {
+ _hue -= delta_hue;
+ } else {
+ my += 1.0;
+ set_from_xy(mx, my);
+ }
+ consumed = true;
+ break;
+
+ case GDK_KEY_Left:
+ case GDK_KEY_KP_Left:
+ if (_focus_on_ring) {
+ _hue += delta_hue;
+ } else {
+ mx -= 1.0;
+ set_from_xy(mx, my);
+ }
+ consumed = true;
+ break;
+
+ case GDK_KEY_Right:
+ case GDK_KEY_KP_Right:
+ if (_focus_on_ring) {
+ _hue -= delta_hue;
+ } else {
+ mx += 1.0;
+ set_from_xy(mx, my);
+ }
+ consumed = true;
+ break;
+
+ }
+
+ if (consumed) {
+ if (_hue >= 1.0) _hue -= 1.0;
+ if (_hue < 0.0) _hue += 1.0;
+ _signal_color_changed.emit();
+ queue_draw();
+ }
+
+ return consumed;
+}
+
+sigc::signal<void>
+ColorWheel::signal_color_changed()
+{
+ return _signal_color_changed;
+}
+
+} // Namespace Inkscape
+}
+}
+
+/*
+ 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/ink-color-wheel.h b/src/ui/widget/ink-color-wheel.h
new file mode 100644
index 0000000..c6333b7
--- /dev/null
+++ b/src/ui/widget/ink-color-wheel.h
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Color wheel widget. Outer ring for Hue. Inner triangle for Saturation and Value.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_COLORWHEEL_H
+#define INK_COLORWHEEL_H
+
+/* Rewrite of the C Gimp ColorWheel which came originally from GTK2. */
+
+#include <gtkmm.h>
+#include <iostream>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class ColorWheel : public Gtk::DrawingArea
+{
+public:
+ ColorWheel();
+ void set_rgb(const double& r, const double& g, const double& b, bool override_hue = true);
+ void get_rgb(double& r, double& g, double& b);
+ guint32 get_rgb();
+ bool is_adjusting() {return _mode != DRAG_NONE;}
+
+protected:
+ bool on_draw(const::Cairo::RefPtr<::Cairo::Context>& cr) override;
+
+private:
+ void triangle_corners(double& x0, double& y0,
+ double& x1, double& y1,
+ double& x2, double& y2);
+ void set_from_xy(const double& x, const double& y);
+ bool is_in_ring( const double& x, const double& y);
+ bool is_in_triangle(const double& x, const double& y);
+
+ enum DragMode {
+ DRAG_NONE,
+ DRAG_H,
+ DRAG_SV
+ };
+
+ double _hue; // Range [0,1)
+ double _saturation;
+ double _value;
+ double _ring_width;
+ DragMode _mode;
+ bool _focus_on_ring;
+
+ // Callbacks
+ bool on_focus(Gtk::DirectionType direction) override;
+ bool on_button_press_event(GdkEventButton* event) override;
+ bool on_button_release_event(GdkEventButton* event) override;
+ bool on_motion_notify_event(GdkEventMotion* event) override;
+ bool on_key_press_event(GdkEventKey* key_event) override;
+
+ // Signals
+public:
+ sigc::signal<void> signal_color_changed();
+
+protected:
+ sigc::signal<void> _signal_color_changed;
+
+};
+
+} // Namespace Inkscape
+}
+}
+#endif // INK_COLOR_WHEEL_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/ink-flow-box.cpp b/src/ui/widget/ink-flow-box.cpp
new file mode 100644
index 0000000..8485dd9
--- /dev/null
+++ b/src/ui/widget/ink-flow-box.cpp
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkflow-box widget.
+ * This widget allow pack widgets in a flowbox with a controller to show-hide
+ *
+ * Author:
+ * Jabier Arraiza <jabier.arraiza@marker.es>
+ *
+ * Copyright (C) 2018 Jabier Arraiza
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "preferences.h"
+#include "ui/icon-loader.h"
+#include "ui/widget/ink-flow-box.h"
+#include <gtkmm/adjustment.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+InkFlowBox::InkFlowBox(const gchar *name)
+{
+ set_name(name);
+ this->pack_start(_controller, false, false, 0);
+ this->pack_start(_flowbox, true, true, 0);
+ _flowbox.set_activate_on_single_click(true);
+ Gtk::ToggleButton *tbutton = new Gtk::ToggleButton("", false);
+ tbutton->set_always_show_image(true);
+ _flowbox.set_selection_mode(Gtk::SelectionMode::SELECTION_NONE);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool(Glib::ustring("/dialogs/") + get_name() + Glib::ustring("/flowbox/lock"), false);
+ tbutton->set_active(prefs->getBool(Glib::ustring("/dialogs/") + get_name() + Glib::ustring("/flowbox/lock"), true));
+ Glib::ustring iconname = "object-unlocked";
+ if (tbutton->get_active()) {
+ iconname = "object-locked";
+ }
+ tbutton->set_image(*sp_get_icon_image(iconname, Gtk::ICON_SIZE_MENU));
+ tbutton->signal_toggled().connect(
+ sigc::bind<Gtk::ToggleButton *>(sigc::mem_fun(*this, &InkFlowBox::on_global_toggle), tbutton));
+ _controller.pack_start(*tbutton);
+ tbutton->hide();
+ tbutton->set_no_show_all(true);
+ showing = 0;
+ sensitive = true;
+}
+
+InkFlowBox::~InkFlowBox() = default;
+
+Glib::ustring InkFlowBox::getPrefsPath(gint pos)
+{
+ return Glib::ustring("/dialogs/") + get_name() + Glib::ustring("/flowbox/index_") + std::to_string(pos);
+}
+
+bool InkFlowBox::on_filter(Gtk::FlowBoxChild *child)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool(getPrefsPath(child->get_index()), true)) {
+ showing++;
+ return true;
+ }
+ return false;
+}
+
+void InkFlowBox::on_toggle(gint pos, Gtk::ToggleButton *tbutton)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool global = prefs->getBool(Glib::ustring("/dialogs/") + get_name() + Glib::ustring("/flowbox/lock"), true);
+ if (global && sensitive) {
+ sensitive = false;
+ bool active = true;
+ for (auto child : tbutton->get_parent()->get_children()) {
+ if (tbutton != child) {
+ dynamic_cast<Gtk::ToggleButton *>(child)->set_active(active);
+ active = false;
+ }
+ }
+ prefs->setBool(getPrefsPath(pos), true);
+ tbutton->set_active(true);
+ sensitive = true;
+ } else {
+ prefs->setBool(getPrefsPath(pos), tbutton->get_active());
+ }
+ showing = 0;
+ _flowbox.set_filter_func(sigc::mem_fun(*this, &InkFlowBox::on_filter));
+ _flowbox.set_max_children_per_line(showing);
+}
+
+void InkFlowBox::on_global_toggle(Gtk::ToggleButton *tbutton)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool(Glib::ustring("/dialogs/") + get_name() + Glib::ustring("/flowbox/lock"), tbutton->get_active());
+ sensitive = true;
+ if (tbutton->get_active()) {
+ sensitive = false;
+ bool active = true;
+ for (auto child : tbutton->get_parent()->get_children()) {
+ if (tbutton != child) {
+ dynamic_cast<Gtk::ToggleButton *>(child)->set_active(active);
+ active = false;
+ }
+ }
+ }
+ Glib::ustring iconname = "object-unlocked";
+ if (tbutton->get_active()) {
+ iconname = "object-locked";
+ }
+ tbutton->set_image(*sp_get_icon_image(iconname, Gtk::ICON_SIZE_MENU));
+ sensitive = true;
+}
+
+void InkFlowBox::insert(Gtk::Widget *widget, Glib::ustring label, gint pos, bool active, int minwidth)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ Gtk::ToggleButton *tbutton = new Gtk::ToggleButton(label, true);
+ tbutton->set_active(prefs->getBool(getPrefsPath(pos), active));
+ tbutton->signal_toggled().connect(
+ sigc::bind<gint, Gtk::ToggleButton *>(sigc::mem_fun(*this, &InkFlowBox::on_toggle), pos, tbutton));
+ _controller.pack_start(*tbutton);
+ tbutton->show();
+ prefs->setBool(getPrefsPath(pos), prefs->getBool(getPrefsPath(pos), active));
+ widget->set_size_request(minwidth, -1);
+ _flowbox.insert(*widget, pos);
+ showing = 0;
+ _flowbox.set_filter_func(sigc::mem_fun(*this, &InkFlowBox::on_filter));
+ _flowbox.set_max_children_per_line(showing);
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/ink-flow-box.h b/src/ui/widget/ink-flow-box.h
new file mode 100644
index 0000000..be84ee9
--- /dev/null
+++ b/src/ui/widget/ink-flow-box.h
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkflow-box widget.
+ * This widget allow pack widgets in a flowbox with a controller to show-hide
+ *
+ * Author:
+ * Jabier Arraiza <jabier.arraiza@marker.es>
+ *
+ * Copyright (C) 2018 Jabier Arraiza
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_INK_FLOW_BOX_H
+#define INKSCAPE_INK_FLOW_BOX_H
+
+#include <gtkmm/actionbar.h>
+#include <gtkmm/box.h>
+#include <gtkmm/flowbox.h>
+#include <gtkmm/flowboxchild.h>
+#include <gtkmm/togglebutton.h>
+#include <sigc++/signal.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * A flowbox widget with filter controller for dialogs.
+ */
+
+class InkFlowBox : public Gtk::VBox {
+ public:
+ InkFlowBox(const gchar *name);
+ ~InkFlowBox() override;
+ void insert(Gtk::Widget *widget, Glib::ustring label, gint pos, bool active, int minwidth);
+ void on_toggle(gint pos, Gtk::ToggleButton *tbutton);
+ void on_global_toggle(Gtk::ToggleButton *tbutton);
+ void set_visible(gint pos, bool visible);
+ bool on_filter(Gtk::FlowBoxChild *child);
+ Glib::ustring getPrefsPath(gint pos);
+ /**
+ * Construct a InkFlowBox.
+ */
+
+ private:
+ Gtk::FlowBox _flowbox;
+ Gtk::ActionBar _controller;
+ gint showing;
+ bool sensitive;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_INK_FLOW_BOX_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/ink-ruler.cpp b/src/ui/widget/ink-ruler.cpp
new file mode 100644
index 0000000..3bb117a
--- /dev/null
+++ b/src/ui/widget/ink-ruler.cpp
@@ -0,0 +1,473 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ruler widget. Indicates horizontal or vertical position of a cursor in a specified widget.
+ *
+ * Copyright (C) 2019 Tavmjong Bah
+ *
+ * Rewrite of the 'C' ruler code which came originally from Gimp.
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include "ink-ruler.h"
+
+#include <iostream>
+#include <cmath>
+
+#include "util/units.h"
+
+struct SPRulerMetric
+{
+ gdouble ruler_scale[16];
+ gint subdivide[5];
+};
+
+// Ruler metric for general use.
+static SPRulerMetric const ruler_metric_general = {
+ { 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000 },
+ { 1, 5, 10, 50, 100 }
+};
+
+// Ruler metric for inch scales.
+static SPRulerMetric const ruler_metric_inches = {
+ { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 },
+ { 1, 2, 4, 8, 16 }
+};
+
+// Half width of pointer triangle.
+static double half_width = 5.0;
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+Ruler::Ruler(Gtk::Orientation orientation)
+ : _orientation(orientation)
+ , _backing_store(nullptr)
+ , _lower(0)
+ , _upper(1000)
+ , _max_size(1000)
+ , _unit(nullptr)
+ , _backing_store_valid(false)
+ , _rect()
+ , _position(0)
+{
+ set_name("InkRuler");
+
+ set_events(Gdk::POINTER_MOTION_MASK);
+
+ signal_motion_notify_event().connect(sigc::mem_fun(*this, &Ruler::draw_marker_callback));
+}
+
+// Set display unit for ruler.
+void
+Ruler::set_unit(Inkscape::Util::Unit const *unit)
+{
+ if (_unit != unit) {
+ _unit = unit;
+
+ _backing_store_valid = false;
+ queue_draw();
+ }
+}
+
+// Set range for ruler, update ticks.
+void
+Ruler::set_range(const double& lower, const double& upper)
+{
+ if (_lower != lower || _upper != upper) {
+
+ _lower = lower;
+ _upper = upper;
+ _max_size = _upper - _lower;
+ if (_max_size == 0) {
+ _max_size = 1;
+ }
+
+ _backing_store_valid = false;
+ queue_draw();
+ }
+}
+
+// Add a widget (i.e. canvas) to monitor. Note, we don't worry about removing this signal as
+// our ruler is tied tightly to the canvas, if one is destroyed, so is the other.
+void
+Ruler::add_track_widget(Gtk::Widget& widget)
+{
+ widget.signal_motion_notify_event().connect(sigc::mem_fun(*this, &Ruler::draw_marker_callback), false); // false => connect first
+}
+
+
+// Draws marker in response to motion events from canvas. Position is defined in ruler pixel
+// coordinates. The routine assumes that the ruler is the same width (height) as the canvas. If
+// not, one could use Gtk::Widget::translate_coordinates() to convert the coordinates.
+bool
+Ruler::draw_marker_callback(GdkEventMotion* motion_event)
+{
+ double position = 0;
+ if (_orientation == Gtk::ORIENTATION_HORIZONTAL) {
+ position = motion_event->x;
+ } else {
+ position = motion_event->y;
+ }
+
+ if (position != _position) {
+
+ _position = position;
+
+ // Find region to repaint (old and new marker positions).
+ Cairo::RectangleInt new_rect = marker_rect();
+ Cairo::RefPtr<Cairo::Region> region = Cairo::Region::create(new_rect);
+ region->do_union(_rect);
+
+ // Queue repaint
+ queue_draw_region(region);
+
+ _rect = new_rect;
+ }
+
+ return false;
+}
+
+
+// Find smallest dimension of ruler based on font size.
+void
+Ruler::size_request (Gtk::Requisition& requisition) const
+{
+ // Get border size
+ Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context();
+ Gtk::Border border = style_context->get_border(get_state_flags());
+
+ // Get font size
+ Pango::FontDescription font = style_context->get_font(get_state_flags());
+ int font_size = font.get_size();
+ if (!font.get_size_is_absolute()) {
+ font_size /= Pango::SCALE;
+ }
+
+ int size = 2 + font_size * 2.0; // Room for labels and ticks
+
+ int width = border.get_left() + border.get_right();
+ int height = border.get_top() + border.get_bottom();
+
+ if (_orientation == Gtk::ORIENTATION_HORIZONTAL) {
+ width += 1;
+ height += size;
+ } else {
+ width += size;
+ height += 1;
+ }
+
+ // Only valid for orientation in question (smallest dimension)!
+ requisition.width = width;
+ requisition.height = height;
+}
+
+void
+Ruler::get_preferred_width_vfunc (int& minimum_width, int& natural_width) const
+{
+ Gtk::Requisition requisition;
+ size_request(requisition);
+ minimum_width = natural_width = requisition.width;
+}
+
+void
+Ruler::get_preferred_height_vfunc (int& minimum_height, int& natural_height) const
+{
+ Gtk::Requisition requisition;
+ size_request(requisition);
+ minimum_height = natural_height = requisition.height;
+}
+
+// Update backing store when scale changes.
+// Note: in principle, there should not be a border (ruler ends should match canvas ends). If there
+// is a border, we calculate tick position ignoring border width at ends of ruler but move the
+// ticks and position marker inside the border.
+bool
+Ruler::draw_scale(const::Cairo::RefPtr<::Cairo::Context>& cr_in)
+{
+
+ // Get style information
+ Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context();
+ Gtk::Border border = style_context->get_border(get_state_flags());
+ Gdk::RGBA foreground = style_context->get_color(get_state_flags());
+
+ Pango::FontDescription font = style_context->get_font(get_state_flags());
+ int font_size = font.get_size();
+ if (!font.get_size_is_absolute()) {
+ font_size /= Pango::SCALE;
+ }
+
+ Gtk::Allocation allocation = get_allocation();
+ int awidth = allocation.get_width();
+ int aheight = allocation.get_height();
+
+ // if (allocation.get_x() != 0 || allocation.get_y() != 0) {
+ // std::cerr << "Ruler::draw_scale: maybe we do have to handle allocation x and y! "
+ // << " x: " << allocation.get_x() << " y: " << allocation.get_y() << std::endl;
+ // }
+
+ // Create backing store (need surface_in to get scale factor correct).
+ Cairo::RefPtr<Cairo::Surface> surface_in = cr_in->get_target();
+ _backing_store = Cairo::Surface::create(surface_in, Cairo::CONTENT_COLOR_ALPHA, awidth, aheight);
+
+ // Get context
+ Cairo::RefPtr<::Cairo::Context> cr = ::Cairo::Context::create(_backing_store);
+ style_context->render_background(cr, 0, 0, awidth, aheight);
+
+ cr->set_line_width(1.0);
+ Gdk::Cairo::set_source_rgba(cr, foreground);
+
+ // Ruler size (only smallest dimension used later).
+ int rwidth = awidth - (border.get_left() + border.get_right());
+ int rheight = aheight - (border.get_top() + border.get_bottom());
+
+ // Draw bottom/right line of ruler
+ if (_orientation == Gtk::ORIENTATION_HORIZONTAL) {
+ cr->rectangle( 0, aheight - border.get_bottom() - 1, awidth, 1);
+ } else {
+ cr->rectangle( awidth - border.get_left() - 1, 0, 1, aheight);
+ std::swap(awidth, aheight);
+ std::swap(rwidth, rheight);
+ }
+ cr->fill();
+
+ // From here on, awidth is the longest dimension of the ruler, rheight is the shortest.
+
+ // Figure out scale. Largest ticks must be far enough apart to fit largest text in vertical ruler.
+ // We actually require twice the distance.
+ unsigned int scale = std::ceil (_max_size); // Largest number
+ Glib::ustring scale_text = std::to_string(scale);
+ unsigned int digits = scale_text.length() + 1; // Add one for negative sign.
+ unsigned int minimum = digits * font_size * 2;
+
+ double pixels_per_unit = awidth/_max_size; // pixel per distance
+
+ SPRulerMetric ruler_metric = ruler_metric_general;
+ if (_unit == Inkscape::Util::unit_table.getUnit("in")) {
+ ruler_metric = ruler_metric_inches;
+ }
+
+ unsigned scale_index;
+ for (scale_index = 0; scale_index < G_N_ELEMENTS (ruler_metric.ruler_scale)-1; ++scale_index) {
+ if (ruler_metric.ruler_scale[scale_index] * std::abs (pixels_per_unit) > minimum) break;
+ }
+
+ // Now we find out what is the subdivide index for the closest ticks we can draw
+ unsigned divide_index;
+ for (divide_index = 0; divide_index < G_N_ELEMENTS (ruler_metric.subdivide)-1; ++divide_index) {
+ if (ruler_metric.ruler_scale[scale_index] * std::abs (pixels_per_unit) < 5 * ruler_metric.subdivide[divide_index+1]) break;
+ }
+
+ // We'll loop over all ticks.
+ double pixels_per_tick = pixels_per_unit *
+ ruler_metric.ruler_scale[scale_index] / ruler_metric.subdivide[divide_index];
+
+ double units_per_tick = pixels_per_tick/pixels_per_unit;
+ double ticks_per_unit = 1.0/units_per_tick;
+
+ // Find first and last ticks
+ int start = 0;
+ int end = 0;
+ if (_lower < _upper) {
+ start = std::floor (_lower * ticks_per_unit);
+ end = std::ceil (_upper * ticks_per_unit);
+ } else {
+ start = std::floor (_upper * ticks_per_unit);
+ end = std::ceil (_lower * ticks_per_unit);
+ }
+
+ // std::cout << " start: " << start
+ // << " end: " << end
+ // << " pixels_per_unit: " << pixels_per_unit
+ // << " pixels_per_tick: " << pixels_per_tick
+ // << std::endl;
+
+ // Loop over all ticks
+ for (int i = start; i < end+1; ++i) {
+
+ // Position of tick (add 0.5 to center tick on pixel).
+ double position = std::floor(i*pixels_per_tick - _lower*pixels_per_unit) + 0.5;
+
+ // Height of tick
+ int height = rheight;
+ for (int j = divide_index; j > 0; --j) {
+ if (i%ruler_metric.subdivide[j] == 0) break;
+ height = height/2 + 1;
+ }
+
+ // Draw text for major ticks.
+ if (i%ruler_metric.subdivide[divide_index] == 0) {
+
+ int label_value = std::round(i*units_per_tick);
+ Glib::ustring label = std::to_string(label_value);
+
+ Glib::RefPtr<Pango::Layout> layout = create_pango_layout("");
+ layout->set_font_description(font);
+
+ if (_orientation == Gtk::ORIENTATION_HORIZONTAL) {
+ layout->set_text(label);
+ cr->move_to (position+2, border.get_top());
+ layout->show_in_cairo_context(cr);
+ } else {
+ cr->move_to (border.get_left(), position);
+ int n = 0;
+ for (char const &c : label) {
+ std::string s(1, c);
+ layout->set_text(s);
+ int text_width;
+ int text_height;
+ layout->get_pixel_size(text_width, text_height);
+ cr->move_to(border.get_left() + (aheight-text_width)/2.0 - 1,
+ position + n*0.7*text_height - 1);
+ layout->show_in_cairo_context(cr);
+ ++n;
+ }
+ // Glyphs are not centered in vertical text... should specify fixed width numbers.
+ // Glib::RefPtr<Pango::Context> context = layout->get_context();
+ // if (_orientation == Gtk::ORIENTATION_VERTICAL) {
+ // context->set_base_gravity(Pango::GRAVITY_EAST);
+ // context->set_gravity_hint(Pango::GRAVITY_HINT_STRONG);
+ // cr->move_to(...)
+ // cr->save();
+ // cr->rotate(M_PI_2);
+ // layout->show_in_cairo_context(cr);
+ // cr->restore();
+ // }
+ }
+ }
+
+ // Draw ticks
+ if (_orientation == Gtk::ORIENTATION_HORIZONTAL) {
+ cr->move_to(position, rheight + border.get_top() - height);
+ cr->line_to(position, rheight + border.get_top());
+ } else {
+ cr->move_to(rheight + border.get_left() - height, position);
+ cr->line_to(rheight + border.get_left(), position);
+ }
+ cr->stroke();
+ }
+
+ _backing_store_valid = true;
+
+ return true;
+}
+
+// Draw position marker, we use doubles here.
+void
+Ruler::draw_marker(const Cairo::RefPtr<::Cairo::Context>& cr)
+{
+
+ // Get style information
+ Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context();
+ Gtk::Border border = style_context->get_border(get_state_flags());
+ Gdk::RGBA foreground = style_context->get_color(get_state_flags());
+
+ Gtk::Allocation allocation = get_allocation();
+ const int awidth = allocation.get_width();
+ const int aheight = allocation.get_height();
+
+ // Temp (to verify our redraw rectangle encloses position marker).
+ // Cairo::RectangleInt rect = marker_rect();
+ // cr->set_source_rgb(0, 1.0, 0);
+ // cr->rectangle (rect.x, rect.y, rect.width, rect.height);
+ // cr->fill();
+
+ Gdk::Cairo::set_source_rgba(cr, foreground);
+ if (_orientation == Gtk::ORIENTATION_HORIZONTAL) {
+ double offset = aheight - border.get_bottom();
+ cr->move_to(_position, offset);
+ cr->line_to(_position - half_width, offset - half_width);
+ cr->line_to(_position + half_width, offset - half_width);
+ cr->close_path();
+ } else {
+ double offset = awidth - border.get_right();
+ cr->move_to(offset, _position);
+ cr->line_to(offset - half_width, _position - half_width);
+ cr->line_to(offset - half_width, _position + half_width);
+ cr->close_path();
+ }
+ cr->fill();
+}
+
+// This is a pixel aligned integer rectangle that encloses the position marker. Used to define the
+// redraw area.
+Cairo::RectangleInt
+Ruler::marker_rect()
+{
+ // Get border size
+ Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context();
+ Gtk::Border border = style_context->get_border(get_state_flags());
+
+ Gtk::Allocation allocation = get_allocation();
+ const int awidth = allocation.get_width();
+ const int aheight = allocation.get_height();
+
+ int rwidth = awidth - border.get_left() - border.get_right();
+ int rheight = aheight - border.get_top() - border.get_bottom();
+
+ Cairo::RectangleInt rect;
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = 0;
+ rect.height = 0;
+
+ // Find size of rectangle to enclose triangle.
+ if (_orientation == Gtk::ORIENTATION_HORIZONTAL) {
+ rect.x = std::floor(_position - half_width);
+ rect.y = std::floor(border.get_top() + rheight - half_width);
+ rect.width = std::ceil(half_width * 2.0 + 1);
+ rect.height = std::ceil(half_width);
+ } else {
+ rect.x = std::floor(border.get_left() + rwidth - half_width);
+ rect.y = std::floor(_position - half_width);
+ rect.width = std::ceil(half_width);
+ rect.height = std::ceil(half_width * 2.0 + 1);
+ }
+
+ return rect;
+}
+
+// Draw the ruler using the tick backing store.
+bool
+Ruler::on_draw(const::Cairo::RefPtr<::Cairo::Context>& cr) {
+
+ if (!_backing_store_valid) {
+ draw_scale (cr);
+ }
+
+ cr->set_source (_backing_store, 0, 0);
+ cr->paint();
+
+ draw_marker (cr);
+
+ return true;
+}
+
+// Update ruler on style change (font-size, etc.)
+void
+Ruler::on_style_updated() {
+
+ Gtk::DrawingArea::on_style_updated();
+
+ _backing_store_valid = false; // If font-size changed we need to regenerate store.
+
+ queue_resize();
+ queue_draw();
+}
+
+} // Namespace Inkscape
+}
+}
+
+/*
+ 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/ink-ruler.h b/src/ui/widget/ink-ruler.h
new file mode 100644
index 0000000..0b9f9af
--- /dev/null
+++ b/src/ui/widget/ink-ruler.h
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ruler widget. Indicates horizontal or vertical position of a cursor in a specified widget.
+ *
+ * Copyright (C) 2019 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_RULER_H
+#define INK_RULER_H
+
+/* Rewrite of the C Ruler. */
+
+#include <gtkmm.h>
+
+namespace Inkscape {
+namespace Util {
+class Unit;
+}
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class Ruler : public Gtk::DrawingArea
+{
+public:
+ Ruler(Gtk::Orientation orientation);
+
+ void set_unit(Inkscape::Util::Unit const *unit);
+ void set_range(const double& lower, const double& upper);
+
+ void add_track_widget(Gtk::Widget& widget);
+ bool draw_marker_callback(GdkEventMotion* motion_event);
+
+ void size_request(Gtk::Requisition& requisition) const;
+ void get_preferred_width_vfunc( int& minimum_width, int& natural_width ) const override;
+ void get_preferred_height_vfunc(int& minimum_height, int& natural_height) const override;
+
+protected:
+ bool draw_scale(const Cairo::RefPtr<::Cairo::Context>& cr);
+ void draw_marker(const Cairo::RefPtr<::Cairo::Context>& cr);
+ Cairo::RectangleInt marker_rect();
+ bool on_draw(const::Cairo::RefPtr<::Cairo::Context>& cr) override;
+ void on_style_updated() override;
+
+private:
+ Gtk::Orientation _orientation;
+
+ Inkscape::Util::Unit const* _unit;
+ double _lower;
+ double _upper;
+ double _position;
+ double _max_size;
+ double _font_scale;
+
+ bool _backing_store_valid;
+
+ Cairo::RefPtr<::Cairo::Surface> _backing_store;
+ Cairo::RectangleInt _rect;
+};
+
+} // Namespace Inkscape
+}
+}
+#endif // INK_RULER_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/ink-spinscale.cpp b/src/ui/widget/ink-spinscale.cpp
new file mode 100644
index 0000000..7c546c2
--- /dev/null
+++ b/src/ui/widget/ink-spinscale.cpp
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+/** \file
+ A widget that allows entering a numerical value either by
+ clicking/dragging on a custom Gtk::Scale or by using a
+ Gtk::SpinButton. The custom Gtk::Scale differs from the stock
+ Gtk::Scale in that it includes a label to save space and has a
+ "slow dragging" mode triggered by the Alt key.
+*/
+
+#include "ink-spinscale.h"
+#include <gdkmm/general.h>
+#include <gdkmm/cursor.h>
+#include <gdkmm/event.h>
+
+#include <gtkmm/spinbutton.h>
+
+#include <gdk/gdk.h>
+
+#include <iostream>
+#include <utility>
+
+InkScale::InkScale(Glib::RefPtr<Gtk::Adjustment> adjustment, Gtk::SpinButton* spinbutton)
+ : Glib::ObjectBase("InkScale")
+ , Gtk::Scale(adjustment)
+ , _spinbutton(spinbutton)
+ , _dragging(false)
+ , _drag_start(0)
+ , _drag_offset(0)
+{
+ set_name("InkScale");
+ // std::cout << "GType name: " << G_OBJECT_TYPE_NAME(gobj()) << std::endl;
+}
+
+void
+InkScale::set_label(Glib::ustring label) {
+ _label = label;
+}
+
+bool
+InkScale::on_draw(const::Cairo::RefPtr<::Cairo::Context>& cr) {
+
+ Gtk::Range::on_draw(cr);
+
+ // Get SpinButton style info...
+ auto style_spin = _spinbutton->get_style_context();
+ auto state_spin = style_spin->get_state();
+ Gdk::RGBA text_color = style_spin->get_color( state_spin );
+
+ // Create Pango layout.
+ auto layout_label = create_pango_layout(_label);
+ layout_label->set_ellipsize( Pango::ELLIPSIZE_END );
+ layout_label->set_width(PANGO_SCALE * get_width());
+
+ // Get y location of SpinButton text (to match vertical position of SpinButton text).
+ int x, y;
+ _spinbutton->get_layout_offsets(x, y);
+
+ // Fill widget proportional to value.
+ double fraction = get_fraction();
+
+ // Get trough rectangle and clipping point for text.
+ Gdk::Rectangle slider_area = get_range_rect();
+ double clip_text_x = slider_area.get_x() + slider_area.get_width() * fraction;
+
+ // Render text in normal text color.
+ cr->save();
+ cr->rectangle(clip_text_x, 0, get_width(), get_height());
+ cr->clip();
+ Gdk::Cairo::set_source_rgba(cr, text_color);
+ //cr->set_source_rgba(0, 0, 0, 1);
+ cr->move_to(5, y );
+ layout_label->show_in_cairo_context(cr);
+ cr->restore();
+
+ // Render text, clipped, in white over bar (TODO: use same color as SpinButton progress bar).
+ cr->save();
+ cr->rectangle(0, 0, clip_text_x, get_height());
+ cr->clip();
+ cr->set_source_rgba(1, 1, 1, 1);
+ cr->move_to(5, y);
+ layout_label->show_in_cairo_context(cr);
+ cr->restore();
+
+ return true;
+}
+
+bool
+InkScale::on_button_press_event(GdkEventButton* button_event) {
+
+ if (! (button_event->state & GDK_MOD1_MASK) ) {
+ bool constrained = button_event->state & GDK_CONTROL_MASK;
+ set_adjustment_value(button_event->x, constrained);
+ }
+
+ // Dragging must be initialized after any adjustment due to button press.
+ _dragging = true;
+ _drag_start = button_event->x;
+ _drag_offset = get_width() * get_fraction();
+
+ return true;
+}
+
+bool
+InkScale::on_button_release_event(GdkEventButton* button_event) {
+
+ _dragging = false;
+ return true;
+}
+
+bool
+InkScale::on_motion_notify_event(GdkEventMotion* motion_event) {
+
+ double x = motion_event->x;
+ double y = motion_event->y;
+
+ if (_dragging) {
+
+ if (! (motion_event->state & GDK_MOD1_MASK) ) {
+ // Absolute change
+ bool constrained = motion_event->state & GDK_CONTROL_MASK;
+ set_adjustment_value(x, constrained);
+ } else {
+ // Relative change
+ double xx = (_drag_offset + (x - _drag_start) * 0.1);
+ set_adjustment_value(xx);
+ }
+ return true;
+ }
+
+ if (! (motion_event->state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK))) {
+
+ auto display = get_display();
+ auto cursor = Gdk::Cursor::create(display, Gdk::SB_UP_ARROW);
+ // Get Gdk::window (not Gtk::window).. set cursor for entire window.
+ // Would need to unset with leave event.
+ // get_window()->set_cursor( cursor );
+
+ // Can't see how to do this the C++ way since GdkEventMotion
+ // is a structure with a C window member. There is a gdkmm
+ // wrapping function for Gdk::EventMotion but only in unstable.
+
+ // If the cursor theme doesn't have the `sb_up_arrow` cursor then the pointer will be NULL
+ if (cursor)
+ gdk_window_set_cursor( motion_event->window, cursor->gobj() );
+ }
+
+ return false;
+}
+
+double
+InkScale::get_fraction() {
+
+ Glib::RefPtr<Gtk::Adjustment> adjustment = get_adjustment();
+ double upper = adjustment->get_upper();
+ double lower = adjustment->get_lower();
+ double value = adjustment->get_value();
+ double fraction = (value - lower)/(upper - lower);
+
+ return fraction;
+}
+
+void
+InkScale::set_adjustment_value(double x, bool constrained) {
+
+ Glib::RefPtr<Gtk::Adjustment> adjustment = get_adjustment();
+ double upper = adjustment->get_upper();
+ double lower = adjustment->get_lower();
+ double range = upper-lower;
+
+ Gdk::Rectangle slider_area = get_range_rect();
+ double fraction = (x - slider_area.get_x()) / (double)slider_area.get_width();
+ double value = fraction * range + lower;
+
+ if (constrained) {
+ // TODO: do we want preferences for (any of) these?
+ if (fmod(range+1,16) == 0) {
+ value = round(value/16) * 16;
+ } else if (range >= 1000 && fmod(upper,100) == 0) {
+ value = round(value/100) * 100;
+ } else if (range >= 100 && fmod(upper,10) == 0) {
+ value = round(value/10) * 10;
+ } else if (range > 20 && fmod(upper,5) == 0) {
+ value = round(value/5) * 5;
+ } else if (range > 2) {
+ value = round(value);
+ } else if (range <= 2) {
+ value = round(value*10) / 10;
+ }
+ }
+
+ adjustment->set_value( value );
+}
+
+/*******************************************************************/
+
+InkSpinScale::InkSpinScale(double value, double lower,
+ double upper, double step_increment,
+ double page_increment, double page_size)
+{
+ set_name("InkSpinScale");
+
+ g_assert (upper - lower > 0);
+
+ _adjustment = Gtk::Adjustment::create(value,
+ lower,
+ upper,
+ step_increment,
+ page_increment,
+ page_size);
+
+ _spinbutton = Gtk::manage(new Gtk::SpinButton(_adjustment));
+ _spinbutton->set_numeric();
+ _spinbutton->signal_key_release_event().connect(sigc::mem_fun(*this,&InkSpinScale::on_key_release_event),false);
+
+ _scale = Gtk::manage(new InkScale(_adjustment, _spinbutton));
+ _scale->set_draw_value(false);
+
+ pack_end( *_spinbutton, Gtk::PACK_SHRINK );
+ pack_end( *_scale, Gtk::PACK_EXPAND_WIDGET );
+}
+
+InkSpinScale::InkSpinScale(Glib::RefPtr<Gtk::Adjustment> adjustment)
+ : _adjustment(std::move(adjustment))
+{
+ set_name("InkSpinScale");
+
+ g_assert (_adjustment->get_upper() - _adjustment->get_lower() > 0);
+
+ _spinbutton = Gtk::manage(new Gtk::SpinButton(_adjustment));
+ _spinbutton->set_numeric();
+
+ _scale = Gtk::manage(new InkScale(_adjustment, _spinbutton));
+ _scale->set_draw_value(false);
+
+ pack_end( *_spinbutton, Gtk::PACK_SHRINK );
+ pack_end( *_scale, Gtk::PACK_EXPAND_WIDGET );
+}
+
+void
+InkSpinScale::set_label(Glib::ustring label) {
+ _scale->set_label(label);
+}
+
+void
+InkSpinScale::set_digits(int digits) {
+ _spinbutton->set_digits(digits);
+}
+
+int
+InkSpinScale::get_digits() const {
+ return _spinbutton->get_digits();
+}
+
+void
+InkSpinScale::set_focus_widget(GtkWidget * focus_widget) {
+ _focus_widget = focus_widget;
+}
+
+// Return focus to canvas.
+bool
+InkSpinScale::on_key_release_event(GdkEventKey* key_event) {
+
+ switch (key_event->keyval) {
+ case GDK_KEY_Escape:
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ {
+ if (_focus_widget) {
+ gtk_widget_grab_focus( _focus_widget );
+ }
+ }
+ break;
+ }
+
+ return false;
+}
diff --git a/src/ui/widget/ink-spinscale.h b/src/ui/widget/ink-spinscale.h
new file mode 100644
index 0000000..ada7efd
--- /dev/null
+++ b/src/ui/widget/ink-spinscale.h
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INK_SPINSCALE_H
+#define INK_SPINSCALE_H
+
+/*
+ * Authors:
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+/**
+ A widget that allows entering a numerical value either by
+ clicking/dragging on a custom Gtk::Scale or by using a
+ Gtk::SpinButton. The custom Gtk::Scale differs from the stock
+ Gtk::Scale in that it includes a label to save space and has a
+ "slow-dragging" mode triggered by the Alt key.
+*/
+
+#include <glibmm/ustring.h>
+
+#include <gtkmm/box.h>
+#include <gtkmm/scale.h>
+
+namespace Gtk {
+ class SpinButton;
+}
+
+class InkScale : public Gtk::Scale
+{
+ public:
+ InkScale(Glib::RefPtr<Gtk::Adjustment>, Gtk::SpinButton* spinbutton);
+ ~InkScale() override = default;;
+
+ void set_label(Glib::ustring label);
+
+ bool on_draw(const::Cairo::RefPtr<::Cairo::Context>& cr) override;
+
+ protected:
+
+ bool on_button_press_event(GdkEventButton* button_event) override;
+ bool on_button_release_event(GdkEventButton* button_event) override;
+ bool on_motion_notify_event(GdkEventMotion* motion_event) override;
+
+ private:
+
+ double get_fraction();
+ void set_adjustment_value(double x, bool constrained = false);
+
+ Gtk::SpinButton * _spinbutton; // Needed to get placement/text color.
+ Glib::ustring _label;
+
+ bool _dragging;
+ double _drag_start;
+ double _drag_offset;
+};
+
+class InkSpinScale : public Gtk::Box
+{
+ public:
+
+ // Create an InkSpinScale with a new adjustment.
+ InkSpinScale(double value,
+ double lower,
+ double upper,
+ double step_increment = 1,
+ double page_increment = 10,
+ double page_size = 0);
+
+ // Create an InkSpinScale with a preexisting adjustment.
+ InkSpinScale(Glib::RefPtr<Gtk::Adjustment>);
+
+ ~InkSpinScale() override = default;;
+
+ void set_label(Glib::ustring label);
+ void set_digits(int digits);
+ int get_digits() const;
+ void set_focus_widget(GtkWidget *focus_widget);
+ Glib::RefPtr<Gtk::Adjustment> get_adjustment() { return _adjustment; };
+
+ protected:
+
+ InkScale* _scale;
+ Gtk::SpinButton* _spinbutton;
+ Glib::RefPtr<Gtk::Adjustment> _adjustment;
+ GtkWidget* _focus_widget = nullptr;
+
+ bool on_key_release_event(GdkEventKey* key_event) override;
+
+ private:
+
+};
+
+#endif // INK_SPINSCALE_H
diff --git a/src/ui/widget/insertordericon.cpp b/src/ui/widget/insertordericon.cpp
new file mode 100644
index 0000000..0a21f3c
--- /dev/null
+++ b/src/ui/widget/insertordericon.cpp
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Theodore Janeczko
+ *
+ * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "ui/widget/insertordericon.h"
+
+#include "layertypeicon.h"
+#include "ui/icon-loader.h"
+#include "ui/icon-names.h"
+#include "widgets/toolbox.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+InsertOrderIcon::InsertOrderIcon() :
+ Glib::ObjectBase(typeid(InsertOrderIcon)),
+ Gtk::CellRendererPixbuf(),
+ _pixTopName(INKSCAPE_ICON("insert-top")),
+ _pixBottomName(INKSCAPE_ICON("insert-bottom")),
+ _property_active(*this, "active", 0),
+ _property_pixbuf_top(*this, "pixbuf_on", Glib::RefPtr<Gdk::Pixbuf>(nullptr)),
+ _property_pixbuf_bottom(*this, "pixbuf_on", Glib::RefPtr<Gdk::Pixbuf>(nullptr))
+{
+
+ property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE;
+
+ _property_pixbuf_top = sp_get_icon_pixbuf(_pixTopName, GTK_ICON_SIZE_MENU);
+ _property_pixbuf_bottom = sp_get_icon_pixbuf(_pixBottomName, GTK_ICON_SIZE_MENU);
+
+ property_pixbuf() = Glib::RefPtr<Gdk::Pixbuf>(nullptr);
+}
+
+
+void InsertOrderIcon::get_preferred_height_vfunc(Gtk::Widget& widget,
+ int& min_h,
+ int& nat_h) const
+{
+ Gtk::CellRendererPixbuf::get_preferred_height_vfunc(widget, min_h, nat_h);
+
+ if (min_h) {
+ min_h += (min_h) >> 1;
+ }
+
+ if (nat_h) {
+ nat_h += (nat_h) >> 1;
+ }
+}
+
+void InsertOrderIcon::get_preferred_width_vfunc(Gtk::Widget& widget,
+ int& min_w,
+ int& nat_w) const
+{
+ Gtk::CellRendererPixbuf::get_preferred_width_vfunc(widget, min_w, nat_w);
+
+ if (min_w) {
+ min_w += (min_w) >> 1;
+ }
+
+ if (nat_w) {
+ nat_w += (nat_w) >> 1;
+ }
+}
+
+void InsertOrderIcon::render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ Gtk::CellRendererState flags )
+{
+ switch (_property_active.get_value())
+ {
+ case 1:
+ property_pixbuf() = _property_pixbuf_top;
+ break;
+ case 2:
+ property_pixbuf() = _property_pixbuf_bottom;
+ break;
+ default:
+ property_pixbuf() = Glib::RefPtr<Gdk::Pixbuf>(nullptr);
+ break;
+ }
+ Gtk::CellRendererPixbuf::render_vfunc( cr, widget, background_area, cell_area, flags );
+}
+
+bool InsertOrderIcon::activate_vfunc(GdkEvent* /*event*/,
+ Gtk::Widget& /*widget*/,
+ const Glib::ustring& /*path*/,
+ const Gdk::Rectangle& /*background_area*/,
+ const Gdk::Rectangle& /*cell_area*/,
+ Gtk::CellRendererState /*flags*/)
+{
+ return false;
+}
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/insertordericon.h b/src/ui/widget/insertordericon.h
new file mode 100644
index 0000000..7ca1cef
--- /dev/null
+++ b/src/ui/widget/insertordericon.h
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef __UI_DIALOG_INSERTORDERICON_H__
+#define __UI_DIALOG_INSERTORDERICON_H__
+/*
+ * Authors:
+ * Theodore Janeczko
+ *
+ * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/cellrendererpixbuf.h>
+#include <gtkmm/widget.h>
+
+#include <glibmm/property.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class InsertOrderIcon : public Gtk::CellRendererPixbuf {
+public:
+ InsertOrderIcon();
+ ~InsertOrderIcon() override = default;;
+
+ Glib::PropertyProxy<int> property_active() { return _property_active.get_proxy(); }
+ Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_on();
+ Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_off();
+
+protected:
+
+ void render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ Gtk::CellRendererState flags ) override;
+
+ void get_preferred_width_vfunc(Gtk::Widget& widget,
+ int& min_w,
+ int& nat_w) const override;
+
+ void get_preferred_height_vfunc(Gtk::Widget& widget,
+ int& min_h,
+ int& nat_h) const override;
+
+ bool activate_vfunc(GdkEvent *event,
+ Gtk::Widget &widget,
+ const Glib::ustring &path,
+ const Gdk::Rectangle &background_area,
+ const Gdk::Rectangle &cell_area,
+ Gtk::CellRendererState flags) override;
+
+
+private:
+ int phys;
+
+ Glib::ustring _pixTopName;
+ Glib::ustring _pixBottomName;
+
+ Glib::Property<int> _property_active;
+ Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_top;
+ Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_bottom;
+
+};
+
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+
+#endif /* __UI_DIALOG_IMAGETOGGLER_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/label-tool-item.cpp b/src/ui/widget/label-tool-item.cpp
new file mode 100644
index 0000000..979cfa2
--- /dev/null
+++ b/src/ui/widget/label-tool-item.cpp
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * A label that can be added to a toolbar
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "label-tool-item.h"
+
+#include <gtkmm/label.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * \brief Create a tool-item containing a label
+ *
+ * \param[in] label The text to display in the label
+ * \param[in] mnemonic True if text should use a mnemonic
+ */
+LabelToolItem::LabelToolItem(const Glib::ustring& label, bool mnemonic)
+ : _label(Gtk::manage(new Gtk::Label(label, mnemonic)))
+{
+ add(*_label);
+ show_all();
+}
+
+/**
+ * \brief Set the markup text in the label
+ *
+ * \param[in] str The markup text
+ */
+void
+LabelToolItem::set_markup(const Glib::ustring& str)
+{
+ _label->set_markup(str);
+}
+
+/**
+ * \brief Sets whether label uses Pango markup
+ *
+ * \param[in] setting true if the label text should be parsed for markup
+ */
+void
+LabelToolItem::set_use_markup(bool setting)
+{
+ _label->set_use_markup(setting);
+}
+
+}
+}
+}
+/*
+ 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/label-tool-item.h b/src/ui/widget/label-tool-item.h
new file mode 100644
index 0000000..1fe6892
--- /dev/null
+++ b/src/ui/widget/label-tool-item.h
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * A label that can be added to a toolbar
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_LABEL_TOOL_ITEM_H
+#define SEEN_LABEL_TOOL_ITEM_H
+
+#include <gtkmm/toolitem.h>
+
+namespace Gtk {
+class Label;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * \brief A label that can be added to a toolbar
+ */
+class LabelToolItem : public Gtk::ToolItem {
+private:
+ Gtk::Label *_label;
+
+public:
+ LabelToolItem(const Glib::ustring& label, bool mnemonic = false);
+
+ void set_markup(const Glib::ustring& str);
+ void set_use_markup(bool setting = true);
+};
+}
+}
+}
+
+#endif
+/*
+ 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/labelled.cpp b/src/ui/widget/labelled.cpp
new file mode 100644
index 0000000..b320b70
--- /dev/null
+++ b/src/ui/widget/labelled.cpp
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Carl Hetherington <inkscape@carlh.net>
+ * Derek P. Moore <derekm@hackunix.org>
+ *
+ * Copyright (C) 2004 Carl Hetherington
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "labelled.h"
+#include "ui/icon-loader.h"
+#include <gtkmm/image.h>
+#include <gtkmm/label.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+Labelled::Labelled(Glib::ustring const &label, Glib::ustring const &tooltip,
+ Gtk::Widget *widget,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : _widget(widget),
+ _label(new Gtk::Label(label, Gtk::ALIGN_START, Gtk::ALIGN_CENTER, mnemonic)),
+ _suffix(new Gtk::Label(suffix, Gtk::ALIGN_START))
+{
+ g_assert(g_utf8_validate(icon.c_str(), -1, nullptr));
+ if (icon != "") {
+ _icon = Gtk::manage(sp_get_icon_image(icon, Gtk::ICON_SIZE_LARGE_TOOLBAR));
+ pack_start(*_icon, Gtk::PACK_SHRINK);
+ }
+
+ set_spacing(6);
+ // Setting margins separately allows for more control over them
+ set_margin_start(6);
+ set_margin_end(6);
+ pack_start(*Gtk::manage(_label), Gtk::PACK_SHRINK);
+ pack_start(*Gtk::manage(_widget), Gtk::PACK_SHRINK);
+ if (mnemonic) {
+ _label->set_mnemonic_widget(*_widget);
+ }
+ widget->set_tooltip_text(tooltip);
+}
+
+
+void Labelled::setWidgetSizeRequest(int width, int height)
+{
+ if (_widget)
+ _widget->set_size_request(width, height);
+
+
+}
+
+Gtk::Widget const *
+Labelled::getWidget() const
+{
+ return _widget;
+}
+
+Gtk::Label const *
+Labelled::getLabel() const
+{
+ return _label;
+}
+
+void
+Labelled::setLabelText(const Glib::ustring &str)
+{
+ _label->set_text(str);
+}
+
+void
+Labelled::setTooltipText(const Glib::ustring &tooltip)
+{
+ _label->set_tooltip_text(tooltip);
+ _widget->set_tooltip_text(tooltip);
+}
+
+bool Labelled::on_mnemonic_activate ( bool group_cycling )
+{
+ return _widget->mnemonic_activate ( group_cycling );
+}
+
+void
+Labelled::set_hexpand(bool expand)
+{
+ // should only have 2 children, but second child may not be _widget
+ child_property_pack_type(*get_children().back()) = expand ? Gtk::PACK_END
+ : Gtk::PACK_START;
+
+ Gtk::HBox::set_hexpand(expand);
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/labelled.h b/src/ui/widget/labelled.h
new file mode 100644
index 0000000..4620b1a
--- /dev/null
+++ b/src/ui/widget/labelled.h
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Carl Hetherington <inkscape@carlh.net>
+ * Derek P. Moore <derekm@hackunix.org>
+ *
+ * Copyright (C) 2004 Carl Hetherington
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_LABELLED_H
+#define INKSCAPE_UI_WIDGET_LABELLED_H
+
+#include <gtkmm/box.h>
+
+namespace Gtk {
+class Image;
+class Label;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * Adds a label with optional icon or suffix to another widget.
+ */
+class Labelled : public Gtk::HBox
+{
+public:
+
+ /**
+ * Construct a Labelled Widget.
+ *
+ * @param label Label.
+ * @param widget Widget to label; should be allocated with new, as it will
+ * be passed to Gtk::manage().
+ * @param suffix Suffix, placed after the widget (defaults to "").
+ * @param icon Icon filename, placed before the label (defaults to "").
+ * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the text
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to true).
+ */
+ Labelled(Glib::ustring const &label, Glib::ustring const &tooltip,
+ Gtk::Widget *widget,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ /**
+ * Allow the setting of the width of the labelled widget
+ */
+ void setWidgetSizeRequest(int width, int height);
+ Gtk::Widget const *getWidget() const;
+ Gtk::Label const *getLabel() const;
+
+ void setLabelText(const Glib::ustring &str);
+ void setTooltipText(const Glib::ustring &tooltip);
+
+ void set_hexpand(bool expand = true);
+
+private:
+ bool on_mnemonic_activate( bool group_cycling ) override;
+
+protected:
+
+ Gtk::Widget *_widget;
+ Gtk::Label *_label;
+ Gtk::Label *_suffix;
+ Gtk::Image *_icon;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_LABELLED_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/layer-selector.cpp b/src/ui/widget/layer-selector.cpp
new file mode 100644
index 0000000..779542c
--- /dev/null
+++ b/src/ui/widget/layer-selector.cpp
@@ -0,0 +1,616 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::Widgets::LayerSelector - layer selector widget
+ *
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2004 MenTaLguY
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstring>
+#include <string>
+
+#include "ui/dialog/layer-properties.h"
+#include "ui/icon-loader.h"
+#include <boost/range/adaptor/filtered.hpp>
+#include <boost/range/adaptor/reversed.hpp>
+#include <glibmm/i18n.h>
+
+#include "desktop.h"
+
+#include "document.h"
+#include "document-undo.h"
+#include "layer-manager.h"
+#include "ui/icon-names.h"
+#include "ui/util.h"
+#include "util/reverse-list.h"
+#include "verbs.h"
+#include "xml/node-event-vector.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+namespace {
+
+class AlternateIcons : public Gtk::HBox {
+public:
+ AlternateIcons(Gtk::BuiltinIconSize size, Glib::ustring const &a, Glib::ustring const &b)
+ : _a(nullptr), _b(nullptr)
+ {
+ set_name("AlternateIcons");
+ if (!a.empty()) {
+ _a = Gtk::manage(sp_get_icon_image(a, size));
+ _a->set_no_show_all(true);
+ add(*_a);
+ }
+ if (!b.empty()) {
+ _b = Gtk::manage(sp_get_icon_image(b, size));
+ _b->set_no_show_all(true);
+ add(*_b);
+ }
+ setState(false);
+ }
+
+ bool state() const { return _state; }
+ void setState(bool state) {
+ _state = state;
+ if (_state) {
+ if (_a) {
+ _a->hide();
+ }
+ if (_b) {
+ _b->show();
+ }
+ } else {
+ if (_a) {
+ _a->show();
+ }
+ if (_b) {
+ _b->hide();
+ }
+ }
+ }
+
+private:
+ Gtk::Image *_a;
+ Gtk::Image *_b;
+ bool _state;
+};
+
+}
+
+/** LayerSelector constructor. Creates lock and hide buttons,
+ * initializes the layer dropdown selector with a label renderer,
+ * and hooks up signal for setting the desktop layer when the
+ * selector is changed.
+ */
+LayerSelector::LayerSelector(SPDesktop *desktop)
+: _desktop(nullptr), _layer(nullptr)
+{
+ set_name("LayerSelector");
+ AlternateIcons *label;
+
+ label = Gtk::manage(new AlternateIcons(Gtk::ICON_SIZE_MENU,
+ INKSCAPE_ICON("object-visible"), INKSCAPE_ICON("object-hidden")));
+ _visibility_toggle.add(*label);
+ _visibility_toggle.signal_toggled().connect(
+ sigc::compose(
+ sigc::mem_fun(*label, &AlternateIcons::setState),
+ sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active)
+ )
+ );
+ _visibility_toggled_connection = _visibility_toggle.signal_toggled().connect(
+ sigc::compose(
+ sigc::mem_fun(*this, &LayerSelector::_hideLayer),
+ sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active)
+ )
+ );
+
+ _visibility_toggle.set_relief(Gtk::RELIEF_NONE);
+ _visibility_toggle.set_tooltip_text(_("Toggle current layer visibility"));
+ pack_start(_visibility_toggle, Gtk::PACK_EXPAND_PADDING);
+
+ label = Gtk::manage(new AlternateIcons(Gtk::ICON_SIZE_MENU,
+ INKSCAPE_ICON("object-unlocked"), INKSCAPE_ICON("object-locked")));
+ _lock_toggle.add(*label);
+ _lock_toggle.signal_toggled().connect(
+ sigc::compose(
+ sigc::mem_fun(*label, &AlternateIcons::setState),
+ sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active)
+ )
+ );
+ _lock_toggled_connection = _lock_toggle.signal_toggled().connect(
+ sigc::compose(
+ sigc::mem_fun(*this, &LayerSelector::_lockLayer),
+ sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active)
+ )
+ );
+
+ _lock_toggle.set_relief(Gtk::RELIEF_NONE);
+ _lock_toggle.set_tooltip_text(_("Lock or unlock current layer"));
+ pack_start(_lock_toggle, Gtk::PACK_EXPAND_PADDING);
+
+ _selector.set_tooltip_text(_("Current layer"));
+ pack_start(_selector, Gtk::PACK_EXPAND_WIDGET);
+
+ _layer_model = Gtk::ListStore::create(_model_columns);
+ _selector.set_model(_layer_model);
+ _selector.pack_start(_label_renderer);
+ _selector.set_cell_data_func(
+ _label_renderer,
+ sigc::mem_fun(*this, &LayerSelector::_prepareLabelRenderer)
+ );
+
+ _selection_changed_connection = _selector.signal_changed().connect(
+ sigc::mem_fun(*this, &LayerSelector::_setDesktopLayer)
+ );
+ setDesktop(desktop);
+}
+
+/** Destructor - disconnects signal handler
+ */
+LayerSelector::~LayerSelector() {
+ setDesktop(nullptr);
+ _selection_changed_connection.disconnect();
+}
+
+/** Sets the desktop for the widget. First disconnects signals
+ * for the current desktop, then stores the pointer to the
+ * given \a desktop, and attaches its signals to this one.
+ * Then it selects the current layer for the desktop.
+ */
+void LayerSelector::setDesktop(SPDesktop *desktop) {
+ if ( desktop == _desktop ) {
+ return;
+ }
+
+ if (_desktop) {
+// _desktop_shutdown_connection.disconnect();
+ if (_current_layer_changed_connection)
+ _current_layer_changed_connection.disconnect();
+ if (_layers_changed_connection)
+ _layers_changed_connection.disconnect();
+// g_signal_handlers_disconnect_by_func(_desktop, (gpointer)&detach, this);
+ }
+ _desktop = desktop;
+ if (_desktop) {
+ // TODO we need a different signal for this, really..s
+// _desktop_shutdown_connection = _desktop->connectShutdown(
+// sigc::bind (sigc::ptr_fun (detach), this));
+// g_signal_connect_after(_desktop, "shutdown", GCallback(detach), this);
+
+ LayerManager *mgr = _desktop->layer_manager;
+ if ( mgr ) {
+ _current_layer_changed_connection = mgr->connectCurrentLayerChanged( sigc::mem_fun(*this, &LayerSelector::_selectLayer) );
+ //_layerUpdatedConnection = mgr->connectLayerDetailsChanged( sigc::mem_fun(*this, &LayerSelector::_updateLayer) );
+ _layers_changed_connection = mgr->connectChanged( sigc::mem_fun(*this, &LayerSelector::_layersChanged) );
+ }
+
+ _selectLayer(_desktop->currentLayer());
+ }
+}
+
+namespace {
+
+class is_layer {
+public:
+ is_layer(SPDesktop *desktop) : _desktop(desktop) {}
+ bool operator()(SPObject &object) const {
+ return _desktop->isLayer(&object);
+ }
+private:
+ SPDesktop *_desktop;
+};
+
+class column_matches_object {
+public:
+ column_matches_object(Gtk::TreeModelColumn<SPObject *> const &column,
+ SPObject &object)
+ : _column(column), _object(object) {}
+ bool operator()(Gtk::TreeModel::const_iterator const &iter) const {
+ SPObject *current=(*iter)[_column];
+ return current == &_object;
+ }
+private:
+ Gtk::TreeModelColumn<SPObject *> const &_column;
+ SPObject &_object;
+};
+
+}
+
+void LayerSelector::_layersChanged()
+{
+ if (_desktop) {
+ /*
+ * This code fixes #166691 but causes issues #1066543 and #1080378.
+ * Comment out until solution found.
+ */
+ //_selectLayer(_desktop->currentLayer());
+ }
+}
+
+/** Selects the given layer in the dropdown selector.
+ */
+void LayerSelector::_selectLayer(SPObject *layer) {
+ using Inkscape::Util::List;
+ using Inkscape::Util::cons;
+ using Inkscape::Util::reverse_list;
+
+ _selection_changed_connection.block();
+ _visibility_toggled_connection.block();
+ _lock_toggled_connection.block();
+
+ while (!_layer_model->children().empty()) {
+ Gtk::ListStore::iterator first_row(_layer_model->children().begin());
+ _destroyEntry(first_row);
+ _layer_model->erase(first_row);
+ }
+
+ SPObject *root=_desktop->currentRoot();
+
+ if (_layer) {
+ sp_object_unref(_layer, nullptr);
+ _layer = nullptr;
+ }
+
+ if (layer) {
+ List<SPObject &> hierarchy=reverse_list<SPObject::ParentIterator>(layer, root);
+ if ( layer == root ) {
+ _buildEntries(0, cons(*root, hierarchy));
+ } else if (hierarchy) {
+ _buildSiblingEntries(0, *root, hierarchy);
+ }
+
+ Gtk::TreeIter row(
+ std::find_if(
+ _layer_model->children().begin(),
+ _layer_model->children().end(),
+ column_matches_object(_model_columns.object, *layer)
+ )
+ );
+ if ( row != _layer_model->children().end() ) {
+ _selector.set_active(row);
+ }
+
+ _layer = layer;
+ sp_object_ref(_layer, nullptr);
+ }
+
+ if ( !layer || layer == root ) {
+ _visibility_toggle.set_sensitive(false);
+ _visibility_toggle.set_active(false);
+ _lock_toggle.set_sensitive(false);
+ _lock_toggle.set_active(false);
+ } else {
+ _visibility_toggle.set_sensitive(true);
+ _visibility_toggle.set_active(( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false ));
+ _lock_toggle.set_sensitive(true);
+ _lock_toggle.set_active(( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false ));
+ }
+
+ _lock_toggled_connection.unblock();
+ _visibility_toggled_connection.unblock();
+ _selection_changed_connection.unblock();
+}
+
+/** Sets the current desktop layer to the actively selected layer.
+ */
+void LayerSelector::_setDesktopLayer() {
+ Gtk::ListStore::iterator selected(_selector.get_active());
+ SPObject *layer=_selector.get_active()->get_value(_model_columns.object);
+ if ( _desktop && layer ) {
+ _current_layer_changed_connection.block();
+ _layers_changed_connection.block();
+
+ _desktop->layer_manager->setCurrentLayer(layer);
+
+ _current_layer_changed_connection.unblock();
+ _layers_changed_connection.unblock();
+
+ _selectLayer(_desktop->currentLayer());
+ }
+ if (_desktop && _desktop->canvas) {
+ gtk_widget_grab_focus (GTK_WIDGET(_desktop->canvas));
+ }
+}
+
+/** Creates rows in the _layer_model data structure for each item
+ * in \a hierarchy, to a given \a depth.
+ */
+void LayerSelector::_buildEntries(unsigned depth,
+ Inkscape::Util::List<SPObject &> hierarchy)
+{
+ using Inkscape::Util::List;
+ using Inkscape::Util::rest;
+
+ _buildEntry(depth, *hierarchy);
+
+ List<SPObject &> remainder=rest(hierarchy);
+ if (remainder) {
+ _buildEntries(depth+1, remainder);
+ } else {
+ _buildSiblingEntries(depth+1, *hierarchy, remainder);
+ }
+}
+
+/** Creates entries in the _layer_model data structure for
+ * all siblings of the first child in \a parent.
+ */
+void LayerSelector::_buildSiblingEntries(
+ unsigned depth, SPObject &parent,
+ Inkscape::Util::List<SPObject &> hierarchy
+) {
+ using Inkscape::Util::rest;
+
+ auto siblings = parent.children | boost::adaptors::filtered(is_layer(_desktop)) | boost::adaptors::reversed;
+
+ SPObject *layer( hierarchy ? &*hierarchy : nullptr );
+
+ for (auto& sib: siblings) {
+ _buildEntry(depth, sib);
+ if ( &sib == layer ) {
+ _buildSiblingEntries(depth+1, *layer, rest(hierarchy));
+ }
+ }
+}
+
+namespace {
+
+struct Callbacks {
+ sigc::slot<void> update_row;
+ sigc::slot<void> update_list;
+};
+
+void attribute_changed(Inkscape::XML::Node */*repr*/, gchar const *name,
+ gchar const */*old_value*/, gchar const */*new_value*/,
+ bool /*is_interactive*/, void *data)
+{
+ if ( !std::strcmp(name, "inkscape:groupmode") ) {
+ reinterpret_cast<Callbacks *>(data)->update_list();
+ } else {
+ reinterpret_cast<Callbacks *>(data)->update_row();
+ }
+}
+
+void node_added(Inkscape::XML::Node */*parent*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, void *data) {
+ gchar const *mode=child->attribute("inkscape:groupmode");
+ if ( mode && !std::strcmp(mode, "layer") ) {
+ reinterpret_cast<Callbacks *>(data)->update_list();
+ }
+}
+
+void node_removed(Inkscape::XML::Node */*parent*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, void *data) {
+ gchar const *mode=child->attribute("inkscape:groupmode");
+ if ( mode && !std::strcmp(mode, "layer") ) {
+ reinterpret_cast<Callbacks *>(data)->update_list();
+ }
+}
+
+void node_reordered(Inkscape::XML::Node */*parent*/, Inkscape::XML::Node *child,
+ Inkscape::XML::Node */*old_ref*/, Inkscape::XML::Node */*new_ref*/,
+ void *data)
+{
+ gchar const *mode=child->attribute("inkscape:groupmode");
+ if ( mode && !std::strcmp(mode, "layer") ) {
+ reinterpret_cast<Callbacks *>(data)->update_list();
+ }
+}
+
+void update_row_for_object(SPObject *object,
+ Gtk::TreeModelColumn<SPObject *> const &column,
+ Glib::RefPtr<Gtk::ListStore> const &model)
+{
+ Gtk::TreeIter row(
+ std::find_if(
+ model->children().begin(),
+ model->children().end(),
+ column_matches_object(column, *object)
+ )
+ );
+ if ( row != model->children().end() ) {
+ model->row_changed(model->get_path(row), row);
+ }
+}
+
+void rebuild_all_rows(sigc::slot<void, SPObject *> rebuild, SPDesktop *desktop)
+{
+ rebuild(desktop->currentLayer());
+}
+
+}
+
+void LayerSelector::_protectUpdate(sigc::slot<void> slot) {
+ bool visibility_blocked=_visibility_toggled_connection.blocked();
+ bool lock_blocked=_lock_toggled_connection.blocked();
+ _visibility_toggled_connection.block(true);
+ _lock_toggled_connection.block(true);
+ slot();
+
+ SPObject *layer = _desktop ? _desktop->currentLayer() : nullptr;
+ if ( layer ) {
+ bool wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false );
+ if ( _lock_toggle.get_active() != wantedValue ) {
+ _lock_toggle.set_active( wantedValue );
+ }
+ wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false );
+ if ( _visibility_toggle.get_active() != wantedValue ) {
+ _visibility_toggle.set_active( wantedValue );
+ }
+ }
+ _visibility_toggled_connection.block(visibility_blocked);
+ _lock_toggled_connection.block(lock_blocked);
+}
+
+/** Builds and appends a row in the layer model object.
+ */
+void LayerSelector::_buildEntry(unsigned depth, SPObject &object) {
+ Inkscape::XML::NodeEventVector *vector;
+
+ Callbacks *callbacks=new Callbacks();
+
+ callbacks->update_row = sigc::bind(
+ sigc::mem_fun(*this, &LayerSelector::_protectUpdate),
+ sigc::bind(
+ sigc::ptr_fun(&update_row_for_object),
+ &object, _model_columns.object, _layer_model
+ )
+ );
+
+ SPObject *layer=_desktop->currentLayer();
+ if ( (&object == layer) || (&object == layer->parent) ) {
+ callbacks->update_list = sigc::bind(
+ sigc::mem_fun(*this, &LayerSelector::_protectUpdate),
+ sigc::bind(
+ sigc::ptr_fun(&rebuild_all_rows),
+ sigc::mem_fun(*this, &LayerSelector::_selectLayer),
+ _desktop
+ )
+ );
+
+ Inkscape::XML::NodeEventVector events = {
+ &node_added,
+ &node_removed,
+ &attribute_changed,
+ nullptr,
+ &node_reordered
+ };
+
+ vector = new Inkscape::XML::NodeEventVector(events);
+ } else {
+ Inkscape::XML::NodeEventVector events = {
+ nullptr,
+ nullptr,
+ &attribute_changed,
+ nullptr,
+ nullptr
+ };
+
+ vector = new Inkscape::XML::NodeEventVector(events);
+ }
+
+ Gtk::ListStore::iterator row(_layer_model->append());
+
+ row->set_value(_model_columns.depth, depth);
+
+ sp_object_ref(&object, nullptr);
+ row->set_value(_model_columns.object, &object);
+
+ Inkscape::GC::anchor(object.getRepr());
+ row->set_value(_model_columns.repr, object.getRepr());
+
+ row->set_value(_model_columns.callbacks, reinterpret_cast<void *>(callbacks));
+
+ sp_repr_add_listener(object.getRepr(), vector, callbacks);
+}
+
+/** Removes a row from the _model_columns object, disconnecting listeners
+ * on the slot.
+ */
+void LayerSelector::_destroyEntry(Gtk::ListStore::iterator const &row) {
+ Callbacks *callbacks=reinterpret_cast<Callbacks *>(row->get_value(_model_columns.callbacks));
+ SPObject *object=row->get_value(_model_columns.object);
+ if (object) {
+ sp_object_unref(object, nullptr);
+ }
+ Inkscape::XML::Node *repr=row->get_value(_model_columns.repr);
+ if (repr) {
+ sp_repr_remove_listener_by_data(repr, callbacks);
+ Inkscape::GC::release(repr);
+ }
+ delete callbacks;
+}
+
+/** Formats the label for a given layer row
+ */
+void LayerSelector::_prepareLabelRenderer(
+ Gtk::TreeModel::const_iterator const &row
+) {
+ unsigned depth=(*row)[_model_columns.depth];
+ SPObject *object=(*row)[_model_columns.object];
+ bool label_defaulted(false);
+
+ // TODO: when the currently selected row is removed,
+ // (or before one has been selected) something appears to
+ // "invent" an iterator with null data and try to render it;
+ // where does it come from, and how can we avoid it?
+ if ( object && object->getRepr() ) {
+ SPObject *layer=( _desktop ? _desktop->currentLayer() : nullptr );
+ SPObject *root=( _desktop ? _desktop->currentRoot() : nullptr );
+
+ bool isancestor = !( (layer && (object->parent == layer->parent)) || ((layer == root) && (object->parent == root)));
+
+ bool iscurrent = ( (object == layer) && (object != root) );
+
+ gchar *format = g_strdup_printf (
+ "<span size=\"smaller\" %s><tt>%*s%s</tt>%s%s%s%%s%s%s%s</span>",
+ ( _desktop && _desktop->itemIsHidden (SP_ITEM(object)) ? "foreground=\"gray50\"" : "" ),
+ depth, "", ( iscurrent ? "&#8226;" : " " ),
+ ( iscurrent ? "<b>" : "" ),
+ ( SP_ITEM(object)->isLocked() ? "[" : "" ),
+ ( isancestor ? "<small>" : "" ),
+ ( isancestor ? "</small>" : "" ),
+ ( SP_ITEM(object)->isLocked() ? "]" : "" ),
+ ( iscurrent ? "</b>" : "" )
+ );
+
+ gchar const *label;
+ if ( object != root ) {
+ label = object->label();
+ if (!object->label()) {
+ label = object->defaultLabel();
+ label_defaulted = true;
+ }
+ } else {
+ label = _("(root)");
+ }
+
+ gchar *text = g_markup_printf_escaped(format, ink_ellipsize_text (label, 50).c_str());
+ _label_renderer.property_markup() = text;
+ g_free(text);
+ g_free(format);
+ } else {
+ _label_renderer.property_markup() = "<small> </small>";
+ }
+
+ _label_renderer.property_ypad() = 1;
+ _label_renderer.property_style() = ( label_defaulted ?
+ Pango::STYLE_ITALIC :
+ Pango::STYLE_NORMAL );
+
+}
+
+void LayerSelector::_lockLayer(bool lock) {
+ if ( _layer && SP_IS_ITEM(_layer) ) {
+ SP_ITEM(_layer)->setLocked(lock);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_NONE,
+ lock? _("Lock layer") : _("Unlock layer"));
+ }
+}
+
+void LayerSelector::_hideLayer(bool hide) {
+ if ( _layer && SP_IS_ITEM(_layer) ) {
+ SP_ITEM(_layer)->setHidden(hide);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_NONE,
+ hide? _("Hide layer") : _("Unhide layer"));
+ }
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/layer-selector.h b/src/ui/widget/layer-selector.h
new file mode 100644
index 0000000..eadfce2
--- /dev/null
+++ b/src/ui/widget/layer-selector.h
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::UI::Widget::LayerSelector - layer selector widget
+ *
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ *
+ * Copyright (C) 2004 MenTaLguY
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_WIDGETS_LAYER_SELECTOR
+#define SEEN_INKSCAPE_WIDGETS_LAYER_SELECTOR
+
+#include <gtkmm/box.h>
+#include <gtkmm/combobox.h>
+#include <gtkmm/togglebutton.h>
+#include <gtkmm/cellrenderertext.h>
+#include <gtkmm/treemodel.h>
+#include <gtkmm/liststore.h>
+#include <sigc++/slot.h>
+#include "util/list.h"
+
+class SPDesktop;
+class SPDocument;
+class SPObject;
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+}
+
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class DocumentTreeModel;
+
+class LayerSelector : public Gtk::HBox {
+public:
+ LayerSelector(SPDesktop *desktop = nullptr);
+ ~LayerSelector() override;
+
+ SPDesktop *desktop() { return _desktop; }
+ void setDesktop(SPDesktop *desktop);
+
+private:
+ class LayerModelColumns : public Gtk::TreeModel::ColumnRecord {
+ public:
+ Gtk::TreeModelColumn<unsigned> depth;
+ Gtk::TreeModelColumn<SPObject *> object;
+ Gtk::TreeModelColumn<Inkscape::XML::Node *> repr;
+ Gtk::TreeModelColumn<void *> callbacks;
+
+ LayerModelColumns() {
+ add(depth); add(object); add(repr); add(callbacks);
+ }
+ };
+
+ SPDesktop *_desktop;
+
+ Gtk::ComboBox _selector;
+ Gtk::ToggleButton _visibility_toggle;
+ Gtk::ToggleButton _lock_toggle;
+
+ LayerModelColumns _model_columns;
+ Gtk::CellRendererText _label_renderer;
+ Glib::RefPtr<Gtk::ListStore> _layer_model;
+
+// sigc::connection _desktop_shutdown_connection;
+ sigc::connection _layers_changed_connection;
+ sigc::connection _current_layer_changed_connection;
+ sigc::connection _selection_changed_connection;
+ sigc::connection _visibility_toggled_connection;
+ sigc::connection _lock_toggled_connection;
+
+ SPObject *_layer;
+
+ void _selectLayer(SPObject *layer);
+ void _layersChanged();
+
+ void _setDesktopLayer();
+
+ void _buildEntry(unsigned depth, SPObject &object);
+ void _buildEntries(unsigned depth,
+ Inkscape::Util::List<SPObject &> hierarchy);
+ void _buildSiblingEntries(unsigned depth,
+ SPObject &parent,
+ Inkscape::Util::List<SPObject &> hierarchy);
+ void _protectUpdate(sigc::slot<void> slot);
+ void _destroyEntry(Gtk::ListStore::iterator const &row);
+ void _hideLayer(bool hide);
+ void _lockLayer(bool lock);
+
+ void _prepareLabelRenderer(Gtk::TreeModel::const_iterator const &row);
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+/*
+ 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/layertypeicon.cpp b/src/ui/widget/layertypeicon.cpp
new file mode 100644
index 0000000..d8b1378
--- /dev/null
+++ b/src/ui/widget/layertypeicon.cpp
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Theodore Janeczko
+ *
+ * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "ui/widget/layertypeicon.h"
+
+#include "ui/icon-loader.h"
+#include "ui/icon-names.h"
+#include "widgets/toolbox.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+LayerTypeIcon::LayerTypeIcon() :
+ Glib::ObjectBase(typeid(LayerTypeIcon)),
+ Gtk::CellRendererPixbuf(),
+ _pixLayerName(INKSCAPE_ICON("dialog-layers")),
+ _pixGroupName(INKSCAPE_ICON("layer-duplicate")),
+ _pixPathName(INKSCAPE_ICON("layer-rename")),
+ _property_active(*this, "active", false),
+ _property_activatable(*this, "activatable", true),
+ _property_pixbuf_layer(*this, "pixbuf_on", Glib::RefPtr<Gdk::Pixbuf>(nullptr)),
+ _property_pixbuf_group(*this, "pixbuf_off", Glib::RefPtr<Gdk::Pixbuf>(nullptr)),
+ _property_pixbuf_path(*this, "pixbuf_off", Glib::RefPtr<Gdk::Pixbuf>(nullptr))
+{
+
+ property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE;
+
+ _property_pixbuf_layer = sp_get_icon_pixbuf(_pixLayerName, GTK_ICON_SIZE_MENU);
+ _property_pixbuf_group = sp_get_icon_pixbuf(_pixGroupName, GTK_ICON_SIZE_MENU);
+ _property_pixbuf_path = sp_get_icon_pixbuf(_pixPathName, GTK_ICON_SIZE_MENU);
+
+ property_pixbuf() = _property_pixbuf_path.get_value();
+}
+
+void LayerTypeIcon::get_preferred_height_vfunc(Gtk::Widget& widget,
+ int& min_h,
+ int& nat_h) const
+{
+ Gtk::CellRendererPixbuf::get_preferred_height_vfunc(widget, min_h, nat_h);
+
+ if (min_h) {
+ min_h += (min_h) >> 1;
+ }
+
+ if (nat_h) {
+ nat_h += (nat_h) >> 1;
+ }
+}
+
+void LayerTypeIcon::get_preferred_width_vfunc(Gtk::Widget& widget,
+ int& min_w,
+ int& nat_w) const
+{
+ Gtk::CellRendererPixbuf::get_preferred_width_vfunc(widget, min_w, nat_w);
+
+ if (min_w) {
+ min_w += (min_w) >> 1;
+ }
+
+ if (nat_w) {
+ nat_w += (nat_w) >> 1;
+ }
+}
+
+void LayerTypeIcon::render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ Gtk::CellRendererState flags )
+{
+ property_pixbuf() = _property_active.get_value() == 1 ? _property_pixbuf_group : (_property_active.get_value() == 2 ? _property_pixbuf_layer : _property_pixbuf_path);
+ Gtk::CellRendererPixbuf::render_vfunc( cr, widget, background_area, cell_area, flags );
+}
+
+bool
+LayerTypeIcon::activate_vfunc(GdkEvent* event,
+ Gtk::Widget& /*widget*/,
+ const Glib::ustring& path,
+ const Gdk::Rectangle& /*background_area*/,
+ const Gdk::Rectangle& /*cell_area*/,
+ Gtk::CellRendererState /*flags*/)
+{
+ _signal_pre_toggle.emit(event);
+ _signal_toggled.emit(path);
+
+ return false;
+}
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/layertypeicon.h b/src/ui/widget/layertypeicon.h
new file mode 100644
index 0000000..7dccf4c
--- /dev/null
+++ b/src/ui/widget/layertypeicon.h
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef __UI_DIALOG_LAYERTYPEICON_H__
+#define __UI_DIALOG_LAYERTYPEICON_H__
+/*
+ * Authors:
+ * Theodore Janeczko
+ *
+ * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/cellrendererpixbuf.h>
+#include <gtkmm/widget.h>
+#include <glibmm/property.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class LayerTypeIcon : public Gtk::CellRendererPixbuf {
+public:
+ LayerTypeIcon();
+ ~LayerTypeIcon() override = default;;
+
+ sigc::signal<void, const Glib::ustring&> signal_toggled() { return _signal_toggled;}
+ sigc::signal<void, GdkEvent const *> signal_pre_toggle() { return _signal_pre_toggle; }
+
+ Glib::PropertyProxy<int> property_active() { return _property_active.get_proxy(); }
+ Glib::PropertyProxy<int> property_activatable() { return _property_activatable.get_proxy(); }
+ Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_on();
+ Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_off();
+
+protected:
+ void render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr,
+ Gtk::Widget& widget,
+ const Gdk::Rectangle& background_area,
+ const Gdk::Rectangle& cell_area,
+ Gtk::CellRendererState flags ) override;
+
+ void get_preferred_width_vfunc(Gtk::Widget& widget,
+ int& min_w,
+ int& nat_w) const override;
+
+ void get_preferred_height_vfunc(Gtk::Widget& widget,
+ int& min_h,
+ int& nat_h) const override;
+
+ bool activate_vfunc(GdkEvent *event,
+ Gtk::Widget &widget,
+ const Glib::ustring &path,
+ const Gdk::Rectangle &background_area,
+ const Gdk::Rectangle &cell_area,
+ Gtk::CellRendererState flags) override;
+
+
+private:
+ Glib::ustring _pixLayerName;
+ Glib::ustring _pixGroupName;
+ Glib::ustring _pixPathName;
+
+ Glib::Property<int> _property_active;
+ Glib::Property<int> _property_activatable;
+ Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_layer;
+ Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_group;
+ Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_path;
+
+ sigc::signal<void, const Glib::ustring&> _signal_toggled;
+ sigc::signal<void, GdkEvent const *> _signal_pre_toggle;
+
+};
+
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+
+#endif /* __UI_DIALOG_IMAGETOGGLER_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/licensor.cpp b/src/ui/widget/licensor.cpp
new file mode 100644
index 0000000..2ad811f
--- /dev/null
+++ b/src/ui/widget/licensor.cpp
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * bulia byak <buliabyak@users.sf.net>
+ * Bryce W. Harrington <bryce@bryceharrington.org>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Jon Phillips <jon@rejon.org>
+ * Ralf Stephan <ralf@ark.in-berlin.de> (Gtkmm)
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2000 - 2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "licensor.h"
+
+#include <gtkmm/entry.h>
+#include <gtkmm/radiobutton.h>
+
+#include "ui/widget/entity-entry.h"
+#include "ui/widget/registry.h"
+#include "rdf.h"
+#include "inkscape.h"
+#include "document-undo.h"
+#include "verbs.h"
+
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+//===================================================
+
+const struct rdf_license_t _proprietary_license =
+ {_("Proprietary"), "", nullptr};
+
+const struct rdf_license_t _other_license =
+ {Q_("MetadataLicence|Other"), "", nullptr};
+
+class LicenseItem : public Gtk::RadioButton {
+public:
+ LicenseItem (struct rdf_license_t const* license, EntityEntry* entity, Registry &wr, Gtk::RadioButtonGroup *group);
+protected:
+ void on_toggled() override;
+ struct rdf_license_t const *_lic;
+ EntityEntry *_eep;
+ Registry &_wr;
+};
+
+LicenseItem::LicenseItem (struct rdf_license_t const* license, EntityEntry* entity, Registry &wr, Gtk::RadioButtonGroup *group)
+: Gtk::RadioButton(_(license->name)), _lic(license), _eep(entity), _wr(wr)
+{
+ if (group) {
+ set_group (*group);
+ }
+}
+
+/// \pre it is assumed that the license URI entry is a Gtk::Entry
+void LicenseItem::on_toggled()
+{
+ if (_wr.isUpdating()) return;
+
+ _wr.setUpdating (true);
+ SPDocument *doc = SP_ACTIVE_DOCUMENT;
+ rdf_set_license (doc, _lic->details ? _lic : nullptr);
+ if (doc->isSensitive()) {
+ DocumentUndo::done(doc, SP_VERB_NONE, _("Document license updated"));
+ }
+ _wr.setUpdating (false);
+ static_cast<Gtk::Entry*>(_eep->_packable)->set_text (_lic->uri);
+ _eep->on_changed();
+}
+
+//---------------------------------------------------
+
+Licensor::Licensor()
+: Gtk::VBox(false,4),
+ _eentry (nullptr)
+{
+}
+
+Licensor::~Licensor()
+{
+ if (_eentry) delete _eentry;
+}
+
+void Licensor::init (Registry& wr)
+{
+ /* add license-specific metadata entry areas */
+ rdf_work_entity_t* entity = rdf_find_entity ( "license_uri" );
+ _eentry = EntityEntry::create (entity, wr);
+
+ LicenseItem *i;
+ wr.setUpdating (true);
+ i = Gtk::manage (new LicenseItem (&_proprietary_license, _eentry, wr, nullptr));
+ Gtk::RadioButtonGroup group = i->get_group();
+ add (*i);
+ LicenseItem *pd = i;
+
+ for (struct rdf_license_t * license = rdf_licenses;
+ license && license->name;
+ license++) {
+ i = Gtk::manage (new LicenseItem (license, _eentry, wr, &group));
+ add(*i);
+ }
+ // add Other at the end before the URI field for the confused ppl.
+ LicenseItem *io = Gtk::manage (new LicenseItem (&_other_license, _eentry, wr, &group));
+ add (*io);
+
+ pd->set_active();
+ wr.setUpdating (false);
+
+ Gtk::HBox *box = Gtk::manage (new Gtk::HBox);
+ pack_start (*box, true, true, 0);
+
+ box->pack_start (_eentry->_label, false, false, 5);
+ box->pack_start (*_eentry->_packable, true, true, 0);
+
+ show_all_children();
+}
+
+void Licensor::update (SPDocument *doc)
+{
+ /* identify the license info */
+ struct rdf_license_t * license = rdf_get_license (doc);
+
+ if (license) {
+ int i;
+ for (i=0; rdf_licenses[i].name; i++)
+ if (license == &rdf_licenses[i])
+ break;
+ static_cast<LicenseItem*>(get_children()[i+1])->set_active();
+ }
+ else {
+ static_cast<LicenseItem*>(get_children()[0])->set_active();
+ }
+
+ /* update the URI */
+ _eentry->update (doc);
+}
+
+} // namespace Dialog
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/licensor.h b/src/ui/widget/licensor.h
new file mode 100644
index 0000000..3e1f0da
--- /dev/null
+++ b/src/ui/widget/licensor.h
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ralf Stephan <ralf@ark.in-berlin.de>
+ *
+ * Copyright (C) 2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_LICENSOR_H
+#define INKSCAPE_UI_WIDGET_LICENSOR_H
+
+#include <gtkmm/box.h>
+
+class SPDocument;
+
+namespace Inkscape {
+ namespace UI {
+ namespace Widget {
+
+class EntityEntry;
+class Registry;
+
+
+/**
+ * Widget for specifying a document's license; part of document
+ * preferences dialog.
+ */
+class Licensor : public Gtk::VBox {
+public:
+ Licensor();
+ ~Licensor() override;
+ void init (Registry&);
+ void update (SPDocument *doc);
+
+protected:
+ EntityEntry *_eentry;
+};
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_LICENSOR_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/notebook-page.cpp b/src/ui/widget/notebook-page.cpp
new file mode 100644
index 0000000..a189d78
--- /dev/null
+++ b/src/ui/widget/notebook-page.cpp
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Notebook page widget.
+ *
+ * Author:
+ * Bryce Harrington <bryce@bryceharrington.org>
+ *
+ * Copyright (C) 2004 Bryce Harrington
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "notebook-page.h"
+
+# include <gtkmm/grid.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+NotebookPage::NotebookPage(int n_rows, int n_columns, bool expand, bool fill, guint padding)
+ :_table(Gtk::manage(new Gtk::Grid()))
+{
+ set_name("NotebookPage");
+ set_border_width(4);
+ set_spacing(4);
+
+ _table->set_row_spacing(4);
+ _table->set_column_spacing(4);
+
+ pack_start(*_table, expand, fill, padding);
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/notebook-page.h b/src/ui/widget/notebook-page.h
new file mode 100644
index 0000000..cc11d30
--- /dev/null
+++ b/src/ui/widget/notebook-page.h
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Bryce Harrington <bryce@bryceharrington.org>
+ *
+ * Copyright (C) 2004 Bryce Harrington
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_NOTEBOOK_PAGE_H
+#define INKSCAPE_UI_WIDGET_NOTEBOOK_PAGE_H
+
+#include <gtkmm/box.h>
+
+namespace Gtk {
+class Grid;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * A tabbed notebook page for dialogs.
+ */
+class NotebookPage : public Gtk::VBox
+{
+public:
+
+ NotebookPage();
+
+ /**
+ * Construct a NotebookPage.
+ */
+ NotebookPage(int n_rows, int n_columns, bool expand=false, bool fill=false, guint padding=0);
+
+ Gtk::Grid& table() { return *_table; }
+
+protected:
+ Gtk::Grid *_table;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_NOTEBOOK_PAGE_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/object-composite-settings.cpp b/src/ui/widget/object-composite-settings.cpp
new file mode 100644
index 0000000..db2e91c
--- /dev/null
+++ b/src/ui/widget/object-composite-settings.cpp
@@ -0,0 +1,325 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A widget for controlling object compositing (filter, opacity, etc.)
+ *
+ * Authors:
+ * Bryce W. Harrington <bryce@bryceharrington.org>
+ * Gustav Broberg <broberg@kth.se>
+ * Niko Kiirala <niko@kiirala.com>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2004--2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "ui/widget/object-composite-settings.h"
+
+#include "desktop.h"
+
+#include "desktop-style.h"
+#include "document.h"
+#include "document-undo.h"
+#include "filter-chemistry.h"
+#include "inkscape.h"
+#include "style.h"
+#include "svg/css-ostringstream.h"
+#include "verbs.h"
+#include "display/sp-canvas.h"
+#include "object/filters/blend.h"
+#include "ui/widget/style-subject.h"
+
+constexpr double BLUR_MULTIPLIER = 4.0;
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+ObjectCompositeSettings::ObjectCompositeSettings(unsigned int verb_code, char const *history_prefix, int flags)
+: _verb_code(verb_code),
+ _blend_tag(Glib::ustring(history_prefix) + ":blend"),
+ _blur_tag(Glib::ustring(history_prefix) + ":blur"),
+ _opacity_tag(Glib::ustring(history_prefix) + ":opacity"),
+ _isolation_tag(Glib::ustring(history_prefix) + ":isolation"),
+ _filter_modifier(flags),
+ _blocked(false)
+{
+ set_name( "ObjectCompositeSettings");
+
+ // Filter Effects
+ pack_start(_filter_modifier, false, false, 2);
+
+ _filter_modifier.signal_blend_changed().connect(sigc::mem_fun(*this, &ObjectCompositeSettings::_blendBlurValueChanged));
+ _filter_modifier.signal_blur_changed().connect(sigc::mem_fun(*this, &ObjectCompositeSettings::_blendBlurValueChanged));
+ _filter_modifier.signal_opacity_changed().connect(sigc::mem_fun(*this, &ObjectCompositeSettings::_opacityValueChanged));
+ _filter_modifier.signal_isolation_changed().connect(
+ sigc::mem_fun(*this, &ObjectCompositeSettings::_isolationValueChanged));
+
+ show_all_children();
+}
+
+ObjectCompositeSettings::~ObjectCompositeSettings() {
+ setSubject(nullptr);
+}
+
+void ObjectCompositeSettings::setSubject(StyleSubject *subject) {
+ _subject_changed.disconnect();
+ if (subject) {
+ _subject = subject;
+ _subject_changed = _subject->connectChanged(sigc::mem_fun(*this, &ObjectCompositeSettings::_subjectChanged));
+ _subject->setDesktop(SP_ACTIVE_DESKTOP);
+ }
+}
+
+// We get away with sharing one callback for blend and blur as this is used by
+// * the Layers dialog where only one layer can be selected at a time,
+// * the Fill and Stroke dialog where only blur is used.
+// If both blend and blur are used in a dialog where more than one object can
+// be selected then this should be split into separate functions for blend and
+// blur (like in the Objects dialog).
+void
+ObjectCompositeSettings::_blendBlurValueChanged()
+{
+ if (!_subject) {
+ return;
+ }
+
+ SPDesktop *desktop = _subject->getDesktop();
+ if (!desktop) {
+ return;
+ }
+ SPDocument *document = desktop->getDocument();
+
+ if (_blocked)
+ return;
+ _blocked = true;
+
+ // FIXME: fix for GTK breakage, see comment in SelectedStyle::on_opacity_changed; here it results in crash 1580903
+ //sp_canvas_force_full_redraw_after_interruptions(desktop->getCanvas(), 0);
+
+ Geom::OptRect bbox = _subject->getBounds(SPItem::GEOMETRIC_BBOX);
+ double radius;
+ if (bbox) {
+ double perimeter = bbox->dimensions()[Geom::X] + bbox->dimensions()[Geom::Y]; // fixme: this is only half the perimeter, is that correct?
+ double blur_value = _filter_modifier.get_blur_value() / 100.0;
+ radius = blur_value * blur_value * perimeter / BLUR_MULTIPLIER;
+ } else {
+ radius = 0;
+ }
+
+ //apply created filter to every selected item
+ std::vector<SPObject*> sel = _subject->list();
+ for (auto i : sel) {
+ if (!SP_IS_ITEM(i)) {
+ continue;
+ }
+ SPItem * item = SP_ITEM(i);
+ SPStyle *style = item->style;
+ g_assert(style != nullptr);
+ bool change_blend = (item->style->mix_blend_mode.set ? item->style->mix_blend_mode.value : SP_CSS_BLEND_NORMAL) != _filter_modifier.get_blend_mode();
+ // < 1.0 filter based blend removal
+ if (!item->style->mix_blend_mode.set && item->style->filter.set && item->style->getFilter()) {
+ remove_filter_legacy_blend(item);
+ }
+ item->style->mix_blend_mode.set = TRUE;
+ if (item->style->isolation.value == SP_CSS_ISOLATION_ISOLATE) {
+ item->style->mix_blend_mode.value = SP_CSS_BLEND_NORMAL;
+ } else {
+ item->style->mix_blend_mode.value = _filter_modifier.get_blend_mode();
+ }
+
+ if (radius == 0 && item->style->filter.set
+ && filter_is_single_gaussian_blur(SP_FILTER(item->style->getFilter()))) {
+ remove_filter(item, false);
+ } else if (radius != 0) {
+ SPFilter *filter = modify_filter_gaussian_blur_from_item(document, item, radius);
+ sp_style_set_property_url(item, "filter", filter, false);
+ }
+ if (change_blend) { //we do blend so we need update display style
+ item->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
+ } else {
+ item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
+ }
+ }
+
+ DocumentUndo::maybeDone(document, _blur_tag.c_str(), _verb_code,
+ _("Change blur/blend filter"));
+
+ // resume interruptibility
+ //sp_canvas_end_forced_full_redraws(desktop->getCanvas());
+
+ _blocked = false;
+}
+
+void
+ObjectCompositeSettings::_opacityValueChanged()
+{
+ if (!_subject) {
+ return;
+ }
+
+ SPDesktop *desktop = _subject->getDesktop();
+ if (!desktop) {
+ return;
+ }
+
+ if (_blocked)
+ return;
+ _blocked = true;
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+
+ Inkscape::CSSOStringStream os;
+ os << CLAMP (_filter_modifier.get_opacity_value() / 100, 0.0, 1.0);
+ sp_repr_css_set_property (css, "opacity", os.str().c_str());
+
+ _subject->setCSS(css);
+
+ sp_repr_css_attr_unref (css);
+
+ DocumentUndo::maybeDone(desktop->getDocument(), _opacity_tag.c_str(), _verb_code,
+ _("Change opacity"));
+
+ // resume interruptibility
+ //sp_canvas_end_forced_full_redraws(desktop->getCanvas());
+
+ _blocked = false;
+}
+
+void ObjectCompositeSettings::_isolationValueChanged()
+{
+ if (!_subject) {
+ return;
+ }
+
+ SPDesktop *desktop = _subject->getDesktop();
+ if (!desktop) {
+ return;
+ }
+
+ if (_blocked)
+ return;
+ _blocked = true;
+
+ for (auto item : _subject->list()) {
+ item->style->isolation.set = TRUE;
+ item->style->isolation.value = _filter_modifier.get_isolation_mode();
+ if (item->style->isolation.value == SP_CSS_ISOLATION_ISOLATE) {
+ item->style->mix_blend_mode.set = TRUE;
+ item->style->mix_blend_mode.value = SP_CSS_BLEND_NORMAL;
+ }
+ item->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
+ }
+
+ DocumentUndo::maybeDone(desktop->getDocument(), _isolation_tag.c_str(), _verb_code, _("Change isolation"));
+
+ // resume interruptibility
+ // sp_canvas_end_forced_full_redraws(desktop->getCanvas());
+
+ _blocked = false;
+}
+
+void
+ObjectCompositeSettings::_subjectChanged() {
+ if (!_subject) {
+ return;
+ }
+
+ SPDesktop *desktop = _subject->getDesktop();
+ if (!desktop) {
+ return;
+ }
+
+ if (_blocked)
+ return;
+ _blocked = true;
+ SPStyle query(desktop->getDocument());
+ int result = _subject->queryStyle(&query, QUERY_STYLE_PROPERTY_MASTEROPACITY);
+
+ switch (result) {
+ case QUERY_STYLE_NOTHING:
+ break;
+ case QUERY_STYLE_SINGLE:
+ case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently
+ case QUERY_STYLE_MULTIPLE_SAME:
+ _filter_modifier.set_opacity_value(100 * SP_SCALE24_TO_FLOAT(query.opacity.value));
+ break;
+ }
+
+ //query now for current filter mode and average blurring of selection
+ const int isolation_result = _subject->queryStyle(&query, QUERY_STYLE_PROPERTY_ISOLATION);
+ switch (isolation_result) {
+ case QUERY_STYLE_NOTHING:
+ _filter_modifier.set_isolation_mode(SP_CSS_ISOLATION_AUTO, false);
+ break;
+ case QUERY_STYLE_SINGLE:
+ case QUERY_STYLE_MULTIPLE_SAME:
+ _filter_modifier.set_isolation_mode(query.isolation.value, true); // here dont work mix_blend_mode.set
+ break;
+ case QUERY_STYLE_MULTIPLE_DIFFERENT:
+ _filter_modifier.set_isolation_mode(SP_CSS_ISOLATION_AUTO, false);
+ // TODO: set text
+ break;
+ }
+
+ // query now for current filter mode and average blurring of selection
+ const int blend_result = _subject->queryStyle(&query, QUERY_STYLE_PROPERTY_BLEND);
+ switch(blend_result) {
+ case QUERY_STYLE_NOTHING:
+ _filter_modifier.set_blend_mode(SP_CSS_BLEND_NORMAL, false);
+ break;
+ case QUERY_STYLE_SINGLE:
+ case QUERY_STYLE_MULTIPLE_SAME:
+ _filter_modifier.set_blend_mode(query.mix_blend_mode.value, true); // here dont work mix_blend_mode.set
+ break;
+ case QUERY_STYLE_MULTIPLE_DIFFERENT:
+ _filter_modifier.set_blend_mode(SP_CSS_BLEND_NORMAL, false);
+ break;
+ }
+
+ int blur_result = _subject->queryStyle(&query, QUERY_STYLE_PROPERTY_BLUR);
+ switch (blur_result) {
+ case QUERY_STYLE_NOTHING: // no blurring
+ _filter_modifier.set_blur_value(0);
+ break;
+ case QUERY_STYLE_SINGLE:
+ case QUERY_STYLE_MULTIPLE_AVERAGED:
+ case QUERY_STYLE_MULTIPLE_SAME:
+ Geom::OptRect bbox = _subject->getBounds(SPItem::GEOMETRIC_BBOX);
+ if (bbox) {
+ double perimeter =
+ bbox->dimensions()[Geom::X] +
+ bbox->dimensions()[Geom::Y]; // fixme: this is only half the perimeter, is that correct?
+ // update blur widget value
+ float radius = query.filter_gaussianBlur_deviation.value;
+ float percent = std::sqrt(radius * BLUR_MULTIPLIER / perimeter) * 100;
+ _filter_modifier.set_blur_value(percent);
+ }
+ break;
+ }
+
+ // If we have nothing selected, disable dialog.
+ if (result == QUERY_STYLE_NOTHING &&
+ blend_result == QUERY_STYLE_NOTHING ) {
+ _filter_modifier.set_sensitive( false );
+ } else {
+ _filter_modifier.set_sensitive( true );
+ }
+
+ _blocked = false;
+}
+
+}
+}
+}
+
+/*
+ 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/object-composite-settings.h b/src/ui/widget/object-composite-settings.h
new file mode 100644
index 0000000..9650118
--- /dev/null
+++ b/src/ui/widget/object-composite-settings.h
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_UI_WIDGET_OBJECT_COMPOSITE_SETTINGS_H
+#define SEEN_UI_WIDGET_OBJECT_COMPOSITE_SETTINGS_H
+
+/*
+ * Authors:
+ * Bryce W. Harrington <bryce@bryceharrington.org>
+ * Gustav Broberg <broberg@kth.se>
+ *
+ * Copyright (C) 2004--2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/box.h>
+#include <gtkmm/adjustment.h>
+#include <gtkmm/label.h>
+#include <gtkmm/scale.h>
+#include <glibmm/ustring.h>
+
+#include "ui/widget/filter-effect-chooser.h"
+
+class SPDesktop;
+struct InkscapeApplication;
+
+namespace Inkscape {
+
+namespace UI {
+namespace Widget {
+
+class StyleSubject;
+
+/*
+ * A widget for controlling object compositing (filter, opacity, etc.)
+ */
+class ObjectCompositeSettings : public Gtk::VBox {
+public:
+ ObjectCompositeSettings(unsigned int verb_code, char const *history_prefix, int flags);
+ ~ObjectCompositeSettings() override;
+
+ void setSubject(StyleSubject *subject);
+
+private:
+ unsigned int _verb_code;
+ Glib::ustring _blend_tag;
+ Glib::ustring _blur_tag;
+ Glib::ustring _opacity_tag;
+ Glib::ustring _isolation_tag;
+
+ StyleSubject *_subject;
+
+ SimpleFilterModifier _filter_modifier;
+
+ bool _blocked;
+ gulong _desktop_activated;
+ sigc::connection _subject_changed;
+
+ static void _on_desktop_activate(SPDesktop *desktop, ObjectCompositeSettings *w);
+ static void _on_desktop_deactivate(SPDesktop *desktop, ObjectCompositeSettings *w);
+ void _subjectChanged();
+ void _blendBlurValueChanged();
+ void _opacityValueChanged();
+ void _isolationValueChanged();
+};
+
+}
+}
+}
+
+#endif
+
+/*
+ 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/page-sizer.cpp b/src/ui/widget/page-sizer.cpp
new file mode 100644
index 0000000..d869a1c
--- /dev/null
+++ b/src/ui/widget/page-sizer.cpp
@@ -0,0 +1,781 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ *
+ * Paper-size widget and helper functions
+ */
+/*
+ * Authors:
+ * bulia byak <buliabyak@users.sf.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Jon Phillips <jon@rejon.org>
+ * Ralf Stephan <ralf@ark.in-berlin.de> (Gtkmm)
+ * Bob Jamison <ishmal@users.sf.net>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2000 - 2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "page-sizer.h"
+#include "pages-skeleton.h"
+#include <glib.h>
+#include <glibmm/i18n.h>
+#include "verbs.h"
+#include "helper/action.h"
+#include "object/sp-root.h"
+#include "io/resource.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+
+//########################################################################
+//# P A G E S I Z E R
+//########################################################################
+
+/**
+ * Constructor
+ */
+PageSizer::PageSizer(Registry & _wr)
+ : Gtk::VBox(false,4),
+ _dimensionUnits( _("U_nits:"), "units", _wr ),
+ _dimensionWidth( _("_Width:"), _("Width of paper"), "width", _dimensionUnits, _wr ),
+ _dimensionHeight( _("_Height:"), _("Height of paper"), "height", _dimensionUnits, _wr ),
+ _marginLock( _("Loc_k margins"), _("Lock margins"), "lock-margins", _wr, false, nullptr, nullptr),
+ _lock_icon(),
+ _marginTop( _("T_op:"), _("Top margin"), "fit-margin-top", _wr ),
+ _marginLeft( _("L_eft:"), _("Left margin"), "fit-margin-left", _wr),
+ _marginRight( _("Ri_ght:"), _("Right margin"), "fit-margin-right", _wr),
+ _marginBottom( _("Botto_m:"), _("Bottom margin"), "fit-margin-bottom", _wr),
+ _lockMarginUpdate(false),
+ _scaleX(_("Scale _x:"), _("Scale X"), "scale-x", _wr),
+ _scaleY(_("Scale _y:"), _("While SVG allows non-uniform scaling it is recommended to use only uniform scaling in Inkscape. To set a non-uniform scaling, set the 'viewBox' directly."), "scale-y", _wr),
+ _lockScaleUpdate(false),
+ _viewboxX(_("X:"), _("X"), "viewbox-x", _wr),
+ _viewboxY(_("Y:"), _("Y"), "viewbox-y", _wr),
+ _viewboxW(_("Width:"), _("Width"), "viewbox-width", _wr),
+ _viewboxH(_("Height:"), _("Height"), "viewbox-height", _wr),
+ _lockViewboxUpdate(false),
+ _widgetRegistry(&_wr)
+{
+ // set precision of scalar entry boxes
+ _wr.setUpdating (true);
+ _dimensionWidth.setDigits(5);
+ _dimensionHeight.setDigits(5);
+ _marginTop.setDigits(5);
+ _marginLeft.setDigits(5);
+ _marginRight.setDigits(5);
+ _marginBottom.setDigits(5);
+ _scaleX.setDigits(5);
+ _scaleY.setDigits(5);
+ _viewboxX.setDigits(5);
+ _viewboxY.setDigits(5);
+ _viewboxW.setDigits(5);
+ _viewboxH.setDigits(5);
+ _dimensionWidth.setRange( 0.00001, 10000000 );
+ _dimensionHeight.setRange( 0.00001, 10000000 );
+ _scaleX.setRange( 0.00001, 100000 );
+ _scaleY.setRange( 0.00001, 100000 );
+ _viewboxX.setRange( -10000000, 10000000 );
+ _viewboxY.setRange( -10000000, 10000000 );
+ _viewboxW.setRange( 0.00001, 10000000 );
+ _viewboxH.setRange( 0.00001, 10000000 );
+
+ _scaleY.set_sensitive (false); // We only want to display Y scale.
+
+ _wr.setUpdating (false);
+
+ //# Set up the Paper Size combo box
+ _paperSizeListStore = Gtk::ListStore::create(_paperSizeListColumns);
+ _paperSizeList.set_model(_paperSizeListStore);
+ _paperSizeList.append_column(_("Name"),
+ _paperSizeListColumns.nameColumn);
+ _paperSizeList.append_column(_("Description"),
+ _paperSizeListColumns.descColumn);
+ _paperSizeList.set_headers_visible(false);
+ _paperSizeListSelection = _paperSizeList.get_selection();
+ _paper_size_list_connection =
+ _paperSizeListSelection->signal_changed().connect (
+ sigc::mem_fun (*this, &PageSizer::on_paper_size_list_changed));
+ _paperSizeListScroller.add(_paperSizeList);
+ _paperSizeListScroller.set_shadow_type(Gtk::SHADOW_IN);
+ _paperSizeListScroller.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
+ _paperSizeListScroller.set_size_request(-1, 130);
+
+
+ char *path = Inkscape::IO::Resource::profile_path("pages.csv");
+ if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
+ if (!g_file_set_contents(path, pages_skeleton, -1, nullptr)) {
+ g_warning("%s", _("Failed to create the page file."));
+ }
+ }
+
+ gchar *content = nullptr;
+ if (g_file_get_contents(path, &content, nullptr, nullptr)) {
+
+ gchar **lines = g_strsplit_set(content, "\n", 0);
+
+ for (int i = 0; lines && lines[i]; ++i) {
+ gchar **line = g_strsplit_set(lines[i], ",", 5);
+ if (!line[0] || !line[1] || !line[2] || !line[3] || line[0][0]=='#')
+ continue;
+ //name, width, height, unit
+ double width = g_ascii_strtod(line[1], nullptr);
+ double height = g_ascii_strtod(line[2], nullptr);
+ g_strstrip(line[0]);
+ g_strstrip(line[3]);
+ Glib::ustring name = line[0];
+ char formatBuf[80];
+ snprintf(formatBuf, 79, "%0.1f x %0.1f", width, height);
+ Glib::ustring desc = formatBuf;
+ desc.append(" " + std::string(line[3]));
+ PaperSize paper(name, width, height, Inkscape::Util::unit_table.getUnit(line[3]));
+ _paperSizeTable[name] = paper;
+ Gtk::TreeModel::Row row = *(_paperSizeListStore->append());
+ row[_paperSizeListColumns.nameColumn] = name;
+ row[_paperSizeListColumns.descColumn] = desc;
+ g_strfreev(line);
+ }
+ g_strfreev(lines);
+ g_free(content);
+ }
+ g_free(path);
+
+ pack_start (_paperSizeListScroller, true, true, 0);
+
+ //## Set up orientation radio buttons
+ pack_start (_orientationBox, false, false, 0);
+ _orientationLabel.set_label(_("Orientation:"));
+ _orientationBox.pack_start(_orientationLabel, false, false, 0);
+ _landscapeButton.set_use_underline();
+ _landscapeButton.set_label(_("_Landscape"));
+ _landscapeButton.set_active(true);
+ Gtk::RadioButton::Group group = _landscapeButton.get_group();
+ _orientationBox.pack_end (_landscapeButton, false, false, 5);
+ _portraitButton.set_use_underline();
+ _portraitButton.set_label(_("_Portrait"));
+ _portraitButton.set_active(true);
+ _orientationBox.pack_end (_portraitButton, false, false, 5);
+ _portraitButton.set_group (group);
+ _portraitButton.set_active (true);
+
+ // Setting default custom unit to document unit
+ SPDesktop *dt = SP_ACTIVE_DESKTOP;
+ SPNamedView *nv = dt->getNamedView();
+ _wr.setUpdating (true);
+ if (nv->page_size_units) {
+ _dimensionUnits.setUnit(nv->page_size_units->abbr);
+ } else if (nv->display_units) {
+ _dimensionUnits.setUnit(nv->display_units->abbr);
+ }
+ _wr.setUpdating (false);
+
+
+ //## Set up custom size frame
+ _customFrame.set_label(_("Custom size"));
+ pack_start (_customFrame, false, false, 0);
+ _customFrame.add(_customDimTable);
+
+ _customDimTable.set_border_width(4);
+ _customDimTable.set_row_spacing(4);
+ _customDimTable.set_column_spacing(4);
+
+ _dimensionHeight.set_halign(Gtk::ALIGN_CENTER);
+ _dimensionUnits.set_halign(Gtk::ALIGN_END);
+ _customDimTable.attach(_dimensionWidth, 0, 0, 1, 1);
+ _customDimTable.attach(_dimensionHeight, 1, 0, 1, 1);
+ _customDimTable.attach(_dimensionUnits, 2, 0, 1, 1);
+
+ _customDimTable.attach(_fitPageMarginExpander, 0, 1, 3, 1);
+
+ //## Set up fit page expander
+ _fitPageMarginExpander.set_use_underline();
+ _fitPageMarginExpander.set_label(_("Resi_ze page to content..."));
+ _fitPageMarginExpander.add(_marginTable);
+
+ _marginTable.set_border_width(4);
+ _marginTable.set_row_spacing(4);
+ _marginTable.set_column_spacing(4);
+
+ //### margin label and lock button
+ _marginLabel.set_markup(Glib::ustring("<b><i>") + _("Margins") + "</i></b>");
+ _marginLabel.set_halign(Gtk::ALIGN_CENTER);
+
+ _lock_icon.set_from_icon_name("object-unlocked", Gtk::ICON_SIZE_LARGE_TOOLBAR);
+ _lock_icon.show();
+ _marginLock.set_active(false);
+ _marginLock.add(_lock_icon);
+
+ _marginBox.set_spacing(4);
+ _marginBox.add(_marginLabel);
+ _marginBox.add(_marginLock);
+ _marginBox.set_halign(Gtk::ALIGN_CENTER);
+ _marginTable.attach(_marginBox, 1, 1, 1, 1);
+
+ //### margins
+ _marginTop.set_halign(Gtk::ALIGN_CENTER);
+ _marginLeft.set_halign(Gtk::ALIGN_START);
+ _marginRight.set_halign(Gtk::ALIGN_END);
+ _marginBottom.set_halign(Gtk::ALIGN_CENTER);
+
+ _marginTable.attach(_marginTop, 0, 0, 3, 1);
+ _marginTable.attach(_marginLeft, 0, 1, 1, 1);
+ _marginTable.attach(_marginRight, 2, 1, 1, 1);
+ _marginTable.attach(_marginBottom, 0, 2, 3, 1);
+
+ //### fit page to drawing button
+ _fitPageButton.set_use_underline();
+ _fitPageButton.set_label(_("_Resize page to drawing or selection (Ctrl+Shift+R)"));
+ _fitPageButton.set_tooltip_text(_("Resize the page to fit the current selection, or the entire drawing if there is no selection"));
+
+ _fitPageButton.set_hexpand();
+ _fitPageButton.set_halign(Gtk::ALIGN_CENTER);
+ _marginTable.attach(_fitPageButton, 0, 3, 3, 1);
+
+
+ //## Set up scale frame
+ _scaleFrame.set_label(_("Scale"));
+ pack_start (_scaleFrame, false, false, 0);
+ _scaleFrame.add(_scaleTable);
+
+ _scaleTable.set_border_width(4);
+ _scaleTable.set_row_spacing(4);
+ _scaleTable.set_column_spacing(4);
+
+ _scaleTable.attach(_scaleX, 0, 0, 1, 1);
+ _scaleTable.attach(_scaleY, 1, 0, 1, 1);
+ _scaleTable.attach(_scaleLabel, 2, 0, 1, 1);
+
+ _viewboxExpander.set_hexpand();
+ _scaleTable.attach(_viewboxExpander, 0, 2, 3, 1);
+
+ _viewboxExpander.set_use_underline();
+ _viewboxExpander.set_label(_("_Viewbox..."));
+ _viewboxExpander.add(_viewboxTable);
+
+ _viewboxTable.set_border_width(4);
+ _viewboxTable.set_row_spacing(4);
+ _viewboxTable.set_column_spacing(4);
+
+ _viewboxX.set_halign(Gtk::ALIGN_END);
+ _viewboxY.set_halign(Gtk::ALIGN_END);
+ _viewboxW.set_halign(Gtk::ALIGN_END);
+ _viewboxH.set_halign(Gtk::ALIGN_END);
+ _viewboxSpacer.set_hexpand();
+ _viewboxTable.attach(_viewboxX, 0, 0, 1, 1);
+ _viewboxTable.attach(_viewboxY, 1, 0, 1, 1);
+ _viewboxTable.attach(_viewboxW, 0, 1, 1, 1);
+ _viewboxTable.attach(_viewboxH, 1, 1, 1, 1);
+ _viewboxTable.attach(_viewboxSpacer, 2, 0, 3, 1);
+
+ _wr.setUpdating (true);
+ updateScaleUI();
+ _wr.setUpdating (false);
+}
+
+
+/**
+ * Destructor
+ */
+PageSizer::~PageSizer()
+= default;
+
+
+
+/**
+ * Initialize or reset this widget
+ */
+void
+PageSizer::init ()
+{
+ _landscape_connection = _landscapeButton.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_landscape));
+ _portrait_connection = _portraitButton.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_portrait));
+ _changedw_connection = _dimensionWidth.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_value_changed));
+ _changedh_connection = _dimensionHeight.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_value_changed));
+ _changedu_connection = _dimensionUnits.getUnitMenu()->signal_changed().connect (sigc::mem_fun (*this, &PageSizer::on_units_changed));
+ _fitPageButton.signal_clicked().connect(sigc::mem_fun(*this, &PageSizer::fire_fit_canvas_to_selection_or_drawing));
+ _changeds_connection = _scaleX.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_scale_changed));
+ _changedvx_connection = _viewboxX.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_viewbox_changed));
+ _changedvy_connection = _viewboxY.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_viewbox_changed));
+ _changedvw_connection = _viewboxW.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_viewbox_changed));
+ _changedvh_connection = _viewboxH.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_viewbox_changed));
+ _changedlk_connection = _marginLock.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_margin_lock_changed));
+ _changedmt_connection = _marginTop.signal_value_changed().connect (sigc::bind<RegisteredScalar*>(sigc::mem_fun (*this, &PageSizer::on_margin_changed), &_marginTop));
+ _changedmb_connection = _marginBottom.signal_value_changed().connect (sigc::bind<RegisteredScalar*>(sigc::mem_fun (*this, &PageSizer::on_margin_changed), &_marginBottom));
+ _changedml_connection = _marginLeft.signal_value_changed().connect (sigc::bind<RegisteredScalar*>(sigc::mem_fun (*this, &PageSizer::on_margin_changed), &_marginLeft));
+ _changedmr_connection = _marginRight.signal_value_changed().connect (sigc::bind<RegisteredScalar*>(sigc::mem_fun (*this, &PageSizer::on_margin_changed), &_marginRight));
+ show_all_children();
+}
+
+
+/**
+ * Set document dimensions (if not called by Doc prop's update()) and
+ * set the PageSizer's widgets and text entries accordingly. If
+ * 'changeList' is true, then adjust the paperSizeList to show the closest
+ * standard page size.
+ *
+ * \param w, h
+ * \param changeList whether to modify the paper size list
+ */
+void
+PageSizer::setDim (Inkscape::Util::Quantity w, Inkscape::Util::Quantity h, bool changeList, bool changeSize)
+{
+ static bool _called = false;
+ if (_called) {
+ return;
+ }
+
+ _called = true;
+
+ _paper_size_list_connection.block();
+ _landscape_connection.block();
+ _portrait_connection.block();
+ _changedw_connection.block();
+ _changedh_connection.block();
+
+ _unit = w.unit->abbr;
+
+ if (SP_ACTIVE_DESKTOP && !_widgetRegistry->isUpdating()) {
+ SPDocument *doc = SP_ACTIVE_DESKTOP->getDocument();
+ Inkscape::Util::Quantity const old_height = doc->getHeight();
+ doc->setWidthAndHeight (w, h, changeSize);
+ // The origin for the user is in the lower left corner; this point should remain stationary when
+ // changing the page size. The SVG's origin however is in the upper left corner, so we must compensate for this
+ if (changeSize && !doc->is_yaxisdown()) {
+ Geom::Translate const vert_offset(Geom::Point(0, (old_height.value("px") - h.value("px"))));
+ doc->getRoot()->translateChildItems(vert_offset);
+ }
+ DocumentUndo::done(doc, SP_VERB_NONE, _("Set page size"));
+ }
+
+ if ( w != h ) {
+ _landscapeButton.set_sensitive(true);
+ _portraitButton.set_sensitive (true);
+ _landscape = ( w > h );
+ _landscapeButton.set_active(_landscape ? true : false);
+ _portraitButton.set_active (_landscape ? false : true);
+ } else {
+ _landscapeButton.set_sensitive(false);
+ _portraitButton.set_sensitive (false);
+ }
+
+ if (changeList)
+ {
+ Gtk::TreeModel::Row row = (*find_paper_size(w, h));
+ if (row)
+ _paperSizeListSelection->select(row);
+ }
+
+ _dimensionWidth.setUnit(w.unit->abbr);
+ _dimensionWidth.setValue (w.quantity);
+ _dimensionHeight.setUnit(h.unit->abbr);
+ _dimensionHeight.setValue (h.quantity);
+
+
+ _paper_size_list_connection.unblock();
+ _landscape_connection.unblock();
+ _portrait_connection.unblock();
+ _changedw_connection.unblock();
+ _changedh_connection.unblock();
+
+ _called = false;
+}
+
+/**
+ * Updates the scalar widgets for the fit margins. (Just changes the value
+ * of the ui widgets to match the xml).
+ */
+void
+PageSizer::updateFitMarginsUI(Inkscape::XML::Node *nv_repr)
+{
+ if (!_lockMarginUpdate) {
+ double value = 0.0;
+ if (sp_repr_get_double(nv_repr, "fit-margin-top", &value)) {
+ _marginTop.setValue(value);
+ }
+ if (sp_repr_get_double(nv_repr, "fit-margin-left", &value)) {
+ _marginLeft.setValue(value);
+ }
+ if (sp_repr_get_double(nv_repr, "fit-margin-right", &value)) {
+ _marginRight.setValue(value);
+ }
+ if (sp_repr_get_double(nv_repr, "fit-margin-bottom", &value)) {
+ _marginBottom.setValue(value);
+ }
+ }
+}
+
+
+/**
+ * Returns an iterator pointing to a row in paperSizeListStore which
+ * contains a paper of the specified size, or
+ * paperSizeListStore->children().end() if no such paper exists.
+ *
+ * The code is not tested for the case where w and h have different units.
+ */
+Gtk::ListStore::iterator
+PageSizer::find_paper_size (Inkscape::Util::Quantity w, Inkscape::Util::Quantity h) const
+{
+ // The code below assumes that w < h, so make sure that's the case:
+ if ( h < w ) {
+ std::swap(h,w);
+ }
+
+ std::map<Glib::ustring, PaperSize>::const_iterator iter;
+ for (iter = _paperSizeTable.begin() ;
+ iter != _paperSizeTable.end() ; ++iter) {
+ PaperSize paper = iter->second;
+ Inkscape::Util::Quantity smallX (paper.smaller, paper.unit);
+ Inkscape::Util::Quantity largeX (paper.larger, paper.unit);
+
+ // account for landscape formats (e.g. business cards)
+ if (largeX < smallX) {
+ std::swap(largeX, smallX);
+ }
+
+ if ( are_near(w, smallX, 0.1) && are_near(h, largeX, 0.1) ) {
+ Gtk::ListStore::iterator p = _paperSizeListStore->children().begin();
+ Gtk::ListStore::iterator pend = _paperSizeListStore->children().end();
+ // We need to search paperSizeListStore explicitly for the
+ // specified paper size because it is sorted in a different
+ // way than paperSizeTable (which is sorted alphabetically)
+ for ( ; p != pend; ++p) {
+ if ((*p)[_paperSizeListColumns.nameColumn] == paper.name) {
+ return p;
+ }
+ }
+ }
+ }
+ return _paperSizeListStore->children().end();
+}
+
+
+
+/**
+ * Tell the desktop to fit the page size to the selection or drawing.
+ */
+void
+PageSizer::fire_fit_canvas_to_selection_or_drawing()
+{
+ SPDesktop *dt = SP_ACTIVE_DESKTOP;
+ if (!dt) {
+ return;
+ }
+ SPDocument *doc;
+ SPNamedView *nv;
+ Inkscape::XML::Node *nv_repr;
+
+ if ((doc = SP_ACTIVE_DESKTOP->getDocument())
+ && (nv = sp_document_namedview(doc, nullptr))
+ && (nv_repr = nv->getRepr())) {
+ _lockMarginUpdate = true;
+ sp_repr_set_svg_double(nv_repr, "fit-margin-top", _marginTop.getValue());
+ sp_repr_set_svg_double(nv_repr, "fit-margin-left", _marginLeft.getValue());
+ sp_repr_set_svg_double(nv_repr, "fit-margin-right", _marginRight.getValue());
+ sp_repr_set_svg_double(nv_repr, "fit-margin-bottom", _marginBottom.getValue());
+ _lockMarginUpdate = false;
+ }
+
+ Verb *verb = Verb::get( SP_VERB_FIT_CANVAS_TO_SELECTION_OR_DRAWING );
+ if (verb) {
+ SPAction *action = verb->get_action(Inkscape::ActionContext(dt));
+ if (action) {
+ sp_action_perform(action, nullptr);
+ }
+ }
+}
+
+
+
+/**
+ * Paper Size list callback for when a user changes the selection
+ */
+void
+PageSizer::on_paper_size_list_changed()
+{
+ //Glib::ustring name = _paperSizeList.get_active_text();
+ Gtk::TreeModel::iterator miter = _paperSizeListSelection->get_selected();
+ if(!miter)
+ {
+ //error?
+ return;
+ }
+ Gtk::TreeModel::Row row = *miter;
+ Glib::ustring name = row[_paperSizeListColumns.nameColumn];
+ std::map<Glib::ustring, PaperSize>::const_iterator piter =
+ _paperSizeTable.find(name);
+ if (piter == _paperSizeTable.end()) {
+ g_warning("paper size '%s' not found in table", name.c_str());
+ return;
+ }
+ PaperSize paper = piter->second;
+ Inkscape::Util::Quantity w = Inkscape::Util::Quantity(paper.smaller, paper.unit);
+ Inkscape::Util::Quantity h = Inkscape::Util::Quantity(paper.larger, paper.unit);
+
+ if ( w > h ) {
+ // enforce landscape mode if this is desired for the given page format
+ _landscape = true;
+ } else {
+ // otherwise we keep the current mode
+ _landscape = _landscapeButton.get_active();
+ }
+
+ if ((_landscape && (w < h)) || (!_landscape && (w > h)))
+ setDim (h, w, false);
+ else
+ setDim (w, h, false);
+
+}
+
+
+/**
+ * Portrait button callback
+ */
+void
+PageSizer::on_portrait()
+{
+ if (!_portraitButton.get_active())
+ return;
+ Inkscape::Util::Quantity w = Inkscape::Util::Quantity(_dimensionWidth.getValue(""), _dimensionWidth.getUnit());
+ Inkscape::Util::Quantity h = Inkscape::Util::Quantity(_dimensionHeight.getValue(""), _dimensionHeight.getUnit());
+ if (h < w) {
+ setDim (h, w);
+ }
+}
+
+
+/**
+ * Landscape button callback
+ */
+void
+PageSizer::on_landscape()
+{
+ if (!_landscapeButton.get_active())
+ return;
+ Inkscape::Util::Quantity w = Inkscape::Util::Quantity(_dimensionWidth.getValue(""), _dimensionWidth.getUnit());
+ Inkscape::Util::Quantity h = Inkscape::Util::Quantity(_dimensionHeight.getValue(""), _dimensionHeight.getUnit());
+ if (w < h) {
+ setDim (h, w);
+ }
+}
+
+
+/**
+ * Update scale widgets
+ */
+void
+PageSizer::updateScaleUI()
+{
+
+ static bool _called = false;
+ if (_called) {
+ return;
+ }
+
+ _called = true;
+
+ _changeds_connection.block();
+ _changedvx_connection.block();
+ _changedvy_connection.block();
+ _changedvw_connection.block();
+ _changedvh_connection.block();
+
+ SPDesktop *dt = SP_ACTIVE_DESKTOP;
+ if (dt) {
+ SPDocument *doc = dt->getDocument();
+
+ // Update scale
+ Geom::Scale scale = doc->getDocumentScale();
+ SPNamedView *nv = dt->getNamedView();
+
+ std::stringstream ss;
+ ss << _("User units per ") << nv->display_units->abbr << "." ;
+ _scaleLabel.set_text( ss.str() );
+
+ if( !_lockScaleUpdate ) {
+
+ double scaleX_inv =
+ Inkscape::Util::Quantity::convert( scale[Geom::X], "px", nv->display_units );
+ if( scaleX_inv > 0 ) {
+ _scaleX.setValue(1.0/scaleX_inv);
+ } else {
+ // Should never happen
+ std::cerr << "PageSizer::updateScaleUI(): Invalid scale value: " << scaleX_inv << std::endl;
+ _scaleX.setValue(1.0);
+ }
+ }
+
+ { // Don't need to lock as scaleY widget not linked to callback.
+ double scaleY_inv =
+ Inkscape::Util::Quantity::convert( scale[Geom::Y], "px", nv->display_units );
+ if( scaleY_inv > 0 ) {
+ _scaleY.setValue(1.0/scaleY_inv);
+ } else {
+ // Should never happen
+ std::cerr << "PageSizer::updateScaleUI(): Invalid scale value: " << scaleY_inv << std::endl;
+ _scaleY.setValue(1.0);
+ }
+ }
+
+ if( !_lockViewboxUpdate ) {
+ Geom::Rect viewBox = doc->getViewBox();
+ _viewboxX.setValue( viewBox.min()[Geom::X] );
+ _viewboxY.setValue( viewBox.min()[Geom::Y] );
+ _viewboxW.setValue( viewBox.width() );
+ _viewboxH.setValue( viewBox.height() );
+ }
+
+ } else {
+ // Should never happen
+ std::cerr << "PageSizer::updateScaleUI(): No active desktop." << std::endl;
+ _scaleLabel.set_text( "Unknown scale" );
+ }
+
+ _changeds_connection.unblock();
+ _changedvx_connection.unblock();
+ _changedvy_connection.unblock();
+ _changedvw_connection.unblock();
+ _changedvh_connection.unblock();
+
+ _called = false;
+}
+
+
+/**
+ * Callback for the dimension widgets
+ */
+void
+PageSizer::on_value_changed()
+{
+ if (_widgetRegistry->isUpdating()) return;
+ if (_unit != _dimensionUnits.getUnit()->abbr) return;
+ setDim (Inkscape::Util::Quantity(_dimensionWidth.getValue(""), _dimensionUnits.getUnit()),
+ Inkscape::Util::Quantity(_dimensionHeight.getValue(""), _dimensionUnits.getUnit()));
+}
+
+void
+PageSizer::on_units_changed()
+{
+ if (_widgetRegistry->isUpdating()) return;
+ _unit = _dimensionUnits.getUnit()->abbr;
+ setDim (Inkscape::Util::Quantity(_dimensionWidth.getValue(""), _dimensionUnits.getUnit()),
+ Inkscape::Util::Quantity(_dimensionHeight.getValue(""), _dimensionUnits.getUnit()),
+ true, false);
+}
+
+/**
+ * Callback for scale widgets
+ */
+void
+PageSizer::on_scale_changed()
+{
+ if (_widgetRegistry->isUpdating()) return;
+
+ double value = _scaleX.getValue();
+ if( value > 0 ) {
+
+ SPDesktop *dt = SP_ACTIVE_DESKTOP;
+ if (dt) {
+ SPDocument *doc = dt->getDocument();
+ SPNamedView *nv = dt->getNamedView();
+
+ double scaleX_inv = Inkscape::Util::Quantity(1.0/value, nv->display_units ).value("px");
+
+ _lockScaleUpdate = true;
+ doc->setDocumentScale( 1.0/scaleX_inv );
+ updateScaleUI();
+ _lockScaleUpdate = false;
+ DocumentUndo::done(doc, SP_VERB_NONE, _("Set page scale"));
+ }
+ }
+}
+
+/**
+ * Callback for viewbox widgets
+ */
+void
+PageSizer::on_viewbox_changed()
+{
+ if (_widgetRegistry->isUpdating()) return;
+
+ double viewboxX = _viewboxX.getValue();
+ double viewboxY = _viewboxY.getValue();
+ double viewboxW = _viewboxW.getValue();
+ double viewboxH = _viewboxH.getValue();
+
+ if( viewboxW > 0 && viewboxH > 0) {
+ SPDesktop *dt = SP_ACTIVE_DESKTOP;
+ if (dt) {
+ SPDocument *doc = dt->getDocument();
+ _lockViewboxUpdate = true;
+ doc->setViewBox( Geom::Rect::from_xywh( viewboxX, viewboxY, viewboxW, viewboxH ) );
+ updateScaleUI();
+ _lockViewboxUpdate = false;
+ DocumentUndo::done(doc, SP_VERB_NONE, _("Set 'viewBox'"));
+ }
+ } else {
+ std::cerr
+ << "PageSizer::on_viewbox_changed(): width and height must both be greater than zero."
+ << std::endl;
+ }
+}
+
+void
+PageSizer::on_margin_lock_changed()
+{
+ if (_marginLock.get_active()) {
+ _lock_icon.set_from_icon_name("object-locked", Gtk::ICON_SIZE_LARGE_TOOLBAR);
+ double left = _marginLeft.getValue();
+ double right = _marginRight.getValue();
+ double top = _marginTop.getValue();
+ //double bottom = _marginBottom.getValue();
+ if (Geom::are_near(left,right)) {
+ if (Geom::are_near(left, top)) {
+ on_margin_changed(&_marginBottom);
+ } else {
+ on_margin_changed(&_marginTop);
+ }
+ } else {
+ if (Geom::are_near(left, top)) {
+ on_margin_changed(&_marginRight);
+ } else {
+ on_margin_changed(&_marginLeft);
+ }
+ }
+ } else {
+ _lock_icon.set_from_icon_name("object-unlocked", Gtk::ICON_SIZE_LARGE_TOOLBAR);
+ }
+}
+
+void
+PageSizer::on_margin_changed(RegisteredScalar* widg)
+{
+ double value = widg->getValue();
+ if (_widgetRegistry->isUpdating()) return;
+ if (_marginLock.get_active() && !_lockMarginUpdate) {
+ _lockMarginUpdate = true;
+ _marginLeft.setValue(value);
+ _marginRight.setValue(value);
+ _marginTop.setValue(value);
+ _marginBottom.setValue(value);
+ _lockMarginUpdate = false;
+ }
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/page-sizer.h b/src/ui/widget/page-sizer.h
new file mode 100644
index 0000000..b399835
--- /dev/null
+++ b/src/ui/widget/page-sizer.h
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Ralf Stephan <ralf@ark.in-berlin.de>
+ *
+ * Copyright (C) 2005-2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_PAGE_SIZER_H
+#define INKSCAPE_UI_WIDGET_PAGE_SIZER_H
+
+#include <cstddef>
+#include "ui/widget/registered-widget.h"
+#include <sigc++/sigc++.h>
+
+#include "util/units.h"
+
+#include <gtkmm/expander.h>
+#include <gtkmm/frame.h>
+#include <gtkmm/grid.h>
+#include <gtkmm/liststore.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/radiobutton.h>
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+
+namespace UI {
+namespace Widget {
+
+class Registry;
+
+/**
+ * Data class used to store common paper dimensions. Used to make
+ * PageSizer's _paperSizeTable.
+ */
+class PaperSize
+{
+public:
+
+ /**
+ * Default constructor
+ */
+ PaperSize()
+ { init(); }
+
+ /**
+ * Main constructor. Use this one.
+ */
+ PaperSize(const Glib::ustring &nameArg,
+ double smallerArg,
+ double largerArg,
+ Inkscape::Util::Unit const *unitArg)
+ {
+ name = nameArg;
+ smaller = smallerArg;
+ larger = largerArg;
+ unit = unitArg;
+ }
+
+ /**
+ * Copy constructor
+ */
+ PaperSize(const PaperSize &other)
+ { assign(other); }
+
+ /**
+ * Assignment operator
+ */
+ PaperSize &operator=(const PaperSize &other)
+ { assign(other); return *this; }
+
+ /**
+ * Destructor
+ */
+ virtual ~PaperSize()
+ = default;
+
+ /**
+ * Name of this paper specification
+ */
+ Glib::ustring name;
+
+ /**
+ * The lesser of the two dimensions
+ */
+ double smaller;
+
+ /**
+ * The greater of the two dimensions
+ */
+ double larger;
+
+ /**
+ * The units (px, pt, mm, etc) of this specification
+ */
+ Inkscape::Util::Unit const *unit; /// pointer to object in UnitTable, do not delete
+
+private:
+
+ void init()
+ {
+ name = "";
+ smaller = 0.0;
+ larger = 0.0;
+ unit = unit_table.getUnit("px");
+ }
+
+ void assign(const PaperSize &other)
+ {
+ name = other.name;
+ smaller = other.smaller;
+ larger = other.larger;
+ unit = other.unit;
+ }
+
+};
+
+
+
+
+
+/**
+ * A compound widget that allows the user to select the desired
+ * page size. This widget is used in DocumentPreferences
+ */
+class PageSizer : public Gtk::VBox
+{
+public:
+
+ /**
+ * Constructor
+ */
+ PageSizer(Registry & _wr);
+
+ /**
+ * Destructor
+ */
+ ~PageSizer() override;
+
+ /**
+ * Set up or reset this widget
+ */
+ void init ();
+
+ /**
+ * Set the page size to the given dimensions. If 'changeList' is
+ * true, then reset the paper size list to the closest match
+ */
+ void setDim (Inkscape::Util::Quantity w, Inkscape::Util::Quantity h, bool changeList=true, bool changeSize=true);
+
+ /**
+ * Updates the scalar widgets for the fit margins. (Just changes the value
+ * of the ui widgets to match the xml).
+ */
+ void updateFitMarginsUI(Inkscape::XML::Node *nv_repr);
+
+ /**
+ * Updates the margin widgets. If lock widget is active
+ */
+ void on_margin_changed(RegisteredScalar* widg);
+
+ void on_margin_lock_changed();
+
+ /**
+ * Updates the scale widgets. (Just changes the values of the ui widgets.)
+ */
+ void updateScaleUI();
+
+protected:
+
+ /**
+ * Our handy table of all 'standard' paper sizes.
+ */
+ std::map<Glib::ustring, PaperSize> _paperSizeTable;
+
+ /**
+ * Find the closest standard paper size in the table, to the
+ */
+ Gtk::ListStore::iterator find_paper_size (Inkscape::Util::Quantity w, Inkscape::Util::Quantity h) const;
+
+ void fire_fit_canvas_to_selection_or_drawing();
+
+ //### The Paper Size selection list
+ Gtk::HBox _paperSizeListBox;
+ Gtk::Label _paperSizeListLabel;
+ class PaperSizeColumns : public Gtk::TreeModel::ColumnRecord
+ {
+ public:
+ PaperSizeColumns()
+ { add(nameColumn); add(descColumn); }
+ Gtk::TreeModelColumn<Glib::ustring> nameColumn;
+ Gtk::TreeModelColumn<Glib::ustring> descColumn;
+ };
+
+ PaperSizeColumns _paperSizeListColumns;
+ Glib::RefPtr<Gtk::ListStore> _paperSizeListStore;
+ Gtk::TreeView _paperSizeList;
+ Glib::RefPtr<Gtk::TreeSelection> _paperSizeListSelection;
+ Gtk::ScrolledWindow _paperSizeListScroller;
+ //callback
+ void on_paper_size_list_changed();
+ sigc::connection _paper_size_list_connection;
+
+ //### Portrait or landscape orientation
+ Gtk::HBox _orientationBox;
+ Gtk::Label _orientationLabel;
+ Gtk::RadioButton _portraitButton;
+ Gtk::RadioButton _landscapeButton;
+ //callbacks
+ void on_portrait();
+ void on_landscape();
+ sigc::connection _portrait_connection;
+ sigc::connection _landscape_connection;
+
+ //### Custom size frame
+ Gtk::Frame _customFrame;
+ Gtk::Grid _customDimTable;
+
+ RegisteredUnitMenu _dimensionUnits;
+ RegisteredScalarUnit _dimensionWidth;
+ RegisteredScalarUnit _dimensionHeight;
+
+ //### Fit Page options
+ Gtk::Expander _fitPageMarginExpander;
+
+ Gtk::Grid _marginTable;
+ Gtk::Box _marginBox;
+ Gtk::Label _marginLabel;
+ RegisteredToggleButton _marginLock;
+ Gtk::Image _lock_icon;
+ RegisteredScalar _marginTop;
+ RegisteredScalar _marginLeft;
+ RegisteredScalar _marginRight;
+ RegisteredScalar _marginBottom;
+ Gtk::Button _fitPageButton;
+ bool _lockMarginUpdate;
+
+ // Document scale
+ Gtk::Frame _scaleFrame;
+ Gtk::Grid _scaleTable;
+
+ Gtk::Label _scaleLabel;
+ RegisteredScalar _scaleX;
+ RegisteredScalar _scaleY;
+ bool _lockScaleUpdate;
+
+ // Viewbox
+ Gtk::Expander _viewboxExpander;
+ Gtk::Grid _viewboxTable;
+
+ RegisteredScalar _viewboxX;
+ RegisteredScalar _viewboxY;
+ RegisteredScalar _viewboxW;
+ RegisteredScalar _viewboxH;
+ Gtk::Box _viewboxSpacer;
+ bool _lockViewboxUpdate;
+
+ //callback
+ void on_value_changed();
+ void on_units_changed();
+ void on_scale_changed();
+ void on_viewbox_changed();
+ sigc::connection _changedw_connection;
+ sigc::connection _changedh_connection;
+ sigc::connection _changedu_connection;
+ sigc::connection _changeds_connection;
+ sigc::connection _changedvx_connection;
+ sigc::connection _changedvy_connection;
+ sigc::connection _changedvw_connection;
+ sigc::connection _changedvh_connection;
+ sigc::connection _changedlk_connection;
+ sigc::connection _changedmt_connection;
+ sigc::connection _changedmb_connection;
+ sigc::connection _changedml_connection;
+ sigc::connection _changedmr_connection;
+
+ Registry *_widgetRegistry;
+
+ //### state - whether we are currently landscape or portrait
+ bool _landscape;
+
+ Glib::ustring _unit;
+
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+
+#endif // INKSCAPE_UI_WIDGET_PAGE_SIZER_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/pages-skeleton.h b/src/ui/widget/pages-skeleton.h
new file mode 100644
index 0000000..c62e03e
--- /dev/null
+++ b/src/ui/widget/pages-skeleton.h
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * List of paper sizes
+ */
+/*
+ * Authors:
+ * bulia byak <buliabyak@users.sf.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Jon Phillips <jon@rejon.org>
+ * Ralf Stephan <ralf@ark.in-berlin.de> (Gtkmm)
+ * Bob Jamison <ishmal@users.sf.net>
+ * Abhishek Sharma
+ * + see git history
+ *
+ * Copyright (C) 2000 - 2018 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_PAGES_SKELETON_H
+#define SEEN_PAGES_SKELETON_H
+
+
+ /** \note
+ * The ISO page sizes in the table below differ from ghostscript's idea of page sizes (by
+ * less than 1pt). Being off by <1pt should be OK for most purposes, but may cause fuzziness
+ * (antialiasing) problems when printing to 72dpi or 144dpi printers or bitmap files due to
+ * postscript's different coordinate system (y=0 meaning bottom of page in postscript and top
+ * of page in SVG). I haven't looked into whether this does in fact cause fuzziness, I merely
+ * note the possibility. Rounding done by extension/internal/ps.cpp (e.g. floor/ceil calls)
+ * will also affect whether fuzziness occurs.
+ *
+ * The remainder of this comment discusses the origin of the numbers used for ISO page sizes in
+ * this table and in ghostscript.
+ *
+ * The versions here, in mm, are the official sizes according to
+ * <a href="http://en.wikipedia.org/wiki/Paper_sizes">http://en.wikipedia.org/wiki/Paper_sizes</a>
+ * at 2005-01-25. (The ISO entries in the below table
+ * were produced mechanically from the table on that page.)
+ *
+ * (The rule seems to be that A0, B0, ..., D0. sizes are rounded to the nearest number of mm
+ * from the "theoretical size" (i.e. 1000 * sqrt(2) or pow(2.0, .25) or the like), whereas
+ * going from e.g. A0 to A1 always take the floor of halving -- which by chance coincides
+ * exactly with flooring the "theoretical size" for n != 0 instead of the rounding to nearest
+ * done for n==0.)
+ *
+ * Ghostscript paper sizes are given in gs_statd.ps according to gs(1). gs_statd.ps always
+ * uses an integer number ofpt: sometimes gs_statd.ps rounds to nearest (e.g. a1), sometimes
+ * floors (e.g. a10), sometimes ceils (e.g. a8).
+ *
+ * I'm not sure how ghostscript's gs_statd.ps was calculated: it isn't just rounding the
+ * "theoretical size" of each page topt (see a0), nor is it rounding the a0 size times an
+ * appropriate power of two (see a1). Possibly it was prepared manually, with a human applying
+ * inconsistent rounding rules when converting from mm to pt.
+ */
+ /** \todo
+ * Should we include the JIS B series (used in Japan)
+ * (JIS B0 is sometimes called JB0, and similarly for JB1 etc)?
+ * Should we exclude B7--B10 and A7--10 to make the list smaller ?
+ * Should we include any of the ISO C, D and E series (see below) ?
+ */
+
+
+
+ /* See http://www.hbp.com/content/PCR_envelopes.cfm for a much larger list of US envelope
+ sizes. */
+ /* Note that `Folio' (used in QPrinter/KPrinter) is deliberately absent from this list, as it
+ means different sizes to different people: different people may expect the width to be
+ either 8, 8.25 or 8.5 inches, and the height to be either 13 or 13.5 inches, even
+ restricting our interpretation to foolscap folio. If you wish to introduce a folio-like
+ page size to the list, then please consider using a name more specific than just `Folio' or
+ `Foolscap Folio'. */
+
+static char const pages_skeleton[] = R"(#Inkscape page sizes
+#NAME, WIDTH, HEIGHT, UNIT
+A4, 210, 297, mm
+US Letter, 8.5, 11, in
+US Legal, 8.5, 14, in
+US Executive, 7.25, 10.5, in
+A0, 841, 1189, mm
+A1, 594, 841, mm
+A2, 420, 594, mm
+A3, 297, 420, mm
+A5, 148, 210, mm
+A6, 105, 148, mm
+A7, 74, 105, mm
+A8, 52, 74, mm
+A9, 37, 52, mm
+A10, 26, 37, mm
+B0, 1000, 1414, mm
+B1, 707, 1000, mm
+B2, 500, 707, mm
+B3, 353, 500, mm
+B4, 250, 353, mm
+B5, 176, 250, mm
+B6, 125, 176, mm
+B7, 88, 125, mm
+B8, 62, 88, mm
+B9, 44, 62, mm
+B10, 31, 44, mm
+C0, 917, 1297, mm
+C1, 648, 917, mm
+C2, 458, 648, mm
+C3, 324, 458, mm
+C4, 229, 324, mm
+C5, 162, 229, mm
+C6, 114, 162, mm
+C7, 81, 114, mm
+C8, 57, 81, mm
+C9, 40, 57, mm
+C10, 28, 40, mm
+D1, 545, 771, mm
+D2, 385, 545, mm
+D3, 272, 385, mm
+D4, 192, 272, mm
+D5, 136, 192, mm
+D6, 96, 136, mm
+D7, 68, 96, mm
+E3, 400, 560, mm
+E4, 280, 400, mm
+E5, 200, 280, mm
+E6, 140, 200, mm
+CSE, 462, 649, pt
+US #10 Envelope, 9.5, 4.125, in
+DL Envelope, 220, 110, mm
+Ledger/Tabloid, 11, 17, in
+Banner 468x60, 468, 60, px
+Icon 16x16, 16, 16, px
+Icon 32x32, 32, 32, px
+Icon 48x48, 48, 48, px
+ID Card (ISO 7810), 85.60, 53.98, mm
+Business Card (US), 3.5, 2, in
+Business Card (Europe), 85, 55, mm
+Business Card (Aus/NZ), 90, 55, mm
+Arch A, 9, 12, in
+Arch B, 12, 18, in
+Arch C, 18, 24, in
+Arch D, 24, 36, in
+Arch E, 36, 48, in
+Arch E1, 30, 42, in
+Video SD / PAL, 768, 576, px
+Video SD-Widescreen / PAL, 1024, 576, px
+Video SD / NTSC, 544, 480, px
+Video SD-Widescreen / NTSC, 872, 486, px
+Video HD 720p, 1280, 720, px
+Video HD 1080p, 1920, 1080, px
+Video DCI 2k (Full Frame), 2048, 1080, px
+Video UHD 4k, 3840, 2160, px
+Video DCI 4k (Full Frame), 4096, 2160, px
+Video UHD 8k, 7680, 4320, px
+)";
+
+#endif
diff --git a/src/ui/widget/panel.cpp b/src/ui/widget/panel.cpp
new file mode 100644
index 0000000..8ca08a0
--- /dev/null
+++ b/src/ui/widget/panel.cpp
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Bryce Harrington <bryce@bryceharrington.org>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Gustav Broberg <broberg@kth.se>
+ *
+ * Copyright (C) 2004 Bryce Harrington
+ * Copyright (C) 2005 Jon A. Cruz
+ * Copyright (C) 2007 Gustav Broberg
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/dialog.h> // for Gtk::RESPONSE_*
+
+#include <glibmm/i18n.h>
+
+#include "panel.h"
+#include "desktop.h"
+
+#include "inkscape.h"
+#include "preview.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+void Panel::prep() {
+ GtkIconSize sizes[] = {
+ GTK_ICON_SIZE_MENU,
+ GTK_ICON_SIZE_MENU,
+ GTK_ICON_SIZE_SMALL_TOOLBAR,
+ GTK_ICON_SIZE_BUTTON,
+ GTK_ICON_SIZE_DND, // Not used by options, but included to make the last size larger
+ GTK_ICON_SIZE_DIALOG
+ };
+ Preview::set_size_mappings( G_N_ELEMENTS(sizes), sizes );
+}
+
+Panel::Panel(gchar const *prefs_path, int verb_num) :
+ _prefs_path(prefs_path),
+ _desktop(SP_ACTIVE_DESKTOP),
+ _verb_num(verb_num),
+ _action_area(nullptr)
+{
+ set_name("InkscapePanel");
+ set_orientation(Gtk::ORIENTATION_VERTICAL);
+
+ signalResponse().connect(sigc::mem_fun(*this, &Panel::_handleResponse));
+ signalActivateDesktop().connect(sigc::mem_fun(*this, &Panel::setDesktop));
+
+ pack_start(_contents, true, true);
+
+ show_all_children();
+}
+
+Panel::~Panel()
+= default;
+
+void Panel::present()
+{
+ _signal_present.emit();
+}
+
+sigc::signal<void, int> &Panel::signalResponse()
+{
+ return _signal_response;
+}
+
+sigc::signal<void> &Panel::signalPresent()
+{
+ return _signal_present;
+}
+
+gchar const *Panel::getPrefsPath() const
+{
+ return _prefs_path.data();
+}
+
+int const &Panel::getVerb() const
+{
+ return _verb_num;
+}
+
+void Panel::setDesktop(SPDesktop *desktop)
+{
+ _desktop = desktop;
+}
+
+void Panel::_apply()
+{
+ g_warning("Apply button clicked for panel [Panel::_apply()]");
+}
+
+Gtk::Button *Panel::addResponseButton(const Glib::ustring &button_text, int response_id, bool pack_start)
+{
+ // Create a button box for the response buttons if it's the first button to be added
+ if (!_action_area) {
+ _action_area = new Gtk::ButtonBox();
+ _action_area->set_layout(Gtk::BUTTONBOX_END);
+ _action_area->set_spacing(6);
+ _action_area->set_border_width(4);
+ pack_end(*_action_area, Gtk::PACK_SHRINK, 0);
+ }
+
+ Gtk::Button *button = new Gtk::Button(button_text, true);
+
+ _action_area->pack_end(*button);
+
+ if (pack_start) {
+ _action_area->set_child_secondary(*button , true);
+ }
+
+ if (response_id != 0) {
+ // Re-emit clicked signals as response signals
+ button->signal_clicked().connect(sigc::bind(_signal_response.make_slot(), response_id));
+ _response_map[response_id] = button;
+ }
+
+ return button;
+}
+
+void Panel::setResponseSensitive(int response_id, bool setting)
+{
+ if (_response_map[response_id])
+ _response_map[response_id]->set_sensitive(setting);
+}
+
+sigc::signal<void, SPDesktop *, SPDocument *> &
+Panel::signalDocumentReplaced()
+{
+ return _signal_document_replaced;
+}
+
+sigc::signal<void, SPDesktop *> &
+Panel::signalActivateDesktop()
+{
+ return _signal_activate_desktop;
+}
+
+sigc::signal<void, SPDesktop *> &
+Panel::signalDeactiveDesktop()
+{
+ return _signal_deactive_desktop;
+}
+
+void Panel::_handleResponse(int response_id)
+{
+ switch (response_id) {
+ case Gtk::RESPONSE_APPLY: {
+ _apply();
+ break;
+ }
+ }
+}
+
+Inkscape::Selection *Panel::_getSelection()
+{
+ return _desktop->getSelection();
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/panel.h b/src/ui/widget/panel.h
new file mode 100644
index 0000000..3f08218
--- /dev/null
+++ b/src/ui/widget/panel.h
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Bryce Harrington <bryce@bryceharrington.org>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2004 Bryce Harrington
+ * Copyright (C) 2005 Jon A. Cruz
+ * Copyright (C) 2012 Kris De Gussem
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_UI_WIDGET_PANEL_H
+#define SEEN_INKSCAPE_UI_WIDGET_PANEL_H
+
+#include <gtkmm/box.h>
+#include <map>
+
+class SPDesktop;
+class SPDocument;
+
+namespace Gtk {
+ class Button;
+ class ButtonBox;
+}
+
+struct InkscapeApplication;
+
+namespace Inkscape {
+
+class Selection;
+
+namespace UI {
+
+namespace Widget {
+
+/**
+ * A generic dockable container.
+ *
+ * Inkscape::UI::Widget::Panel is a base class from which dockable dialogs
+ * are created. A new dockable dialog is created by deriving a class from panel.
+ * Child widgets are private data members of Panel (no need to use pointers and
+ * new).
+ *
+ * @see UI::Dialog::DesktopTracker to handle desktop change, selection change and selected object modifications.
+ * @see UI::Dialog::DialogManager manages the dialogs within inkscape.
+ */
+class Panel : public Gtk::Box {
+public:
+ static void prep();
+
+ /**
+ * Construct a Panel.
+ *
+ * @param prefs_path characteristic path to load/save dialog position.
+ * @param verb_num the dialog verb.
+ */
+ Panel(gchar const *prefs_path = nullptr, int verb_num = 0);
+ ~Panel() override;
+
+ gchar const *getPrefsPath() const;
+
+ int const &getVerb() const;
+
+ virtual void present(); //< request to be present
+
+ void restorePanelPrefs();
+
+ virtual void setDesktop(SPDesktop *desktop);
+ SPDesktop *getDesktop() { return _desktop; }
+
+ /* Signal accessors */
+ virtual sigc::signal<void, int> &signalResponse();
+ virtual sigc::signal<void> &signalPresent();
+
+ /* Methods providing a Gtk::Dialog like interface for adding buttons that emit Gtk::RESPONSE
+ * signals on click. */
+ Gtk::Button* addResponseButton (const Glib::ustring &button_text, int response_id, bool pack_start=false);
+ void setResponseSensitive(int response_id, bool setting);
+
+ /* Return signals. Signals emitted by PanelDialog. */
+ virtual sigc::signal<void, SPDesktop *, SPDocument *> &signalDocumentReplaced();
+ virtual sigc::signal<void, SPDesktop *> &signalActivateDesktop();
+ virtual sigc::signal<void, SPDesktop *> &signalDeactiveDesktop();
+
+protected:
+ /**
+ * Returns a pointer to a Gtk::Box containing the child widgets.
+ */
+ Gtk::Box *_getContents() { return &_contents; }
+ virtual void _apply();
+
+ virtual void _handleResponse(int response_id);
+
+ /* Helper methods */
+ Inkscape::Selection *_getSelection();
+
+ /**
+ * Stores characteristic path for loading/saving the dialog position.
+ */
+ Glib::ustring const _prefs_path;
+
+ /* Signals */
+ sigc::signal<void, int> _signal_response;
+ sigc::signal<void> _signal_present;
+ sigc::signal<void, SPDesktop *, SPDocument *> _signal_document_replaced;
+ sigc::signal<void, SPDesktop *> _signal_activate_desktop;
+ sigc::signal<void, SPDesktop *> _signal_deactive_desktop;
+
+private:
+ SPDesktop *_desktop;
+
+ int _verb_num;
+
+ Gtk::VBox _contents;
+ Gtk::ButtonBox *_action_area; //< stores response buttons
+
+ /* A map to store which widget that emits a certain response signal */
+ typedef std::map<int, Gtk::Widget *> ResponseMap;
+ ResponseMap _response_map;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // SEEN_INKSCAPE_UI_WIDGET_PANEL_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/point.cpp b/src/ui/widget/point.cpp
new file mode 100644
index 0000000..e0d6eed
--- /dev/null
+++ b/src/ui/widget/point.cpp
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@utwente.nl>
+ * Carl Hetherington <inkscape@carlh.net>
+ * Derek P. Moore <derekm@hackunix.org>
+ * Bryce Harrington <bryce@bryceharrington.org>
+ *
+ * Copyright (C) 2007 Authors
+ * Copyright (C) 2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "ui/widget/point.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+Point::Point(Glib::ustring const &label, Glib::ustring const &tooltip,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Labelled(label, tooltip, new Gtk::VBox(), suffix, icon, mnemonic),
+ xwidget("X:",""),
+ ywidget("Y:","")
+{
+ static_cast<Gtk::VBox*>(_widget)->pack_start(xwidget, true, true);
+ static_cast<Gtk::VBox*>(_widget)->pack_start(ywidget, true, true);
+ static_cast<Gtk::VBox*>(_widget)->show_all_children();
+}
+
+Point::Point(Glib::ustring const &label, Glib::ustring const &tooltip,
+ unsigned digits,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Labelled(label, tooltip, new Gtk::VBox(), suffix, icon, mnemonic),
+ xwidget("X:","", digits),
+ ywidget("Y:","", digits)
+{
+ static_cast<Gtk::VBox*>(_widget)->pack_start(xwidget, true, true);
+ static_cast<Gtk::VBox*>(_widget)->pack_start(ywidget, true, true);
+ static_cast<Gtk::VBox*>(_widget)->show_all_children();
+}
+
+Point::Point(Glib::ustring const &label, Glib::ustring const &tooltip,
+ Glib::RefPtr<Gtk::Adjustment> &adjust,
+ unsigned digits,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Labelled(label, tooltip, new Gtk::VBox(), suffix, icon, mnemonic),
+ xwidget("X:","", adjust, digits),
+ ywidget("Y:","", adjust, digits)
+{
+ static_cast<Gtk::VBox*>(_widget)->pack_start(xwidget, true, true);
+ static_cast<Gtk::VBox*>(_widget)->pack_start(ywidget, true, true);
+ static_cast<Gtk::VBox*>(_widget)->show_all_children();
+}
+
+unsigned Point::getDigits() const
+{
+ return xwidget.getDigits();
+}
+
+double Point::getStep() const
+{
+ return xwidget.getStep();
+}
+
+double Point::getPage() const
+{
+ return xwidget.getPage();
+}
+
+double Point::getRangeMin() const
+{
+ return xwidget.getRangeMin();
+}
+
+double Point::getRangeMax() const
+{
+ return xwidget.getRangeMax();
+}
+
+double Point::getXValue() const
+{
+ return xwidget.getValue();
+}
+
+double Point::getYValue() const
+{
+ return ywidget.getValue();
+}
+
+Geom::Point Point::getValue() const
+{
+ return Geom::Point( getXValue() , getYValue() );
+}
+
+int Point::getXValueAsInt() const
+{
+ return xwidget.getValueAsInt();
+}
+
+int Point::getYValueAsInt() const
+{
+ return ywidget.getValueAsInt();
+}
+
+
+void Point::setDigits(unsigned digits)
+{
+ xwidget.setDigits(digits);
+ ywidget.setDigits(digits);
+}
+
+void Point::setIncrements(double step, double page)
+{
+ xwidget.setIncrements(step, page);
+ ywidget.setIncrements(step, page);
+}
+
+void Point::setRange(double min, double max)
+{
+ xwidget.setRange(min, max);
+ ywidget.setRange(min, max);
+}
+
+void Point::setValue(Geom::Point const & p)
+{
+ xwidget.setValue(p[0]);
+ ywidget.setValue(p[1]);
+}
+
+void Point::update()
+{
+ xwidget.update();
+ ywidget.update();
+}
+
+bool Point::setProgrammatically()
+{
+ return (xwidget.setProgrammatically || ywidget.setProgrammatically);
+}
+
+void Point::clearProgrammatically()
+{
+ xwidget.setProgrammatically = false;
+ ywidget.setProgrammatically = false;
+}
+
+
+Glib::SignalProxy0<void> Point::signal_x_value_changed()
+{
+ return xwidget.signal_value_changed();
+}
+
+Glib::SignalProxy0<void> Point::signal_y_value_changed()
+{
+ return ywidget.signal_value_changed();
+}
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/point.h b/src/ui/widget/point.h
new file mode 100644
index 0000000..018be5b
--- /dev/null
+++ b/src/ui/widget/point.h
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@utwente.nl>
+ * Carl Hetherington <inkscape@carlh.net>
+ * Derek P. Moore <derekm@hackunix.org>
+ * Bryce Harrington <bryce@bryceharrington.org>
+ *
+ * Copyright (C) 2007 Authors
+ * Copyright (C) 2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef INKSCAPE_UI_WIDGET_POINT_H
+#define INKSCAPE_UI_WIDGET_POINT_H
+
+#include "ui/widget/labelled.h"
+#include <2geom/point.h>
+#include "ui/widget/scalar.h"
+
+namespace Gtk {
+class Adjustment;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * A labelled text box, with spin buttons and optional icon or suffix, for
+ * entering arbitrary coordinate values.
+ */
+class Point : public Labelled
+{
+public:
+
+
+ /**
+ * Construct a Point Widget.
+ *
+ * @param label Label.
+ * @param suffix Suffix, placed after the widget (defaults to "").
+ * @param icon Icon filename, placed before the label (defaults to "").
+ * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to false).
+ */
+ Point( Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ /**
+ * Construct a Point Widget.
+ *
+ * @param label Label.
+ * @param digits Number of decimal digits to display.
+ * @param suffix Suffix, placed after the widget (defaults to "").
+ * @param icon Icon filename, placed before the label (defaults to "").
+ * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to false).
+ */
+ Point( Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ unsigned digits,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ /**
+ * Construct a Point Widget.
+ *
+ * @param label Label.
+ * @param adjust Adjustment to use for the SpinButton.
+ * @param digits Number of decimal digits to display (defaults to 0).
+ * @param suffix Suffix, placed after the widget (defaults to "").
+ * @param icon Icon filename, placed before the label (defaults to "").
+ * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to true).
+ */
+ Point( Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ Glib::RefPtr<Gtk::Adjustment> &adjust,
+ unsigned digits = 0,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ /**
+ * Fetches the precision of the spin button.
+ */
+ unsigned getDigits() const;
+
+ /**
+ * Gets the current step increment used by the spin button.
+ */
+ double getStep() const;
+
+ /**
+ * Gets the current page increment used by the spin button.
+ */
+ double getPage() const;
+
+ /**
+ * Gets the minimum range value allowed for the spin button.
+ */
+ double getRangeMin() const;
+
+ /**
+ * Gets the maximum range value allowed for the spin button.
+ */
+ double getRangeMax() const;
+
+ bool getSnapToTicks() const;
+
+ /**
+ * Get the value in the spin_button.
+ */
+ double getXValue() const;
+
+ double getYValue() const;
+
+ Geom::Point getValue() const;
+
+ /**
+ * Get the value spin_button represented as an integer.
+ */
+ int getXValueAsInt() const;
+
+ int getYValueAsInt() const;
+
+ /**
+ * Sets the precision to be displayed by the spin button.
+ */
+ void setDigits(unsigned digits);
+
+ /**
+ * Sets the step and page increments for the spin button.
+ */
+ void setIncrements(double step, double page);
+
+ /**
+ * Sets the minimum and maximum range allowed for the spin button.
+ */
+ void setRange(double min, double max);
+
+ /**
+ * Sets the value of the spin button.
+ */
+ void setValue(Geom::Point const & p);
+
+ /**
+ * Manually forces an update of the spin button.
+ */
+ void update();
+
+ /**
+ * Signal raised when the spin button's value changes.
+ */
+ Glib::SignalProxy0<void> signal_x_value_changed();
+
+ Glib::SignalProxy0<void> signal_y_value_changed();
+
+ /**
+ * Check 'setProgrammatically' of both scalar widgets. False if value is changed by user by clicking the widget.
+ * true if the value was set by setValue, not changed by the user;
+ * if a callback checks it, it must reset it back to false.
+ */
+ bool setProgrammatically();
+
+ void clearProgrammatically();
+
+protected:
+ Scalar xwidget;
+ Scalar ywidget;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_POINT_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/preferences-widget.cpp b/src/ui/widget/preferences-widget.cpp
new file mode 100644
index 0000000..92c101b
--- /dev/null
+++ b/src/ui/widget/preferences-widget.cpp
@@ -0,0 +1,1023 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape Preferences dialog.
+ *
+ * Authors:
+ * Marco Scholten
+ * Bruno Dilly <bruno.dilly@gmail.com>
+ *
+ * Copyright (C) 2004, 2006, 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/i18n.h>
+#include <glibmm/convert.h>
+#include <glibmm/regex.h>
+
+#include <gtkmm/box.h>
+#include <gtkmm/frame.h>
+#include <gtkmm/scale.h>
+#include <gtkmm/table.h>
+
+
+#include "desktop.h"
+#include "inkscape.h"
+#include "message-stack.h"
+#include "preferences.h"
+#include "selcue.h"
+#include "selection-chemistry.h"
+#include "verbs.h"
+
+#include "include/gtkmm_version.h"
+
+#include "io/sys.h"
+
+#include "ui/dialog/filedialog.h"
+#include "ui/icon-loader.h"
+#include "ui/widget/preferences-widget.h"
+
+
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
+using namespace Inkscape::UI::Widget;
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+DialogPage::DialogPage()
+{
+ set_border_width(12);
+
+ set_orientation(Gtk::ORIENTATION_VERTICAL);
+ set_column_spacing(12);
+ set_row_spacing(6);
+}
+
+/**
+ * Add a widget to the bottom row of the dialog page
+ *
+ * \param[in] indent Whether the widget should be indented by one column
+ * \param[in] label The label text for the widget
+ * \param[in] widget The widget to add to the page
+ * \param[in] suffix Text for an optional label at the right of the widget
+ * \param[in] tip Tooltip text for the widget
+ * \param[in] expand_widget Whether to expand the widget horizontally
+ * \param[in] other_widget An optional additional widget to display at the right of the first one
+ */
+void DialogPage::add_line(bool indent,
+ Glib::ustring const &label,
+ Gtk::Widget &widget,
+ Glib::ustring const &suffix,
+ const Glib::ustring &tip,
+ bool expand_widget,
+ Gtk::Widget *other_widget)
+{
+ if (tip != "")
+ widget.set_tooltip_text (tip);
+
+ auto hb = Gtk::manage(new Gtk::Box());
+ hb->set_spacing(12);
+ hb->set_hexpand(true);
+ hb->pack_start(widget, expand_widget, expand_widget);
+
+ // Pack an additional widget into a box with the widget if desired
+ if (other_widget)
+ hb->pack_start(*other_widget, expand_widget, expand_widget);
+
+ hb->set_valign(Gtk::ALIGN_CENTER);
+
+ // Add a label in the first column if provided
+ if (label != "")
+ {
+ Gtk::Label* label_widget = Gtk::manage(new Gtk::Label(label, Gtk::ALIGN_START,
+ Gtk::ALIGN_CENTER, true));
+ label_widget->set_mnemonic_widget(widget);
+ label_widget->set_markup(label_widget->get_text());
+
+ if (indent) {
+ label_widget->set_margin_start(12);
+ }
+
+ label_widget->set_valign(Gtk::ALIGN_CENTER);
+ add(*label_widget);
+ attach_next_to(*hb, *label_widget, Gtk::POS_RIGHT, 1, 1);
+ }
+
+ // Now add the widget to the bottom of the dialog
+ if (label == "")
+ {
+ if (indent) {
+ hb->set_margin_start(12);
+ }
+
+ add(*hb);
+
+ GValue width = G_VALUE_INIT;
+ g_value_init(&width, G_TYPE_INT);
+ g_value_set_int(&width, 2);
+ gtk_container_child_set_property(GTK_CONTAINER(gobj()), GTK_WIDGET(hb->gobj()), "width", &width);
+ }
+
+ // Add a label on the right of the widget if desired
+ if (suffix != "")
+ {
+ Gtk::Label* suffix_widget = Gtk::manage(new Gtk::Label(suffix , Gtk::ALIGN_START , Gtk::ALIGN_CENTER, true));
+ suffix_widget->set_markup(suffix_widget->get_text());
+ hb->pack_start(*suffix_widget,false,false);
+ }
+
+}
+
+void DialogPage::add_group_header(Glib::ustring name)
+{
+ if (name != "")
+ {
+ Gtk::Label* label_widget = Gtk::manage(new Gtk::Label(Glib::ustring(/*"<span size='large'>*/"<b>") + name +
+ Glib::ustring("</b>"/*</span>"*/) , Gtk::ALIGN_START , Gtk::ALIGN_CENTER, true));
+
+ label_widget->set_use_markup(true);
+ label_widget->set_valign(Gtk::ALIGN_CENTER);
+ add(*label_widget);
+ }
+}
+
+void DialogPage::set_tip(Gtk::Widget& widget, Glib::ustring const &tip)
+{
+ widget.set_tooltip_text (tip);
+}
+
+void PrefCheckButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path,
+ bool default_value)
+{
+ _prefs_path = prefs_path;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ this->set_label(label);
+ this->set_active( prefs->getBool(_prefs_path, default_value) );
+}
+
+void PrefCheckButton::on_toggled()
+{
+ this->changed_signal.emit(this->get_active());
+ if (this->get_visible()) //only take action if the user toggled it
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool(_prefs_path, this->get_active());
+ }
+}
+
+void PrefRadioButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path,
+ Glib::ustring const &string_value, bool default_value, PrefRadioButton* group_member)
+{
+ _prefs_path = prefs_path;
+ _value_type = VAL_STRING;
+ _string_value = string_value;
+ (void)default_value;
+ this->set_label(label);
+ if (group_member)
+ {
+ Gtk::RadioButtonGroup rbg = group_member->get_group();
+ this->set_group(rbg);
+ }
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ Glib::ustring val = prefs->getString(_prefs_path);
+ if ( !val.empty() )
+ this->set_active(val == _string_value);
+ else
+ this->set_active( false );
+}
+
+void PrefRadioButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path,
+ int int_value, bool default_value, PrefRadioButton* group_member)
+{
+ _prefs_path = prefs_path;
+ _value_type = VAL_INT;
+ _int_value = int_value;
+ this->set_label(label);
+ if (group_member)
+ {
+ Gtk::RadioButtonGroup rbg = group_member->get_group();
+ this->set_group(rbg);
+ }
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (default_value)
+ this->set_active( prefs->getInt(_prefs_path, int_value) == _int_value );
+ else
+ this->set_active( prefs->getInt(_prefs_path, int_value + 1) == _int_value );
+}
+
+void PrefRadioButton::on_toggled()
+{
+ this->changed_signal.emit(this->get_active());
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (this->get_visible() && this->get_active() ) //only take action if toggled by user (to active)
+ {
+ if ( _value_type == VAL_STRING )
+ prefs->setString(_prefs_path, _string_value);
+ else if ( _value_type == VAL_INT )
+ prefs->setInt(_prefs_path, _int_value);
+ }
+}
+
+void PrefSpinButton::init(Glib::ustring const &prefs_path,
+ double lower, double upper, double step_increment, double /*page_increment*/,
+ double default_value, bool is_int, bool is_percent)
+{
+ _prefs_path = prefs_path;
+ _is_int = is_int;
+ _is_percent = is_percent;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double value;
+ if (is_int) {
+ if (is_percent) {
+ value = 100 * prefs->getDoubleLimited(prefs_path, default_value, lower/100.0, upper/100.0);
+ } else {
+ value = (double) prefs->getIntLimited(prefs_path, (int) default_value, (int) lower, (int) upper);
+ }
+ } else {
+ value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper);
+ }
+
+ this->set_range (lower, upper);
+ this->set_increments (step_increment, 0);
+ this->set_value (value);
+ this->set_width_chars(6);
+ if (is_int)
+ this->set_digits(0);
+ else if (step_increment < 0.1)
+ this->set_digits(4);
+ else
+ this->set_digits(2);
+
+}
+
+void PrefSpinButton::on_value_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (this->get_visible()) //only take action if user changed value
+ {
+ if (_is_int) {
+ if (_is_percent) {
+ prefs->setDouble(_prefs_path, this->get_value()/100.0);
+ } else {
+ prefs->setInt(_prefs_path, (int) this->get_value());
+ }
+ } else {
+ prefs->setDouble(_prefs_path, this->get_value());
+ }
+ }
+}
+
+void PrefSpinUnit::init(Glib::ustring const &prefs_path,
+ double lower, double upper, double step_increment,
+ double default_value, UnitType unit_type, Glib::ustring const &default_unit)
+{
+ _prefs_path = prefs_path;
+ _is_percent = (unit_type == UNIT_TYPE_DIMENSIONLESS);
+
+ resetUnitType(unit_type);
+ setUnit(default_unit);
+ setRange (lower, upper); /// @fixme this disregards changes of units
+ setIncrements (step_increment, 0);
+ if (step_increment < 0.1) {
+ setDigits(4);
+ } else {
+ setDigits(2);
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper);
+ Glib::ustring unitstr = prefs->getUnit(prefs_path);
+ if (unitstr.length() == 0) {
+ unitstr = default_unit;
+ // write the assumed unit to preferences:
+ prefs->setDoubleUnit(_prefs_path, value, unitstr);
+ }
+ setValue(value, unitstr);
+
+ signal_value_changed().connect_notify(sigc::mem_fun(*this, &PrefSpinUnit::on_my_value_changed));
+}
+
+void PrefSpinUnit::on_my_value_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (getWidget()->get_visible()) //only take action if user changed value
+ {
+ prefs->setDoubleUnit(_prefs_path, getValue(getUnit()->abbr), getUnit()->abbr);
+ }
+}
+
+const double ZoomCorrRuler::textsize = 7;
+const double ZoomCorrRuler::textpadding = 5;
+
+ZoomCorrRuler::ZoomCorrRuler(int width, int height) :
+ _unitconv(1.0),
+ _border(5)
+{
+ set_size(width, height);
+}
+
+void ZoomCorrRuler::set_size(int x, int y)
+{
+ _min_width = x;
+ _height = y;
+ set_size_request(x + _border*2, y + _border*2);
+}
+
+// The following two functions are borrowed from 2geom's toy-framework-2; if they are useful in
+// other locations, we should perhaps make them (or adapted versions of them) publicly available
+static void
+draw_text(cairo_t *cr, Geom::Point loc, const char* txt, bool bottom = false,
+ double fontsize = ZoomCorrRuler::textsize, std::string fontdesc = "Sans") {
+ PangoLayout* layout = pango_cairo_create_layout (cr);
+ pango_layout_set_text(layout, txt, -1);
+
+ // set font and size
+ std::ostringstream sizestr;
+ sizestr << fontsize;
+ fontdesc = fontdesc + " " + sizestr.str();
+ PangoFontDescription *font_desc = pango_font_description_from_string(fontdesc.c_str());
+ pango_layout_set_font_description(layout, font_desc);
+ pango_font_description_free (font_desc);
+
+ PangoRectangle logical_extent;
+ pango_layout_get_pixel_extents(layout, nullptr, &logical_extent);
+ cairo_move_to(cr, loc[Geom::X], loc[Geom::Y] - (bottom ? logical_extent.height : 0));
+ pango_cairo_show_layout(cr, layout);
+}
+
+static void
+draw_number(cairo_t *cr, Geom::Point pos, double num) {
+ std::ostringstream number;
+ number << num;
+ draw_text(cr, pos, number.str().c_str(), true);
+}
+
+/*
+ * \arg dist The distance between consecutive minor marks
+ * \arg major_interval Number of marks after which to draw a major mark
+ */
+void
+ZoomCorrRuler::draw_marks(Cairo::RefPtr<Cairo::Context> cr, double dist, int major_interval) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ const double zoomcorr = prefs->getDouble("/options/zoomcorrection/value", 1.0);
+ double mark = 0;
+ int i = 0;
+ while (mark <= _drawing_width) {
+ cr->move_to(mark, _height);
+ if ((i % major_interval) == 0) {
+ // major mark
+ cr->line_to(mark, 0);
+ Geom::Point textpos(mark + 3, ZoomCorrRuler::textsize + ZoomCorrRuler::textpadding);
+ draw_number(cr->cobj(), textpos, dist * i);
+ } else {
+ // minor mark
+ cr->line_to(mark, ZoomCorrRuler::textsize + 2 * ZoomCorrRuler::textpadding);
+ }
+ mark += dist * zoomcorr / _unitconv;
+ ++i;
+ }
+}
+
+bool
+ZoomCorrRuler::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) {
+ Glib::RefPtr<Gdk::Window> window = get_window();
+
+ int w = window->get_width();
+ _drawing_width = w - _border * 2;
+
+ cr->set_source_rgb(1.0, 1.0, 1.0);
+ cr->set_fill_rule(Cairo::FILL_RULE_WINDING);
+ cr->rectangle(0, 0, w, _height + _border*2);
+ cr->fill();
+
+ cr->set_source_rgb(0.0, 0.0, 0.0);
+ cr->set_line_width(0.5);
+
+ cr->translate(_border, _border); // so that we have a small white border around the ruler
+ cr->move_to (0, _height);
+ cr->line_to (_drawing_width, _height);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ Glib::ustring abbr = prefs->getString("/options/zoomcorrection/unit");
+ if (abbr == "cm") {
+ draw_marks(cr, 0.1, 10);
+ } else if (abbr == "in") {
+ draw_marks(cr, 0.25, 4);
+ } else if (abbr == "mm") {
+ draw_marks(cr, 10, 10);
+ } else if (abbr == "pc") {
+ draw_marks(cr, 1, 10);
+ } else if (abbr == "pt") {
+ draw_marks(cr, 10, 10);
+ } else if (abbr == "px") {
+ draw_marks(cr, 10, 10);
+ } else {
+ draw_marks(cr, 1, 1);
+ }
+ cr->stroke();
+
+ return true;
+}
+
+
+void
+ZoomCorrRulerSlider::on_slider_value_changed()
+{
+ if (this->get_visible() || freeze) //only take action if user changed value
+ {
+ freeze = true;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble("/options/zoomcorrection/value", _slider->get_value() / 100.0);
+ _sb.set_value(_slider->get_value());
+ _ruler.queue_draw();
+ freeze = false;
+ }
+}
+
+void
+ZoomCorrRulerSlider::on_spinbutton_value_changed()
+{
+ if (this->get_visible() || freeze) //only take action if user changed value
+ {
+ freeze = true;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble("/options/zoomcorrection/value", _sb.get_value() / 100.0);
+ _slider->set_value(_sb.get_value());
+ _ruler.queue_draw();
+ freeze = false;
+ }
+}
+
+void
+ZoomCorrRulerSlider::on_unit_changed() {
+ if (GPOINTER_TO_INT(_unit.get_data("sensitive")) == 0) {
+ // when the unit menu is initialized, the unit is set to the default but
+ // it needs to be reset later so we don't perform the change in this case
+ return;
+ }
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString("/options/zoomcorrection/unit", _unit.getUnitAbbr());
+ double conv = _unit.getConversion(_unit.getUnitAbbr(), "px");
+ _ruler.set_unit_conversion(conv);
+ if (_ruler.get_visible()) {
+ _ruler.queue_draw();
+ }
+}
+
+bool ZoomCorrRulerSlider::on_mnemonic_activate ( bool group_cycling )
+{
+ return _sb.mnemonic_activate ( group_cycling );
+}
+
+
+void
+ZoomCorrRulerSlider::init(int ruler_width, int ruler_height, double lower, double upper,
+ double step_increment, double page_increment, double default_value)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double value = prefs->getDoubleLimited("/options/zoomcorrection/value", default_value, lower, upper) * 100.0;
+
+ freeze = false;
+
+ _ruler.set_size(ruler_width, ruler_height);
+
+ _slider = Gtk::manage(new Gtk::Scale(Gtk::ORIENTATION_HORIZONTAL));
+
+ _slider->set_size_request(_ruler.width(), -1);
+ _slider->set_range (lower, upper);
+ _slider->set_increments (step_increment, page_increment);
+ _slider->set_value (value);
+ _slider->set_digits(2);
+
+ _slider->signal_value_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_slider_value_changed));
+ _sb.signal_value_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_spinbutton_value_changed));
+ _unit.signal_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_unit_changed));
+
+ _sb.set_range (lower, upper);
+ _sb.set_increments (step_increment, 0);
+ _sb.set_value (value);
+ _sb.set_digits(2);
+ _sb.set_halign(Gtk::ALIGN_CENTER);
+ _sb.set_valign(Gtk::ALIGN_END);
+
+ _unit.set_data("sensitive", GINT_TO_POINTER(0));
+ _unit.setUnitType(UNIT_TYPE_LINEAR);
+ _unit.set_data("sensitive", GINT_TO_POINTER(1));
+ _unit.setUnit(prefs->getString("/options/zoomcorrection/unit"));
+ _unit.set_halign(Gtk::ALIGN_CENTER);
+ _unit.set_valign(Gtk::ALIGN_END);
+
+ auto table = Gtk::manage(new Gtk::Grid());
+ table->attach(*_slider, 0, 0, 1, 1);
+ table->attach(_sb, 1, 0, 1, 1);
+ table->attach(_ruler, 0, 1, 1, 1);
+ table->attach(_unit, 1, 1, 1, 1);
+
+ pack_start(*table, Gtk::PACK_SHRINK);
+}
+
+void
+PrefSlider::on_slider_value_changed()
+{
+ if (this->get_visible() || freeze) //only take action if user changed value
+ {
+ freeze = true;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble(_prefs_path, _slider->get_value());
+ _sb.set_value(_slider->get_value());
+ freeze = false;
+ }
+}
+
+void
+PrefSlider::on_spinbutton_value_changed()
+{
+ if (this->get_visible() || freeze) //only take action if user changed value
+ {
+ freeze = true;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble(_prefs_path, _sb.get_value());
+ _slider->set_value(_sb.get_value());
+ freeze = false;
+ }
+}
+
+bool PrefSlider::on_mnemonic_activate ( bool group_cycling )
+{
+ return _sb.mnemonic_activate ( group_cycling );
+}
+
+void
+PrefSlider::init(Glib::ustring const &prefs_path,
+ double lower, double upper, double step_increment, double page_increment, double default_value, int digits)
+{
+ _prefs_path = prefs_path;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper);
+
+ freeze = false;
+
+ _slider = Gtk::manage(new Gtk::Scale(Gtk::ORIENTATION_HORIZONTAL));
+
+ _slider->set_range (lower, upper);
+ _slider->set_increments (step_increment, page_increment);
+ _slider->set_value (value);
+ _slider->set_digits(digits);
+ _slider->signal_value_changed().connect(sigc::mem_fun(*this, &PrefSlider::on_slider_value_changed));
+
+ _sb.signal_value_changed().connect(sigc::mem_fun(*this, &PrefSlider::on_spinbutton_value_changed));
+ _sb.set_range (lower, upper);
+ _sb.set_increments (step_increment, 0);
+ _sb.set_value (value);
+ _sb.set_digits(digits);
+ _sb.set_halign(Gtk::ALIGN_CENTER);
+ _sb.set_valign(Gtk::ALIGN_END);
+
+ auto table = Gtk::manage(new Gtk::Grid());
+ _slider->set_hexpand();
+ table->attach(*_slider, 0, 0, 1, 1);
+ table->attach(_sb, 1, 0, 1, 1);
+
+ this->pack_start(*table, Gtk::PACK_EXPAND_WIDGET);
+}
+
+void PrefCombo::init(Glib::ustring const &prefs_path,
+ Glib::ustring labels[], int values[], int num_items, int default_value)
+{
+ _prefs_path = prefs_path;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int row = 0;
+ int value = prefs->getInt(_prefs_path, default_value);
+
+ for (int i = 0 ; i < num_items; ++i)
+ {
+ this->append(labels[i]);
+ _values.push_back(values[i]);
+ if (value == values[i])
+ row = i;
+ }
+ this->set_active(row);
+}
+
+void PrefCombo::init(Glib::ustring const &prefs_path,
+ Glib::ustring labels[], Glib::ustring values[], int num_items, Glib::ustring default_value)
+{
+ _prefs_path = prefs_path;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int row = 0;
+ Glib::ustring value = prefs->getString(_prefs_path);
+ if(value.empty())
+ {
+ value = default_value;
+ }
+
+ for (int i = 0 ; i < num_items; ++i)
+ {
+ this->append(labels[i]);
+ _ustr_values.push_back(values[i]);
+ if (value == values[i])
+ row = i;
+ }
+ this->set_active(row);
+}
+
+void PrefCombo::init(Glib::ustring const &prefs_path, std::vector<Glib::ustring> labels, std::vector<int> values,
+ int default_value)
+{
+ size_t labels_size = labels.size();
+ size_t values_size = values.size();
+ if (values_size != labels_size) {
+ std::cout << "PrefCombo::"
+ << "Different number of values/labels in " << prefs_path << std::endl;
+ return;
+ }
+ _prefs_path = prefs_path;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int row = 0;
+ int value = prefs->getInt(_prefs_path, default_value);
+
+ for (int i = 0; i < labels_size; ++i) {
+ this->append(labels[i]);
+ _values.push_back(values[i]);
+ if (value == values[i])
+ row = i;
+ }
+ this->set_active(row);
+}
+
+void PrefCombo::init(Glib::ustring const &prefs_path, std::vector<Glib::ustring> labels,
+ std::vector<Glib::ustring> values, Glib::ustring default_value)
+{
+ size_t labels_size = labels.size();
+ size_t values_size = values.size();
+ if (values_size != labels_size) {
+ std::cout << "PrefCombo::"
+ << "Different number of values/labels in " << prefs_path << std::endl;
+ return;
+ }
+ _prefs_path = prefs_path;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int row = 0;
+ Glib::ustring value = prefs->getString(_prefs_path);
+ if (value.empty()) {
+ value = default_value;
+ }
+
+ for (int i = 0; i < labels_size; ++i) {
+ this->append(labels[i]);
+ _ustr_values.push_back(values[i]);
+ if (value == values[i])
+ row = i;
+ }
+ this->set_active(row);
+}
+
+void PrefCombo::on_changed()
+{
+ if (this->get_visible()) //only take action if user changed value
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if(!_values.empty())
+ {
+ prefs->setInt(_prefs_path, _values[this->get_active_row_number()]);
+ }
+ else
+ {
+ prefs->setString(_prefs_path, _ustr_values[this->get_active_row_number()]);
+ }
+ }
+}
+
+void PrefEntryButtonHBox::init(Glib::ustring const &prefs_path,
+ bool visibility, Glib::ustring const &default_string)
+{
+ _prefs_path = prefs_path;
+ _default_string = default_string;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ relatedEntry = new Gtk::Entry();
+ relatedButton = new Gtk::Button(_("Reset"));
+ relatedEntry->set_invisible_char('*');
+ relatedEntry->set_visibility(visibility);
+ relatedEntry->set_text(prefs->getString(_prefs_path));
+ this->pack_start(*relatedEntry);
+ this->pack_start(*relatedButton);
+ relatedButton->signal_clicked().connect(
+ sigc::mem_fun(*this, &PrefEntryButtonHBox::onRelatedButtonClickedCallback));
+ relatedEntry->signal_changed().connect(
+ sigc::mem_fun(*this, &PrefEntryButtonHBox::onRelatedEntryChangedCallback));
+}
+
+void PrefEntryButtonHBox::onRelatedEntryChangedCallback()
+{
+ if (this->get_visible()) //only take action if user changed value
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString(_prefs_path, relatedEntry->get_text());
+ }
+}
+
+void PrefEntryButtonHBox::onRelatedButtonClickedCallback()
+{
+ if (this->get_visible()) //only take action if user changed value
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString(_prefs_path, _default_string);
+ relatedEntry->set_text(_default_string);
+ }
+}
+
+bool PrefEntryButtonHBox::on_mnemonic_activate ( bool group_cycling )
+{
+ return relatedEntry->mnemonic_activate ( group_cycling );
+}
+
+void PrefEntryFileButtonHBox::init(Glib::ustring const &prefs_path,
+ bool visibility)
+{
+ _prefs_path = prefs_path;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ relatedEntry = new Gtk::Entry();
+ relatedEntry->set_invisible_char('*');
+ relatedEntry->set_visibility(visibility);
+ relatedEntry->set_text(prefs->getString(_prefs_path));
+
+ relatedButton = new Gtk::Button();
+ Gtk::HBox* pixlabel = new Gtk::HBox(false, 3);
+ Gtk::Image *im = sp_get_icon_image("applications-graphics", Gtk::ICON_SIZE_BUTTON);
+ pixlabel->pack_start(*im);
+ Gtk::Label *l = new Gtk::Label();
+ l->set_markup_with_mnemonic(_("_Browse..."));
+ pixlabel->pack_start(*l);
+ relatedButton->add(*pixlabel);
+
+ this->pack_end(*relatedButton, false, false, 4);
+ this->pack_start(*relatedEntry, true, true, 0);
+
+ relatedButton->signal_clicked().connect(
+ sigc::mem_fun(*this, &PrefEntryFileButtonHBox::onRelatedButtonClickedCallback));
+ relatedEntry->signal_changed().connect(
+ sigc::mem_fun(*this, &PrefEntryFileButtonHBox::onRelatedEntryChangedCallback));
+}
+
+void PrefEntryFileButtonHBox::onRelatedEntryChangedCallback()
+{
+ if (this->get_visible()) //only take action if user changed value
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString(_prefs_path, relatedEntry->get_text());
+ }
+}
+
+static Inkscape::UI::Dialog::FileOpenDialog * selectPrefsFileInstance = nullptr;
+
+void PrefEntryFileButtonHBox::onRelatedButtonClickedCallback()
+{
+ if (this->get_visible()) //only take action if user changed value
+ {
+ //# Get the current directory for finding files
+ static Glib::ustring open_path;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+
+ Glib::ustring attr = prefs->getString(_prefs_path);
+ if (!attr.empty()) open_path = attr;
+
+ //# Test if the open_path directory exists
+ if (!Inkscape::IO::file_test(open_path.c_str(),
+ (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
+ open_path = "";
+
+#ifdef _WIN32
+ //# If no open path, default to our win32 documents folder
+ if (open_path.empty())
+ {
+ // The path to the My Documents folder is read from the
+ // value "HKEY_CURRENT_USER\Software\Windows\CurrentVersion\Explorer\Shell Folders\Personal"
+ HKEY key = NULL;
+ if(RegOpenKeyExA(HKEY_CURRENT_USER,
+ "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders",
+ 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
+ {
+ WCHAR utf16path[_MAX_PATH];
+ DWORD value_type;
+ DWORD data_size = sizeof(utf16path);
+ if(RegQueryValueExW(key, L"Personal", NULL, &value_type,
+ (BYTE*)utf16path, &data_size) == ERROR_SUCCESS)
+ {
+ g_assert(value_type == REG_SZ);
+ gchar *utf8path = g_utf16_to_utf8(
+ (const gunichar2*)utf16path, -1, NULL, NULL, NULL);
+ if(utf8path)
+ {
+ open_path = Glib::ustring(utf8path);
+ g_free(utf8path);
+ }
+ }
+ }
+ }
+#endif
+
+ //# If no open path, default to our home directory
+ if (open_path.empty())
+ {
+ open_path = g_get_home_dir();
+ open_path.append(G_DIR_SEPARATOR_S);
+ }
+
+ //# Create a dialog
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (!selectPrefsFileInstance) {
+ selectPrefsFileInstance =
+ Inkscape::UI::Dialog::FileOpenDialog::create(
+ *desktop->getToplevel(),
+ open_path,
+ Inkscape::UI::Dialog::EXE_TYPES,
+ _("Select a bitmap editor"));
+ }
+
+ //# Show the dialog
+ bool const success = selectPrefsFileInstance->show();
+
+ if (!success) {
+ return;
+ }
+
+ //# User selected something. Get name and type
+ Glib::ustring fileName = selectPrefsFileInstance->getFilename();
+
+ if (!fileName.empty())
+ {
+ Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
+
+ if ( newFileName.size() > 0)
+ open_path = newFileName;
+ else
+ g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" );
+
+ prefs->setString(_prefs_path, open_path);
+ }
+
+ relatedEntry->set_text(fileName);
+ }
+}
+
+bool PrefEntryFileButtonHBox::on_mnemonic_activate ( bool group_cycling )
+{
+ return relatedEntry->mnemonic_activate ( group_cycling );
+}
+
+void PrefOpenFolder::init(Glib::ustring const &entry_string, Glib::ustring const &tooltip)
+{
+ relatedEntry = new Gtk::Entry();
+ relatedButton = new Gtk::Button();
+ Gtk::HBox *pixlabel = new Gtk::HBox(false, 3);
+ Gtk::Image *im = sp_get_icon_image("document-open", Gtk::ICON_SIZE_BUTTON);
+ pixlabel->pack_start(*im);
+ Gtk::Label *l = new Gtk::Label();
+ l->set_markup_with_mnemonic(_("Open"));
+ pixlabel->pack_start(*l);
+ relatedButton->add(*pixlabel);
+ relatedButton->set_tooltip_text(tooltip);
+ relatedEntry->set_text(entry_string);
+ relatedEntry->set_sensitive(false);
+ this->pack_end(*relatedButton, false, false, 4);
+ this->pack_start(*relatedEntry, true, true, 0);
+ relatedButton->signal_clicked().connect(sigc::mem_fun(*this, &PrefOpenFolder::onRelatedButtonClickedCallback));
+}
+
+void PrefOpenFolder::onRelatedButtonClickedCallback()
+{
+ g_mkdir_with_parents(relatedEntry->get_text().c_str(), 0700);
+ // https://stackoverflow.com/questions/42442189/how-to-open-spawn-a-file-with-glib-gtkmm-in-windows
+#ifdef _WIN32
+ ShellExecute(NULL, "open", relatedEntry->get_text().c_str(), NULL, NULL, SW_SHOWDEFAULT);
+#elif defined(__APPLE__)
+ std::vector<std::string> argv = { "open", relatedEntry->get_text().raw() };
+ Glib::spawn_async("", argv, Glib::SpawnFlags::SPAWN_SEARCH_PATH);
+#else
+ gchar *path = g_filename_to_uri(relatedEntry->get_text().c_str(), NULL, NULL);
+ Glib::ustring xgd = "xdg-open ";
+ xgd += path;
+ system((xgd).c_str());
+ g_free(path);
+#endif
+}
+
+void PrefFileButton::init(Glib::ustring const &prefs_path)
+{
+ _prefs_path = prefs_path;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ select_filename(Glib::filename_from_utf8(prefs->getString(_prefs_path)));
+
+ signal_selection_changed().connect(sigc::mem_fun(*this, &PrefFileButton::onFileChanged));
+}
+
+void PrefFileButton::onFileChanged()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString(_prefs_path, Glib::filename_to_utf8(get_filename()));
+}
+
+void PrefEntry::init(Glib::ustring const &prefs_path, bool visibility)
+{
+ _prefs_path = prefs_path;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ this->set_invisible_char('*');
+ this->set_visibility(visibility);
+ this->set_text(prefs->getString(_prefs_path));
+}
+
+void PrefEntry::on_changed()
+{
+ if (this->get_visible()) //only take action if user changed value
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString(_prefs_path, this->get_text());
+ }
+}
+
+void PrefMultiEntry::init(Glib::ustring const &prefs_path, int height)
+{
+ // TODO: Figure out if there's a way to specify height in lines instead of px
+ // and how to obtain a reasonable default width if 'expand_widget' is not used
+ set_size_request(100, height);
+ set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+ set_shadow_type(Gtk::SHADOW_IN);
+
+ add(_text);
+
+ _prefs_path = prefs_path;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ Glib::ustring value = prefs->getString(_prefs_path);
+ value = Glib::Regex::create("\\|")->replace_literal(value, 0, "\n", (Glib::RegexMatchFlags)0);
+ _text.get_buffer()->set_text(value);
+ _text.get_buffer()->signal_changed().connect(sigc::mem_fun(*this, &PrefMultiEntry::on_changed));
+}
+
+void PrefMultiEntry::on_changed()
+{
+ if (get_visible()) //only take action if user changed value
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ Glib::ustring value = _text.get_buffer()->get_text();
+ value = Glib::Regex::create("\\n")->replace_literal(value, 0, "|", (Glib::RegexMatchFlags)0);
+ prefs->setString(_prefs_path, value);
+ }
+}
+
+void PrefColorPicker::init(Glib::ustring const &label, Glib::ustring const &prefs_path,
+ guint32 default_rgba)
+{
+ _prefs_path = prefs_path;
+ _title = label;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ this->setRgba32( prefs->getInt(_prefs_path, (int)default_rgba) );
+}
+
+void PrefColorPicker::on_changed (guint32 rgba)
+{
+ if (this->get_visible()) //only take action if the user toggled it
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt(_prefs_path, (int) rgba);
+ }
+}
+
+void PrefUnit::init(Glib::ustring const &prefs_path)
+{
+ _prefs_path = prefs_path;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ setUnitType(UNIT_TYPE_LINEAR);
+ setUnit(prefs->getString(_prefs_path));
+}
+
+void PrefUnit::on_changed()
+{
+ if (this->get_visible()) //only take action if user changed value
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString(_prefs_path, getUnitAbbr());
+ }
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/preferences-widget.h b/src/ui/widget/preferences-widget.h
new file mode 100644
index 0000000..3e132c0
--- /dev/null
+++ b/src/ui/widget/preferences-widget.h
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Widgets for Inkscape Preferences dialog.
+ */
+/*
+ * Authors:
+ * Marco Scholten
+ * Bruno Dilly <bruno.dilly@gmail.com>
+ *
+ * Copyright (C) 2004, 2006, 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_INKSCAPE_PREFERENCES_H
+#define INKSCAPE_UI_WIDGET_INKSCAPE_PREFERENCES_H
+
+#include <iostream>
+#include <vector>
+
+#include <gtkmm/filechooserbutton.h>
+#include "ui/widget/spinbutton.h"
+#include <cstddef>
+#include <sigc++/sigc++.h>
+#include <gtkmm/checkbutton.h>
+#include <gtkmm/radiobutton.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/textview.h>
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/drawingarea.h>
+#include <gtkmm/grid.h>
+
+#include "ui/widget/color-picker.h"
+#include "ui/widget/unit-menu.h"
+#include "ui/widget/spinbutton.h"
+#include "ui/widget/scalar-unit.h"
+
+namespace Gtk {
+class Scale;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class PrefCheckButton : public Gtk::CheckButton
+{
+public:
+ void init(Glib::ustring const &label, Glib::ustring const &prefs_path,
+ bool default_value);
+ sigc::signal<void, bool> changed_signal;
+protected:
+ Glib::ustring _prefs_path;
+ void on_toggled() override;
+};
+
+class PrefRadioButton : public Gtk::RadioButton
+{
+public:
+ void init(Glib::ustring const &label, Glib::ustring const &prefs_path,
+ int int_value, bool default_value, PrefRadioButton* group_member);
+ void init(Glib::ustring const &label, Glib::ustring const &prefs_path,
+ Glib::ustring const &string_value, bool default_value, PrefRadioButton* group_member);
+ sigc::signal<void, bool> changed_signal;
+protected:
+ Glib::ustring _prefs_path;
+ Glib::ustring _string_value;
+ int _value_type;
+ enum
+ {
+ VAL_INT,
+ VAL_STRING
+ };
+ int _int_value;
+ void on_toggled() override;
+};
+
+class PrefSpinButton : public SpinButton
+{
+public:
+ void init(Glib::ustring const &prefs_path,
+ double lower, double upper, double step_increment, double page_increment,
+ double default_value, bool is_int, bool is_percent);
+protected:
+ Glib::ustring _prefs_path;
+ bool _is_int;
+ bool _is_percent;
+ void on_value_changed() override;
+};
+
+class PrefSpinUnit : public ScalarUnit
+{
+public:
+ PrefSpinUnit() : ScalarUnit("", "") {};
+
+ void init(Glib::ustring const &prefs_path,
+ double lower, double upper, double step_increment,
+ double default_value,
+ UnitType unit_type, Glib::ustring const &default_unit);
+protected:
+ Glib::ustring _prefs_path;
+ bool _is_percent;
+ void on_my_value_changed();
+};
+
+class ZoomCorrRuler : public Gtk::DrawingArea {
+public:
+ ZoomCorrRuler(int width = 100, int height = 20);
+ void set_size(int x, int y);
+ void set_unit_conversion(double conv) { _unitconv = conv; }
+
+ int width() { return _min_width + _border*2; }
+
+ static const double textsize;
+ static const double textpadding;
+
+private:
+ bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;
+
+ void draw_marks(Cairo::RefPtr<Cairo::Context> cr, double dist, int major_interval);
+
+ double _unitconv;
+ int _min_width;
+ int _height;
+ int _border;
+ int _drawing_width;
+};
+
+class ZoomCorrRulerSlider : public Gtk::VBox
+{
+public:
+ void init(int ruler_width, int ruler_height, double lower, double upper,
+ double step_increment, double page_increment, double default_value);
+
+private:
+ void on_slider_value_changed();
+ void on_spinbutton_value_changed();
+ void on_unit_changed();
+ bool on_mnemonic_activate( bool group_cycling ) override;
+
+ Inkscape::UI::Widget::SpinButton _sb;
+ UnitMenu _unit;
+ Gtk::Scale* _slider;
+ ZoomCorrRuler _ruler;
+ bool freeze; // used to block recursive updates of slider and spinbutton
+};
+
+class PrefSlider : public Gtk::HBox
+{
+public:
+ void init(Glib::ustring const &prefs_path,
+ double lower, double upper, double step_increment, double page_increment, double default_value, int digits);
+
+private:
+ void on_slider_value_changed();
+ void on_spinbutton_value_changed();
+ bool on_mnemonic_activate( bool group_cycling ) override;
+
+ Glib::ustring _prefs_path;
+ Inkscape::UI::Widget::SpinButton _sb;
+
+ Gtk::Scale* _slider;
+
+ bool freeze; // used to block recursive updates of slider and spinbutton
+};
+
+
+class PrefCombo : public Gtk::ComboBoxText
+{
+public:
+ void init(Glib::ustring const &prefs_path,
+ Glib::ustring labels[], int values[], int num_items, int default_value);
+
+ /**
+ * Initialize a combo box.
+ * second form uses strings as key values.
+ */
+ void init(Glib::ustring const &prefs_path,
+ Glib::ustring labels[], Glib::ustring values[], int num_items, Glib::ustring default_value);
+ /**
+ * Initialize a combo box.
+ * with vectors.
+ */
+ void init(Glib::ustring const &prefs_path, std::vector<Glib::ustring> labels, std::vector<int> values,
+ int default_value);
+
+ void init(Glib::ustring const &prefs_path, std::vector<Glib::ustring> labels, std::vector<Glib::ustring> values,
+ Glib::ustring default_value);
+
+ protected:
+ Glib::ustring _prefs_path;
+ std::vector<int> _values;
+ std::vector<Glib::ustring> _ustr_values; ///< string key values used optionally instead of numeric _values
+ void on_changed() override;
+};
+
+class PrefEntry : public Gtk::Entry
+{
+public:
+ void init(Glib::ustring const &prefs_path, bool mask);
+protected:
+ Glib::ustring _prefs_path;
+ void on_changed() override;
+};
+
+class PrefMultiEntry : public Gtk::ScrolledWindow
+{
+public:
+ void init(Glib::ustring const &prefs_path, int height);
+protected:
+ Glib::ustring _prefs_path;
+ Gtk::TextView _text;
+ void on_changed();
+};
+
+class PrefEntryButtonHBox : public Gtk::HBox
+{
+public:
+ void init(Glib::ustring const &prefs_path,
+ bool mask, Glib::ustring const &default_string);
+
+protected:
+ Glib::ustring _prefs_path;
+ Glib::ustring _default_string;
+ Gtk::Button *relatedButton;
+ Gtk::Entry *relatedEntry;
+ void onRelatedEntryChangedCallback();
+ void onRelatedButtonClickedCallback();
+ bool on_mnemonic_activate( bool group_cycling ) override;
+};
+
+class PrefEntryFileButtonHBox : public Gtk::HBox
+{
+public:
+ void init(Glib::ustring const &prefs_path,
+ bool mask);
+protected:
+ Glib::ustring _prefs_path;
+ Gtk::Button *relatedButton;
+ Gtk::Entry *relatedEntry;
+ void onRelatedEntryChangedCallback();
+ void onRelatedButtonClickedCallback();
+ bool on_mnemonic_activate( bool group_cycling ) override;
+};
+
+class PrefOpenFolder : public Gtk::HBox {
+ public:
+ void init(Glib::ustring const &entry_string, Glib::ustring const &tooltip);
+
+ protected:
+ Gtk::Button *relatedButton;
+ Gtk::Entry *relatedEntry;
+ void onRelatedButtonClickedCallback();
+};
+
+class PrefFileButton : public Gtk::FileChooserButton
+{
+public:
+ void init(Glib::ustring const &prefs_path);
+
+protected:
+ Glib::ustring _prefs_path;
+ void onFileChanged();
+};
+
+class PrefColorPicker : public ColorPicker
+{
+public:
+ PrefColorPicker() : ColorPicker("", "", 0, false) {};
+ ~PrefColorPicker() override = default;;
+
+ void init(Glib::ustring const &abel, Glib::ustring const &prefs_path,
+ guint32 default_rgba);
+
+protected:
+ Glib::ustring _prefs_path;
+ void on_changed (guint32 rgba) override;
+};
+
+class PrefUnit : public UnitMenu
+{
+public:
+ void init(Glib::ustring const &prefs_path);
+protected:
+ Glib::ustring _prefs_path;
+ void on_changed() override;
+};
+
+class DialogPage : public Gtk::Grid
+{
+public:
+ DialogPage();
+ void add_line(bool indent, Glib::ustring const &label, Gtk::Widget& widget, Glib::ustring const &suffix, Glib::ustring const &tip, bool expand = true, Gtk::Widget *other_widget = nullptr);
+ void add_group_header(Glib::ustring name);
+ void set_tip(Gtk::Widget &widget, Glib::ustring const &tip);
+};
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif //INKSCAPE_UI_WIDGET_INKSCAPE_PREFERENCES_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/preview.cpp b/src/ui/widget/preview.cpp
new file mode 100644
index 0000000..a56639c
--- /dev/null
+++ b/src/ui/widget/preview.cpp
@@ -0,0 +1,502 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MPL-1.1 OR LGPL-2.1-or-later
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Eek Preview Stuffs.
+ *
+ * The Initial Developer of the Original Code is
+ * Jon A. Cruz.
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include <algorithm>
+#include <gdkmm/general.h>
+#include "preview.h"
+#include "preferences.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+#define PRIME_BUTTON_MAGIC_NUMBER 1
+
+/* Keep in sync with last value in eek-preview.h */
+#define PREVIEW_SIZE_LAST PREVIEW_SIZE_HUGE
+#define PREVIEW_SIZE_NEXTFREE (PREVIEW_SIZE_HUGE + 1)
+
+#define PREVIEW_MAX_RATIO 500
+
+void
+Preview::set_color(int r, int g, int b )
+{
+ _r = r;
+ _g = g;
+ _b = b;
+
+ queue_draw();
+}
+
+
+void
+Preview::set_pixbuf(const Glib::RefPtr<Gdk::Pixbuf> &pixbuf)
+{
+ _previewPixbuf = pixbuf;
+
+ queue_draw();
+
+ if (_scaled)
+ {
+ _scaled.reset();
+ }
+
+ _scaledW = _previewPixbuf->get_width();
+ _scaledH = _previewPixbuf->get_height();
+}
+
+static gboolean setupDone = FALSE;
+static GtkRequisition sizeThings[PREVIEW_SIZE_NEXTFREE];
+
+void
+Preview::set_size_mappings( guint count, GtkIconSize const* sizes )
+{
+ gint width = 0;
+ gint height = 0;
+ gint smallest = 512;
+ gint largest = 0;
+ guint i = 0;
+ guint delta = 0;
+
+ for ( i = 0; i < count; ++i ) {
+ gboolean worked = gtk_icon_size_lookup( sizes[i], &width, &height );
+ if ( worked ) {
+ if ( width < smallest ) {
+ smallest = width;
+ }
+ if ( width > largest ) {
+ largest = width;
+ }
+ }
+ }
+
+ smallest = (smallest * 3) / 4;
+
+ delta = largest - smallest;
+
+ for ( i = 0; i < G_N_ELEMENTS(sizeThings); ++i ) {
+ guint val = smallest + ( (i * delta) / (G_N_ELEMENTS(sizeThings) - 1) );
+ sizeThings[i].width = val;
+ sizeThings[i].height = val;
+ }
+
+ setupDone = TRUE;
+}
+
+void
+Preview::size_request(GtkRequisition* req) const
+{
+ int width = 0;
+ int height = 0;
+
+ if ( !setupDone ) {
+ GtkIconSize sizes[] = {
+ GTK_ICON_SIZE_MENU,
+ GTK_ICON_SIZE_SMALL_TOOLBAR,
+ GTK_ICON_SIZE_LARGE_TOOLBAR,
+ GTK_ICON_SIZE_BUTTON,
+ GTK_ICON_SIZE_DIALOG
+ };
+ set_size_mappings( G_N_ELEMENTS(sizes), sizes );
+ }
+
+ width = sizeThings[_size].width;
+ height = sizeThings[_size].height;
+
+ if ( _view == VIEW_TYPE_LIST ) {
+ width *= 3;
+ }
+
+ if ( _ratio != 100 ) {
+ width = (width * _ratio) / 100;
+ if ( width < 0 ) {
+ width = 1;
+ }
+ }
+
+ req->width = width;
+ req->height = height;
+}
+
+void
+Preview::get_preferred_width_vfunc(int &minimal_width, int &natural_width) const
+{
+ GtkRequisition requisition;
+ size_request(&requisition);
+ minimal_width = natural_width = requisition.width;
+}
+
+void
+Preview::get_preferred_height_vfunc(int &minimal_height, int &natural_height) const
+{
+ GtkRequisition requisition;
+ size_request(&requisition);
+ minimal_height = natural_height = requisition.height;
+}
+
+bool
+Preview::on_draw(const Cairo::RefPtr<Cairo::Context> &cr)
+{
+ auto allocation = get_allocation();
+
+ gint insetTop = 0, insetBottom = 0;
+ gint insetLeft = 0, insetRight = 0;
+
+ if (_border == BORDER_SOLID) {
+ insetTop = 1;
+ insetLeft = 1;
+ }
+ if (_border == BORDER_SOLID_LAST_ROW) {
+ insetTop = insetBottom = 1;
+ insetLeft = 1;
+ }
+ if (_border == BORDER_WIDE) {
+ insetTop = insetBottom = 1;
+ insetLeft = insetRight = 1;
+ }
+
+ auto context = get_style_context();
+
+ context->render_frame(cr,
+ 0, 0,
+ allocation.get_width(), allocation.get_height());
+
+ context->render_background(cr,
+ 0, 0,
+ allocation.get_width(), allocation.get_height());
+
+ // Border
+ if (_border != BORDER_NONE) {
+ cr->set_source_rgb(0.0, 0.0, 0.0);
+ cr->rectangle(0, 0, allocation.get_width(), allocation.get_height());
+ cr->fill();
+ }
+
+ cr->set_source_rgb(_r/65535.0, _g/65535.0, _b/65535.0 );
+ cr->rectangle(insetLeft, insetTop, allocation.get_width() - (insetLeft + insetRight), allocation.get_height() - (insetTop + insetBottom));
+ cr->fill();
+
+ if (_previewPixbuf )
+ {
+ if ((allocation.get_width() != _scaledW) || (allocation.get_height() != _scaledH)) {
+ if (_scaled)
+ {
+ _scaled.reset();
+ }
+
+ _scaledW = allocation.get_width() - (insetLeft + insetRight);
+ _scaledH = allocation.get_height() - (insetTop + insetBottom);
+
+ _scaled = _previewPixbuf->scale_simple(_scaledW,
+ _scaledH,
+ Gdk::INTERP_BILINEAR);
+ }
+
+ Glib::RefPtr<Gdk::Pixbuf> pix = (_scaled) ? _scaled : _previewPixbuf;
+
+ // Border
+ if (_border != BORDER_NONE) {
+ cr->set_source_rgb(0.0, 0.0, 0.0);
+ cr->rectangle(0, 0, allocation.get_width(), allocation.get_height());
+ cr->fill();
+ }
+
+ Gdk::Cairo::set_source_pixbuf(cr, pix, insetLeft, insetTop);
+ cr->paint();
+ }
+
+ if (_linked)
+ {
+ /* Draw arrow */
+ GdkRectangle possible = {insetLeft,
+ insetTop,
+ (allocation.get_width() - (insetLeft + insetRight)),
+ (allocation.get_height() - (insetTop + insetBottom))
+ };
+
+ GdkRectangle area = {possible.x,
+ possible.y,
+ possible.width / 2,
+ possible.height / 2 };
+
+ /* Make it square */
+ if ( area.width > area.height )
+ area.width = area.height;
+ if ( area.height > area.width )
+ area.height = area.width;
+
+ /* Center it horizontally */
+ if ( area.width < possible.width ) {
+ int diff = (possible.width - area.width) / 2;
+ area.x += diff;
+ }
+
+ if (_linked & PREVIEW_LINK_IN)
+ {
+ context->render_arrow(cr,
+ G_PI, // Down-pointing arrow
+ area.x, area.y,
+ std::min(area.width, area.height)
+ );
+ }
+
+ if (_linked & PREVIEW_LINK_OUT)
+ {
+ GdkRectangle otherArea = {area.x, area.y, area.width, area.height};
+ if ( otherArea.height < possible.height ) {
+ otherArea.y = possible.y + (possible.height - otherArea.height);
+ }
+
+ context->render_arrow(cr,
+ G_PI, // Down-pointing arrow
+ otherArea.x, otherArea.y,
+ std::min(otherArea.width, otherArea.height)
+ );
+ }
+
+ if (_linked & PREVIEW_LINK_OTHER)
+ {
+ GdkRectangle otherArea = {insetLeft, area.y, area.width, area.height};
+ if ( otherArea.height < possible.height ) {
+ otherArea.y = possible.y + (possible.height - otherArea.height) / 2;
+ }
+
+ context->render_arrow(cr,
+ 1.5*G_PI, // Left-pointing arrow
+ otherArea.x, otherArea.y,
+ std::min(otherArea.width, otherArea.height)
+ );
+ }
+
+
+ if (_linked & PREVIEW_FILL)
+ {
+ GdkRectangle otherArea = {possible.x + ((possible.width / 4) - (area.width / 2)),
+ area.y,
+ area.width, area.height};
+ if ( otherArea.height < possible.height ) {
+ otherArea.y = possible.y + (possible.height - otherArea.height) / 2;
+ }
+ context->render_check(cr,
+ otherArea.x, otherArea.y,
+ otherArea.width, otherArea.height );
+ }
+
+ if (_linked & PREVIEW_STROKE)
+ {
+ GdkRectangle otherArea = {possible.x + (((possible.width * 3) / 4) - (area.width / 2)),
+ area.y,
+ area.width, area.height};
+ if ( otherArea.height < possible.height ) {
+ otherArea.y = possible.y + (possible.height - otherArea.height) / 2;
+ }
+ // This should be a diamond too?
+ context->render_check(cr,
+ otherArea.x, otherArea.y,
+ otherArea.width, otherArea.height );
+ }
+ }
+
+
+ if ( has_focus() ) {
+ allocation = get_allocation();
+
+ context->render_focus(cr,
+ 0 + 1, 0 + 1,
+ allocation.get_width() - 2, allocation.get_height() - 2 );
+ }
+
+ return false;
+}
+
+
+bool
+Preview::on_enter_notify_event(GdkEventCrossing* event )
+{
+ _within = true;
+ set_state_flags(_hot ? Gtk::STATE_FLAG_ACTIVE : Gtk::STATE_FLAG_PRELIGHT, false);
+
+ return false;
+}
+
+bool
+Preview::on_leave_notify_event(GdkEventCrossing* event)
+{
+ _within = false;
+ set_state_flags(Gtk::STATE_FLAG_NORMAL, false);
+
+ return false;
+}
+
+bool
+Preview::on_button_press_event(GdkEventButton *event)
+{
+ if (_takesFocus && !has_focus() )
+ {
+ grab_focus();
+ }
+
+ if ( event->button == PRIME_BUTTON_MAGIC_NUMBER ||
+ event->button == 2 )
+ {
+ _hot = true;
+
+ if ( _within )
+ {
+ set_state_flags(Gtk::STATE_FLAG_ACTIVE, false);
+ }
+ }
+
+ return false;
+}
+
+bool
+Preview::on_button_release_event(GdkEventButton* event)
+{
+ _hot = false;
+ set_state_flags(Gtk::STATE_FLAG_NORMAL, false);
+
+ if (_within &&
+ (event->button == PRIME_BUTTON_MAGIC_NUMBER ||
+ event->button == 2))
+ {
+ gboolean isAlt = ( ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) ||
+ (event->button == 2));
+
+ if ( isAlt )
+ {
+ _signal_alt_clicked(2);
+ }
+ else
+ {
+ _signal_clicked.emit();
+ }
+ }
+
+ return false;
+}
+
+void
+Preview::set_linked(LinkType link)
+{
+ link = (LinkType)(link & PREVIEW_LINK_ALL);
+
+ if (link != _linked)
+ {
+ _linked = link;
+
+ queue_draw();
+ }
+}
+
+LinkType
+Preview::get_linked() const
+{
+ return (LinkType)_linked;
+}
+
+void
+Preview::set_details(ViewType view,
+ PreviewSize size,
+ guint ratio,
+ guint border)
+{
+ _view = view;
+
+ if ( size > PREVIEW_SIZE_LAST )
+ {
+ size = PREVIEW_SIZE_LAST;
+ }
+
+ _size = size;
+
+ if ( ratio > PREVIEW_MAX_RATIO )
+ {
+ ratio = PREVIEW_MAX_RATIO;
+ }
+
+ _ratio = ratio;
+ _border = border;
+
+ queue_draw();
+}
+
+Preview::Preview()
+ : _r(0x80),
+ _g(0x80),
+ _b(0xcc),
+ _scaledW(0),
+ _scaledH(0),
+ _hot(false),
+ _within(false),
+ _takesFocus(false),
+ _view(VIEW_TYPE_LIST),
+ _size(PREVIEW_SIZE_SMALL),
+ _ratio(100),
+ _border(BORDER_NONE),
+ _previewPixbuf(nullptr),
+ _scaled(nullptr),
+ _linked(PREVIEW_LINK_NONE)
+{
+ set_can_focus(true);
+ set_receives_default(true);
+
+ set_sensitive(true);
+
+ add_events(Gdk::BUTTON_PRESS_MASK
+ |Gdk::BUTTON_RELEASE_MASK
+ |Gdk::KEY_PRESS_MASK
+ |Gdk::KEY_RELEASE_MASK
+ |Gdk::FOCUS_CHANGE_MASK
+ |Gdk::ENTER_NOTIFY_MASK
+ |Gdk::LEAVE_NOTIFY_MASK );
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/preview.h b/src/ui/widget/preview.h
new file mode 100644
index 0000000..b455367
--- /dev/null
+++ b/src/ui/widget/preview.h
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MPL-1.1 OR LGPL-2.1-or-later
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Eek Preview Stuffs.
+ *
+ * The Initial Developer of the Original Code is
+ * Jon A. Cruz.
+ * Portions created by the Initial Developer are Copyright (C) 2005-2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef SEEN_EEK_PREVIEW_H
+#define SEEN_EEK_PREVIEW_H
+
+#include <gtkmm/drawingarea.h>
+
+/**
+ * @file
+ * Generic implementation of an object that can be shown by a preview.
+ */
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+enum PreviewStyle {
+ PREVIEW_STYLE_ICON = 0,
+ PREVIEW_STYLE_PREVIEW,
+ PREVIEW_STYLE_NAME,
+ PREVIEW_STYLE_BLURB,
+ PREVIEW_STYLE_ICON_NAME,
+ PREVIEW_STYLE_ICON_BLURB,
+ PREVIEW_STYLE_PREVIEW_NAME,
+ PREVIEW_STYLE_PREVIEW_BLURB
+};
+
+enum ViewType {
+ VIEW_TYPE_LIST = 0,
+ VIEW_TYPE_GRID
+};
+
+enum PreviewSize {
+ PREVIEW_SIZE_TINY = 0,
+ PREVIEW_SIZE_SMALL,
+ PREVIEW_SIZE_MEDIUM,
+ PREVIEW_SIZE_BIG,
+ PREVIEW_SIZE_BIGGER,
+ PREVIEW_SIZE_HUGE
+};
+
+enum LinkType {
+ PREVIEW_LINK_NONE = 0,
+ PREVIEW_LINK_IN = 1,
+ PREVIEW_LINK_OUT = 2,
+ PREVIEW_LINK_OTHER = 4,
+ PREVIEW_FILL = 8,
+ PREVIEW_STROKE = 16,
+ PREVIEW_LINK_ALL = 31
+};
+
+enum BorderStyle {
+ BORDER_NONE = 0,
+ BORDER_SOLID,
+ BORDER_WIDE,
+ BORDER_SOLID_LAST_ROW,
+};
+
+class Preview : public Gtk::DrawingArea {
+private:
+ int _scaledW;
+ int _scaledH;
+
+ int _r;
+ int _g;
+ int _b;
+
+ bool _hot;
+ bool _within;
+ bool _takesFocus; ///< flag to grab focus when clicked
+ ViewType _view;
+ PreviewSize _size;
+ unsigned int _ratio;
+ LinkType _linked;
+ unsigned int _border;
+
+ Glib::RefPtr<Gdk::Pixbuf> _previewPixbuf;
+ Glib::RefPtr<Gdk::Pixbuf> _scaled;
+
+ // signals
+ sigc::signal<void> _signal_clicked;
+ sigc::signal<void, int> _signal_alt_clicked;
+
+ void size_request(GtkRequisition *req) const;
+
+protected:
+ void get_preferred_width_vfunc(int &minimal_width, int &natural_width) const override;
+ void get_preferred_height_vfunc(int &minimal_height, int &natural_height) const override;
+ bool on_draw(const Cairo::RefPtr<Cairo::Context> &cr) override;
+ bool on_button_press_event(GdkEventButton *button_event) override;
+ bool on_button_release_event(GdkEventButton *button_event) override;
+ bool on_enter_notify_event(GdkEventCrossing* event ) override;
+ bool on_leave_notify_event(GdkEventCrossing* event ) override;
+
+public:
+ Preview();
+ bool get_focus_on_click() const {return _takesFocus;}
+ void set_focus_on_click(bool focus_on_click) {_takesFocus = focus_on_click;}
+ LinkType get_linked() const;
+ void set_linked(LinkType link);
+ void set_details(ViewType view,
+ PreviewSize size,
+ guint ratio,
+ guint border);
+ void set_color(int r, int g, int b);
+ void set_pixbuf(const Glib::RefPtr<Gdk::Pixbuf> &pixbuf);
+ static void set_size_mappings(guint count, GtkIconSize const* sizes);
+
+ decltype(_signal_clicked) signal_clicked() {return _signal_clicked;}
+ decltype(_signal_alt_clicked) signal_alt_clicked() {return _signal_alt_clicked;}
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif /* SEEN_EEK_PREVIEW_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/random.cpp b/src/ui/widget/random.cpp
new file mode 100644
index 0000000..495a778
--- /dev/null
+++ b/src/ui/widget/random.cpp
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Carl Hetherington <inkscape@carlh.net>
+ * Derek P. Moore <derekm@hackunix.org>
+ * Bryce Harrington <bryce@bryceharrington.org>
+ *
+ * Copyright (C) 2004 Carl Hetherington
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "random.h"
+#include "ui/icon-loader.h"
+#include <glibmm/i18n.h>
+
+#include <gtkmm/button.h>
+#include <gtkmm/image.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+Random::Random(Glib::ustring const &label, Glib::ustring const &tooltip,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Scalar(label, tooltip, suffix, icon, mnemonic)
+{
+ startseed = 0;
+ addReseedButton();
+}
+
+Random::Random(Glib::ustring const &label, Glib::ustring const &tooltip,
+ unsigned digits,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Scalar(label, tooltip, digits, suffix, icon, mnemonic)
+{
+ startseed = 0;
+ addReseedButton();
+}
+
+Random::Random(Glib::ustring const &label, Glib::ustring const &tooltip,
+ Glib::RefPtr<Gtk::Adjustment> &adjust,
+ unsigned digits,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Scalar(label, tooltip, adjust, digits, suffix, icon, mnemonic)
+{
+ startseed = 0;
+ addReseedButton();
+}
+
+long Random::getStartSeed() const
+{
+ return startseed;
+}
+
+void Random::setStartSeed(long newseed)
+{
+ startseed = newseed;
+}
+
+void Random::addReseedButton()
+{
+ Gtk::Image *pIcon = Gtk::manage(sp_get_icon_image("randomize", Gtk::ICON_SIZE_BUTTON));
+ Gtk::Button * pButton = Gtk::manage(new Gtk::Button());
+ pButton->set_relief(Gtk::RELIEF_NONE);
+ pIcon->show();
+ pButton->add(*pIcon);
+ pButton->show();
+ pButton->signal_clicked().connect(sigc::mem_fun(*this, &Random::onReseedButtonClick));
+ pButton->set_tooltip_text(_("Reseed the random number generator; this creates a different sequence of random numbers."));
+
+ pack_start(*pButton, Gtk::PACK_SHRINK, 0);
+}
+
+void
+Random::onReseedButtonClick()
+{
+ startseed = g_random_int();
+ signal_reseeded.emit();
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/random.h b/src/ui/widget/random.h
new file mode 100644
index 0000000..2648cb2
--- /dev/null
+++ b/src/ui/widget/random.h
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ *
+ * Copyright (C) 2007 Author
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_RANDOM_H
+#define INKSCAPE_UI_WIDGET_RANDOM_H
+
+#include "scalar.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * A labelled text box, with spin buttons and optional
+ * icon or suffix, for entering arbitrary number values. It adds an extra
+ * number called "startseed", that is not UI edittable, but should be put in SVG.
+ * This does NOT generate a random number, but provides merely the saving of
+ * the startseed value.
+ */
+class Random : public Scalar
+{
+public:
+
+ /**
+ * Construct a Random scalar Widget.
+ *
+ * @param label Label.
+ * @param suffix Suffix, placed after the widget (defaults to "").
+ * @param icon Icon filename, placed before the label (defaults to "").
+ * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to false).
+ */
+ Random(Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ /**
+ * Construct a Random Scalar Widget.
+ *
+ * @param label Label.
+ * @param digits Number of decimal digits to display.
+ * @param suffix Suffix, placed after the widget (defaults to "").
+ * @param icon Icon filename, placed before the label (defaults to "").
+ * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to false).
+ */
+ Random(Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ unsigned digits,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ /**
+ * Construct a Random Scalar Widget.
+ *
+ * @param label Label.
+ * @param adjust Adjustment to use for the SpinButton.
+ * @param digits Number of decimal digits to display (defaults to 0).
+ * @param suffix Suffix, placed after the widget (defaults to "").
+ * @param icon Icon filename, placed before the label (defaults to "").
+ * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to true).
+ */
+ Random(Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ Glib::RefPtr<Gtk::Adjustment> &adjust,
+ unsigned digits = 0,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ /**
+ * Gets the startseed.
+ */
+ long getStartSeed() const;
+
+ /**
+ * Sets the startseed number.
+ */
+ void setStartSeed(long newseed);
+
+ sigc::signal <void> signal_reseeded;
+
+protected:
+ long startseed;
+
+private:
+
+ /**
+ * Add reseed button to the widget.
+ */
+ void addReseedButton();
+
+ void onReseedButtonClick();
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_RANDOM_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/registered-enums.h b/src/ui/widget/registered-enums.h
new file mode 100644
index 0000000..b0cc199
--- /dev/null
+++ b/src/ui/widget/registered-enums.h
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_REGISTERED_ENUMS_H
+#define INKSCAPE_UI_WIDGET_REGISTERED_ENUMS_H
+
+#include "ui/widget/combo-enums.h"
+#include "ui/widget/registered-widget.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * Simplified management of enumerations in the UI as combobox.
+ */
+template<typename E> class RegisteredEnum : public RegisteredWidget< LabelledComboBoxEnum<E> >
+{
+public:
+ ~RegisteredEnum() override {
+ _changed_connection.disconnect();
+ }
+
+ RegisteredEnum ( const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ const Util::EnumDataConverter<E>& c,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in = nullptr,
+ SPDocument *doc_in = nullptr,
+ bool sorted = true )
+ : RegisteredWidget< LabelledComboBoxEnum<E> >(label, tip, c, (const Glib::ustring &)"", (const Glib::ustring &)"", true, sorted)
+ {
+ RegisteredWidget< LabelledComboBoxEnum<E> >::init_parent(key, wr, repr_in, doc_in);
+ _changed_connection = combobox()->signal_changed().connect (sigc::mem_fun (*this, &RegisteredEnum::on_changed));
+ }
+
+ void set_active_by_id (E id) {
+ combobox()->set_active_by_id(id);
+ };
+
+ void set_active_by_key (const Glib::ustring& key) {
+ combobox()->set_active_by_key(key);
+ }
+
+ inline const Util::EnumData<E>* get_active_data() {
+ combobox()->get_active_data();
+ }
+
+ ComboBoxEnum<E> * combobox() {
+ return LabelledComboBoxEnum<E>::getCombobox();
+ }
+
+ sigc::connection _changed_connection;
+
+protected:
+ void on_changed() {
+ if (combobox()->setProgrammatically) {
+ combobox()->setProgrammatically = false;
+ return;
+ }
+
+ if (RegisteredWidget< LabelledComboBoxEnum<E> >::_wr->isUpdating())
+ return;
+
+ RegisteredWidget< LabelledComboBoxEnum<E> >::_wr->setUpdating (true);
+
+ const Util::EnumData<E>* data = combobox()->get_active_data();
+ if (data) {
+ RegisteredWidget< LabelledComboBoxEnum<E> >::write_to_xml(data->key.c_str());
+ }
+
+ RegisteredWidget< LabelledComboBoxEnum<E> >::_wr->setUpdating (false);
+ }
+};
+
+}
+}
+}
+
+#endif
+
+/*
+ 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/registered-widget.cpp b/src/ui/widget/registered-widget.cpp
new file mode 100644
index 0000000..bd62b73
--- /dev/null
+++ b/src/ui/widget/registered-widget.cpp
@@ -0,0 +1,845 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@utwente.nl>
+ * bulia byak <buliabyak@users.sf.net>
+ * Bryce W. Harrington <bryce@bryceharrington.org>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Jon Phillips <jon@rejon.org>
+ * Ralf Stephan <ralf@ark.in-berlin.de> (Gtkmm)
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2000 - 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "registered-widget.h"
+
+#include <gtkmm/radiobutton.h>
+
+#include "verbs.h"
+
+#include "object/sp-root.h"
+
+#include "svg/svg-color.h"
+#include "svg/stringstream.h"
+
+#include "widgets/spinbutton-events.h"
+
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/*#########################################
+ * Registered CHECKBUTTON
+ */
+
+RegisteredCheckButton::~RegisteredCheckButton()
+{
+ _toggled_connection.disconnect();
+}
+
+RegisteredCheckButton::RegisteredCheckButton (const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key, Registry& wr, bool right, Inkscape::XML::Node* repr_in, SPDocument *doc_in, char const *active_str, char const *inactive_str)
+ : RegisteredWidget<Gtk::CheckButton>()
+ , _active_str(active_str)
+ , _inactive_str(inactive_str)
+{
+ init_parent(key, wr, repr_in, doc_in);
+
+ setProgrammatically = false;
+
+ set_tooltip_text (tip);
+ Gtk::Label *l = new Gtk::Label();
+ l->set_markup(label);
+ l->set_use_underline (true);
+ add (*manage (l));
+
+ if(right) set_halign(Gtk::ALIGN_END);
+ else set_halign(Gtk::ALIGN_START);
+
+ set_valign(Gtk::ALIGN_CENTER);
+ _toggled_connection = signal_toggled().connect (sigc::mem_fun (*this, &RegisteredCheckButton::on_toggled));
+}
+
+void
+RegisteredCheckButton::setActive (bool b)
+{
+ setProgrammatically = true;
+ set_active (b);
+ //The slave button is greyed out if the master button is unchecked
+ for (std::list<Gtk::Widget*>::const_iterator i = _slavewidgets.begin(); i != _slavewidgets.end(); ++i) {
+ (*i)->set_sensitive(b);
+ }
+ setProgrammatically = false;
+}
+
+void
+RegisteredCheckButton::on_toggled()
+{
+ if (setProgrammatically) {
+ setProgrammatically = false;
+ return;
+ }
+
+ if (_wr->isUpdating())
+ return;
+ _wr->setUpdating (true);
+
+ write_to_xml(get_active() ? _active_str : _inactive_str);
+ //The slave button is greyed out if the master button is unchecked
+ for (std::list<Gtk::Widget*>::const_iterator i = _slavewidgets.begin(); i != _slavewidgets.end(); ++i) {
+ (*i)->set_sensitive(get_active());
+ }
+
+ _wr->setUpdating (false);
+}
+
+/*#########################################
+ * Registered TOGGLEBUTTON
+ */
+
+RegisteredToggleButton::~RegisteredToggleButton()
+{
+ _toggled_connection.disconnect();
+}
+
+RegisteredToggleButton::RegisteredToggleButton (const Glib::ustring& /*label*/, const Glib::ustring& tip, const Glib::ustring& key, Registry& wr, bool right, Inkscape::XML::Node* repr_in, SPDocument *doc_in, char const *icon_active, char const *icon_inactive)
+ : RegisteredWidget<Gtk::ToggleButton>()
+{
+ init_parent(key, wr, repr_in, doc_in);
+ setProgrammatically = false;
+ set_tooltip_text (tip);
+
+ if(right) set_halign(Gtk::ALIGN_END);
+ else set_halign(Gtk::ALIGN_START);
+
+ set_valign(Gtk::ALIGN_CENTER);
+ _toggled_connection = signal_toggled().connect (sigc::mem_fun (*this, &RegisteredToggleButton::on_toggled));
+}
+
+void
+RegisteredToggleButton::setActive (bool b)
+{
+ setProgrammatically = true;
+ set_active (b);
+ //The slave button is greyed out if the master button is untoggled
+ for (std::list<Gtk::Widget*>::const_iterator i = _slavewidgets.begin(); i != _slavewidgets.end(); ++i) {
+ (*i)->set_sensitive(b);
+ }
+ setProgrammatically = false;
+}
+
+void
+RegisteredToggleButton::on_toggled()
+{
+ if (setProgrammatically) {
+ setProgrammatically = false;
+ return;
+ }
+
+ if (_wr->isUpdating())
+ return;
+ _wr->setUpdating (true);
+
+ write_to_xml(get_active() ? "true" : "false");
+ //The slave button is greyed out if the master button is untoggled
+ for (std::list<Gtk::Widget*>::const_iterator i = _slavewidgets.begin(); i != _slavewidgets.end(); ++i) {
+ (*i)->set_sensitive(get_active());
+ }
+
+ _wr->setUpdating (false);
+}
+
+/*#########################################
+ * Registered UNITMENU
+ */
+
+RegisteredUnitMenu::~RegisteredUnitMenu()
+{
+ _changed_connection.disconnect();
+}
+
+RegisteredUnitMenu::RegisteredUnitMenu (const Glib::ustring& label, const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in)
+ : RegisteredWidget<Labelled> (label, "" /*tooltip*/, new UnitMenu())
+{
+ init_parent(key, wr, repr_in, doc_in);
+
+ getUnitMenu()->setUnitType (UNIT_TYPE_LINEAR);
+ _changed_connection = getUnitMenu()->signal_changed().connect (sigc::mem_fun (*this, &RegisteredUnitMenu::on_changed));
+}
+
+void
+RegisteredUnitMenu::setUnit (Glib::ustring unit)
+{
+ getUnitMenu()->setUnit(unit);
+}
+
+void
+RegisteredUnitMenu::on_changed()
+{
+ if (_wr->isUpdating())
+ return;
+
+ Inkscape::SVGOStringStream os;
+ os << getUnitMenu()->getUnitAbbr();
+
+ _wr->setUpdating (true);
+
+ write_to_xml(os.str().c_str());
+
+ _wr->setUpdating (false);
+}
+
+
+/*#########################################
+ * Registered SCALARUNIT
+ */
+
+RegisteredScalarUnit::~RegisteredScalarUnit()
+{
+ _value_changed_connection.disconnect();
+}
+
+RegisteredScalarUnit::RegisteredScalarUnit (const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key, const RegisteredUnitMenu &rum, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in, RSU_UserUnits user_units)
+ : RegisteredWidget<ScalarUnit>(label, tip, UNIT_TYPE_LINEAR, "", "", rum.getUnitMenu()),
+ _um(nullptr)
+{
+ init_parent(key, wr, repr_in, doc_in);
+
+ setProgrammatically = false;
+
+ initScalar (-1e6, 1e6);
+ setUnit (rum.getUnitMenu()->getUnitAbbr());
+ setDigits (2);
+ _um = rum.getUnitMenu();
+ _user_units = user_units;
+ _value_changed_connection = signal_value_changed().connect (sigc::mem_fun (*this, &RegisteredScalarUnit::on_value_changed));
+}
+
+
+void
+RegisteredScalarUnit::on_value_changed()
+{
+ if (setProgrammatically) {
+ setProgrammatically = false;
+ return;
+ }
+
+ if (_wr->isUpdating())
+ return;
+
+ _wr->setUpdating (true);
+
+ Inkscape::SVGOStringStream os;
+ if (_user_units != RSU_none) {
+ // Output length in 'user units', taking into account scale in 'x' or 'y'.
+ double scale = 1.0;
+ if (doc) {
+ SPRoot *root = doc->getRoot();
+ if (root->viewBox_set) {
+ // check to see if scaling is uniform
+ if(Geom::are_near((root->viewBox.width() * root->height.computed) / (root->width.computed * root->viewBox.height()), 1.0, Geom::EPSILON)) {
+ scale = (root->viewBox.width() / root->width.computed + root->viewBox.height() / root->height.computed)/2.0;
+ } else if (_user_units == RSU_x) {
+ scale = root->viewBox.width() / root->width.computed;
+ } else {
+ scale = root->viewBox.height() / root->height.computed;
+ }
+ }
+ }
+ os << getValue("px") * scale;
+ } else {
+ // Output using unit identifiers.
+ os << getValue("");
+ if (_um)
+ os << _um->getUnitAbbr();
+ }
+
+ write_to_xml(os.str().c_str());
+ _wr->setUpdating (false);
+}
+
+
+/*#########################################
+ * Registered SCALAR
+ */
+
+RegisteredScalar::~RegisteredScalar()
+{
+ _value_changed_connection.disconnect();
+}
+
+RegisteredScalar::RegisteredScalar ( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in,
+ SPDocument * doc_in )
+ : RegisteredWidget<Scalar>(label, tip)
+{
+ init_parent(key, wr, repr_in, doc_in);
+
+ setProgrammatically = false;
+ setRange (-1e6, 1e6);
+ setDigits (2);
+ setIncrements(0.1, 1.0);
+ _value_changed_connection = signal_value_changed().connect (sigc::mem_fun (*this, &RegisteredScalar::on_value_changed));
+}
+
+void
+RegisteredScalar::on_value_changed()
+{
+ if (setProgrammatically) {
+ setProgrammatically = false;
+ return;
+ }
+ if (_wr->isUpdating()) {
+ return;
+ }
+ _wr->setUpdating (true);
+
+ Inkscape::SVGOStringStream os;
+ //Force exact 0 if decimals over to 6
+ double val = getValue() < 1e-6 && getValue() > -1e-6?0.0:getValue();
+ os << val;
+ //TODO: Test is ok remove this sensitives
+ //also removed in registered text and in registered random
+ //set_sensitive(false);
+ write_to_xml(os.str().c_str());
+ //set_sensitive(true);
+ _wr->setUpdating (false);
+}
+
+
+/*#########################################
+ * Registered TEXT
+ */
+
+RegisteredText::~RegisteredText()
+{
+ _activate_connection.disconnect();
+}
+
+RegisteredText::RegisteredText ( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in,
+ SPDocument * doc_in )
+ : RegisteredWidget<Text>(label, tip)
+{
+ init_parent(key, wr, repr_in, doc_in);
+
+ setProgrammatically = false;
+ _activate_connection = signal_activate().connect (sigc::mem_fun (*this, &RegisteredText::on_activate));
+}
+
+void
+RegisteredText::on_activate()
+{
+ if (setProgrammatically) {
+ setProgrammatically = false;
+ return;
+ }
+
+ if (_wr->isUpdating()) {
+ return;
+ }
+ _wr->setUpdating (true);
+ Glib::ustring str(getText());
+ Inkscape::SVGOStringStream os;
+ os << str;
+ write_to_xml(os.str().c_str());
+ _wr->setUpdating (false);
+}
+
+
+/*#########################################
+ * Registered COLORPICKER
+ */
+
+RegisteredColorPicker::RegisteredColorPicker(const Glib::ustring& label,
+ const Glib::ustring& title,
+ const Glib::ustring& tip,
+ const Glib::ustring& ckey,
+ const Glib::ustring& akey,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in,
+ SPDocument *doc_in)
+ : RegisteredWidget<LabelledColorPicker> (label, title, tip, 0, true)
+{
+ init_parent("", wr, repr_in, doc_in);
+
+ _ckey = ckey;
+ _akey = akey;
+ _changed_connection = connectChanged (sigc::mem_fun (*this, &RegisteredColorPicker::on_changed));
+}
+
+RegisteredColorPicker::~RegisteredColorPicker()
+{
+ _changed_connection.disconnect();
+}
+
+void
+RegisteredColorPicker::setRgba32 (guint32 rgba)
+{
+ LabelledColorPicker::setRgba32 (rgba);
+}
+
+void
+RegisteredColorPicker::closeWindow()
+{
+ LabelledColorPicker::closeWindow();
+}
+
+void
+RegisteredColorPicker::on_changed (guint32 rgba)
+{
+ if (_wr->isUpdating())
+ return;
+
+ _wr->setUpdating (true);
+
+ // Use local repr here. When repr is specified, use that one, but
+ // if repr==NULL, get the repr of namedview of active desktop.
+ Inkscape::XML::Node *local_repr = repr;
+ SPDocument *local_doc = doc;
+ if (!local_repr) {
+ // no repr specified, use active desktop's namedview's repr
+ SPDesktop *dt = SP_ACTIVE_DESKTOP;
+ if (!dt)
+ return;
+ local_repr = dt->getNamedView()->getRepr();
+ local_doc = dt->getDocument();
+ }
+ gchar c[32];
+ if (_akey == _ckey + "_opacity_LPE") { //For LPE parameter we want stored with alpha
+ sprintf(c, "#%08x", rgba);
+ } else {
+ sp_svg_write_color(c, sizeof(c), rgba);
+ }
+ bool saved = DocumentUndo::getUndoSensitive(local_doc);
+ DocumentUndo::setUndoSensitive(local_doc, false);
+ local_repr->setAttribute(_ckey, c);
+ sp_repr_set_css_double(local_repr, _akey.c_str(), (rgba & 0xff) / 255.0);
+ DocumentUndo::setUndoSensitive(local_doc, saved);
+
+ local_doc->setModifiedSinceSave();
+ DocumentUndo::done(local_doc, SP_VERB_NONE,
+ /* TODO: annotate */ "registered-widget.cpp: RegisteredColorPicker::on_changed");
+
+ _wr->setUpdating (false);
+}
+
+
+/*#########################################
+ * Registered SUFFIXEDINTEGER
+ */
+
+RegisteredSuffixedInteger::~RegisteredSuffixedInteger()
+{
+ _changed_connection.disconnect();
+}
+
+RegisteredSuffixedInteger::RegisteredSuffixedInteger (const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& suffix, const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in)
+ : RegisteredWidget<Scalar>(label, tip, 0, suffix),
+ setProgrammatically(false)
+{
+ init_parent(key, wr, repr_in, doc_in);
+
+ setRange (0, 1e6);
+ setDigits (0);
+ setIncrements(1, 10);
+
+ _changed_connection = signal_value_changed().connect (sigc::mem_fun(*this, &RegisteredSuffixedInteger::on_value_changed));
+}
+
+void
+RegisteredSuffixedInteger::on_value_changed()
+{
+ if (setProgrammatically) {
+ setProgrammatically = false;
+ return;
+ }
+
+ if (_wr->isUpdating())
+ return;
+
+ _wr->setUpdating (true);
+
+ Inkscape::SVGOStringStream os;
+ os << getValue();
+
+ write_to_xml(os.str().c_str());
+
+ _wr->setUpdating (false);
+}
+
+
+/*#########################################
+ * Registered RADIOBUTTONPAIR
+ */
+
+RegisteredRadioButtonPair::~RegisteredRadioButtonPair()
+{
+ _changed_connection.disconnect();
+}
+
+RegisteredRadioButtonPair::RegisteredRadioButtonPair (const Glib::ustring& label,
+ const Glib::ustring& label1, const Glib::ustring& label2,
+ const Glib::ustring& tip1, const Glib::ustring& tip2,
+ const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in)
+ : RegisteredWidget<Gtk::HBox>(),
+ _rb1(nullptr),
+ _rb2(nullptr)
+{
+ init_parent(key, wr, repr_in, doc_in);
+
+ setProgrammatically = false;
+
+ add(*Gtk::manage(new Gtk::Label(label)));
+ _rb1 = Gtk::manage(new Gtk::RadioButton(label1, true));
+ add (*_rb1);
+ Gtk::RadioButtonGroup group = _rb1->get_group();
+ _rb2 = Gtk::manage(new Gtk::RadioButton(group, label2, true));
+ add (*_rb2);
+ _rb2->set_active();
+ _rb1->set_tooltip_text(tip1);
+ _rb2->set_tooltip_text(tip2);
+ _changed_connection = _rb1->signal_toggled().connect (sigc::mem_fun (*this, &RegisteredRadioButtonPair::on_value_changed));
+}
+
+void
+RegisteredRadioButtonPair::setValue (bool second)
+{
+ if (!_rb1 || !_rb2)
+ return;
+
+ setProgrammatically = true;
+ if (second) {
+ _rb2->set_active();
+ } else {
+ _rb1->set_active();
+ }
+}
+
+void
+RegisteredRadioButtonPair::on_value_changed()
+{
+ if (setProgrammatically) {
+ setProgrammatically = false;
+ return;
+ }
+
+ if (_wr->isUpdating())
+ return;
+
+ _wr->setUpdating (true);
+
+ bool second = _rb2->get_active();
+ write_to_xml(second ? "true" : "false");
+
+ _wr->setUpdating (false);
+}
+
+
+/*#########################################
+ * Registered POINT
+ */
+
+RegisteredPoint::~RegisteredPoint()
+{
+ _value_x_changed_connection.disconnect();
+ _value_y_changed_connection.disconnect();
+}
+
+RegisteredPoint::RegisteredPoint ( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in,
+ SPDocument* doc_in )
+ : RegisteredWidget<Point> (label, tip)
+{
+ init_parent(key, wr, repr_in, doc_in);
+
+ setRange (-1e6, 1e6);
+ setDigits (2);
+ setIncrements(0.1, 1.0);
+ _value_x_changed_connection = signal_x_value_changed().connect (sigc::mem_fun (*this, &RegisteredPoint::on_value_changed));
+ _value_y_changed_connection = signal_y_value_changed().connect (sigc::mem_fun (*this, &RegisteredPoint::on_value_changed));
+}
+
+void
+RegisteredPoint::on_value_changed()
+{
+ if (setProgrammatically()) {
+ clearProgrammatically();
+ return;
+ }
+
+ if (_wr->isUpdating())
+ return;
+
+ _wr->setUpdating (true);
+
+ Inkscape::SVGOStringStream os;
+ os << getXValue() << "," << getYValue();
+
+ write_to_xml(os.str().c_str());
+
+ _wr->setUpdating (false);
+}
+
+/*#########################################
+ * Registered TRANSFORMEDPOINT
+ */
+
+RegisteredTransformedPoint::~RegisteredTransformedPoint()
+{
+ _value_x_changed_connection.disconnect();
+ _value_y_changed_connection.disconnect();
+}
+
+RegisteredTransformedPoint::RegisteredTransformedPoint ( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in,
+ SPDocument* doc_in )
+ : RegisteredWidget<Point> (label, tip),
+ to_svg(Geom::identity())
+{
+ init_parent(key, wr, repr_in, doc_in);
+
+ setRange (-1e6, 1e6);
+ setDigits (2);
+ setIncrements(0.1, 1.0);
+ _value_x_changed_connection = signal_x_value_changed().connect (sigc::mem_fun (*this, &RegisteredTransformedPoint::on_value_changed));
+ _value_y_changed_connection = signal_y_value_changed().connect (sigc::mem_fun (*this, &RegisteredTransformedPoint::on_value_changed));
+}
+
+void
+RegisteredTransformedPoint::setValue(Geom::Point const & p)
+{
+ Geom::Point new_p = p * to_svg.inverse();
+ Point::setValue(new_p); // the Point widget should display things in canvas coordinates
+}
+
+void
+RegisteredTransformedPoint::setTransform(Geom::Affine const & canvas_to_svg)
+{
+ // check if matrix is singular / has inverse
+ if ( ! canvas_to_svg.isSingular() ) {
+ to_svg = canvas_to_svg;
+ } else {
+ // set back to default
+ to_svg = Geom::identity();
+ }
+}
+
+void
+RegisteredTransformedPoint::on_value_changed()
+{
+ if (setProgrammatically()) {
+ clearProgrammatically();
+ return;
+ }
+
+ if (_wr->isUpdating())
+ return;
+
+ _wr->setUpdating (true);
+
+ Geom::Point pos = getValue() * to_svg;
+
+ Inkscape::SVGOStringStream os;
+ os << pos;
+
+ write_to_xml(os.str().c_str());
+
+ _wr->setUpdating (false);
+}
+
+/*#########################################
+ * Registered TRANSFORMEDPOINT
+ */
+
+RegisteredVector::~RegisteredVector()
+{
+ _value_x_changed_connection.disconnect();
+ _value_y_changed_connection.disconnect();
+}
+
+RegisteredVector::RegisteredVector ( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in,
+ SPDocument* doc_in )
+ : RegisteredWidget<Point> (label, tip),
+ _polar_coords(false)
+{
+ init_parent(key, wr, repr_in, doc_in);
+
+ setRange (-1e6, 1e6);
+ setDigits (2);
+ setIncrements(0.1, 1.0);
+ _value_x_changed_connection = signal_x_value_changed().connect (sigc::mem_fun (*this, &RegisteredVector::on_value_changed));
+ _value_y_changed_connection = signal_y_value_changed().connect (sigc::mem_fun (*this, &RegisteredVector::on_value_changed));
+}
+
+void
+RegisteredVector::setValue(Geom::Point const & p)
+{
+ if (!_polar_coords) {
+ Point::setValue(p);
+ } else {
+ Geom::Point polar;
+ polar[Geom::X] = atan2(p) *180/M_PI;
+ polar[Geom::Y] = p.length();
+ Point::setValue(polar);
+ }
+}
+
+void
+RegisteredVector::setValue(Geom::Point const & p, Geom::Point const & origin)
+{
+ RegisteredVector::setValue(p);
+ _origin = origin;
+}
+
+void RegisteredVector::setPolarCoords(bool polar_coords)
+{
+ _polar_coords = polar_coords;
+ if (polar_coords) {
+ xwidget.setLabelText("Angle:");
+ ywidget.setLabelText("Distance:");
+ } else {
+ xwidget.setLabelText("X:");
+ ywidget.setLabelText("Y:");
+ }
+}
+
+void
+RegisteredVector::on_value_changed()
+{
+ if (setProgrammatically()) {
+ clearProgrammatically();
+ return;
+ }
+
+ if (_wr->isUpdating())
+ return;
+
+ _wr->setUpdating (true);
+
+ Geom::Point origin = _origin;
+ Geom::Point vector = getValue();
+ if (_polar_coords) {
+ vector = Geom::Point::polar(vector[Geom::X]*M_PI/180, vector[Geom::Y]);
+ }
+
+ Inkscape::SVGOStringStream os;
+ os << origin << " , " << vector;
+
+ write_to_xml(os.str().c_str());
+
+ _wr->setUpdating (false);
+}
+
+/*#########################################
+ * Registered RANDOM
+ */
+
+RegisteredRandom::~RegisteredRandom()
+{
+ _value_changed_connection.disconnect();
+ _reseeded_connection.disconnect();
+}
+
+RegisteredRandom::RegisteredRandom ( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in,
+ SPDocument * doc_in )
+ : RegisteredWidget<Random> (label, tip)
+{
+ init_parent(key, wr, repr_in, doc_in);
+
+ setProgrammatically = false;
+ setRange (-1e6, 1e6);
+ setDigits (2);
+ setIncrements(0.1, 1.0);
+ _value_changed_connection = signal_value_changed().connect (sigc::mem_fun (*this, &RegisteredRandom::on_value_changed));
+ _reseeded_connection = signal_reseeded.connect(sigc::mem_fun(*this, &RegisteredRandom::on_value_changed));
+}
+
+void
+RegisteredRandom::setValue (double val, long startseed)
+{
+ Scalar::setValue (val);
+ setStartSeed(startseed);
+}
+
+void
+RegisteredRandom::on_value_changed()
+{
+ if (setProgrammatically) {
+ setProgrammatically = false;
+ return;
+ }
+
+ if (_wr->isUpdating()) {
+ return;
+ }
+ _wr->setUpdating (true);
+
+ Inkscape::SVGOStringStream os;
+ //Force exact 0 if decimals over to 6
+ double val = getValue() < 1e-6 && getValue() > -1e-6?0.0:getValue();
+ os << val << ';' << getStartSeed();
+ write_to_xml(os.str().c_str());
+ _wr->setUpdating (false);
+}
+
+/*#########################################
+ * Registered FONT-BUTTON
+ */
+
+RegisteredFontButton::~RegisteredFontButton()
+{
+ _signal_font_set.disconnect();
+}
+
+RegisteredFontButton::RegisteredFontButton ( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in,
+ SPDocument* doc_in )
+ : RegisteredWidget<FontButton>(label, tip)
+{
+ init_parent(key, wr, repr_in, doc_in);
+ _signal_font_set = signal_font_value_changed().connect (sigc::mem_fun (*this, &RegisteredFontButton::on_value_changed));
+}
+
+void
+RegisteredFontButton::setValue (Glib::ustring fontspec)
+{
+ FontButton::setValue(fontspec);
+}
+
+void
+RegisteredFontButton::on_value_changed()
+{
+
+ if (_wr->isUpdating())
+ return;
+
+ _wr->setUpdating (true);
+
+ Inkscape::SVGOStringStream os;
+ os << getValue();
+
+ write_to_xml(os.str().c_str());
+
+ _wr->setUpdating (false);
+}
+
+} // namespace Dialog
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/registered-widget.h b/src/ui/widget/registered-widget.h
new file mode 100644
index 0000000..d0b728a
--- /dev/null
+++ b/src/ui/widget/registered-widget.h
@@ -0,0 +1,458 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ralf Stephan <ralf@ark.in-berlin.de>
+ * Johan Engelen <j.b.c.engelen@utwente.nl>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2005-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_REGISTERED_WIDGET__H_
+#define INKSCAPE_UI_WIDGET_REGISTERED_WIDGET__H_
+
+#include <2geom/affine.h>
+#include "xml/node.h"
+#include "registry.h"
+
+#include "ui/widget/scalar.h"
+#include "ui/widget/scalar-unit.h"
+#include "ui/widget/point.h"
+#include "ui/widget/text.h"
+#include "ui/widget/random.h"
+#include "ui/widget/unit-menu.h"
+#include "ui/widget/font-button.h"
+#include "ui/widget/color-picker.h"
+#include "inkscape.h"
+
+#include "document.h"
+#include "document-undo.h"
+#include "desktop.h"
+#include "object/sp-namedview.h"
+
+#include <gtkmm/checkbutton.h>
+
+class SPDocument;
+
+namespace Gtk {
+ class HScale;
+ class RadioButton;
+ class SpinButton;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class Registry;
+
+template <class W>
+class RegisteredWidget : public W {
+public:
+ void set_undo_parameters(const unsigned int _event_type, Glib::ustring _event_description)
+ {
+ event_type = _event_type;
+ event_description = _event_description;
+ write_undo = true;
+ }
+ void set_xml_target(Inkscape::XML::Node *xml_node, SPDocument *document)
+ {
+ repr = xml_node;
+ doc = document;
+ }
+
+ bool is_updating() {if (_wr) return _wr->isUpdating(); else return false;}
+
+protected:
+ RegisteredWidget() : W() { construct(); }
+ template< typename A >
+ explicit RegisteredWidget( A& a ): W( a ) { construct(); }
+ template< typename A, typename B >
+ RegisteredWidget( A& a, B& b ): W( a, b ) { construct(); }
+ template< typename A, typename B, typename C >
+ RegisteredWidget( A& a, B& b, C* c ): W( a, b, c ) { construct(); }
+ template< typename A, typename B, typename C >
+ RegisteredWidget( A& a, B& b, C& c ): W( a, b, c ) { construct(); }
+ template< typename A, typename B, typename C, typename D >
+ RegisteredWidget( A& a, B& b, C c, D d ): W( a, b, c, d ) { construct(); }
+ template< typename A, typename B, typename C, typename D, typename E >
+ RegisteredWidget( A& a, B& b, C& c, D d, E e ): W( a, b, c, d, e ) { construct(); }
+ template< typename A, typename B, typename C, typename D, typename E , typename F>
+ RegisteredWidget( A& a, B& b, C c, D& d, E& e, F* f): W( a, b, c, d, e, f) { construct(); }
+ template< typename A, typename B, typename C, typename D, typename E , typename F, typename G>
+ RegisteredWidget( A& a, B& b, C& c, D& d, E& e, F f, G& g): W( a, b, c, d, e, f, g) { construct(); }
+
+ ~RegisteredWidget() override = default;;
+
+ void init_parent(const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in)
+ {
+ _wr = &wr;
+ _key = key;
+ repr = repr_in;
+ doc = doc_in;
+ if (repr && !doc) // doc cannot be NULL when repr is not NULL
+ g_warning("Initialization of registered widget using defined repr but with doc==NULL");
+ }
+
+ void write_to_xml(const char * svgstr)
+ {
+ // Use local repr here. When repr is specified, use that one, but
+ // if repr==NULL, get the repr of namedview of active desktop.
+ Inkscape::XML::Node *local_repr = repr;
+ SPDocument *local_doc = doc;
+ if (!local_repr) {
+ // no repr specified, use active desktop's namedview's repr
+ SPDesktop* dt = SP_ACTIVE_DESKTOP;
+ local_repr = reinterpret_cast<SPObject *>(dt->getNamedView())->getRepr();
+ local_doc = dt->getDocument();
+ }
+
+ bool saved = DocumentUndo::getUndoSensitive(local_doc);
+ DocumentUndo::setUndoSensitive(local_doc, false);
+ const char * svgstr_old = local_repr->attribute(_key.c_str());
+ if (!write_undo) {
+ local_repr->setAttribute(_key, svgstr);
+ }
+ DocumentUndo::setUndoSensitive(local_doc, saved);
+ if (svgstr_old && svgstr && strcmp(svgstr_old,svgstr)) {
+ local_doc->setModifiedSinceSave();
+ }
+
+ if (write_undo) {
+ local_repr->setAttribute(_key, svgstr);
+ DocumentUndo::done(local_doc, event_type, event_description);
+ }
+ }
+
+ Registry * _wr;
+ Glib::ustring _key;
+ Inkscape::XML::Node * repr;
+ SPDocument * doc;
+ unsigned int event_type;
+ Glib::ustring event_description;
+ bool write_undo;
+
+private:
+ void construct() {
+ _wr = nullptr;
+ repr = nullptr;
+ doc = nullptr;
+ write_undo = false;
+ event_type = 0; //SP_VERB_INVALID
+ }
+};
+
+//#######################################################
+
+class RegisteredCheckButton : public RegisteredWidget<Gtk::CheckButton> {
+public:
+ ~RegisteredCheckButton() override;
+ RegisteredCheckButton (const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key, Registry& wr, bool right=false, Inkscape::XML::Node* repr_in=nullptr, SPDocument *doc_in=nullptr, char const *active_str = "true", char const *inactive_str = "false");
+
+ void setActive (bool);
+
+ std::list<Gtk::Widget*> _slavewidgets;
+
+ // a slave button is only sensitive when the master button is active
+ // i.e. a slave button is greyed-out when the master button is not checked
+
+ void setSlaveWidgets(std::list<Gtk::Widget*> btns) {
+ _slavewidgets = btns;
+ }
+
+ bool setProgrammatically; // true if the value was set by setActive, not changed by the user;
+ // if a callback checks it, it must reset it back to false
+
+protected:
+ char const *_active_str, *_inactive_str;
+ sigc::connection _toggled_connection;
+ void on_toggled() override;
+};
+
+class RegisteredToggleButton : public RegisteredWidget<Gtk::ToggleButton> {
+public:
+ ~RegisteredToggleButton() override;
+ RegisteredToggleButton (const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key, Registry& wr, bool right=true, Inkscape::XML::Node* repr_in=nullptr, SPDocument *doc_in=nullptr, char const *icon_active = "true", char const *icon_inactive = "false");
+
+ void setActive (bool);
+
+ std::list<Gtk::Widget*> _slavewidgets;
+
+ // a slave button is only sensitive when the master button is active
+ // i.e. a slave button is greyed-out when the master button is not checked
+
+ void setSlaveWidgets(std::list<Gtk::Widget*> btns) {
+ _slavewidgets = btns;
+ }
+
+ bool setProgrammatically; // true if the value was set by setActive, not changed by the user;
+ // if a callback checks it, it must reset it back to false
+
+protected:
+ sigc::connection _toggled_connection;
+ void on_toggled() override;
+};
+
+class RegisteredUnitMenu : public RegisteredWidget<Labelled> {
+public:
+ ~RegisteredUnitMenu() override;
+ RegisteredUnitMenu ( const Glib::ustring& label,
+ const Glib::ustring& key,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in = nullptr,
+ SPDocument *doc_in = nullptr );
+
+ void setUnit (const Glib::ustring);
+ Unit const * getUnit() const { return static_cast<UnitMenu*>(_widget)->getUnit(); };
+ UnitMenu* getUnitMenu() const { return static_cast<UnitMenu*>(_widget); };
+ sigc::connection _changed_connection;
+
+protected:
+ void on_changed();
+};
+
+// Allow RegisteredScalarUnit to output lengths in 'user units' (which may have direction dependent
+// scale factors).
+enum RSU_UserUnits {
+ RSU_none,
+ RSU_x,
+ RSU_y
+};
+
+class RegisteredScalarUnit : public RegisteredWidget<ScalarUnit> {
+public:
+ ~RegisteredScalarUnit() override;
+ RegisteredScalarUnit ( const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ const RegisteredUnitMenu &rum,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in = nullptr,
+ SPDocument *doc_in = nullptr,
+ RSU_UserUnits _user_units = RSU_none );
+
+protected:
+ sigc::connection _value_changed_connection;
+ UnitMenu *_um;
+ void on_value_changed();
+ RSU_UserUnits _user_units;
+};
+
+class RegisteredScalar : public RegisteredWidget<Scalar> {
+public:
+ ~RegisteredScalar() override;
+ RegisteredScalar (const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in = nullptr,
+ SPDocument *doc_in = nullptr );
+protected:
+ sigc::connection _value_changed_connection;
+ void on_value_changed();
+};
+
+class RegisteredText : public RegisteredWidget<Text> {
+public:
+ ~RegisteredText() override;
+ RegisteredText (const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in = nullptr,
+ SPDocument *doc_in = nullptr );
+
+protected:
+ sigc::connection _activate_connection;
+ void on_activate();
+};
+
+class RegisteredColorPicker : public RegisteredWidget<LabelledColorPicker> {
+public:
+ ~RegisteredColorPicker() override;
+
+ RegisteredColorPicker (const Glib::ustring& label,
+ const Glib::ustring& title,
+ const Glib::ustring& tip,
+ const Glib::ustring& ckey,
+ const Glib::ustring& akey,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in = nullptr,
+ SPDocument *doc_in = nullptr);
+
+ void setRgba32 (guint32);
+ void closeWindow();
+
+protected:
+ Glib::ustring _ckey, _akey;
+ void on_changed (guint32);
+ sigc::connection _changed_connection;
+};
+
+class RegisteredSuffixedInteger : public RegisteredWidget<Scalar> {
+public:
+ ~RegisteredSuffixedInteger() override;
+ RegisteredSuffixedInteger ( const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& suffix,
+ const Glib::ustring& key,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in = nullptr,
+ SPDocument *doc_in = nullptr );
+
+ bool setProgrammatically; // true if the value was set by setValue, not changed by the user;
+ // if a callback checks it, it must reset it back to false
+
+protected:
+ sigc::connection _changed_connection;
+ void on_value_changed();
+};
+
+class RegisteredRadioButtonPair : public RegisteredWidget<Gtk::HBox> {
+public:
+ ~RegisteredRadioButtonPair() override;
+ RegisteredRadioButtonPair ( const Glib::ustring& label,
+ const Glib::ustring& label1,
+ const Glib::ustring& label2,
+ const Glib::ustring& tip1,
+ const Glib::ustring& tip2,
+ const Glib::ustring& key,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in = nullptr,
+ SPDocument *doc_in = nullptr );
+
+ void setValue (bool second);
+
+ bool setProgrammatically; // true if the value was set by setValue, not changed by the user;
+ // if a callback checks it, it must reset it back to false
+protected:
+ Gtk::RadioButton *_rb1, *_rb2;
+ sigc::connection _changed_connection;
+ void on_value_changed();
+};
+
+class RegisteredPoint : public RegisteredWidget<Point> {
+public:
+ ~RegisteredPoint() override;
+ RegisteredPoint ( const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in = nullptr,
+ SPDocument *doc_in = nullptr );
+
+protected:
+ sigc::connection _value_x_changed_connection;
+ sigc::connection _value_y_changed_connection;
+ void on_value_changed();
+};
+
+
+class RegisteredTransformedPoint : public RegisteredWidget<Point> {
+public:
+ ~RegisteredTransformedPoint() override;
+ RegisteredTransformedPoint ( const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in = nullptr,
+ SPDocument *doc_in = nullptr );
+
+ // redefine setValue, because transform must be applied
+ void setValue(Geom::Point const & p);
+
+ void setTransform(Geom::Affine const & canvas_to_svg);
+
+protected:
+ sigc::connection _value_x_changed_connection;
+ sigc::connection _value_y_changed_connection;
+ void on_value_changed();
+
+ Geom::Affine to_svg;
+};
+
+
+class RegisteredVector : public RegisteredWidget<Point> {
+public:
+ ~RegisteredVector() override;
+ RegisteredVector (const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in = nullptr,
+ SPDocument *doc_in = nullptr );
+
+ // redefine setValue, because transform must be applied
+ void setValue(Geom::Point const & p);
+ void setValue(Geom::Point const & p, Geom::Point const & origin);
+
+ /**
+ * Changes the widgets text to polar coordinates. The SVG output will still be a normal carthesian vector.
+ * Careful: when calling getValue(), the return value's X-coord will be the angle, Y-value will be the distance/length.
+ * After changing the coords type (polar/non-polar), the value has to be reset (setValue).
+ */
+ void setPolarCoords(bool polar_coords = true);
+
+protected:
+ sigc::connection _value_x_changed_connection;
+ sigc::connection _value_y_changed_connection;
+ void on_value_changed();
+
+ Geom::Point _origin;
+ bool _polar_coords;
+};
+
+
+class RegisteredRandom : public RegisteredWidget<Random> {
+public:
+ ~RegisteredRandom() override;
+ RegisteredRandom ( const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in = nullptr,
+ SPDocument *doc_in = nullptr);
+
+ void setValue (double val, long startseed);
+
+protected:
+ sigc::connection _value_changed_connection;
+ sigc::connection _reseeded_connection;
+ void on_value_changed();
+};
+
+class RegisteredFontButton : public RegisteredWidget<FontButton> {
+public:
+ ~RegisteredFontButton() override;
+ RegisteredFontButton ( const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in = nullptr,
+ SPDocument *doc_in = nullptr);
+
+ void setValue (Glib::ustring fontspec);
+
+protected:
+ sigc::connection _signal_font_set;
+ void on_value_changed();
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_REGISTERED_WIDGET__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/registry.cpp b/src/ui/widget/registry.cpp
new file mode 100644
index 0000000..2834007
--- /dev/null
+++ b/src/ui/widget/registry.cpp
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ralf Stephan <ralf@ark.in-berlin.de>
+ *
+ * Copyright (C) 2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "registry.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+//===================================================
+
+//---------------------------------------------------
+
+Registry::Registry() : _updating(false) {}
+
+Registry::~Registry() = default;
+
+bool
+Registry::isUpdating()
+{
+ return _updating;
+}
+
+void
+Registry::setUpdating (bool upd)
+{
+ _updating = upd;
+}
+
+//====================================================
+
+
+} // namespace Dialog
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/registry.h b/src/ui/widget/registry.h
new file mode 100644
index 0000000..190aaac
--- /dev/null
+++ b/src/ui/widget/registry.h
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ralf Stephan <ralf@ark.in-berlin.de>
+ *
+ * Copyright (C) 2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef INKSCAPE_UI_WIDGET_REGISTRY__H
+#define INKSCAPE_UI_WIDGET_REGISTRY__H
+
+namespace Gtk {
+ class Object;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class Registry {
+public:
+ Registry();
+ ~Registry();
+
+ bool isUpdating();
+ void setUpdating (bool);
+
+protected:
+ bool _updating;
+};
+
+} // namespace Dialog
+} // namespace UI
+} // namespace Widget
+
+#endif // INKSCAPE_UI_WIDGET_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/rendering-options.cpp b/src/ui/widget/rendering-options.cpp
new file mode 100644
index 0000000..549f494
--- /dev/null
+++ b/src/ui/widget/rendering-options.cpp
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Kees Cook <kees@outflux.net>
+ *
+ * Copyright (C) 2007 Kees Cook
+ * Copyright (C) 2004 Bryce Harrington
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm.h>
+
+#include "preferences.h"
+#include "rendering-options.h"
+#include "util/units.h"
+#include <glibmm/i18n.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+void RenderingOptions::_toggled()
+{
+ _frame_bitmap.set_sensitive(as_bitmap());
+}
+
+RenderingOptions::RenderingOptions () :
+ Gtk::VBox (),
+ _frame_backends ( Glib::ustring(_("Backend")) ),
+ _radio_vector ( Glib::ustring(_("Vector")) ),
+ _radio_bitmap ( Glib::ustring(_("Bitmap")) ),
+ _frame_bitmap ( Glib::ustring(_("Bitmap options")) ),
+ _dpi( _("DPI"),
+ Glib::ustring(_("Preferred resolution of rendering, "
+ "in dots per inch.")),
+ 1,
+ Glib::ustring(""), Glib::ustring(""),
+ false)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ // set up tooltips
+ _radio_vector.set_tooltip_text(
+ _("Render using Cairo vector operations. "
+ "The resulting image is usually smaller in file "
+ "size and can be arbitrarily scaled, but some "
+ "filter effects will not be correctly rendered."));
+ _radio_bitmap.set_tooltip_text(
+ _("Render everything as bitmap. The resulting image "
+ "is usually larger in file size and cannot be "
+ "arbitrarily scaled without quality loss, but all "
+ "objects will be rendered exactly as displayed."));
+
+ set_border_width(2);
+
+ Gtk::RadioButtonGroup group = _radio_vector.get_group ();
+ _radio_bitmap.set_group (group);
+ _radio_bitmap.signal_toggled().connect(sigc::mem_fun(*this, &RenderingOptions::_toggled));
+
+ // default to vector operations
+ if (prefs->getBool("/dialogs/printing/asbitmap", false)) {
+ _radio_bitmap.set_active();
+ } else {
+ _radio_vector.set_active();
+ }
+
+ // configure default DPI
+ _dpi.setRange(Inkscape::Util::Quantity::convert(1, "in", "pt"),2400.0);
+ _dpi.setValue(prefs->getDouble("/dialogs/printing/dpi",
+ Inkscape::Util::Quantity::convert(1, "in", "pt")));
+ _dpi.setIncrements(1.0,10.0);
+ _dpi.setDigits(0);
+ _dpi.update();
+
+ // fill frames
+ Gtk::VBox *box_vector = Gtk::manage( new Gtk::VBox () );
+ box_vector->set_border_width (2);
+ box_vector->add (_radio_vector);
+ box_vector->add (_radio_bitmap);
+ _frame_backends.add (*box_vector);
+
+ Gtk::HBox *box_bitmap = Gtk::manage( new Gtk::HBox () );
+ box_bitmap->set_border_width (2);
+ box_bitmap->add (_dpi);
+ _frame_bitmap.add (*box_bitmap);
+
+ // fill up container
+ add (_frame_backends);
+ add (_frame_bitmap);
+
+ // initialize states
+ _toggled();
+
+ show_all_children ();
+}
+
+bool
+RenderingOptions::as_bitmap ()
+{
+ return _radio_bitmap.get_active();
+}
+
+double
+RenderingOptions::bitmap_dpi ()
+{
+ return _dpi.getValue();
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ 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/rendering-options.h b/src/ui/widget/rendering-options.h
new file mode 100644
index 0000000..2e10ff3
--- /dev/null
+++ b/src/ui/widget/rendering-options.h
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Kees Cook <kees@outflux.net>
+ *
+ * Copyright (C) 2007 Kees Cook
+ * Copyright (C) 2004 Bryce Harrington
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_RENDERING_OPTIONS_H
+#define INKSCAPE_UI_WIDGET_RENDERING_OPTIONS_H
+
+#include "scalar.h"
+
+#include <gtkmm/frame.h>
+#include <gtkmm/radiobutton.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * A container for selecting rendering options.
+ */
+class RenderingOptions : public Gtk::VBox
+{
+public:
+
+ /**
+ * Construct a Rendering Options widget.
+ */
+ RenderingOptions();
+
+ bool as_bitmap(); // should we render as a bitmap?
+ double bitmap_dpi(); // at what DPI should we render the bitmap?
+
+protected:
+ // Radio buttons to select desired rendering
+ Gtk::Frame _frame_backends;
+ Gtk::RadioButton _radio_vector;
+ Gtk::RadioButton _radio_bitmap;
+
+ // Bitmap options
+ Gtk::Frame _frame_bitmap;
+ Scalar _dpi; // DPI of bitmap to render
+
+ // callback for bitmap button
+ void _toggled();
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_RENDERING_OPTIONS_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ 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/rotateable.cpp b/src/ui/widget/rotateable.cpp
new file mode 100644
index 0000000..639f8d1
--- /dev/null
+++ b/src/ui/widget/rotateable.cpp
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * buliabyak@gmail.com
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/box.h>
+#include <gtkmm/eventbox.h>
+#include <2geom/point.h>
+#include "ui/tools/tool-base.h"
+#include "rotateable.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+Rotateable::Rotateable():
+ axis(-M_PI/4),
+ maxdecl(M_PI/4)
+{
+ dragging = false;
+ working = false;
+ scrolling = false;
+ modifier = 0;
+ current_axis = axis;
+
+ signal_button_press_event().connect(sigc::mem_fun(*this, &Rotateable::on_click));
+ signal_motion_notify_event().connect(sigc::mem_fun(*this, &Rotateable::on_motion));
+ signal_button_release_event().connect(sigc::mem_fun(*this, &Rotateable::on_release));
+ gtk_widget_add_events(GTK_WIDGET(gobj()), GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK);
+ signal_scroll_event().connect(sigc::mem_fun(*this, &Rotateable::on_scroll));
+
+}
+
+bool Rotateable::on_click(GdkEventButton *event) {
+ if (event->button == 1) {
+ drag_started_x = event->x;
+ drag_started_y = event->y;
+ modifier = get_single_modifier(modifier, event->state);
+ dragging = true;
+ working = false;
+ current_axis = axis;
+ return true;
+ }
+ return false;
+}
+
+guint Rotateable::get_single_modifier(guint old, guint state) {
+
+ if (old == 0 || old == 3) {
+ if (state & GDK_CONTROL_MASK)
+ return 1; // ctrl
+ if (state & GDK_SHIFT_MASK)
+ return 2; // shift
+ if (state & GDK_MOD1_MASK)
+ return 3; // alt
+ return 0;
+ } else {
+ if (!(state & GDK_CONTROL_MASK) && !(state & GDK_SHIFT_MASK)) {
+ if (state & GDK_MOD1_MASK)
+ return 3; // alt
+ else
+ return 0; // none
+ }
+ if (old == 1) {
+ if (state & GDK_SHIFT_MASK && !(state & GDK_CONTROL_MASK))
+ return 2; // shift
+ if (state & GDK_MOD1_MASK && !(state & GDK_CONTROL_MASK))
+ return 3; // alt
+ return 1;
+ }
+ if (old == 2) {
+ if (state & GDK_CONTROL_MASK && !(state & GDK_SHIFT_MASK))
+ return 1; // ctrl
+ if (state & GDK_MOD1_MASK && !(state & GDK_SHIFT_MASK))
+ return 3; // alt
+ return 2;
+ }
+ return old;
+ }
+}
+
+
+bool Rotateable::on_motion(GdkEventMotion *event) {
+ if (dragging) {
+ double dist = Geom::L2(Geom::Point(event->x, event->y) - Geom::Point(drag_started_x, drag_started_y));
+ double angle = atan2(event->y - drag_started_y, event->x - drag_started_x);
+ if (dist > 20) {
+ working = true;
+ double force = CLAMP (-(angle - current_axis)/maxdecl, -1, 1);
+ if (fabs(force) < 0.002)
+ force = 0; // snap to zero
+ if (modifier != get_single_modifier(modifier, event->state)) {
+ // user has switched modifiers in mid drag, close past drag and start a new
+ // one, redefining axis temporarily
+ do_release(force, modifier);
+ current_axis = angle;
+ modifier = get_single_modifier(modifier, event->state);
+ } else {
+ do_motion(force, modifier);
+ }
+ }
+ Inkscape::UI::Tools::gobble_motion_events(GDK_BUTTON1_MASK);
+ return true;
+ }
+ return false;
+}
+
+
+bool Rotateable::on_release(GdkEventButton *event) {
+ if (dragging && working) {
+ double angle = atan2(event->y - drag_started_y, event->x - drag_started_x);
+ double force = CLAMP(-(angle - current_axis) / maxdecl, -1, 1);
+ if (fabs(force) < 0.002)
+ force = 0; // snap to zero
+ do_release(force, modifier);
+ current_axis = axis;
+ dragging = false;
+ working = false;
+ return true;
+ }
+ dragging = false;
+ working = false;
+ return false;
+}
+
+bool Rotateable::on_scroll(GdkEventScroll* event)
+{
+ double change = 0.0;
+
+ if (event->direction == GDK_SCROLL_UP) {
+ change = 1.0;
+ } else if (event->direction == GDK_SCROLL_DOWN) {
+ change = -1.0;
+ } else if (event->direction == GDK_SCROLL_SMOOTH) {
+ double delta_y_clamped = CLAMP(event->delta_y, -1.0, 1.0); // values > 1 result in excessive changes
+ change = 1.0 * -delta_y_clamped;
+ } else {
+ return FALSE;
+ }
+
+ drag_started_x = event->x;
+ drag_started_y = event->y;
+ modifier = get_single_modifier(modifier, event->state);
+ dragging = false;
+ working = false;
+ scrolling = true;
+ current_axis = axis;
+
+ do_scroll(change, modifier);
+
+ dragging = false;
+ working = false;
+ scrolling = false;
+
+ return TRUE;
+}
+
+Rotateable::~Rotateable() = default;
+
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/rotateable.h b/src/ui/widget/rotateable.h
new file mode 100644
index 0000000..c174a09
--- /dev/null
+++ b/src/ui/widget/rotateable.h
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * buliabyak@gmail.com
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_ROTATEABLE_H
+#define INKSCAPE_UI_ROTATEABLE_H
+
+#include <gtkmm/box.h>
+#include <gtkmm/eventbox.h>
+#include <glibmm/i18n.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * Widget adjustable by dragging it to rotate away from a zero-change axis.
+ */
+class Rotateable: public Gtk::EventBox
+{
+public:
+ Rotateable();
+
+ ~Rotateable() override;
+
+ bool on_click(GdkEventButton *event);
+ bool on_motion(GdkEventMotion *event);
+ bool on_release(GdkEventButton *event);
+ bool on_scroll(GdkEventScroll* event);
+
+ double axis;
+ double current_axis;
+ double maxdecl;
+ bool scrolling;
+
+private:
+ double drag_started_x;
+ double drag_started_y;
+ guint modifier;
+ bool dragging;
+ bool working;
+
+ guint get_single_modifier(guint old, guint state);
+
+ virtual void do_motion (double /*by*/, guint /*state*/) {}
+ virtual void do_release (double /*by*/, guint /*state*/) {}
+ virtual void do_scroll (double /*by*/, guint /*state*/) {}
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_ROTATEABLE_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/scalar-unit.cpp b/src/ui/widget/scalar-unit.cpp
new file mode 100644
index 0000000..2b6d001
--- /dev/null
+++ b/src/ui/widget/scalar-unit.cpp
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Bryce Harrington <bryce@bryceharrington.org>
+ * Derek P. Moore <derekm@hackunix.org>
+ * buliabyak@gmail.com
+ *
+ * Copyright (C) 2004-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "scalar-unit.h"
+#include "spinbutton.h"
+
+using Inkscape::Util::unit_table;
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+ScalarUnit::ScalarUnit(Glib::ustring const &label, Glib::ustring const &tooltip,
+ UnitType unit_type,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ UnitMenu *unit_menu,
+ bool mnemonic)
+ : Scalar(label, tooltip, suffix, icon, mnemonic),
+ _unit_menu(unit_menu),
+ _hundred_percent(0),
+ _absolute_is_increment(false),
+ _percentage_is_increment(false)
+{
+ if (_unit_menu == nullptr) {
+ _unit_menu = new UnitMenu();
+ g_assert(_unit_menu);
+ _unit_menu->setUnitType(unit_type);
+
+ remove(*_widget);
+ Gtk::Box *widget_holder = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 6);
+ widget_holder->pack_start(*_widget, Gtk::PACK_SHRINK);
+ widget_holder->pack_start(*Gtk::manage(_unit_menu), Gtk::PACK_SHRINK);
+ pack_start(*Gtk::manage(widget_holder), Gtk::PACK_SHRINK);
+ }
+ _unit_menu->signal_changed()
+ .connect_notify(sigc::mem_fun(*this, &ScalarUnit::on_unit_changed));
+
+ static_cast<SpinButton*>(_widget)->setUnitMenu(_unit_menu);
+
+ lastUnits = _unit_menu->getUnitAbbr();
+}
+
+ScalarUnit::ScalarUnit(Glib::ustring const &label, Glib::ustring const &tooltip,
+ ScalarUnit &take_unitmenu,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Scalar(label, tooltip, suffix, icon, mnemonic),
+ _unit_menu(take_unitmenu._unit_menu),
+ _hundred_percent(0),
+ _absolute_is_increment(false),
+ _percentage_is_increment(false)
+{
+ _unit_menu->signal_changed()
+ .connect_notify(sigc::mem_fun(*this, &ScalarUnit::on_unit_changed));
+
+ static_cast<SpinButton*>(_widget)->setUnitMenu(_unit_menu);
+
+ lastUnits = _unit_menu->getUnitAbbr();
+}
+
+
+void ScalarUnit::initScalar(double min_value, double max_value)
+{
+ g_assert(_unit_menu != nullptr);
+ Scalar::setDigits(_unit_menu->getDefaultDigits());
+ Scalar::setIncrements(_unit_menu->getDefaultStep(),
+ _unit_menu->getDefaultPage());
+ Scalar::setRange(min_value, max_value);
+}
+
+bool ScalarUnit::setUnit(Glib::ustring const &unit)
+{
+ g_assert(_unit_menu != nullptr);
+ // First set the unit
+ if (!_unit_menu->setUnit(unit)) {
+ return false;
+ }
+ lastUnits = unit;
+ return true;
+}
+
+void ScalarUnit::setUnitType(UnitType unit_type)
+{
+ g_assert(_unit_menu != nullptr);
+ _unit_menu->setUnitType(unit_type);
+ lastUnits = _unit_menu->getUnitAbbr();
+}
+
+void ScalarUnit::resetUnitType(UnitType unit_type)
+{
+ g_assert(_unit_menu != nullptr);
+ _unit_menu->resetUnitType(unit_type);
+ lastUnits = _unit_menu->getUnitAbbr();
+}
+
+Unit const * ScalarUnit::getUnit() const
+{
+ g_assert(_unit_menu != nullptr);
+ return _unit_menu->getUnit();
+}
+
+UnitType ScalarUnit::getUnitType() const
+{
+ g_assert(_unit_menu);
+ return _unit_menu->getUnitType();
+}
+
+void ScalarUnit::setValue(double number, Glib::ustring const &units)
+{
+ g_assert(_unit_menu != nullptr);
+ _unit_menu->setUnit(units);
+ Scalar::setValue(number);
+}
+
+void ScalarUnit::setValueKeepUnit(double number, Glib::ustring const &units)
+{
+ g_assert(_unit_menu != nullptr);
+ if (units == "") {
+ // set the value in the default units
+ Scalar::setValue(number);
+ } else {
+ double conversion = _unit_menu->getConversion(units);
+ Scalar::setValue(number / conversion);
+ }
+}
+
+void ScalarUnit::setValue(double number)
+{
+ Scalar::setValue(number);
+}
+
+double ScalarUnit::getValue(Glib::ustring const &unit_name) const
+{
+ g_assert(_unit_menu != nullptr);
+ if (unit_name == "") {
+ // Return the value in the default units
+ return Scalar::getValue();
+ } else {
+ double conversion = _unit_menu->getConversion(unit_name);
+ return conversion * Scalar::getValue();
+ }
+}
+
+void ScalarUnit::grabFocusAndSelectEntry()
+{
+ _widget->grab_focus();
+ static_cast<SpinButton*>(_widget)->select_region(0, 20);
+}
+
+
+void ScalarUnit::setHundredPercent(double number)
+{
+ _hundred_percent = number;
+}
+
+void ScalarUnit::setAbsoluteIsIncrement(bool value)
+{
+ _absolute_is_increment = value;
+}
+
+void ScalarUnit::setPercentageIsIncrement(bool value)
+{
+ _percentage_is_increment = value;
+}
+
+double ScalarUnit::PercentageToAbsolute(double value)
+{
+ // convert from percent to absolute
+ double convertedVal = 0;
+ double hundred_converted = _hundred_percent / _unit_menu->getConversion("px"); // _hundred_percent is in px
+ if (_percentage_is_increment)
+ value += 100;
+ convertedVal = 0.01 * hundred_converted * value;
+ if (_absolute_is_increment)
+ convertedVal -= hundred_converted;
+
+ return convertedVal;
+}
+
+double ScalarUnit::AbsoluteToPercentage(double value)
+{
+ double convertedVal = 0;
+ // convert from absolute to percent
+ if (_hundred_percent == 0) {
+ if (_percentage_is_increment)
+ convertedVal = 0;
+ else
+ convertedVal = 100;
+ } else {
+ double hundred_converted = _hundred_percent / _unit_menu->getConversion("px", lastUnits); // _hundred_percent is in px
+ if (_absolute_is_increment)
+ value += hundred_converted;
+ convertedVal = 100 * value / hundred_converted;
+ if (_percentage_is_increment)
+ convertedVal -= 100;
+ }
+
+ return convertedVal;
+}
+
+double ScalarUnit::getAsPercentage()
+{
+ double convertedVal = AbsoluteToPercentage(Scalar::getValue());
+ return convertedVal;
+}
+
+
+void ScalarUnit::setFromPercentage(double value)
+{
+ double absolute = PercentageToAbsolute(value);
+ Scalar::setValue(absolute);
+}
+
+
+void ScalarUnit::on_unit_changed()
+{
+ g_assert(_unit_menu != nullptr);
+
+ Glib::ustring abbr = _unit_menu->getUnitAbbr();
+ _suffix->set_label(abbr);
+
+ Inkscape::Util::Unit const *new_unit = unit_table.getUnit(abbr);
+ Inkscape::Util::Unit const *old_unit = unit_table.getUnit(lastUnits);
+
+ double convertedVal = 0;
+ if (old_unit->type == UNIT_TYPE_DIMENSIONLESS && new_unit->type == UNIT_TYPE_LINEAR) {
+ convertedVal = PercentageToAbsolute(Scalar::getValue());
+ } else if (old_unit->type == UNIT_TYPE_LINEAR && new_unit->type == UNIT_TYPE_DIMENSIONLESS) {
+ convertedVal = AbsoluteToPercentage(Scalar::getValue());
+ } else {
+ double conversion = _unit_menu->getConversion(lastUnits);
+ convertedVal = Scalar::getValue() / conversion;
+ }
+ Scalar::setValue(convertedVal);
+
+ lastUnits = abbr;
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/scalar-unit.h b/src/ui/widget/scalar-unit.h
new file mode 100644
index 0000000..e82c41d
--- /dev/null
+++ b/src/ui/widget/scalar-unit.h
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Bryce Harrington <bryce@bryceharrington.org>
+ * Derek P. Moore <derekm@hackunix.org>
+ * buliabyak@gmail.com
+ *
+ * Copyright (C) 2004-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_SCALAR_UNIT_H
+#define INKSCAPE_UI_WIDGET_SCALAR_UNIT_H
+
+#include "scalar.h"
+#include "unit-menu.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * A labelled text box, with spin buttons and optional icon or suffix, for
+ * entering the values of various unit types.
+ *
+ * A ScalarUnit is a control for entering, viewing, or manipulating
+ * numbers with units. This differs from ordinary numbers like 2 or
+ * 3.14 because the number portion of a scalar *only* has meaning
+ * when considered with its unit type. For instance, 12 m and 12 in
+ * have very different actual values, but 1 m and 100 cm have the same
+ * value. The ScalarUnit allows us to abstract the presentation of
+ * the scalar to the user from the internal representations used by
+ * the program.
+ */
+class ScalarUnit : public Scalar
+{
+public:
+ /**
+ * Construct a ScalarUnit.
+ *
+ * @param label Label.
+ * @param unit_type Unit type (defaults to UNIT_TYPE_LINEAR).
+ * @param suffix Suffix, placed after the widget (defaults to "").
+ * @param icon Icon filename, placed before the label (defaults to "").
+ * @param unit_menu UnitMenu drop down; if not specified, one will be created
+ * and displayed after the widget (defaults to NULL).
+ * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to true).
+ */
+ ScalarUnit(Glib::ustring const &label, Glib::ustring const &tooltip,
+ UnitType unit_type = UNIT_TYPE_LINEAR,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ UnitMenu *unit_menu = nullptr,
+ bool mnemonic = true);
+
+ /**
+ * Construct a ScalarUnit.
+ *
+ * @param label Label.
+ * @param tooltip Tooltip text.
+ * @param take_unitmenu Use the unitmenu from this parameter.
+ * @param suffix Suffix, placed after the widget (defaults to "").
+ * @param icon Icon filename, placed before the label (defaults to "").
+ * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to true).
+ */
+ ScalarUnit(Glib::ustring const &label, Glib::ustring const &tooltip,
+ ScalarUnit &take_unitmenu,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ /**
+ * Initializes the scalar based on the settings in _unit_menu.
+ * Requires that _unit_menu has already been initialized.
+ */
+ void initScalar(double min_value, double max_value);
+
+ /**
+ * Gets the object for the currently selected unit.
+ */
+ Unit const * getUnit() const;
+
+ /**
+ * Gets the UnitType ID for the unit.
+ */
+ UnitType getUnitType() const;
+
+ /**
+ * Returns the value in the given unit system.
+ */
+ double getValue(Glib::ustring const &units) const;
+
+ /**
+ * Sets the unit for the ScalarUnit widget.
+ */
+ bool setUnit(Glib::ustring const &units);
+
+ /**
+ * Adds the unit type to the ScalarUnit widget.
+ */
+ void setUnitType(UnitType unit_type);
+
+ /**
+ * Resets the unit type for the ScalarUnit widget.
+ */
+ void resetUnitType(UnitType unit_type);
+
+ /**
+ * Sets the number and unit system.
+ */
+ void setValue(double number, Glib::ustring const &units);
+
+ /**
+ * Convert and sets the number only and keeps the current unit.
+ */
+ void setValueKeepUnit(double number, Glib::ustring const &units);
+
+ /**
+ * Sets the number only.
+ */
+ void setValue(double number);
+
+ /**
+ * Grab focus, and select the text that is in the entry field.
+ */
+ void grabFocusAndSelectEntry();
+
+ void setHundredPercent(double number);
+
+ void setAbsoluteIsIncrement(bool value);
+
+ void setPercentageIsIncrement(bool value);
+
+ /**
+ * Convert value from % to absolute, using _hundred_percent and *_is_increment flags.
+ */
+ double PercentageToAbsolute(double value);
+
+ /**
+ * Convert value from absolute to %, using _hundred_percent and *_is_increment flags.
+ */
+ double AbsoluteToPercentage(double value);
+
+ /**
+ * Assuming the current unit is absolute, get the corresponding % value.
+ */
+ double getAsPercentage();
+
+ /**
+ * Assuming the current unit is absolute, set the value corresponding to a given %.
+ */
+ void setFromPercentage(double value);
+
+ /**
+ * Signal handler for updating the value and suffix label when unit is changed.
+ */
+ void on_unit_changed();
+
+protected:
+ UnitMenu *_unit_menu;
+
+ double _hundred_percent; // the length that corresponds to 100%, in px, for %-to/from-absolute conversions
+
+ bool _absolute_is_increment; // if true, 120% with _hundred_percent=100px gets converted to/from 20px; otherwise, to/from 120px
+ bool _percentage_is_increment; // if true, 120px with _hundred_percent=100px gets converted to/from 20%; otherwise, to/from 120%
+ // if both are true, 20px is converted to/from 20% if _hundred_percent=100px
+
+ Glib::ustring lastUnits; // previously selected unit, for conversions
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_SCALAR_UNIT_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/scalar.cpp b/src/ui/widget/scalar.cpp
new file mode 100644
index 0000000..471de49
--- /dev/null
+++ b/src/ui/widget/scalar.cpp
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Carl Hetherington <inkscape@carlh.net>
+ * Derek P. Moore <derekm@hackunix.org>
+ * Bryce Harrington <bryce@bryceharrington.org>
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ *
+ * Copyright (C) 2004-2011 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "scalar.h"
+#include "spinbutton.h"
+#include <gtkmm/scale.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+Scalar::Scalar(Glib::ustring const &label, Glib::ustring const &tooltip,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Labelled(label, tooltip, new SpinButton(), suffix, icon, mnemonic),
+ setProgrammatically(false)
+{
+}
+
+Scalar::Scalar(Glib::ustring const &label, Glib::ustring const &tooltip,
+ unsigned digits,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Labelled(label, tooltip, new SpinButton(0.0, digits), suffix, icon, mnemonic),
+ setProgrammatically(false)
+{
+}
+
+Scalar::Scalar(Glib::ustring const &label, Glib::ustring const &tooltip,
+ Glib::RefPtr<Gtk::Adjustment> &adjust,
+ unsigned digits,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Labelled(label, tooltip, new SpinButton(adjust, 0.0, digits), suffix, icon, mnemonic),
+ setProgrammatically(false)
+{
+}
+
+unsigned Scalar::getDigits() const
+{
+ g_assert(_widget != nullptr);
+ return static_cast<SpinButton*>(_widget)->get_digits();
+}
+
+double Scalar::getStep() const
+{
+ g_assert(_widget != nullptr);
+ double step, page;
+ static_cast<SpinButton*>(_widget)->get_increments(step, page);
+ return step;
+}
+
+double Scalar::getPage() const
+{
+ g_assert(_widget != nullptr);
+ double step, page;
+ static_cast<SpinButton*>(_widget)->get_increments(step, page);
+ return page;
+}
+
+double Scalar::getRangeMin() const
+{
+ g_assert(_widget != nullptr);
+ double min, max;
+ static_cast<SpinButton*>(_widget)->get_range(min, max);
+ return min;
+}
+
+double Scalar::getRangeMax() const
+{
+ g_assert(_widget != nullptr);
+ double min, max;
+ static_cast<SpinButton*>(_widget)->get_range(min, max);
+ return max;
+}
+
+double Scalar::getValue() const
+{
+ g_assert(_widget != nullptr);
+ return static_cast<SpinButton*>(_widget)->get_value();
+}
+
+int Scalar::getValueAsInt() const
+{
+ g_assert(_widget != nullptr);
+ return static_cast<SpinButton*>(_widget)->get_value_as_int();
+}
+
+
+void Scalar::setDigits(unsigned digits)
+{
+ g_assert(_widget != nullptr);
+ static_cast<SpinButton*>(_widget)->set_digits(digits);
+}
+
+void Scalar::setIncrements(double step, double /*page*/)
+{
+ g_assert(_widget != nullptr);
+ static_cast<SpinButton*>(_widget)->set_increments(step, 0);
+}
+
+void Scalar::setRange(double min, double max)
+{
+ g_assert(_widget != nullptr);
+ static_cast<SpinButton*>(_widget)->set_range(min, max);
+}
+
+void Scalar::setValue(double value, bool setProg)
+{
+ g_assert(_widget != nullptr);
+ if (setProg) {
+ setProgrammatically = true; // callback is supposed to reset back, if it cares
+ }
+ static_cast<SpinButton*>(_widget)->set_value(value);
+}
+
+void Scalar::setWidthChars(unsigned chars)
+{
+ g_assert(_widget != NULL);
+ static_cast<SpinButton*>(_widget)->set_width_chars(chars);
+}
+
+void Scalar::update()
+{
+ g_assert(_widget != nullptr);
+ static_cast<SpinButton*>(_widget)->update();
+}
+
+void Scalar::addSlider()
+{
+ auto scale = new Gtk::Scale(static_cast<SpinButton*>(_widget)->get_adjustment());
+ scale->set_draw_value(false);
+ add (*manage (scale));
+}
+
+Glib::SignalProxy0<void> Scalar::signal_value_changed()
+{
+ return static_cast<SpinButton*>(_widget)->signal_value_changed();
+}
+
+Glib::SignalProxy1<bool, GdkEventButton*> Scalar::signal_button_release_event()
+{
+ return static_cast<SpinButton*>(_widget)->signal_button_release_event();
+}
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/scalar.h b/src/ui/widget/scalar.h
new file mode 100644
index 0000000..29a14d1
--- /dev/null
+++ b/src/ui/widget/scalar.h
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Carl Hetherington <inkscape@carlh.net>
+ * Derek P. Moore <derekm@hackunix.org>
+ * Bryce Harrington <bryce@bryceharrington.org>
+ *
+ * Copyright (C) 2004 Carl Hetherington
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_SCALAR_H
+#define INKSCAPE_UI_WIDGET_SCALAR_H
+
+#include "labelled.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * A labelled text box, with spin buttons and optional
+ * icon or suffix, for entering arbitrary number values.
+ */
+class Scalar : public Labelled
+{
+public:
+ /**
+ * Construct a Scalar Widget.
+ *
+ * @param label Label.
+ * @param suffix Suffix, placed after the widget (defaults to "").
+ * @param icon Icon filename, placed before the label (defaults to "").
+ * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to false).
+ */
+ Scalar(Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ /**
+ * Construct a Scalar Widget.
+ *
+ * @param label Label.
+ * @param digits Number of decimal digits to display.
+ * @param suffix Suffix, placed after the widget (defaults to "").
+ * @param icon Icon filename, placed before the label (defaults to "").
+ * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to false).
+ */
+ Scalar(Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ unsigned digits,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ /**
+ * Construct a Scalar Widget.
+ *
+ * @param label Label.
+ * @param adjust Adjustment to use for the SpinButton.
+ * @param digits Number of decimal digits to display (defaults to 0).
+ * @param suffix Suffix, placed after the widget (defaults to "").
+ * @param icon Icon filename, placed before the label (defaults to "").
+ * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to true).
+ */
+ Scalar(Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ Glib::RefPtr<Gtk::Adjustment> &adjust,
+ unsigned digits = 0,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ /**
+ * Fetches the precision of the spin button.
+ */
+ unsigned getDigits() const;
+
+ /**
+ * Gets the current step increment used by the spin button.
+ */
+ double getStep() const;
+
+ /**
+ * Gets the current page increment used by the spin button.
+ */
+ double getPage() const;
+
+ /**
+ * Gets the minimum range value allowed for the spin button.
+ */
+ double getRangeMin() const;
+
+ /**
+ * Gets the maximum range value allowed for the spin button.
+ */
+ double getRangeMax() const;
+
+ bool getSnapToTicks() const;
+
+ /**
+ * Get the value in the spin_button.
+ */
+ double getValue() const;
+
+ /**
+ * Get the value spin_button represented as an integer.
+ */
+ int getValueAsInt() const;
+
+ /**
+ * Sets the precision to be displayed by the spin button.
+ */
+ void setDigits(unsigned digits);
+
+ /**
+ * Sets the step and page increments for the spin button.
+ * @todo Remove the second parameter - deprecated
+ */
+ void setIncrements(double step, double page);
+
+ /**
+ * Sets the minimum and maximum range allowed for the spin button.
+ */
+ void setRange(double min, double max);
+
+ /**
+ * Sets the value of the spin button.
+ */
+ void setValue(double value, bool setProg = true);
+
+ /**
+ * Sets the width of the spin button by number of characters.
+ */
+ void setWidthChars(unsigned chars);
+
+ /**
+ * Manually forces an update of the spin button.
+ */
+ void update();
+
+ /**
+ * Adds a slider (HScale) to the left of the spinbox.
+ */
+ void addSlider();
+
+ /**
+ * Signal raised when the spin button's value changes.
+ */
+ Glib::SignalProxy0<void> signal_value_changed();
+
+ /**
+ * Signal raised when the spin button's pressed.
+ */
+ Glib::SignalProxy1<bool, GdkEventButton*> signal_button_release_event();
+
+ /**
+ * true if the value was set by setValue, not changed by the user;
+ * if a callback checks it, it must reset it back to false.
+ */
+ bool setProgrammatically;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_SCALAR_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/selected-style.cpp b/src/ui/widget/selected-style.cpp
new file mode 100644
index 0000000..9417a9f
--- /dev/null
+++ b/src/ui/widget/selected-style.cpp
@@ -0,0 +1,1488 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * buliabyak@gmail.com
+ * Abhishek Sharma
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2005 author
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "selected-style.h"
+
+#include <gtkmm/separatormenuitem.h>
+
+
+#include "desktop-style.h"
+#include "document-undo.h"
+#include "gradient-chemistry.h"
+#include "message-context.h"
+#include "selection.h"
+#include "sp-cursor.h"
+
+#include "display/sp-canvas.h"
+
+#include "include/gtkmm_version.h"
+
+#include "object/sp-hatch.h"
+#include "object/sp-linear-gradient.h"
+#include "object/sp-mesh-gradient.h"
+#include "object/sp-namedview.h"
+#include "object/sp-pattern.h"
+#include "object/sp-radial-gradient.h"
+#include "style.h"
+
+#include "ui/pixmaps/cursor-adj-a.xpm"
+#include "ui/pixmaps/cursor-adj-h.xpm"
+#include "ui/pixmaps/cursor-adj-l.xpm"
+#include "ui/pixmaps/cursor-adj-s.xpm"
+
+#include "svg/css-ostringstream.h"
+#include "svg/svg-color.h"
+
+#include "ui/dialog/dialog-manager.h"
+#include "ui/dialog/fill-and-stroke.h"
+#include "ui/dialog/panel-dialog.h"
+#include "ui/tools/tool-base.h"
+#include "ui/widget/color-preview.h"
+
+#include "widgets/ege-paint-def.h"
+#include "widgets/gradient-image.h"
+#include "widgets/spinbutton-events.h"
+#include "widgets/spw-utilities.h"
+#include "widgets/widget-sizes.h"
+
+using Inkscape::Util::unit_table;
+
+static gdouble const _sw_presets[] = { 32 , 16 , 10 , 8 , 6 , 4 , 3 , 2 , 1.5 , 1 , 0.75 , 0.5 , 0.25 , 0.1 };
+static gchar const *const _sw_presets_str[] = {"32", "16", "10", "8", "6", "4", "3", "2", "1.5", "1", "0.75", "0.5", "0.25", "0.1"};
+
+static void
+ss_selection_changed (Inkscape::Selection *, gpointer data)
+{
+ Inkscape::UI::Widget::SelectedStyle *ss = (Inkscape::UI::Widget::SelectedStyle *) data;
+ ss->update();
+}
+
+static void
+ss_selection_modified( Inkscape::Selection *selection, guint flags, gpointer data )
+{
+ // Don't update the style when dragging or doing non-style related changes
+ if (flags & (SP_OBJECT_STYLE_MODIFIED_FLAG)) {
+ ss_selection_changed (selection, data);
+ }
+}
+
+static void
+ss_subselection_changed( gpointer /*dragger*/, gpointer data )
+{
+ ss_selection_changed (nullptr, data);
+}
+
+namespace {
+
+void clearTooltip( Gtk::Widget &widget )
+{
+ widget.set_tooltip_text("");
+ widget.set_has_tooltip(false);
+}
+
+} // namespace
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+
+struct DropTracker {
+ SelectedStyle* parent;
+ int item;
+};
+
+/* Drag and Drop */
+enum ui_drop_target_info {
+ APP_OSWB_COLOR
+};
+
+//TODO: warning: deprecated conversion from string constant to ‘gchar*’
+//
+//Turn out to be warnings that we should probably leave in place. The
+// pointers/types used need to be read-only. So until we correct the using
+// code, those warnings are actually desired. They say "Hey! Fix this". We
+// definitely don't want to hide/ignore them. --JonCruz
+static const GtkTargetEntry ui_drop_target_entries [] = {
+ {g_strdup("application/x-oswb-color"), 0, APP_OSWB_COLOR}
+};
+
+static guint nui_drop_target_entries = G_N_ELEMENTS(ui_drop_target_entries);
+
+/* convenience function */
+static Dialog::FillAndStroke *get_fill_and_stroke_panel(SPDesktop *desktop);
+
+SelectedStyle::SelectedStyle(bool /*layout*/)
+ : current_stroke_width(0)
+ , _sw_unit(nullptr)
+ , _desktop(nullptr)
+ , _table()
+ , _fill_label(_("Fill:"))
+ , _stroke_label(_("Stroke:"))
+ , _opacity_label(_("O:"))
+ , _fill_place(this, SS_FILL)
+ , _stroke_place(this, SS_STROKE)
+ , _fill_flag_place()
+ , _stroke_flag_place()
+ , _opacity_place()
+ , _opacity_adjustment(Gtk::Adjustment::create(100, 0.0, 100, 1.0, 10.0))
+ , _opacity_sb(0.02, 0)
+ , _fill()
+ , _stroke()
+ , _stroke_width_place(this)
+ , _stroke_width("")
+ , _fill_empty_space("")
+ , _opacity_blocked(false)
+{
+ set_name("SelectedStyle");
+ _drop[0] = _drop[1] = nullptr;
+ _dropEnabled[0] = _dropEnabled[1] = false;
+
+ _fill_label.set_halign(Gtk::ALIGN_START);
+ _fill_label.set_valign(Gtk::ALIGN_CENTER);
+ _fill_label.set_margin_top(0);
+ _fill_label.set_margin_bottom(0);
+ _stroke_label.set_halign(Gtk::ALIGN_START);
+ _stroke_label.set_valign(Gtk::ALIGN_CENTER);
+ _stroke_label.set_margin_top(0);
+ _stroke_label.set_margin_bottom(0);
+ _opacity_label.set_halign(Gtk::ALIGN_START);
+ _opacity_label.set_valign(Gtk::ALIGN_CENTER);
+ _opacity_label.set_margin_top(0);
+ _opacity_label.set_margin_bottom(0);
+ _stroke_width.set_name("monoStrokeWidth");
+ _fill_empty_space.set_name("fillEmptySpace");
+
+ _fill_label.set_margin_start(0);
+ _fill_label.set_margin_end(0);
+ _stroke_label.set_margin_start(0);
+ _stroke_label.set_margin_end(0);
+ _opacity_label.set_margin_start(0);
+ _opacity_label.set_margin_end(0);
+
+ _table.set_column_spacing(2);
+ _table.set_row_spacing(0);
+
+ for (int i = SS_FILL; i <= SS_STROKE; i++) {
+
+ _na[i].set_markup (_("N/A"));
+ _na[i].show_all();
+ __na[i] = (_("Nothing selected"));
+
+ if (i == SS_FILL) {
+ _none[i].set_markup (C_("Fill", "<i>None</i>"));
+ } else {
+ _none[i].set_markup (C_("Stroke", "<i>None</i>"));
+ }
+ _none[i].show_all();
+ __none[i] = (i == SS_FILL)? (C_("Fill and stroke", "No fill, middle-click for black fill")) : (C_("Fill and stroke", "No stroke, middle-click for black stroke"));
+
+ _pattern[i].set_markup (_("Pattern"));
+ _pattern[i].show_all();
+ __pattern[i] = (i == SS_FILL)? (_("Pattern fill")) : (_("Pattern stroke"));
+
+ _hatch[i].set_markup(_("Hatch"));
+ _hatch[i].show_all();
+ __hatch[i] = (i == SS_FILL) ? (_("Hatch fill")) : (_("Hatch stroke"));
+
+ _lgradient[i].set_markup (_("<b>L</b>"));
+ _lgradient[i].show_all();
+ __lgradient[i] = (i == SS_FILL)? (_("Linear gradient fill")) : (_("Linear gradient stroke"));
+
+ _gradient_preview_l[i] = GTK_WIDGET(sp_gradient_image_new (nullptr));
+ _gradient_box_l[i].pack_start(_lgradient[i]);
+ _gradient_box_l[i].pack_start(*(Glib::wrap(_gradient_preview_l[i])));
+ _gradient_box_l[i].show_all();
+
+ _rgradient[i].set_markup (_("<b>R</b>"));
+ _rgradient[i].show_all();
+ __rgradient[i] = (i == SS_FILL)? (_("Radial gradient fill")) : (_("Radial gradient stroke"));
+
+ _gradient_preview_r[i] = GTK_WIDGET(sp_gradient_image_new (nullptr));
+ _gradient_box_r[i].pack_start(_rgradient[i]);
+ _gradient_box_r[i].pack_start(*(Glib::wrap(_gradient_preview_r[i])));
+ _gradient_box_r[i].show_all();
+
+#ifdef WITH_MESH
+ _mgradient[i].set_markup (_("<b>M</b>"));
+ _mgradient[i].show_all();
+ __mgradient[i] = (i == SS_FILL)? (_("Mesh gradient fill")) : (_("Mesh gradient stroke"));
+
+ _gradient_preview_m[i] = GTK_WIDGET(sp_gradient_image_new (nullptr));
+ _gradient_box_m[i].pack_start(_mgradient[i]);
+ _gradient_box_m[i].pack_start(*(Glib::wrap(_gradient_preview_m[i])));
+ _gradient_box_m[i].show_all();
+#endif
+
+ _many[i].set_markup (_("Different"));
+ _many[i].show_all();
+ __many[i] = (i == SS_FILL)? (_("Different fills")) : (_("Different strokes"));
+
+ _unset[i].set_markup (_("<b>Unset</b>"));
+ _unset[i].show_all();
+ __unset[i] = (i == SS_FILL)? (_("Unset fill")) : (_("Unset stroke"));
+
+ _color_preview[i] = new Inkscape::UI::Widget::ColorPreview (0);
+ __color[i] = (i == SS_FILL)? (_("Flat color fill")) : (_("Flat color stroke"));
+
+ // TRANSLATORS: A means "Averaged"
+ _averaged[i].set_markup (_("<b>a</b>"));
+ _averaged[i].show_all();
+ __averaged[i] = (i == SS_FILL)? (_("Fill is averaged over selected objects")) : (_("Stroke is averaged over selected objects"));
+
+ // TRANSLATORS: M means "Multiple"
+ _multiple[i].set_markup (_("<b>m</b>"));
+ _multiple[i].show_all();
+ __multiple[i] = (i == SS_FILL)? (_("Multiple selected objects have the same fill")) : (_("Multiple selected objects have the same stroke"));
+
+ _popup_edit[i].add(*(new Gtk::Label((i == SS_FILL)? _("Edit fill...") : _("Edit stroke..."), Gtk::ALIGN_START)));
+ _popup_edit[i].signal_activate().connect(sigc::mem_fun(*this,
+ (i == SS_FILL)? &SelectedStyle::on_fill_edit : &SelectedStyle::on_stroke_edit ));
+
+ _popup_lastused[i].add(*(new Gtk::Label(_("Last set color"), Gtk::ALIGN_START)));
+ _popup_lastused[i].signal_activate().connect(sigc::mem_fun(*this,
+ (i == SS_FILL)? &SelectedStyle::on_fill_lastused : &SelectedStyle::on_stroke_lastused ));
+
+ _popup_lastselected[i].add(*(new Gtk::Label(_("Last selected color"), Gtk::ALIGN_START)));
+ _popup_lastselected[i].signal_activate().connect(sigc::mem_fun(*this,
+ (i == SS_FILL)? &SelectedStyle::on_fill_lastselected : &SelectedStyle::on_stroke_lastselected ));
+
+ _popup_invert[i].add(*(new Gtk::Label(_("Invert"), Gtk::ALIGN_START)));
+ _popup_invert[i].signal_activate().connect(sigc::mem_fun(*this,
+ (i == SS_FILL)? &SelectedStyle::on_fill_invert : &SelectedStyle::on_stroke_invert ));
+
+ _popup_white[i].add(*(new Gtk::Label(_("White"), Gtk::ALIGN_START)));
+ _popup_white[i].signal_activate().connect(sigc::mem_fun(*this,
+ (i == SS_FILL)? &SelectedStyle::on_fill_white : &SelectedStyle::on_stroke_white ));
+
+ _popup_black[i].add(*(new Gtk::Label(_("Black"), Gtk::ALIGN_START)));
+ _popup_black[i].signal_activate().connect(sigc::mem_fun(*this,
+ (i == SS_FILL)? &SelectedStyle::on_fill_black : &SelectedStyle::on_stroke_black ));
+
+ _popup_copy[i].add(*(new Gtk::Label(_("Copy color"), Gtk::ALIGN_START)));
+ _popup_copy[i].signal_activate().connect(sigc::mem_fun(*this,
+ (i == SS_FILL)? &SelectedStyle::on_fill_copy : &SelectedStyle::on_stroke_copy ));
+
+ _popup_paste[i].add(*(new Gtk::Label(_("Paste color"), Gtk::ALIGN_START)));
+ _popup_paste[i].signal_activate().connect(sigc::mem_fun(*this,
+ (i == SS_FILL)? &SelectedStyle::on_fill_paste : &SelectedStyle::on_stroke_paste ));
+
+ _popup_swap[i].add(*(new Gtk::Label(_("Swap fill and stroke"), Gtk::ALIGN_START)));
+ _popup_swap[i].signal_activate().connect(sigc::mem_fun(*this,
+ &SelectedStyle::on_fillstroke_swap));
+
+ _popup_opaque[i].add(*(new Gtk::Label((i == SS_FILL)? _("Make fill opaque") : _("Make stroke opaque"), Gtk::ALIGN_START)));
+ _popup_opaque[i].signal_activate().connect(sigc::mem_fun(*this,
+ (i == SS_FILL)? &SelectedStyle::on_fill_opaque : &SelectedStyle::on_stroke_opaque ));
+
+ //TRANSLATORS COMMENT: unset is a verb here
+ _popup_unset[i].add(*(new Gtk::Label((i == SS_FILL)? _("Unset fill") : _("Unset stroke"), Gtk::ALIGN_START)));
+ _popup_unset[i].signal_activate().connect(sigc::mem_fun(*this,
+ (i == SS_FILL)? &SelectedStyle::on_fill_unset : &SelectedStyle::on_stroke_unset ));
+
+ _popup_remove[i].add(*(new Gtk::Label((i == SS_FILL)? _("Remove fill") : _("Remove stroke"), Gtk::ALIGN_START)));
+ _popup_remove[i].signal_activate().connect(sigc::mem_fun(*this,
+ (i == SS_FILL)? &SelectedStyle::on_fill_remove : &SelectedStyle::on_stroke_remove ));
+
+ _popup[i].attach(_popup_edit[i], 0,1, 0,1);
+ _popup[i].attach(*(new Gtk::SeparatorMenuItem()), 0,1, 1,2);
+ _popup[i].attach(_popup_lastused[i], 0,1, 2,3);
+ _popup[i].attach(_popup_lastselected[i], 0,1, 3,4);
+ _popup[i].attach(*(new Gtk::SeparatorMenuItem()), 0,1, 4,5);
+ _popup[i].attach(_popup_invert[i], 0,1, 5,6);
+ _popup[i].attach(*(new Gtk::SeparatorMenuItem()), 0,1, 6,7);
+ _popup[i].attach(_popup_white[i], 0,1, 7,8);
+ _popup[i].attach(_popup_black[i], 0,1, 8,9);
+ _popup[i].attach(*(new Gtk::SeparatorMenuItem()), 0,1, 9,10);
+ _popup[i].attach(_popup_copy[i], 0,1, 10,11);
+ _popup_copy[i].set_sensitive(false);
+ _popup[i].attach(_popup_paste[i], 0,1, 11,12);
+ _popup[i].attach(_popup_swap[i], 0,1, 12,13);
+ _popup[i].attach(*(new Gtk::SeparatorMenuItem()), 0,1, 13,14);
+ _popup[i].attach(_popup_opaque[i], 0,1, 14,15);
+ _popup[i].attach(_popup_unset[i], 0,1, 15,16);
+ _popup[i].attach(_popup_remove[i], 0,1, 16,17);
+ _popup[i].show_all();
+
+ _mode[i] = SS_NA;
+ }
+
+ {
+ int row = 0;
+
+ Inkscape::Util::UnitTable::UnitMap m = unit_table.units(Inkscape::Util::UNIT_TYPE_LINEAR);
+ Inkscape::Util::UnitTable::UnitMap::iterator iter = m.begin();
+ while(iter != m.end()) {
+ Gtk::RadioMenuItem *mi = Gtk::manage(new Gtk::RadioMenuItem(_sw_group));
+ mi->add(*(new Gtk::Label(iter->first, Gtk::ALIGN_START)));
+ _unit_mis.push_back(mi);
+ Inkscape::Util::Unit const *u = unit_table.getUnit(iter->first);
+ mi->signal_activate().connect(sigc::bind<Inkscape::Util::Unit const *>(sigc::mem_fun(*this, &SelectedStyle::on_popup_units), u));
+ _popup_sw.attach(*mi, 0,1, row, row+1);
+ row++;
+ ++iter;
+ }
+
+ _popup_sw.attach(*(new Gtk::SeparatorMenuItem()), 0,1, row, row+1);
+ row++;
+
+ for (guint i = 0; i < G_N_ELEMENTS(_sw_presets_str); ++i) {
+ Gtk::MenuItem *mi = Gtk::manage(new Gtk::MenuItem());
+ mi->add(*(new Gtk::Label(_sw_presets_str[i], Gtk::ALIGN_START)));
+ mi->signal_activate().connect(sigc::bind<int>(sigc::mem_fun(*this, &SelectedStyle::on_popup_preset), i));
+ _popup_sw.attach(*mi, 0,1, row, row+1);
+ row++;
+ }
+
+ _popup_sw.attach(*(new Gtk::SeparatorMenuItem()), 0,1, row, row+1);
+ row++;
+
+ _popup_sw_remove.add(*(new Gtk::Label(_("Remove"), Gtk::ALIGN_START)));
+ _popup_sw_remove.signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::on_stroke_remove));
+ _popup_sw.attach(_popup_sw_remove, 0,1, row, row+1);
+ row++;
+
+ _popup_sw.show_all();
+ }
+
+ _fill_place.add(_na[SS_FILL]);
+ _fill_place.set_tooltip_text(__na[SS_FILL]);
+ _fill.pack_start(_fill_place, Gtk::PACK_SHRINK);
+ _fill.pack_start(_fill_empty_space, Gtk::PACK_SHRINK);
+
+ _stroke_place.add(_na[SS_STROKE]);
+ _stroke_place.set_tooltip_text(__na[SS_STROKE]);
+
+ _stroke.pack_start(_stroke_place);
+ _stroke_width_place.add(_stroke_width);
+ _stroke.pack_start(_stroke_width_place, Gtk::PACK_SHRINK);
+
+ _opacity_sb.set_adjustment(_opacity_adjustment);
+ _opacity_sb.set_size_request (SELECTED_STYLE_SB_WIDTH, -1);
+ _opacity_sb.set_sensitive (false);
+
+ _table.attach(_fill_label, 0, 0, 1, 1);
+ _table.attach(_stroke_label, 0, 1, 1, 1);
+
+ _table.attach(_fill_flag_place, 1, 0, 1, 1);
+ _table.attach(_stroke_flag_place, 1, 1, 1, 1);
+
+ _table.attach(_fill, 2, 0, 1, 1);
+ _table.attach(_stroke, 2, 1, 1, 1);
+
+ _opacity_place.add(_opacity_label);
+
+ _table.attach(_opacity_place, 4, 0, 1, 2);
+ _table.attach(_opacity_sb, 5, 0, 1, 2);
+
+ pack_start(_table, true, true, 2);
+
+ set_size_request (SELECTED_STYLE_WIDTH, -1);
+
+ _drop[SS_FILL] = new DropTracker();
+ ((DropTracker*)_drop[SS_FILL])->parent = this;
+ ((DropTracker*)_drop[SS_FILL])->item = SS_FILL;
+
+ _drop[SS_STROKE] = new DropTracker();
+ ((DropTracker*)_drop[SS_STROKE])->parent = this;
+ ((DropTracker*)_drop[SS_STROKE])->item = SS_STROKE;
+
+ g_signal_connect(_stroke_place.gobj(),
+ "drag_data_received",
+ G_CALLBACK(dragDataReceived),
+ _drop[SS_STROKE]);
+
+ g_signal_connect(_fill_place.gobj(),
+ "drag_data_received",
+ G_CALLBACK(dragDataReceived),
+ _drop[SS_FILL]);
+
+ _fill_place.signal_button_release_event().connect(sigc::mem_fun(*this, &SelectedStyle::on_fill_click));
+ _stroke_place.signal_button_release_event().connect(sigc::mem_fun(*this, &SelectedStyle::on_stroke_click));
+ _opacity_place.signal_button_press_event().connect(sigc::mem_fun(*this, &SelectedStyle::on_opacity_click));
+ _stroke_width_place.signal_button_press_event().connect(sigc::mem_fun(*this, &SelectedStyle::on_sw_click));
+ _stroke_width_place.signal_button_release_event().connect(sigc::mem_fun(*this, &SelectedStyle::on_sw_click));
+ _opacity_sb.signal_populate_popup().connect(sigc::mem_fun(*this, &SelectedStyle::on_opacity_menu));
+ _opacity_sb.signal_value_changed().connect(sigc::mem_fun(*this, &SelectedStyle::on_opacity_changed));
+ // Connect to key-press to ensure focus is consistent with other spin buttons when using the keys vs mouse-click
+ g_signal_connect (G_OBJECT (_opacity_sb.gobj()), "key-press-event", G_CALLBACK (spinbutton_keypress), _opacity_sb.gobj());
+ g_signal_connect (G_OBJECT (_opacity_sb.gobj()), "focus-in-event", G_CALLBACK (spinbutton_focus_in), _opacity_sb.gobj());
+}
+
+SelectedStyle::~SelectedStyle()
+{
+ selection_changed_connection->disconnect();
+ delete selection_changed_connection;
+ selection_modified_connection->disconnect();
+ delete selection_modified_connection;
+ subselection_changed_connection->disconnect();
+ delete subselection_changed_connection;
+ _unit_mis.clear();
+
+ for (int i = SS_FILL; i <= SS_STROKE; i++) {
+ delete _color_preview[i];
+ // FIXME: do we need this? the destroy methods are not exported
+ //sp_gradient_image_destroy(GTK_OBJECT(_gradient_preview_l[i]));
+ //sp_gradient_image_destroy(GTK_OBJECT(_gradient_preview_r[i]));
+ }
+
+ delete (DropTracker*)_drop[SS_FILL];
+ delete (DropTracker*)_drop[SS_STROKE];
+}
+
+void
+SelectedStyle::setDesktop(SPDesktop *desktop)
+{
+ _desktop = desktop;
+ g_object_set_data (G_OBJECT(_opacity_sb.gobj()), "dtw", _desktop->canvas);
+
+ Inkscape::Selection *selection = desktop->getSelection();
+
+ selection_changed_connection = new sigc::connection (selection->connectChanged(
+ sigc::bind (
+ sigc::ptr_fun(&ss_selection_changed),
+ this )
+ ));
+ selection_modified_connection = new sigc::connection (selection->connectModified(
+ sigc::bind (
+ sigc::ptr_fun(&ss_selection_modified),
+ this )
+ ));
+ subselection_changed_connection = new sigc::connection (desktop->connectToolSubselectionChanged(
+ sigc::bind (
+ sigc::ptr_fun(&ss_subselection_changed),
+ this )
+ ));
+
+ _sw_unit = desktop->getNamedView()->display_units;
+
+ // Set the doc default unit active in the units list
+ for ( auto mi:_unit_mis ) {
+ if (mi && mi->get_label() == _sw_unit->abbr) {
+ mi->set_active();
+ break;
+ }
+ }
+}
+
+void SelectedStyle::dragDataReceived( GtkWidget */*widget*/,
+ GdkDragContext */*drag_context*/,
+ gint /*x*/, gint /*y*/,
+ GtkSelectionData *data,
+ guint /*info*/,
+ guint /*event_time*/,
+ gpointer user_data )
+{
+ DropTracker* tracker = (DropTracker*)user_data;
+
+ // copied from drag-and-drop.cpp, case APP_OSWB_COLOR
+ bool worked = false;
+ Glib::ustring colorspec;
+ if (gtk_selection_data_get_format(data) == 8) {
+ ege::PaintDef color;
+ worked = color.fromMIMEData("application/x-oswb-color",
+ reinterpret_cast<char const *>(gtk_selection_data_get_data(data)),
+ gtk_selection_data_get_length(data),
+ gtk_selection_data_get_format(data));
+ if (worked) {
+ if (color.getType() == ege::PaintDef::CLEAR) {
+ colorspec = ""; // TODO check if this is sufficient
+ } else if (color.getType() == ege::PaintDef::NONE) {
+ colorspec = "none";
+ } else {
+ unsigned int r = color.getR();
+ unsigned int g = color.getG();
+ unsigned int b = color.getB();
+
+ gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b);
+ colorspec = tmp;
+ g_free(tmp);
+ }
+ }
+ }
+ if (worked) {
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ sp_repr_css_set_property(css, (tracker->item == SS_FILL) ? "fill":"stroke", colorspec.c_str());
+
+ sp_desktop_set_style(tracker->parent->_desktop, css);
+ sp_repr_css_attr_unref(css);
+ DocumentUndo::done(tracker->parent->_desktop->getDocument(), SP_VERB_NONE, _("Drop color"));
+ }
+}
+
+void SelectedStyle::on_fill_remove() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ sp_repr_css_set_property (css, "fill", "none");
+ sp_desktop_set_style (_desktop, css, true, true);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Remove fill"));
+}
+
+void SelectedStyle::on_stroke_remove() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ sp_repr_css_set_property (css, "stroke", "none");
+ sp_desktop_set_style (_desktop, css, true, true);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Remove stroke"));
+}
+
+void SelectedStyle::on_fill_unset() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ sp_repr_css_unset_property (css, "fill");
+ sp_desktop_set_style (_desktop, css, true, true);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Unset fill"));
+}
+
+void SelectedStyle::on_stroke_unset() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ sp_repr_css_unset_property (css, "stroke");
+ sp_repr_css_unset_property (css, "stroke-opacity");
+ sp_repr_css_unset_property (css, "stroke-width");
+ sp_repr_css_unset_property (css, "stroke-miterlimit");
+ sp_repr_css_unset_property (css, "stroke-linejoin");
+ sp_repr_css_unset_property (css, "stroke-linecap");
+ sp_repr_css_unset_property (css, "stroke-dashoffset");
+ sp_repr_css_unset_property (css, "stroke-dasharray");
+ sp_desktop_set_style (_desktop, css, true, true);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Unset stroke"));
+}
+
+void SelectedStyle::on_fill_opaque() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ sp_repr_css_set_property (css, "fill-opacity", "1");
+ sp_desktop_set_style (_desktop, css, true);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Make fill opaque"));
+}
+
+void SelectedStyle::on_stroke_opaque() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ sp_repr_css_set_property (css, "stroke-opacity", "1");
+ sp_desktop_set_style (_desktop, css, true);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Make fill opaque"));
+}
+
+void SelectedStyle::on_fill_lastused() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ guint32 color = sp_desktop_get_color(_desktop, true);
+ gchar c[64];
+ sp_svg_write_color (c, sizeof(c), color);
+ sp_repr_css_set_property (css, "fill", c);
+ sp_desktop_set_style (_desktop, css);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Apply last set color to fill"));
+}
+
+void SelectedStyle::on_stroke_lastused() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ guint32 color = sp_desktop_get_color(_desktop, false);
+ gchar c[64];
+ sp_svg_write_color (c, sizeof(c), color);
+ sp_repr_css_set_property (css, "stroke", c);
+ sp_desktop_set_style (_desktop, css);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Apply last set color to stroke"));
+}
+
+void SelectedStyle::on_fill_lastselected() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ gchar c[64];
+ sp_svg_write_color (c, sizeof(c), _lastselected[SS_FILL]);
+ sp_repr_css_set_property (css, "fill", c);
+ sp_desktop_set_style (_desktop, css);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Apply last selected color to fill"));
+}
+
+void SelectedStyle::on_stroke_lastselected() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ gchar c[64];
+ sp_svg_write_color (c, sizeof(c), _lastselected[SS_STROKE]);
+ sp_repr_css_set_property (css, "stroke", c);
+ sp_desktop_set_style (_desktop, css);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Apply last selected color to stroke"));
+}
+
+void SelectedStyle::on_fill_invert() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ guint32 color = _thisselected[SS_FILL];
+ gchar c[64];
+ if (_mode[SS_FILL] == SS_LGRADIENT || _mode[SS_FILL] == SS_RGRADIENT) {
+ sp_gradient_invert_selected_gradients(_desktop, Inkscape::FOR_FILL);
+ return;
+
+ }
+
+ if (_mode[SS_FILL] != SS_COLOR) return;
+ sp_svg_write_color (c, sizeof(c),
+ SP_RGBA32_U_COMPOSE(
+ (255 - SP_RGBA32_R_U(color)),
+ (255 - SP_RGBA32_G_U(color)),
+ (255 - SP_RGBA32_B_U(color)),
+ SP_RGBA32_A_U(color)
+ )
+ );
+ sp_repr_css_set_property (css, "fill", c);
+ sp_desktop_set_style (_desktop, css);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Invert fill"));
+}
+
+void SelectedStyle::on_stroke_invert() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ guint32 color = _thisselected[SS_STROKE];
+ gchar c[64];
+ if (_mode[SS_STROKE] == SS_LGRADIENT || _mode[SS_STROKE] == SS_RGRADIENT) {
+ sp_gradient_invert_selected_gradients(_desktop, Inkscape::FOR_STROKE);
+ return;
+ }
+ if (_mode[SS_STROKE] != SS_COLOR) return;
+ sp_svg_write_color (c, sizeof(c),
+ SP_RGBA32_U_COMPOSE(
+ (255 - SP_RGBA32_R_U(color)),
+ (255 - SP_RGBA32_G_U(color)),
+ (255 - SP_RGBA32_B_U(color)),
+ SP_RGBA32_A_U(color)
+ )
+ );
+ sp_repr_css_set_property (css, "stroke", c);
+ sp_desktop_set_style (_desktop, css);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Invert stroke"));
+}
+
+void SelectedStyle::on_fill_white() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ gchar c[64];
+ sp_svg_write_color (c, sizeof(c), 0xffffffff);
+ sp_repr_css_set_property (css, "fill", c);
+ sp_repr_css_set_property (css, "fill-opacity", "1");
+ sp_desktop_set_style (_desktop, css);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("White fill"));
+}
+
+void SelectedStyle::on_stroke_white() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ gchar c[64];
+ sp_svg_write_color (c, sizeof(c), 0xffffffff);
+ sp_repr_css_set_property (css, "stroke", c);
+ sp_repr_css_set_property (css, "stroke-opacity", "1");
+ sp_desktop_set_style (_desktop, css);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("White stroke"));
+}
+
+void SelectedStyle::on_fill_black() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ gchar c[64];
+ sp_svg_write_color (c, sizeof(c), 0x000000ff);
+ sp_repr_css_set_property (css, "fill", c);
+ sp_repr_css_set_property (css, "fill-opacity", "1.0");
+ sp_desktop_set_style (_desktop, css);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Black fill"));
+}
+
+void SelectedStyle::on_stroke_black() {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ gchar c[64];
+ sp_svg_write_color (c, sizeof(c), 0x000000ff);
+ sp_repr_css_set_property (css, "stroke", c);
+ sp_repr_css_set_property (css, "stroke-opacity", "1.0");
+ sp_desktop_set_style (_desktop, css);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Black stroke"));
+}
+
+void SelectedStyle::on_fill_copy() {
+ if (_mode[SS_FILL] == SS_COLOR) {
+ gchar c[64];
+ sp_svg_write_color (c, sizeof(c), _thisselected[SS_FILL]);
+ Glib::ustring text;
+ text += c;
+ if (!text.empty()) {
+ Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
+ refClipboard->set_text(text);
+ }
+ }
+}
+
+void SelectedStyle::on_stroke_copy() {
+ if (_mode[SS_STROKE] == SS_COLOR) {
+ gchar c[64];
+ sp_svg_write_color (c, sizeof(c), _thisselected[SS_STROKE]);
+ Glib::ustring text;
+ text += c;
+ if (!text.empty()) {
+ Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
+ refClipboard->set_text(text);
+ }
+ }
+}
+
+void SelectedStyle::on_fill_paste() {
+ Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
+ Glib::ustring const text = refClipboard->wait_for_text();
+
+ if (!text.empty()) {
+ guint32 color = sp_svg_read_color(text.c_str(), 0x000000ff); // impossible value, as SVG color cannot have opacity
+ if (color == 0x000000ff) // failed to parse color string
+ return;
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ sp_repr_css_set_property (css, "fill", text.c_str());
+ sp_desktop_set_style (_desktop, css);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Paste fill"));
+ }
+}
+
+void SelectedStyle::on_stroke_paste() {
+ Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
+ Glib::ustring const text = refClipboard->wait_for_text();
+
+ if (!text.empty()) {
+ guint32 color = sp_svg_read_color(text.c_str(), 0x000000ff); // impossible value, as SVG color cannot have opacity
+ if (color == 0x000000ff) // failed to parse color string
+ return;
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ sp_repr_css_set_property (css, "stroke", text.c_str());
+ sp_desktop_set_style (_desktop, css);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Paste stroke"));
+ }
+}
+
+void SelectedStyle::on_fillstroke_swap() {
+ _desktop->getSelection()->swapFillStroke();
+}
+
+void SelectedStyle::on_fill_edit() {
+ if (Dialog::FillAndStroke *fs = get_fill_and_stroke_panel(_desktop))
+ fs->showPageFill();
+}
+
+void SelectedStyle::on_stroke_edit() {
+ if (Dialog::FillAndStroke *fs = get_fill_and_stroke_panel(_desktop))
+ fs->showPageStrokePaint();
+}
+
+bool
+SelectedStyle::on_fill_click(GdkEventButton *event)
+{
+ if (event->button == 1) { // click, open fill&stroke
+
+ if (Dialog::FillAndStroke *fs = get_fill_and_stroke_panel(_desktop))
+ fs->showPageFill();
+
+ } else if (event->button == 3) { // right-click, popup menu
+ _popup[SS_FILL].popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
+ } else if (event->button == 2) { // middle click, toggle none/lastcolor
+ if (_mode[SS_FILL] == SS_NONE) {
+ on_fill_lastused();
+ } else {
+ on_fill_remove();
+ }
+ }
+ return true;
+}
+
+bool
+SelectedStyle::on_stroke_click(GdkEventButton *event)
+{
+ if (event->button == 1) { // click, open fill&stroke
+ if (Dialog::FillAndStroke *fs = get_fill_and_stroke_panel(_desktop))
+ fs->showPageStrokePaint();
+ } else if (event->button == 3) { // right-click, popup menu
+ _popup[SS_STROKE].popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
+ } else if (event->button == 2) { // middle click, toggle none/lastcolor
+ if (_mode[SS_STROKE] == SS_NONE) {
+ on_stroke_lastused();
+ } else {
+ on_stroke_remove();
+ }
+ }
+ return true;
+}
+
+bool
+SelectedStyle::on_sw_click(GdkEventButton *event)
+{
+ if (event->button == 1) { // click, open fill&stroke
+ if (Dialog::FillAndStroke *fs = get_fill_and_stroke_panel(_desktop))
+ fs->showPageStrokeStyle();
+ } else if (event->button == 3) { // right-click, popup menu
+ _popup_sw.popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
+ } else if (event->button == 2) { // middle click, toggle none/lastwidth?
+ //
+ }
+ return true;
+}
+
+bool
+SelectedStyle::on_opacity_click(GdkEventButton *event)
+{
+ if (event->button == 2) { // middle click
+ const char* opacity = _opacity_sb.get_value() < 50? "0.5" : (_opacity_sb.get_value() == 100? "0" : "1");
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ sp_repr_css_set_property (css, "opacity", opacity);
+ sp_desktop_set_style (_desktop, css);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
+ _("Change opacity"));
+ return true;
+ }
+
+ return false;
+}
+
+void SelectedStyle::on_popup_units(Inkscape::Util::Unit const *unit) {
+ _sw_unit = unit;
+ update();
+}
+
+void SelectedStyle::on_popup_preset(int i) {
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ gdouble w;
+ if (_sw_unit) {
+ w = Inkscape::Util::Quantity::convert(_sw_presets[i], _sw_unit, "px");
+ } else {
+ w = _sw_presets[i];
+ }
+ Inkscape::CSSOStringStream os;
+ os << w;
+ sp_repr_css_set_property (css, "stroke-width", os.str().c_str());
+ // FIXME: update dash patterns!
+ sp_desktop_set_style (_desktop, css, true);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_SWATCHES,
+ _("Change stroke width"));
+}
+
+void
+SelectedStyle::update()
+{
+ if (_desktop == nullptr)
+ return;
+
+ // create temporary style
+ SPStyle query(_desktop->getDocument());
+
+ for (int i = SS_FILL; i <= SS_STROKE; i++) {
+ Gtk::EventBox *place = (i == SS_FILL)? &_fill_place : &_stroke_place;
+ Gtk::EventBox *flag_place = (i == SS_FILL)? &_fill_flag_place : &_stroke_flag_place;
+
+ place->remove();
+ flag_place->remove();
+
+ clearTooltip(*place);
+ clearTooltip(*flag_place);
+
+ _mode[i] = SS_NA;
+ _paintserver_id[i].clear();
+
+ _popup_copy[i].set_sensitive(false);
+
+ // query style from desktop. This returns a result flag and fills query with the style of subselection, if any, or selection
+ int result = sp_desktop_query_style (_desktop, &query,
+ (i == SS_FILL)? QUERY_STYLE_PROPERTY_FILL : QUERY_STYLE_PROPERTY_STROKE);
+ switch (result) {
+ case QUERY_STYLE_NOTHING:
+ place->add(_na[i]);
+ place->set_tooltip_text(__na[i]);
+ _mode[i] = SS_NA;
+ if ( _dropEnabled[i] ) {
+ gtk_drag_dest_unset( GTK_WIDGET((i==SS_FILL) ? _fill_place.gobj():_stroke_place.gobj()) );
+ _dropEnabled[i] = false;
+ }
+ break;
+ case QUERY_STYLE_SINGLE:
+ case QUERY_STYLE_MULTIPLE_AVERAGED:
+ case QUERY_STYLE_MULTIPLE_SAME:
+ if ( !_dropEnabled[i] ) {
+ gtk_drag_dest_set( GTK_WIDGET( (i==SS_FILL) ? _fill_place.gobj():_stroke_place.gobj()),
+ GTK_DEST_DEFAULT_ALL,
+ ui_drop_target_entries,
+ nui_drop_target_entries,
+ GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE) );
+ _dropEnabled[i] = true;
+ }
+ SPIPaint *paint;
+ if (i == SS_FILL) {
+ paint = &(query.fill);
+ } else {
+ paint = &(query.stroke);
+ }
+ if (paint->set && paint->isPaintserver()) {
+ SPPaintServer *server = (i == SS_FILL)? SP_STYLE_FILL_SERVER (&query) : SP_STYLE_STROKE_SERVER (&query);
+ if ( server ) {
+ Inkscape::XML::Node *srepr = server->getRepr();
+ _paintserver_id[i] += "url(#";
+ _paintserver_id[i] += srepr->attribute("id");
+ _paintserver_id[i] += ")";
+
+ if (SP_IS_LINEARGRADIENT(server)) {
+ SPGradient *vector = SP_GRADIENT(server)->getVector();
+ sp_gradient_image_set_gradient(SP_GRADIENT_IMAGE(_gradient_preview_l[i]), vector);
+ place->add(_gradient_box_l[i]);
+ place->set_tooltip_text(__lgradient[i]);
+ _mode[i] = SS_LGRADIENT;
+ } else if (SP_IS_RADIALGRADIENT(server)) {
+ SPGradient *vector = SP_GRADIENT(server)->getVector();
+ sp_gradient_image_set_gradient(SP_GRADIENT_IMAGE(_gradient_preview_r[i]), vector);
+ place->add(_gradient_box_r[i]);
+ place->set_tooltip_text(__rgradient[i]);
+ _mode[i] = SS_RGRADIENT;
+#ifdef WITH_MESH
+ } else if (SP_IS_MESHGRADIENT(server)) {
+ SPGradient *array = SP_GRADIENT(server)->getArray();
+ sp_gradient_image_set_gradient(SP_GRADIENT_IMAGE(_gradient_preview_m[i]), array);
+ place->add(_gradient_box_m[i]);
+ place->set_tooltip_text(__mgradient[i]);
+ _mode[i] = SS_MGRADIENT;
+#endif
+ } else if (SP_IS_PATTERN(server)) {
+ place->add(_pattern[i]);
+ place->set_tooltip_text(__pattern[i]);
+ _mode[i] = SS_PATTERN;
+ } else if (SP_IS_HATCH(server)) {
+ place->add(_hatch[i]);
+ place->set_tooltip_text(__hatch[i]);
+ _mode[i] = SS_HATCH;
+ }
+ } else {
+ g_warning ("file %s: line %d: Unknown paint server", __FILE__, __LINE__);
+ }
+ } else if (paint->set && paint->isColor()) {
+ guint32 color = paint->value.color.toRGBA32(
+ SP_SCALE24_TO_FLOAT ((i == SS_FILL)? query.fill_opacity.value : query.stroke_opacity.value));
+ _lastselected[i] = _thisselected[i];
+ _thisselected[i] = color; // include opacity
+ ((Inkscape::UI::Widget::ColorPreview*)_color_preview[i])->setRgba32 (color);
+ _color_preview[i]->show_all();
+ place->add(*_color_preview[i]);
+ gchar c_string[64];
+ g_snprintf (c_string, 64, "%06x/%.3g", color >> 8, SP_RGBA32_A_F(color));
+ place->set_tooltip_text(__color[i] + ": " + c_string + _(", drag to adjust, middle-click to remove"));
+ _mode[i] = SS_COLOR;
+ _popup_copy[i].set_sensitive(true);
+
+ } else if (paint->set && paint->isNone()) {
+ place->add(_none[i]);
+ place->set_tooltip_text(__none[i]);
+ _mode[i] = SS_NONE;
+ } else if (!paint->set) {
+ place->add(_unset[i]);
+ place->set_tooltip_text(__unset[i]);
+ _mode[i] = SS_UNSET;
+ }
+ if (result == QUERY_STYLE_MULTIPLE_AVERAGED) {
+ flag_place->add(_averaged[i]);
+ flag_place->set_tooltip_text(__averaged[i]);
+ } else if (result == QUERY_STYLE_MULTIPLE_SAME) {
+ flag_place->add(_multiple[i]);
+ flag_place->set_tooltip_text(__multiple[i]);
+ }
+ break;
+ case QUERY_STYLE_MULTIPLE_DIFFERENT:
+ place->add(_many[i]);
+ place->set_tooltip_text(__many[i]);
+ _mode[i] = SS_MANY;
+ break;
+ default:
+ break;
+ }
+ }
+
+// Now query opacity
+ clearTooltip(_opacity_place);
+ clearTooltip(_opacity_sb);
+
+ int result = sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_MASTEROPACITY);
+
+ switch (result) {
+ case QUERY_STYLE_NOTHING:
+ _opacity_place.set_tooltip_text(_("Nothing selected"));
+ _opacity_sb.set_tooltip_text(_("Nothing selected"));
+ _opacity_sb.set_sensitive(false);
+ break;
+ case QUERY_STYLE_SINGLE:
+ case QUERY_STYLE_MULTIPLE_AVERAGED:
+ case QUERY_STYLE_MULTIPLE_SAME:
+ _opacity_place.set_tooltip_text(_("Opacity (%)"));
+ _opacity_sb.set_tooltip_text(_("Opacity (%)"));
+ if (_opacity_blocked) break;
+ _opacity_blocked = true;
+ _opacity_sb.set_sensitive(true);
+ _opacity_adjustment->set_value(SP_SCALE24_TO_FLOAT(query.opacity.value) * 100);
+ _opacity_blocked = false;
+ break;
+ }
+
+// Now query stroke_width
+ int result_sw = sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_STROKEWIDTH);
+ switch (result_sw) {
+ case QUERY_STYLE_NOTHING:
+ _stroke_width.set_markup("");
+ current_stroke_width = 0;
+ break;
+ case QUERY_STYLE_SINGLE:
+ case QUERY_STYLE_MULTIPLE_AVERAGED:
+ case QUERY_STYLE_MULTIPLE_SAME:
+ {
+ double w;
+ if (_sw_unit) {
+ w = Inkscape::Util::Quantity::convert(query.stroke_width.computed, "px", _sw_unit);
+ } else {
+ w = query.stroke_width.computed;
+ }
+ current_stroke_width = w;
+
+ {
+ gchar *str = g_strdup_printf(" %#.3g", w);
+ if (str[strlen(str) - 1] == ',' || str[strlen(str) - 1] == '.') {
+ str[strlen(str)-1] = '\0';
+ }
+ _stroke_width.set_markup(str);
+ g_free (str);
+ }
+ {
+ gchar *str = g_strdup_printf(_("Stroke width: %.5g%s%s"),
+ w,
+ _sw_unit? _sw_unit->abbr.c_str() : "px",
+ (result_sw == QUERY_STYLE_MULTIPLE_AVERAGED)?
+ _(" (averaged)") : "");
+ _stroke_width_place.set_tooltip_text(str);
+ g_free (str);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void SelectedStyle::opacity_0() {_opacity_sb.set_value(0);}
+void SelectedStyle::opacity_025() {_opacity_sb.set_value(25);}
+void SelectedStyle::opacity_05() {_opacity_sb.set_value(50);}
+void SelectedStyle::opacity_075() {_opacity_sb.set_value(75);}
+void SelectedStyle::opacity_1() {_opacity_sb.set_value(100);}
+
+void SelectedStyle::on_opacity_menu (Gtk::Menu *menu) {
+
+ Glib::ListHandle<Gtk::Widget *> children = menu->get_children();
+ for (auto iter : children) {
+ menu->remove(*iter);
+ }
+
+ {
+ Gtk::MenuItem *item = new Gtk::MenuItem;
+ item->add(*(new Gtk::Label(_("0 (transparent)"), Gtk::ALIGN_START, Gtk::ALIGN_START)));
+ item->signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::opacity_0 ));
+ menu->add(*item);
+ }
+ {
+ Gtk::MenuItem *item = new Gtk::MenuItem;
+ item->add(*(new Gtk::Label("25%", Gtk::ALIGN_START, Gtk::ALIGN_START)));
+ item->signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::opacity_025 ));
+ menu->add(*item);
+ }
+ {
+ Gtk::MenuItem *item = new Gtk::MenuItem;
+ item->add(*(new Gtk::Label("50%", Gtk::ALIGN_START, Gtk::ALIGN_START)));
+ item->signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::opacity_05 ));
+ menu->add(*item);
+ }
+ {
+ Gtk::MenuItem *item = new Gtk::MenuItem;
+ item->add(*(new Gtk::Label("75%", Gtk::ALIGN_START, Gtk::ALIGN_START)));
+ item->signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::opacity_075 ));
+ menu->add(*item);
+ }
+ {
+ Gtk::MenuItem *item = new Gtk::MenuItem;
+ item->add(*(new Gtk::Label(_("100% (opaque)"), Gtk::ALIGN_START, Gtk::ALIGN_START)));
+ item->signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::opacity_1 ));
+ menu->add(*item);
+ }
+
+ menu->show_all();
+}
+
+void SelectedStyle::on_opacity_changed ()
+{
+ g_return_if_fail(_desktop); // TODO this shouldn't happen!
+ if (_opacity_blocked)
+ return;
+ _opacity_blocked = true;
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ Inkscape::CSSOStringStream os;
+ os << CLAMP ((_opacity_adjustment->get_value() / 100), 0.0, 1.0);
+ sp_repr_css_set_property (css, "opacity", os.str().c_str());
+ // FIXME: workaround for GTK breakage: display interruptibility sometimes results in GTK
+ // sending multiple value-changed events. As if when Inkscape interrupts redraw for main loop
+ // iterations, GTK discovers that this callback hasn't finished yet, and for some weird reason
+ // decides to add yet another value-changed event to the queue. Totally braindead if you ask
+ // me. As a result, scrolling the spinbutton once results in runaway change until it hits 1.0
+ // or 0.0. (And no, this is not a race with ::update, I checked that.)
+ // Sigh. So we disable interruptibility while we're setting the new value.
+ _desktop->getCanvas()->forceFullRedrawAfterInterruptions(0);
+ sp_desktop_set_style (_desktop, css);
+ sp_repr_css_attr_unref (css);
+ DocumentUndo::maybeDone(_desktop->getDocument(), "fillstroke:opacity", SP_VERB_DIALOG_FILL_STROKE,
+ _("Change opacity"));
+ // resume interruptibility
+ _desktop->getCanvas()->endForcedFullRedraws();
+ // spinbutton_defocus(GTK_WIDGET(_opacity_sb.gobj()));
+ _opacity_blocked = false;
+}
+
+/* ============================================= RotateableSwatch */
+
+RotateableSwatch::RotateableSwatch(SelectedStyle *parent, guint mode) :
+ fillstroke(mode),
+ parent(parent),
+ startcolor(0),
+ startcolor_set(false),
+ undokey("ssrot1"),
+ cr(nullptr),
+ cr_set(false)
+
+{
+}
+
+RotateableSwatch::~RotateableSwatch() = default;
+
+double
+RotateableSwatch::color_adjust(float *hsla, double by, guint32 cc, guint modifier)
+{
+ SPColor::rgb_to_hsl_floatv (hsla, SP_RGBA32_R_F(cc), SP_RGBA32_G_F(cc), SP_RGBA32_B_F(cc));
+ hsla[3] = SP_RGBA32_A_F(cc);
+ double diff = 0;
+ if (modifier == 2) { // saturation
+ double old = hsla[1];
+ if (by > 0) {
+ hsla[1] += by * (1 - hsla[1]);
+ } else {
+ hsla[1] += by * (hsla[1]);
+ }
+ diff = hsla[1] - old;
+ } else if (modifier == 1) { // lightness
+ double old = hsla[2];
+ if (by > 0) {
+ hsla[2] += by * (1 - hsla[2]);
+ } else {
+ hsla[2] += by * (hsla[2]);
+ }
+ diff = hsla[2] - old;
+ } else if (modifier == 3) { // alpha
+ double old = hsla[3];
+ hsla[3] += by/2;
+ if (hsla[3] < 0) {
+ hsla[3] = 0;
+ } else if (hsla[3] > 1) {
+ hsla[3] = 1;
+ }
+ diff = hsla[3] - old;
+ } else { // hue
+ double old = hsla[0];
+ hsla[0] += by/2;
+ while (hsla[0] < 0)
+ hsla[0] += 1;
+ while (hsla[0] > 1)
+ hsla[0] -= 1;
+ diff = hsla[0] - old;
+ }
+
+ float rgb[3];
+ SPColor::hsl_to_rgb_floatv (rgb, hsla[0], hsla[1], hsla[2]);
+
+ gchar c[64];
+ sp_svg_write_color (c, sizeof(c),
+ SP_RGBA32_U_COMPOSE(
+ (SP_COLOR_F_TO_U(rgb[0])),
+ (SP_COLOR_F_TO_U(rgb[1])),
+ (SP_COLOR_F_TO_U(rgb[2])),
+ 0xff
+ )
+ );
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+
+ if (modifier == 3) { // alpha
+ Inkscape::CSSOStringStream osalpha;
+ osalpha << hsla[3];
+ sp_repr_css_set_property(css, (fillstroke == SS_FILL) ? "fill-opacity" : "stroke-opacity", osalpha.str().c_str());
+ } else {
+ sp_repr_css_set_property (css, (fillstroke == SS_FILL) ? "fill" : "stroke", c);
+ }
+ sp_desktop_set_style (parent->getDesktop(), css);
+ sp_repr_css_attr_unref (css);
+ return diff;
+}
+
+void
+RotateableSwatch::do_motion(double by, guint modifier) {
+ if (parent->_mode[fillstroke] != SS_COLOR)
+ return;
+
+ if (!scrolling && !cr_set) {
+ GtkWidget *w = GTK_WIDGET(gobj());
+ GdkPixbuf *pixbuf = nullptr;
+
+ if (modifier == 2) { // saturation
+ pixbuf = gdk_pixbuf_new_from_xpm_data((const gchar **)cursor_adj_s_xpm);
+ } else if (modifier == 1) { // lightness
+ pixbuf = gdk_pixbuf_new_from_xpm_data((const gchar **)cursor_adj_l_xpm);
+ } else if (modifier == 3) { // alpha
+ pixbuf = gdk_pixbuf_new_from_xpm_data((const gchar **)cursor_adj_a_xpm);
+ } else { // hue
+ pixbuf = gdk_pixbuf_new_from_xpm_data((const gchar **)cursor_adj_h_xpm);
+ }
+
+ if (pixbuf != nullptr) {
+ cr = gdk_cursor_new_from_pixbuf(gdk_display_get_default(), pixbuf, 16, 16);
+ g_object_unref(pixbuf);
+ gdk_window_set_cursor(gtk_widget_get_window(w), cr);
+ g_object_unref(cr);
+ cr = nullptr;
+ cr_set = true;
+ }
+ }
+
+ guint32 cc;
+ if (!startcolor_set) {
+ cc = startcolor = parent->_thisselected[fillstroke];
+ startcolor_set = true;
+ } else {
+ cc = startcolor;
+ }
+
+ float hsla[4];
+ double diff = 0;
+
+ diff = color_adjust(hsla, by, cc, modifier);
+
+ if (modifier == 3) { // alpha
+ DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey,
+ SP_VERB_DIALOG_FILL_STROKE, (_("Adjust alpha")));
+ double ch = hsla[3];
+ parent->getDesktop()->event_context->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Adjusting <b>alpha</b>: was %.3g, now <b>%.3g</b> (diff %.3g); with <b>Ctrl</b> to adjust lightness, with <b>Shift</b> to adjust saturation, without modifiers to adjust hue"), ch - diff, ch, diff);
+
+ } else if (modifier == 2) { // saturation
+ DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey,
+ SP_VERB_DIALOG_FILL_STROKE, (_("Adjust saturation")));
+ double ch = hsla[1];
+ parent->getDesktop()->event_context->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Adjusting <b>saturation</b>: was %.3g, now <b>%.3g</b> (diff %.3g); with <b>Ctrl</b> to adjust lightness, with <b>Alt</b> to adjust alpha, without modifiers to adjust hue"), ch - diff, ch, diff);
+
+ } else if (modifier == 1) { // lightness
+ DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey,
+ SP_VERB_DIALOG_FILL_STROKE, (_("Adjust lightness")));
+ double ch = hsla[2];
+ parent->getDesktop()->event_context->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Adjusting <b>lightness</b>: was %.3g, now <b>%.3g</b> (diff %.3g); with <b>Shift</b> to adjust saturation, with <b>Alt</b> to adjust alpha, without modifiers to adjust hue"), ch - diff, ch, diff);
+
+ } else { // hue
+ DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey,
+ SP_VERB_DIALOG_FILL_STROKE, (_("Adjust hue")));
+ double ch = hsla[0];
+ parent->getDesktop()->event_context->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Adjusting <b>hue</b>: was %.3g, now <b>%.3g</b> (diff %.3g); with <b>Shift</b> to adjust saturation, with <b>Alt</b> to adjust alpha, with <b>Ctrl</b> to adjust lightness"), ch - diff, ch, diff);
+ }
+}
+
+
+void
+RotateableSwatch::do_scroll(double by, guint modifier) {
+ do_motion(by/30.0, modifier);
+ do_release(by/30.0, modifier);
+}
+
+void
+RotateableSwatch::do_release(double by, guint modifier) {
+ if (parent->_mode[fillstroke] != SS_COLOR)
+ return;
+
+ float hsla[4];
+ color_adjust(hsla, by, startcolor, modifier);
+
+ if (cr_set) {
+ GtkWidget *w = GTK_WIDGET(gobj());
+ gdk_window_set_cursor(gtk_widget_get_window(w), nullptr);
+ if (cr) {
+ g_object_unref(cr);
+ cr = nullptr;
+ }
+ cr_set = false;
+ }
+
+ if (modifier == 3) { // alpha
+ DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey,
+ SP_VERB_DIALOG_FILL_STROKE, ("Adjust alpha"));
+ } else if (modifier == 2) { // saturation
+ DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey,
+ SP_VERB_DIALOG_FILL_STROKE, ("Adjust saturation"));
+
+ } else if (modifier == 1) { // lightness
+ DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey,
+ SP_VERB_DIALOG_FILL_STROKE, ("Adjust lightness"));
+
+ } else { // hue
+ DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey,
+ SP_VERB_DIALOG_FILL_STROKE, ("Adjust hue"));
+ }
+
+ if (!strcmp(undokey, "ssrot1")) {
+ undokey = "ssrot2";
+ } else {
+ undokey = "ssrot1";
+ }
+
+ parent->getDesktop()->event_context->message_context->clear();
+ startcolor_set = false;
+}
+
+/* ============================================= RotateableStrokeWidth */
+
+RotateableStrokeWidth::RotateableStrokeWidth(SelectedStyle *parent) :
+ parent(parent),
+ startvalue(0),
+ startvalue_set(false),
+ undokey("swrot1")
+{
+}
+
+RotateableStrokeWidth::~RotateableStrokeWidth() = default;
+
+double
+RotateableStrokeWidth::value_adjust(double current, double by, guint /*modifier*/, bool final)
+{
+ double newval;
+ // by is -1..1
+ double max_f = 50; // maximum width is (current * max_f), minimum - zero
+ newval = current * (std::exp(std::log(max_f-1) * (by+1)) - 1) / (max_f-2);
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ if (final && newval < 1e-6) {
+ // if dragged into zero and this is the final adjust on mouse release, delete stroke;
+ // if it's not final, leave it a chance to increase again (which is not possible with "none")
+ sp_repr_css_set_property (css, "stroke", "none");
+ } else {
+ newval = Inkscape::Util::Quantity::convert(newval, parent->_sw_unit, "px");
+ Inkscape::CSSOStringStream os;
+ os << newval;
+ sp_repr_css_set_property (css, "stroke-width", os.str().c_str());
+ }
+
+ sp_desktop_set_style (parent->getDesktop(), css);
+ sp_repr_css_attr_unref (css);
+ return newval - current;
+}
+
+void
+RotateableStrokeWidth::do_motion(double by, guint modifier) {
+
+ // if this is the first motion after a mouse grab, remember the current width
+ if (!startvalue_set) {
+ startvalue = parent->current_stroke_width;
+ // if it's 0, adjusting (which uses multiplication) will not be able to change it, so we
+ // cheat and provide a non-zero value
+ if (startvalue == 0)
+ startvalue = 1;
+ startvalue_set = true;
+ }
+
+ if (modifier == 3) { // Alt, do nothing
+ } else {
+ double diff = value_adjust(startvalue, by, modifier, false);
+ DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey,
+ SP_VERB_DIALOG_FILL_STROKE, (_("Adjust stroke width")));
+ parent->getDesktop()->event_context->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Adjusting <b>stroke width</b>: was %.3g, now <b>%.3g</b> (diff %.3g)"), startvalue, startvalue + diff, diff);
+ }
+}
+
+void
+RotateableStrokeWidth::do_release(double by, guint modifier) {
+
+ if (modifier == 3) { // do nothing
+
+ } else {
+ value_adjust(startvalue, by, modifier, true);
+ startvalue_set = false;
+ DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey,
+ SP_VERB_DIALOG_FILL_STROKE, (_("Adjust stroke width")));
+ }
+
+ if (!strcmp(undokey, "swrot1")) {
+ undokey = "swrot2";
+ } else {
+ undokey = "swrot1";
+ }
+ parent->getDesktop()->event_context->message_context->clear();
+}
+
+void
+RotateableStrokeWidth::do_scroll(double by, guint modifier) {
+ do_motion(by/10.0, modifier);
+ startvalue_set = false;
+}
+
+Dialog::FillAndStroke *get_fill_and_stroke_panel(SPDesktop *desktop)
+{
+ if (Dialog::PanelDialogBase *panel_dialog =
+ dynamic_cast<Dialog::PanelDialogBase *>(desktop->_dlg_mgr->getDialog("FillAndStroke"))) {
+ try {
+ Dialog::FillAndStroke &fill_and_stroke =
+ dynamic_cast<Dialog::FillAndStroke &>(panel_dialog->getPanel());
+ return &fill_and_stroke;
+ } catch (std::exception &e) { }
+ }
+
+ return nullptr;
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/selected-style.h b/src/ui/widget/selected-style.h
new file mode 100644
index 0000000..388f802
--- /dev/null
+++ b/src/ui/widget/selected-style.h
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * buliabyak@gmail.com
+ * scislac@users.sf.net
+ *
+ * Copyright (C) 2005 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_CURRENT_STYLE_H
+#define INKSCAPE_UI_CURRENT_STYLE_H
+
+#include <gtkmm/box.h>
+#include <gtkmm/grid.h>
+
+#include <gtkmm/label.h>
+#include <gtkmm/eventbox.h>
+#include <gtkmm/enums.h>
+#include <gtkmm/menu.h>
+#include <gtkmm/menuitem.h>
+#include <gtkmm/adjustment.h>
+#include <gtkmm/radiobuttongroup.h>
+#include <gtkmm/radiomenuitem.h>
+#include "ui/widget/spinbutton.h"
+
+#include <cstddef>
+#include <sigc++/sigc++.h>
+
+#include "rotateable.h"
+
+class SPDesktop;
+
+namespace Inkscape {
+
+namespace Util {
+ class Unit;
+}
+
+namespace UI {
+namespace Widget {
+
+enum {
+ SS_NA,
+ SS_NONE,
+ SS_UNSET,
+ SS_PATTERN,
+ SS_LGRADIENT,
+ SS_RGRADIENT,
+#ifdef WITH_MESH
+ SS_MGRADIENT,
+#endif
+ SS_MANY,
+ SS_COLOR,
+ SS_HATCH
+};
+
+enum {
+ SS_FILL,
+ SS_STROKE
+};
+
+class SelectedStyle;
+
+class RotateableSwatch : public Rotateable {
+ public:
+ RotateableSwatch(SelectedStyle *parent, guint mode);
+ ~RotateableSwatch() override;
+
+ double color_adjust (float *hsl, double by, guint32 cc, guint state);
+
+ void do_motion (double by, guint state) override;
+ void do_release (double by, guint state) override;
+ void do_scroll (double by, guint state) override;
+
+private:
+ guint fillstroke;
+
+ SelectedStyle *parent;
+
+ guint32 startcolor;
+ bool startcolor_set;
+
+ gchar const *undokey;
+
+ GdkCursor *cr;
+ bool cr_set;
+};
+
+class RotateableStrokeWidth : public Rotateable {
+ public:
+ RotateableStrokeWidth(SelectedStyle *parent);
+ ~RotateableStrokeWidth() override;
+
+ double value_adjust(double current, double by, guint modifier, bool final);
+ void do_motion (double by, guint state) override;
+ void do_release (double by, guint state) override;
+ void do_scroll (double by, guint state) override;
+
+private:
+ SelectedStyle *parent;
+
+ double startvalue;
+ bool startvalue_set;
+
+ gchar const *undokey;
+};
+
+/**
+ * Selected style indicator (fill, stroke, opacity).
+ */
+class SelectedStyle : public Gtk::HBox
+{
+public:
+ SelectedStyle(bool layout = true);
+
+ ~SelectedStyle() override;
+
+ void setDesktop(SPDesktop *desktop);
+ SPDesktop *getDesktop() {return _desktop;}
+ void update();
+
+ guint32 _lastselected[2];
+ guint32 _thisselected[2];
+
+ guint _mode[2];
+
+ double current_stroke_width;
+ Inkscape::Util::Unit const *_sw_unit; // points to object in UnitTable, do not delete
+
+protected:
+ SPDesktop *_desktop;
+
+ Gtk::Grid _table;
+
+ Gtk::Label _fill_label;
+ Gtk::Label _stroke_label;
+ Gtk::Label _opacity_label;
+
+ RotateableSwatch _fill_place;
+ RotateableSwatch _stroke_place;
+
+ Gtk::EventBox _fill_flag_place;
+ Gtk::EventBox _stroke_flag_place;
+
+ Gtk::EventBox _opacity_place;
+ Glib::RefPtr<Gtk::Adjustment> _opacity_adjustment;
+ Inkscape::UI::Widget::SpinButton _opacity_sb;
+
+ Gtk::Label _na[2];
+ Glib::ustring __na[2];
+
+ Gtk::Label _none[2];
+ Glib::ustring __none[2];
+
+ Gtk::Label _pattern[2];
+ Glib::ustring __pattern[2];
+
+ Gtk::Label _hatch[2];
+ Glib::ustring __hatch[2];
+
+ Gtk::Label _lgradient[2];
+ Glib::ustring __lgradient[2];
+
+ GtkWidget *_gradient_preview_l[2];
+ Gtk::HBox _gradient_box_l[2];
+
+ Gtk::Label _rgradient[2];
+ Glib::ustring __rgradient[2];
+
+ GtkWidget *_gradient_preview_r[2];
+ Gtk::HBox _gradient_box_r[2];
+
+#ifdef WITH_MESH
+ Gtk::Label _mgradient[2];
+ Glib::ustring __mgradient[2];
+
+ GtkWidget *_gradient_preview_m[2];
+ Gtk::HBox _gradient_box_m[2];
+#endif
+
+ Gtk::Label _many[2];
+ Glib::ustring __many[2];
+
+ Gtk::Label _unset[2];
+ Glib::ustring __unset[2];
+
+ Gtk::Widget *_color_preview[2];
+ Glib::ustring __color[2];
+
+ Gtk::Label _averaged[2];
+ Glib::ustring __averaged[2];
+ Gtk::Label _multiple[2];
+ Glib::ustring __multiple[2];
+
+ Gtk::HBox _fill;
+ Gtk::HBox _stroke;
+ RotateableStrokeWidth _stroke_width_place;
+ Gtk::Label _stroke_width;
+ Gtk::Label _fill_empty_space;
+
+ Glib::ustring _paintserver_id[2];
+
+ sigc::connection *selection_changed_connection;
+ sigc::connection *selection_modified_connection;
+ sigc::connection *subselection_changed_connection;
+
+ static void dragDataReceived( GtkWidget *widget,
+ GdkDragContext *drag_context,
+ gint x, gint y,
+ GtkSelectionData *data,
+ guint info,
+ guint event_time,
+ gpointer user_data );
+
+ bool on_fill_click(GdkEventButton *event);
+ bool on_stroke_click(GdkEventButton *event);
+ bool on_opacity_click(GdkEventButton *event);
+ bool on_sw_click(GdkEventButton *event);
+
+ bool _opacity_blocked;
+ void on_opacity_changed();
+ void on_opacity_menu(Gtk::Menu *menu);
+ void opacity_0();
+ void opacity_025();
+ void opacity_05();
+ void opacity_075();
+ void opacity_1();
+
+ void on_fill_remove();
+ void on_stroke_remove();
+ void on_fill_lastused();
+ void on_stroke_lastused();
+ void on_fill_lastselected();
+ void on_stroke_lastselected();
+ void on_fill_unset();
+ void on_stroke_unset();
+ void on_fill_edit();
+ void on_stroke_edit();
+ void on_fillstroke_swap();
+ void on_fill_invert();
+ void on_stroke_invert();
+ void on_fill_white();
+ void on_stroke_white();
+ void on_fill_black();
+ void on_stroke_black();
+ void on_fill_copy();
+ void on_stroke_copy();
+ void on_fill_paste();
+ void on_stroke_paste();
+ void on_fill_opaque();
+ void on_stroke_opaque();
+
+ Gtk::Menu _popup[2];
+ Gtk::MenuItem _popup_edit[2];
+ Gtk::MenuItem _popup_lastused[2];
+ Gtk::MenuItem _popup_lastselected[2];
+ Gtk::MenuItem _popup_invert[2];
+ Gtk::MenuItem _popup_white[2];
+ Gtk::MenuItem _popup_black[2];
+ Gtk::MenuItem _popup_copy[2];
+ Gtk::MenuItem _popup_paste[2];
+ Gtk::MenuItem _popup_swap[2];
+ Gtk::MenuItem _popup_opaque[2];
+ Gtk::MenuItem _popup_unset[2];
+ Gtk::MenuItem _popup_remove[2];
+
+ Gtk::Menu _popup_sw;
+ Gtk::RadioButtonGroup _sw_group;
+ std::vector<Gtk::RadioMenuItem*> _unit_mis;
+ void on_popup_units(Inkscape::Util::Unit const *u);
+ void on_popup_preset(int i);
+ Gtk::MenuItem _popup_sw_remove;
+
+ void *_drop[2];
+ bool _dropEnabled[2];
+};
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_BUTTON_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/spin-button-tool-item.cpp b/src/ui/widget/spin-button-tool-item.cpp
new file mode 100644
index 0000000..b283939
--- /dev/null
+++ b/src/ui/widget/spin-button-tool-item.cpp
@@ -0,0 +1,532 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "spin-button-tool-item.h"
+
+#include <gtkmm/box.h>
+#include <gtkmm/image.h>
+#include <gtkmm/radiomenuitem.h>
+#include <gtkmm/toolbar.h>
+
+#include <utility>
+
+#include "spinbutton.h"
+#include "ui/icon-loader.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * \brief Handler for the button's "focus-in-event" signal
+ *
+ * \param focus_event The event that triggered the signal
+ *
+ * \detail This just logs the current value of the spin-button
+ * and sets the _transfer_focus flag
+ */
+bool
+SpinButtonToolItem::on_btn_focus_in_event(GdkEventFocus * /* focus_event */)
+{
+ _last_val = _btn->get_value();
+ _transfer_focus = true;
+
+ return false; // Event not consumed
+}
+
+/**
+ * \brief Handler for the button's "focus-out-event" signal
+ *
+ * \param focus_event The event that triggered the signal
+ *
+ * \detail This just unsets the _transfer_focus flag
+ */
+bool
+SpinButtonToolItem::on_btn_focus_out_event(GdkEventFocus * /* focus_event */)
+{
+ _transfer_focus = false;
+
+ return false; // Event not consumed
+}
+
+/**
+ * \brief Handler for the button's "key-press-event" signal
+ *
+ * \param key_event The event that triggered the signal
+ *
+ * \detail If the ESC key was pressed, restore the last value and defocus.
+ * If the Enter key was pressed, just defocus.
+ */
+bool
+SpinButtonToolItem::on_btn_key_press_event(GdkEventKey *key_event)
+{
+ bool was_consumed = false; // Whether event has been consumed or not
+ auto display = Gdk::Display::get_default();
+ auto keymap = display->get_keymap();
+ guint key = 0;
+ gdk_keymap_translate_keyboard_state(keymap, key_event->hardware_keycode,
+ static_cast<GdkModifierType>(key_event->state),
+ 0, &key, 0, 0, 0);
+
+ auto val = _btn->get_value();
+
+ switch(key) {
+ case GDK_KEY_Escape:
+ {
+ _transfer_focus = true;
+ _btn->set_value(_last_val);
+ defocus();
+ was_consumed = true;
+ }
+ break;
+
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ {
+ _transfer_focus = true;
+ defocus();
+ was_consumed = true;
+ }
+ break;
+
+ case GDK_KEY_Tab:
+ {
+ _transfer_focus = false;
+ was_consumed = process_tab(1);
+ }
+ break;
+
+ case GDK_KEY_ISO_Left_Tab:
+ {
+ _transfer_focus = false;
+ was_consumed = process_tab(-1);
+ }
+ break;
+
+ // TODO: Enable variable step-size if this is ever used
+ case GDK_KEY_Up:
+ case GDK_KEY_KP_Up:
+ {
+ _transfer_focus = false;
+ _btn->set_value(val+1);
+ was_consumed=true;
+ }
+ break;
+
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Down:
+ {
+ _transfer_focus = false;
+ _btn->set_value(val-1);
+ was_consumed=true;
+ }
+ break;
+
+ case GDK_KEY_Page_Up:
+ case GDK_KEY_KP_Page_Up:
+ {
+ _transfer_focus = false;
+ _btn->set_value(val+10);
+ was_consumed=true;
+ }
+ break;
+
+ case GDK_KEY_Page_Down:
+ case GDK_KEY_KP_Page_Down:
+ {
+ _transfer_focus = false;
+ _btn->set_value(val-10);
+ was_consumed=true;
+ }
+ break;
+
+ case GDK_KEY_z:
+ case GDK_KEY_Z:
+ {
+ _transfer_focus = false;
+ _btn->set_value(_last_val);
+ was_consumed = true;
+ }
+ break;
+ }
+
+ return was_consumed;
+}
+
+/**
+ * \brief Shift focus to a different widget
+ *
+ * \details This only has an effect if the _transfer_focus flag and the _focus_widget are set
+ */
+void
+SpinButtonToolItem::defocus()
+{
+ if(_transfer_focus && _focus_widget) {
+ _focus_widget->grab_focus();
+ }
+}
+
+/**
+ * \brief Move focus to another spinbutton in the toolbar
+ *
+ * \param increment[in] The number of places to shift within the toolbar
+ */
+bool
+SpinButtonToolItem::process_tab(int increment)
+{
+ // If the increment is zero, do nothing
+ if(increment == 0) return true;
+
+ // Here, we're working through the widget hierarchy:
+ // Toolbar
+ // |- ToolItem (*this)
+ // |-> Box
+ // |-> SpinButton (*_btn)
+ //
+ // Our aim is to find the next/previous spin-button within a toolitem in our toolbar
+
+ bool handled = false;
+
+ // We only bother doing this if the current item is actually in a toolbar!
+ auto toolbar = dynamic_cast<Gtk::Toolbar *>(get_parent());
+
+ if (toolbar) {
+ // Get the index of the current item within the toolbar and the total number of items
+ auto my_index = toolbar->get_item_index(*this);
+ auto n_items = toolbar->get_n_items();
+
+ auto test_index = my_index + increment; // The index of the item we want to check
+
+ // Loop through tool items as long as we're within the bounds of the toolbar and
+ // we haven't yet found our new item to focus on
+ while(test_index > 0 && test_index <= n_items && !handled) {
+
+ auto tool_item = toolbar->get_nth_item(test_index);
+
+ if(tool_item) {
+ // There are now two options that we support:
+ if(dynamic_cast<SpinButtonToolItem *>(tool_item)) {
+ // (1) The tool item is a SpinButtonToolItem, in which case, we just pass
+ // focus to its spin-button
+ dynamic_cast<SpinButtonToolItem *>(tool_item)->grab_button_focus();
+ handled = true;
+ }
+ else if(dynamic_cast<Gtk::SpinButton *>(tool_item->get_child())) {
+ // (2) The tool item contains a plain Gtk::SpinButton, in which case we
+ // pass focus directly to it
+ tool_item->get_child()->grab_focus();
+ }
+ }
+
+ test_index += increment;
+ }
+ }
+
+ return handled;
+}
+
+/**
+ * \brief Handler for toggle events on numeric menu items
+ *
+ * \details Sets the adjustment to the desired value
+ */
+void
+SpinButtonToolItem::on_numeric_menu_item_toggled(double value)
+{
+ auto adj = _btn->get_adjustment();
+ adj->set_value(value);
+}
+
+Gtk::RadioMenuItem *
+SpinButtonToolItem::create_numeric_menu_item(Gtk::RadioButtonGroup *group,
+ double value,
+ const Glib::ustring& label)
+{
+ // Represent the value as a string
+ std::ostringstream ss;
+ ss << value;
+
+ // Append the label if specified
+ if (!label.empty()) {
+ ss << ": " << label;
+ }
+
+ auto numeric_option = Gtk::manage(new Gtk::RadioMenuItem(*group, ss.str()));
+
+ // Set the adjustment value in response to changes in the selected item
+ auto toggled_handler = sigc::bind(sigc::mem_fun(*this, &SpinButtonToolItem::on_numeric_menu_item_toggled), value);
+ numeric_option->signal_toggled().connect(toggled_handler);
+
+ return numeric_option;
+}
+
+/**
+ * \brief Create a menu containing fixed numeric options for the adjustment
+ *
+ * \details Each of these values represents a snap-point for the adjustment's value
+ */
+Gtk::Menu *
+SpinButtonToolItem::create_numeric_menu()
+{
+ auto numeric_menu = Gtk::manage(new Gtk::Menu());
+
+ Gtk::RadioMenuItem::Group group;
+
+ // Get values for the adjustment
+ auto adj = _btn->get_adjustment();
+ auto adj_value = adj->get_value();
+ auto lower = adj->get_lower();
+ auto upper = adj->get_upper();
+ auto step = adj->get_step_increment();
+ auto page = adj->get_page_increment();
+
+ auto digits = _btn->get_digits();
+
+ // A number a little smaller than the smallest increment that can be
+ // displayed in the spinbutton entry.
+ //
+ // For example, if digits = 0, we are displaying integers only and
+ // epsilon = 0.9 * 10^-0 = 0.9
+ //
+ // For digits = 1, we get epsilon = 0.9 * 10^-1 = 0.09
+ // For digits = 2, we get epsilon = 0.9 * 10^-2 = 0.009 etc...
+ auto epsilon = 0.9 * pow(10.0, -float(digits));
+
+ // Start by setting some fixed values based on the adjustment's
+ // parameters.
+ NumericMenuData values;
+ values.push_back(std::make_pair(upper, ""));
+ values.push_back(std::make_pair(adj_value + page, ""));
+ values.push_back(std::make_pair(adj_value + step, ""));
+ values.push_back(std::make_pair(adj_value, ""));
+ values.push_back(std::make_pair(adj_value - step, ""));
+ values.push_back(std::make_pair(adj_value - page, ""));
+ values.push_back(std::make_pair(lower, ""));
+
+ // Now add any custom items
+ for (auto custom_data : _custom_menu_data) {
+ values.push_back(custom_data);
+ }
+
+ // Sort the numeric menu items into reverse numerical order (largest at top of menu)
+ std::sort (begin(values), end(values));
+ std::reverse(begin(values), end(values));
+
+ for (auto value : values)
+ {
+ auto numeric_menu_item = create_numeric_menu_item(&group, value.first, value.second);
+ numeric_menu->append(*numeric_menu_item);
+
+ if (fabs(adj_value - value.first) < epsilon) {
+ // If the adjustment value is very close to the value of this menu item,
+ // make this menu item active
+ numeric_menu_item->set_active();
+ }
+ }
+
+ return numeric_menu;
+}
+
+/**
+ * \brief Create a menu-item in response to the "create-menu-proxy" signal
+ *
+ * \detail This is an override for the default Gtk::ToolItem handler so
+ * we don't need to explicitly connect this to the signal. It
+ * runs if the toolitem is unable to fit on the toolbar, and
+ * must be represented by a menu item instead.
+ */
+bool
+SpinButtonToolItem::on_create_menu_proxy()
+{
+ // The main menu-item. It just contains the label that normally appears
+ // next to the spin-button, and an indicator for a sub-menu.
+ auto menu_item = Gtk::manage(new Gtk::MenuItem(_label_text));
+ auto numeric_menu = create_numeric_menu();
+ menu_item->set_submenu(*numeric_menu);
+
+ set_proxy_menu_item(_name, *menu_item);
+
+ return true; // Finished handling the event
+}
+
+/**
+ * \brief Create a new SpinButtonToolItem
+ *
+ * \param[in] name A unique ID for this tool-item (not translatable)
+ * \param[in] label_text The text to display in the toolbar
+ * \param[in] adjustment The Gtk::Adjustment to attach to the spinbutton
+ * \param[in] climb_rate The climb rate for the spin button (default = 0)
+ * \param[in] digits Number of decimal places to display
+ */
+SpinButtonToolItem::SpinButtonToolItem(const Glib::ustring name,
+ const Glib::ustring& label_text,
+ Glib::RefPtr<Gtk::Adjustment>& adjustment,
+ double climb_rate,
+ int digits)
+ : _btn(Gtk::manage(new SpinButton(adjustment, climb_rate, digits))),
+ _name(std::move(name)),
+ _label_text(label_text),
+ _last_val(0.0),
+ _transfer_focus(false),
+ _focus_widget(nullptr)
+{
+ set_margin_start(3);
+ set_margin_end(3);
+ set_name(_name);
+
+ // Handle popup menu
+ _btn->signal_popup_menu().connect(sigc::mem_fun(*this, &SpinButtonToolItem::on_popup_menu), false);
+
+ // Handle button events
+ auto btn_focus_in_event_cb = sigc::mem_fun(*this, &SpinButtonToolItem::on_btn_focus_in_event);
+ _btn->signal_focus_in_event().connect(btn_focus_in_event_cb, false);
+
+ auto btn_focus_out_event_cb = sigc::mem_fun(*this, &SpinButtonToolItem::on_btn_focus_out_event);
+ _btn->signal_focus_out_event().connect(btn_focus_out_event_cb, false);
+
+ auto btn_key_press_event_cb = sigc::mem_fun(*this, &SpinButtonToolItem::on_btn_key_press_event);
+ _btn->signal_key_press_event().connect(btn_key_press_event_cb, false);
+
+ auto btn_button_press_event_cb = sigc::mem_fun(*this, &SpinButtonToolItem::on_btn_button_press_event);
+ _btn->signal_button_press_event().connect(btn_button_press_event_cb, false);
+
+ _btn->add_events(Gdk::KEY_PRESS_MASK);
+
+ // Create a label
+ _label = Gtk::manage(new Gtk::Label(label_text));
+
+ // Arrange the widgets in a horizontal box
+ _hbox = Gtk::manage(new Gtk::Box());
+ _hbox->set_spacing(3);
+ _hbox->pack_start(*_label);
+ _hbox->pack_start(*_btn);
+ add(*_hbox);
+ show_all();
+}
+
+void
+SpinButtonToolItem::set_icon(const Glib::ustring& icon_name)
+{
+ _hbox->remove(*_label);
+ _icon = Gtk::manage(sp_get_icon_image(icon_name, Gtk::ICON_SIZE_SMALL_TOOLBAR));
+
+ if(_icon) {
+ _hbox->pack_start(*_icon);
+ _hbox->reorder_child(*_icon, 0);
+ }
+
+ show_all();
+}
+
+bool
+SpinButtonToolItem::on_btn_button_press_event(const GdkEventButton *button_event)
+{
+ if (gdk_event_triggers_context_menu(reinterpret_cast<const GdkEvent *>(button_event)) &&
+ button_event->type == GDK_BUTTON_PRESS) {
+ do_popup_menu(button_event);
+ return true;
+ }
+
+ return false;
+}
+
+void
+SpinButtonToolItem::do_popup_menu(const GdkEventButton *button_event)
+{
+ auto menu = create_numeric_menu();
+ menu->attach_to_widget(*_btn);
+ menu->show_all();
+ menu->popup_at_pointer(reinterpret_cast<const GdkEvent *>(button_event));
+}
+
+/**
+ * \brief Create a popup menu
+ */
+bool
+SpinButtonToolItem::on_popup_menu()
+{
+ do_popup_menu(nullptr);
+ return true;
+}
+
+/**
+ * \brief Transfers focus to the child spinbutton by default
+ */
+void
+SpinButtonToolItem::on_grab_focus()
+{
+ grab_button_focus();
+}
+
+/**
+ * \brief Set the tooltip to display on this (and all child widgets)
+ *
+ * \param[in] text The tooltip to display
+ */
+void
+SpinButtonToolItem::set_all_tooltip_text(const Glib::ustring& text)
+{
+ set_tooltip_text(text);
+ _btn->set_tooltip_text(text);
+}
+
+/**
+ * \brief Set the widget that focus moves to when this one loses focus
+ *
+ * \param widget The widget that will gain focus
+ */
+void
+SpinButtonToolItem::set_focus_widget(Gtk::Widget *widget)
+{
+ _focus_widget = widget;
+}
+
+/**
+ * \brief Grab focus on the spin-button widget
+ */
+void
+SpinButtonToolItem::grab_button_focus()
+{
+ _btn->grab_focus();
+}
+
+void
+SpinButtonToolItem::set_custom_numeric_menu_data(std::vector<double>& values,
+ const std::vector<Glib::ustring>& labels)
+{
+ if(values.size() != labels.size() && !labels.empty()) {
+ g_warning("Cannot add custom menu items. Value and label arrays are different sizes");
+ return;
+ }
+
+ _custom_menu_data.clear();
+
+ int i = 0;
+
+ for (auto value : values) {
+ if(labels.empty()) {
+ _custom_menu_data.push_back(std::make_pair(value, ""));
+ }
+ else {
+ _custom_menu_data.push_back(std::make_pair(value, labels[i++]));
+ }
+ }
+}
+
+Glib::RefPtr<Gtk::Adjustment>
+SpinButtonToolItem::get_adjustment()
+{
+ return _btn->get_adjustment();
+}
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+/*
+ 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/spin-button-tool-item.h b/src/ui/widget/spin-button-tool-item.h
new file mode 100644
index 0000000..c073f56
--- /dev/null
+++ b/src/ui/widget/spin-button-tool-item.h
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SPIN_BUTTON_TOOL_ITEM_H
+#define SEEN_SPIN_BUTTON_TOOL_ITEM_H
+
+#include <gtkmm/toolitem.h>
+
+namespace Gtk {
+class Box;
+class RadioButtonGroup;
+class RadioMenuItem;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class SpinButton;
+
+/**
+ * \brief A spin-button with a label that can be added to a toolbar
+ */
+class SpinButtonToolItem : public Gtk::ToolItem
+{
+private:
+ typedef std::vector< std::pair<double, Glib::ustring> > NumericMenuData;
+
+ Glib::ustring _name; ///< A unique ID for the widget (NOT translatable)
+ SpinButton *_btn; ///< The spin-button within the widget
+ Glib::ustring _label_text; ///< A string to use in labels for the widget (translatable)
+ double _last_val; ///< The last value of the adjustment
+ bool _transfer_focus; ///< Whether or not to transfer focus
+
+ Gtk::Box *_hbox; ///< Horizontal box, to store widgets
+ Gtk::Widget *_label; ///< A text label to describe the setting
+ Gtk::Widget *_icon; ///< An icon to describe the setting
+
+ /** A widget that grabs focus when this one loses it */
+ Gtk::Widget * _focus_widget;
+
+ // Custom values and labels to add to the numeric popup-menu
+ NumericMenuData _custom_menu_data;
+
+ // Event handlers
+ bool on_btn_focus_in_event(GdkEventFocus *focus_event);
+ bool on_btn_focus_out_event(GdkEventFocus *focus_event);
+ bool on_btn_key_press_event(GdkEventKey *key_event);
+ bool on_btn_button_press_event(const GdkEventButton *button_event);
+ bool on_popup_menu();
+ void do_popup_menu(const GdkEventButton *button_event);
+
+ void defocus();
+ bool process_tab(int direction);
+
+ void on_numeric_menu_item_toggled(double value);
+
+ Gtk::Menu * create_numeric_menu();
+
+ Gtk::RadioMenuItem * create_numeric_menu_item(Gtk::RadioButtonGroup *group,
+ double value,
+ const Glib::ustring& label = "");
+
+protected:
+ bool on_create_menu_proxy() override;
+ void on_grab_focus() override;
+
+public:
+ SpinButtonToolItem(const Glib::ustring name,
+ const Glib::ustring& label_text,
+ Glib::RefPtr<Gtk::Adjustment>& adjustment,
+ double climb_rate = 0.1,
+ int digits = 3);
+
+ void set_all_tooltip_text(const Glib::ustring& text);
+ void set_focus_widget(Gtk::Widget *widget);
+ void grab_button_focus();
+
+ void set_custom_numeric_menu_data(std::vector<double>& values,
+ const std::vector<Glib::ustring>& labels = std::vector<Glib::ustring>());
+ Glib::RefPtr<Gtk::Adjustment> get_adjustment();
+ void set_icon(const Glib::ustring& icon_name);
+};
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+#endif // SEEN_SPIN_BUTTON_TOOL_ITEM_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/spin-scale.cpp b/src/ui/widget/spin-scale.cpp
new file mode 100644
index 0000000..5e3a2a2
--- /dev/null
+++ b/src/ui/widget/spin-scale.cpp
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ *
+ * Copyright (C) 2012 Author
+ * 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "spin-scale.h"
+
+#include <glibmm/i18n.h>
+#include <glibmm/stringutils.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+SpinScale::SpinScale(const Glib::ustring label, double value,
+ double lower, double upper,
+ double step_increment, double page_increment, int digits,
+ const SPAttributeEnum a, const Glib::ustring tip_text)
+ : AttrWidget(a, value)
+ , _inkspinscale(value, lower, upper, step_increment, page_increment, 0)
+{
+ set_name("SpinScale");
+
+ _inkspinscale.set_label (label);
+ _inkspinscale.set_digits (digits);
+ _inkspinscale.set_tooltip_text (tip_text);
+
+ _adjustment = _inkspinscale.get_adjustment();
+
+ signal_value_changed().connect(signal_attr_changed().make_slot());
+
+ pack_start(_inkspinscale);
+
+ show_all_children();
+}
+
+SpinScale::SpinScale(const Glib::ustring label,
+ Glib::RefPtr<Gtk::Adjustment> adjustment, int digits,
+ const SPAttributeEnum a, const Glib::ustring tip_text)
+ : AttrWidget(a, 0.0)
+ , _inkspinscale(adjustment)
+{
+ set_name("SpinScale");
+
+ _inkspinscale.set_label (label);
+ _inkspinscale.set_digits (digits);
+ _inkspinscale.set_tooltip_text (tip_text);
+
+ _adjustment = _inkspinscale.get_adjustment();
+
+ signal_value_changed().connect(signal_attr_changed().make_slot());
+
+ pack_start(_inkspinscale);
+
+ show_all_children();
+}
+
+Glib::ustring SpinScale::get_as_attribute() const
+{
+ const double val = _adjustment->get_value();
+
+ if( _inkspinscale.get_digits() == 0)
+ return Glib::Ascii::dtostr((int)val);
+ else
+ return Glib::Ascii::dtostr(val);
+}
+
+void SpinScale::set_from_attribute(SPObject* o)
+{
+ const gchar* val = attribute_value(o);
+ if (val)
+ _adjustment->set_value(Glib::Ascii::strtod(val));
+ else
+ _adjustment->set_value(get_default()->as_double());
+}
+
+Glib::SignalProxy0<void> SpinScale::signal_value_changed()
+{
+ return _adjustment->signal_value_changed();
+}
+
+double SpinScale::get_value() const
+{
+ return _adjustment->get_value();
+}
+
+void SpinScale::set_value(const double val)
+{
+ _adjustment->set_value(val);
+}
+
+void SpinScale::set_focuswidget(GtkWidget *widget)
+{
+ _inkspinscale.set_focus_widget(widget);
+}
+
+const decltype(SpinScale::_adjustment) SpinScale::get_adjustment() const
+{
+ return _adjustment;
+}
+
+decltype(SpinScale::_adjustment) SpinScale::get_adjustment()
+{
+ return _adjustment;
+}
+
+
+DualSpinScale::DualSpinScale(const Glib::ustring label1, const Glib::ustring label2,
+ double value, double lower, double upper,
+ double step_increment, double page_increment, int digits,
+ const SPAttributeEnum a,
+ const Glib::ustring tip_text1, const Glib::ustring tip_text2)
+ : AttrWidget(a),
+ _s1(label1, value, lower, upper, step_increment, page_increment, digits, SP_ATTR_INVALID, tip_text1),
+ _s2(label2, value, lower, upper, step_increment, page_increment, digits, SP_ATTR_INVALID, tip_text2),
+ //TRANSLATORS: "Link" means to _link_ two sliders together
+ _link(C_("Sliders", "Link"))
+{
+ set_name("DualSpinScale");
+ signal_value_changed().connect(signal_attr_changed().make_slot());
+
+ _s1.get_adjustment()->signal_value_changed().connect(_signal_value_changed.make_slot());
+ _s2.get_adjustment()->signal_value_changed().connect(_signal_value_changed.make_slot());
+ _s1.get_adjustment()->signal_value_changed().connect(sigc::mem_fun(*this, &DualSpinScale::update_linked));
+
+ _link.signal_toggled().connect(sigc::mem_fun(*this, &DualSpinScale::link_toggled));
+
+ Gtk::Box* vb = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
+ vb->add(_s1);
+ vb->add(_s2);
+ pack_start(*vb);
+ pack_start(_link, false, false);
+ _link.set_active(true);
+
+ show_all();
+}
+
+Glib::ustring DualSpinScale::get_as_attribute() const
+{
+ if(_link.get_active())
+ return _s1.get_as_attribute();
+ else
+ return _s1.get_as_attribute() + " " + _s2.get_as_attribute();
+}
+
+void DualSpinScale::set_from_attribute(SPObject* o)
+{
+ const gchar* val = attribute_value(o);
+ if(val) {
+ // Split val into parts
+ gchar** toks = g_strsplit(val, " ", 2);
+
+ if(toks) {
+ double v1 = 0.0, v2 = 0.0;
+ if(toks[0])
+ v1 = v2 = Glib::Ascii::strtod(toks[0]);
+ if(toks[1])
+ v2 = Glib::Ascii::strtod(toks[1]);
+
+ _link.set_active(toks[1] == nullptr);
+
+ _s1.get_adjustment()->set_value(v1);
+ _s2.get_adjustment()->set_value(v2);
+
+ g_strfreev(toks);
+ }
+ }
+}
+
+sigc::signal<void>& DualSpinScale::signal_value_changed()
+{
+ return _signal_value_changed;
+}
+
+const SpinScale& DualSpinScale::get_SpinScale1() const
+{
+ return _s1;
+}
+
+SpinScale& DualSpinScale::get_SpinScale1()
+{
+ return _s1;
+}
+
+const SpinScale& DualSpinScale::get_SpinScale2() const
+{
+ return _s2;
+}
+
+SpinScale& DualSpinScale::get_SpinScale2()
+{
+ return _s2;
+}
+
+void DualSpinScale::link_toggled()
+{
+ _s2.set_sensitive(!_link.get_active());
+ update_linked();
+}
+
+void DualSpinScale::update_linked()
+{
+ if(_link.get_active())
+ _s2.set_value(_s1.get_value());
+}
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/spin-scale.h b/src/ui/widget/spin-scale.h
new file mode 100644
index 0000000..b154cb3
--- /dev/null
+++ b/src/ui/widget/spin-scale.h
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ *
+ * Copyright (C) 2012 Author
+ * 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_SPIN_SCALE_H
+#define INKSCAPE_UI_WIDGET_SPIN_SCALE_H
+
+#include <gtkmm/adjustment.h>
+#include <gtkmm/box.h>
+#include <gtkmm/togglebutton.h>
+#include "attr-widget.h"
+#include "ink-spinscale.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * Wrap the InkSpinScale class and attach an attribute.
+ * A combo widget with label, scale slider, spinbutton, and adjustment;
+ */
+class SpinScale : public Gtk::Box, public AttrWidget
+{
+
+public:
+ SpinScale(const Glib::ustring label, double value,
+ double lower, double upper,
+ double step_increment, double page_increment, int digits,
+ const SPAttributeEnum a = SP_ATTR_INVALID, const Glib::ustring tip_text = "");
+
+ // Used by extensions
+ SpinScale(const Glib::ustring label,
+ Glib::RefPtr<Gtk::Adjustment> adjustment, int digits,
+ const SPAttributeEnum a = SP_ATTR_INVALID, const Glib::ustring tip_text = "");
+
+ Glib::ustring get_as_attribute() const override;
+ void set_from_attribute(SPObject*) override;
+
+ // Shortcuts to _adjustment
+ Glib::SignalProxy0<void> signal_value_changed();
+ double get_value() const;
+ void set_value(const double);
+ void set_focuswidget(GtkWidget *widget);
+
+private:
+ Glib::RefPtr<Gtk::Adjustment> _adjustment;
+ InkSpinScale _inkspinscale;
+
+public:
+ const decltype(_adjustment) get_adjustment() const;
+ decltype(_adjustment) get_adjustment();
+};
+
+
+/**
+ * Contains two SpinScales for controlling number-opt-number attributes.
+ *
+ * @see SpinScale
+ */
+class DualSpinScale : public Gtk::Box, public AttrWidget
+{
+public:
+ DualSpinScale(const Glib::ustring label1, const Glib::ustring label2,
+ double value, double lower, double upper,
+ double step_increment, double page_increment, int digits,
+ const SPAttributeEnum a,
+ const Glib::ustring tip_text1, const Glib::ustring tip_text2);
+
+ Glib::ustring get_as_attribute() const override;
+ void set_from_attribute(SPObject*) override;
+
+ sigc::signal<void>& signal_value_changed();
+
+ const SpinScale& get_SpinScale1() const;
+ SpinScale& get_SpinScale1();
+
+ const SpinScale& get_SpinScale2() const;
+ SpinScale& get_SpinScale2();
+
+ //void remove_scale();
+private:
+ void link_toggled();
+ void update_linked();
+ sigc::signal<void> _signal_value_changed;
+ SpinScale _s1, _s2;
+ Gtk::ToggleButton _link;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_SPIN_SCALE_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/spin-slider.cpp b/src/ui/widget/spin-slider.cpp
new file mode 100644
index 0000000..e4cd0c6
--- /dev/null
+++ b/src/ui/widget/spin-slider.cpp
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Nicholas Bishop <nicholasbishop@gmail.com>
+ * Felipe C. da S. Sanches <juca@members.fsf.org>
+ *
+ * Copyright (C) 2007 Author
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "spin-slider.h"
+
+#include <glibmm/i18n.h>
+#include <glibmm/stringutils.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+SpinSlider::SpinSlider(double value, double lower, double upper, double step_inc,
+ double climb_rate, int digits, const SPAttributeEnum a, const char* tip_text)
+ : AttrWidget(a, value),
+ _adjustment(Gtk::Adjustment::create(value, lower, upper, step_inc)),
+ _scale(_adjustment), _spin(_adjustment, climb_rate, digits)
+{
+ set_name("SpinSlider");
+ signal_value_changed().connect(signal_attr_changed().make_slot());
+
+ pack_start(_scale);
+ pack_start(_spin, false, false);
+ if (tip_text){
+ _scale.set_tooltip_text(tip_text);
+ _spin.set_tooltip_text(tip_text);
+ }
+
+ _scale.set_draw_value(false);
+
+ show_all_children();
+}
+
+Glib::ustring SpinSlider::get_as_attribute() const
+{
+ const auto val = _adjustment->get_value();
+
+ if(_spin.get_digits() == 0)
+ return Glib::Ascii::dtostr((int)val);
+ else
+ return Glib::Ascii::dtostr(val);
+}
+
+void SpinSlider::set_from_attribute(SPObject* o)
+{
+ const gchar* val = attribute_value(o);
+ if(val)
+ _adjustment->set_value(Glib::Ascii::strtod(val));
+ else
+ _adjustment->set_value(get_default()->as_double());
+}
+
+Glib::SignalProxy0<void> SpinSlider::signal_value_changed()
+{
+ return _adjustment->signal_value_changed();
+}
+
+double SpinSlider::get_value() const
+{
+ return _adjustment->get_value();
+}
+
+void SpinSlider::set_value(const double val)
+{
+ _adjustment->set_value(val);
+}
+
+const decltype(SpinSlider::_adjustment) SpinSlider::get_adjustment() const
+{
+ return _adjustment;
+}
+
+decltype(SpinSlider::_adjustment) SpinSlider::get_adjustment()
+{
+ return _adjustment;
+}
+
+const Gtk::Scale& SpinSlider::get_scale() const
+{
+ return _scale;
+}
+
+Gtk::Scale& SpinSlider::get_scale()
+{
+ return _scale;
+}
+
+const Inkscape::UI::Widget::SpinButton& SpinSlider::get_spin_button() const
+{
+ return _spin;
+}
+Inkscape::UI::Widget::SpinButton& SpinSlider::get_spin_button()
+{
+ return _spin;
+}
+
+void SpinSlider::remove_scale()
+{
+ remove(_scale);
+}
+
+DualSpinSlider::DualSpinSlider(double value, double lower, double upper, double step_inc,
+ double climb_rate, int digits, const SPAttributeEnum a, char* tip_text1, char* tip_text2)
+ : AttrWidget(a),
+ _s1(value, lower, upper, step_inc, climb_rate, digits, SP_ATTR_INVALID, tip_text1),
+ _s2(value, lower, upper, step_inc, climb_rate, digits, SP_ATTR_INVALID, tip_text2),
+ //TRANSLATORS: "Link" means to _link_ two sliders together
+ _link(C_("Sliders", "Link"))
+{
+ signal_value_changed().connect(signal_attr_changed().make_slot());
+
+ _s1.get_adjustment()->signal_value_changed().connect(_signal_value_changed.make_slot());
+ _s2.get_adjustment()->signal_value_changed().connect(_signal_value_changed.make_slot());
+ _s1.get_adjustment()->signal_value_changed().connect(sigc::mem_fun(*this, &DualSpinSlider::update_linked));
+ _link.signal_toggled().connect(sigc::mem_fun(*this, &DualSpinSlider::link_toggled));
+
+ Gtk::VBox* vb = Gtk::manage(new Gtk::VBox);
+ vb->add(_s1);
+ vb->add(_s2);
+ pack_start(*vb);
+ pack_start(_link, false, false);
+ _link.set_active(true);
+
+ show_all();
+}
+
+Glib::ustring DualSpinSlider::get_as_attribute() const
+{
+ if(_link.get_active())
+ return _s1.get_as_attribute();
+ else
+ return _s1.get_as_attribute() + " " + _s2.get_as_attribute();
+}
+
+void DualSpinSlider::set_from_attribute(SPObject* o)
+{
+ const gchar* val = attribute_value(o);
+ if(val) {
+ // Split val into parts
+ gchar** toks = g_strsplit(val, " ", 2);
+
+ if(toks) {
+ double v1 = 0.0, v2 = 0.0;
+ if(toks[0])
+ v1 = v2 = Glib::Ascii::strtod(toks[0]);
+ if(toks[1])
+ v2 = Glib::Ascii::strtod(toks[1]);
+
+ _link.set_active(toks[1] == nullptr);
+
+ _s1.get_adjustment()->set_value(v1);
+ _s2.get_adjustment()->set_value(v2);
+
+ g_strfreev(toks);
+ }
+ }
+}
+
+sigc::signal<void>& DualSpinSlider::signal_value_changed()
+{
+ return _signal_value_changed;
+}
+
+const SpinSlider& DualSpinSlider::get_spinslider1() const
+{
+ return _s1;
+}
+
+SpinSlider& DualSpinSlider::get_spinslider1()
+{
+ return _s1;
+}
+
+const SpinSlider& DualSpinSlider::get_spinslider2() const
+{
+ return _s2;
+}
+
+SpinSlider& DualSpinSlider::get_spinslider2()
+{
+ return _s2;
+}
+
+void DualSpinSlider::remove_scale()
+{
+ _s1.remove_scale();
+ _s2.remove_scale();
+}
+
+void DualSpinSlider::link_toggled()
+{
+ _s2.set_sensitive(!_link.get_active());
+ update_linked();
+}
+
+void DualSpinSlider::update_linked()
+{
+ if(_link.get_active())
+ _s2.set_value(_s1.get_value());
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/spin-slider.h b/src/ui/widget/spin-slider.h
new file mode 100644
index 0000000..24a18a0
--- /dev/null
+++ b/src/ui/widget/spin-slider.h
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Nicholas Bishop <nicholasbishop@gmail.com>
+ *
+ * Copyright (C) 2007 Author
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_SPIN_SLIDER_H
+#define INKSCAPE_UI_WIDGET_SPIN_SLIDER_H
+
+#include <gtkmm/adjustment.h>
+#include <gtkmm/box.h>
+#include <gtkmm/scale.h>
+#include <gtkmm/togglebutton.h>
+#include "spinbutton.h"
+#include "attr-widget.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * Groups an HScale and a SpinButton together using the same Adjustment.
+ */
+class SpinSlider : public Gtk::HBox, public AttrWidget
+{
+public:
+ SpinSlider(double value, double lower, double upper, double step_inc,
+ double climb_rate, int digits, const SPAttributeEnum a = SP_ATTR_INVALID, const char* tip_text = nullptr);
+
+ Glib::ustring get_as_attribute() const override;
+ void set_from_attribute(SPObject*) override;
+
+ // Shortcuts to _adjustment
+ Glib::SignalProxy0<void> signal_value_changed();
+ double get_value() const;
+ void set_value(const double);
+
+ const Gtk::Scale& get_scale() const;
+ Gtk::Scale& get_scale();
+
+ const Inkscape::UI::Widget::SpinButton& get_spin_button() const;
+ Inkscape::UI::Widget::SpinButton& get_spin_button();
+
+ // Change the SpinSlider into a SpinButton with AttrWidget support)
+ void remove_scale();
+private:
+ Glib::RefPtr<Gtk::Adjustment> _adjustment;
+ Gtk::Scale _scale;
+ Inkscape::UI::Widget::SpinButton _spin;
+
+public:
+ const decltype(_adjustment) get_adjustment() const;
+ decltype(_adjustment) get_adjustment();
+};
+
+/**
+ * Contains two SpinSliders for controlling number-opt-number attributes.
+ *
+ * @see SpinSlider
+ */
+class DualSpinSlider : public Gtk::HBox, public AttrWidget
+{
+public:
+ DualSpinSlider(double value, double lower, double upper, double step_inc,
+ double climb_rate, int digits, const SPAttributeEnum, char* tip_text1, char* tip_text2);
+
+ Glib::ustring get_as_attribute() const override;
+ void set_from_attribute(SPObject*) override;
+
+ sigc::signal<void>& signal_value_changed();
+
+ const SpinSlider& get_spinslider1() const;
+ SpinSlider& get_spinslider1();
+
+ const SpinSlider& get_spinslider2() const;
+ SpinSlider& get_spinslider2();
+
+ void remove_scale();
+private:
+ void link_toggled();
+ void update_linked();
+ sigc::signal<void> _signal_value_changed;
+ SpinSlider _s1, _s2;
+ Gtk::ToggleButton _link;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_SPIN_SLIDER_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/spinbutton.cpp b/src/ui/widget/spinbutton.cpp
new file mode 100644
index 0000000..c633035
--- /dev/null
+++ b/src/ui/widget/spinbutton.cpp
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Johan B. C. Engelen
+ *
+ * Copyright (C) 2011 Author
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "spinbutton.h"
+#include "unit-menu.h"
+#include "unit-tracker.h"
+#include "util/expression-evaluator.h"
+#include "ui/tools/tool-base.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+
+void
+SpinButton::connect_signals() {
+ signal_input().connect(sigc::mem_fun(*this, &SpinButton::on_input));
+ signal_focus_in_event().connect(sigc::mem_fun(*this, &SpinButton::on_my_focus_in_event));
+ signal_key_press_event().connect(sigc::mem_fun(*this, &SpinButton::on_my_key_press_event));
+ gtk_widget_add_events(GTK_WIDGET(gobj()), GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK);
+ signal_scroll_event().connect(sigc::mem_fun(*this, &SpinButton::on_scroll_event));
+ set_focus_on_click(true);
+};
+
+int SpinButton::on_input(double* newvalue)
+{
+ try {
+ Inkscape::Util::EvaluatorQuantity result;
+ if (_unit_menu || _unit_tracker) {
+ Unit const *unit = nullptr;
+ if (_unit_menu) {
+ unit = _unit_menu->getUnit();
+ } else {
+ unit = _unit_tracker->getActiveUnit();
+ }
+ Inkscape::Util::ExpressionEvaluator eval = Inkscape::Util::ExpressionEvaluator(get_text().c_str(), unit);
+ result = eval.evaluate();
+ // check if output dimension corresponds to input unit
+ if (result.dimension != (unit->isAbsolute() ? 1 : 0) ) {
+ throw Inkscape::Util::EvaluatorException("Input dimensions do not match with parameter dimensions.","");
+ }
+ } else {
+ Inkscape::Util::ExpressionEvaluator eval = Inkscape::Util::ExpressionEvaluator(get_text().c_str(), nullptr);
+ result = eval.evaluate();
+ }
+ *newvalue = result.value;
+ }
+ catch(Inkscape::Util::EvaluatorException &e) {
+ g_message ("%s", e.what());
+
+ return false;
+ }
+
+ return true;
+}
+
+bool SpinButton::on_my_focus_in_event(GdkEventFocus* /*event*/)
+{
+ _on_focus_in_value = get_value();
+ return false; // do not consume the event
+}
+
+
+
+bool SpinButton::on_scroll_event(GdkEventScroll *event)
+{
+ if (!is_focus()) {
+ return false;
+ }
+ double step, page;
+ get_increments(step, page);
+ if (event->state & GDK_CONTROL_MASK) {
+ step = page;
+ }
+ double change = 0.0;
+ if (event->direction == GDK_SCROLL_UP) {
+ change = step;
+ } else if (event->direction == GDK_SCROLL_DOWN) {
+ change = -step;
+ } else if (event->direction == GDK_SCROLL_SMOOTH) {
+ double delta_y_clamped = CLAMP(event->delta_y, -1, 1); // values > 1 result in excessive changes
+ change = step * -delta_y_clamped;
+ } else {
+ return false;
+ }
+ set_value(get_value() + change);
+ return true;
+}
+
+bool SpinButton::on_my_key_press_event(GdkEventKey* event)
+{
+ switch (Inkscape::UI::Tools::get_latin_keyval (event)) {
+ case GDK_KEY_Escape:
+ undo();
+ return true; // I consumed the event
+ break;
+ case GDK_KEY_z:
+ case GDK_KEY_Z:
+ if (event->state & GDK_CONTROL_MASK) {
+ undo();
+ return true; // I consumed the event
+ }
+ break;
+ default:
+ break;
+ }
+
+ return false; // do not consume the event
+}
+
+void SpinButton::undo()
+{
+ set_value(_on_focus_in_value);
+}
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/spinbutton.h b/src/ui/widget/spinbutton.h
new file mode 100644
index 0000000..710b511
--- /dev/null
+++ b/src/ui/widget/spinbutton.h
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Johan B. C. Engelen
+ *
+ * Copyright (C) 2011 Author
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_SPINBUTTON_H
+#define INKSCAPE_UI_WIDGET_SPINBUTTON_H
+
+#include <gtkmm/spinbutton.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class UnitMenu;
+class UnitTracker;
+
+/**
+ * SpinButton widget, that allows entry of simple math expressions (also units, when linked with UnitMenu),
+ * and allows entry of both '.' and ',' for the decimal, even when in numeric mode.
+ *
+ * Calling "set_numeric()" effectively disables the expression parsing. If no unit menu is linked, all unitlike characters are ignored.
+ */
+class SpinButton : public Gtk::SpinButton
+{
+public:
+ SpinButton(double climb_rate = 0.0, guint digits = 0)
+ : Gtk::SpinButton(climb_rate, digits),
+ _unit_menu(nullptr),
+ _unit_tracker(nullptr),
+ _on_focus_in_value(0.)
+ {
+ connect_signals();
+ };
+ explicit SpinButton(Glib::RefPtr<Gtk::Adjustment>& adjustment, double climb_rate = 0.0, guint digits = 0)
+ : Gtk::SpinButton(adjustment, climb_rate, digits),
+ _unit_menu(nullptr),
+ _unit_tracker(nullptr),
+ _on_focus_in_value(0.)
+ {
+ connect_signals();
+ };
+
+ ~SpinButton() override = default;
+
+ // noncopyable
+ SpinButton(const SpinButton&) = delete;
+ SpinButton& operator=(const SpinButton&) = delete;
+
+ void setUnitMenu(UnitMenu* unit_menu) { _unit_menu = unit_menu; };
+
+ void addUnitTracker(UnitTracker* ut) { _unit_tracker = ut; };
+
+protected:
+ UnitMenu *_unit_menu; /// Linked unit menu for unit conversion in entered expressions.
+ UnitTracker *_unit_tracker; // Linked unit tracker for unit conversion in entered expressions.
+ double _on_focus_in_value;
+
+ void connect_signals();
+
+ /**
+ * This callback function should try to convert the entered text to a number and write it to newvalue.
+ * It calls a method to evaluate the (potential) mathematical expression.
+ *
+ * @retval false No conversion done, continue with default handler.
+ * @retval true Conversion successful, don't call default handler.
+ */
+ int on_input(double* newvalue) override;
+
+ /**
+ * When focus is obtained, save the value to enable undo later.
+ * @retval false continue with default handler.
+ * @retval true don't call default handler.
+ */
+ bool on_my_focus_in_event(GdkEventFocus* event);
+
+ /**
+ * When scroll is done.
+ * @retval false continue with default handler.
+ * @retval true don't call default handler.
+ */
+ bool on_scroll_event(GdkEventScroll *event) override;
+ /**
+ * Handle specific keypress events, like Ctrl+Z.
+ *
+ * @retval false continue with default handler.
+ * @retval true don't call default handler.
+ */
+ bool on_my_key_press_event(GdkEventKey* event);
+
+ /**
+ * Undo the editing, by resetting the value upon when the spinbutton got focus.
+ */
+ void undo();
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_SPINBUTTON_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/style-subject.cpp b/src/ui/widget/style-subject.cpp
new file mode 100644
index 0000000..9c30a42
--- /dev/null
+++ b/src/ui/widget/style-subject.cpp
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 MenTaLguY <mental@rydia.net>
+ * Abhishek Sharma
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "style-subject.h"
+
+#include "desktop.h"
+#include "desktop-style.h"
+#include "selection.h"
+
+#include "xml/sp-css-attr.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+StyleSubject::StyleSubject() : _desktop(nullptr) {
+}
+
+StyleSubject::~StyleSubject() {
+ setDesktop(nullptr);
+}
+
+void StyleSubject::setDesktop(SPDesktop *desktop) {
+ if (desktop != _desktop) {
+ if (desktop) {
+ GC::anchor(desktop);
+ }
+ if (_desktop) {
+ GC::release(_desktop);
+ }
+ _desktop = desktop;
+ _afterDesktopSwitch(desktop);
+ _emitChanged();
+ }
+}
+
+StyleSubject::Selection::Selection() = default;
+
+StyleSubject::Selection::~Selection() = default;
+
+Inkscape::Selection *StyleSubject::Selection::_getSelection() const {
+ SPDesktop *desktop = getDesktop();
+ if (desktop) {
+ return desktop->getSelection();
+ } else {
+ return nullptr;
+ }
+}
+
+std::vector<SPObject*> StyleSubject::Selection::list() {
+ Inkscape::Selection *selection = _getSelection();
+ if(selection) {
+ return std::vector<SPObject *>(selection->objects().begin(), selection->objects().end());
+ }
+
+ return std::vector<SPObject*>();
+}
+
+Geom::OptRect StyleSubject::Selection::getBounds(SPItem::BBoxType type) {
+ Inkscape::Selection *selection = _getSelection();
+ if (selection) {
+ return selection->bounds(type);
+ } else {
+ return Geom::OptRect();
+ }
+}
+
+int StyleSubject::Selection::queryStyle(SPStyle *query, int property) {
+ SPDesktop *desktop = getDesktop();
+ if (desktop) {
+ return sp_desktop_query_style(desktop, query, property);
+ } else {
+ return QUERY_STYLE_NOTHING;
+ }
+}
+
+void StyleSubject::Selection::_afterDesktopSwitch(SPDesktop *desktop) {
+ _sel_changed.disconnect();
+ _subsel_changed.disconnect();
+ _sel_modified.disconnect();
+ if (desktop) {
+ _subsel_changed = desktop->connectToolSubselectionChanged(sigc::hide(sigc::mem_fun(*this, &Selection::_emitChanged)));
+ Inkscape::Selection *selection = desktop->getSelection();
+ if (selection) {
+ _sel_changed = selection->connectChanged(sigc::hide(sigc::mem_fun(*this, &Selection::_emitChanged)));
+ _sel_modified = selection->connectModified(sigc::mem_fun(*this, &Selection::_emitModified));
+ }
+ }
+}
+
+void StyleSubject::Selection::setCSS(SPCSSAttr *css) {
+ SPDesktop *desktop = getDesktop();
+ if (desktop) {
+ sp_desktop_set_style(desktop, css);
+ }
+}
+
+StyleSubject::CurrentLayer::CurrentLayer() {
+ _element = nullptr;
+}
+
+StyleSubject::CurrentLayer::~CurrentLayer() = default;
+
+void StyleSubject::CurrentLayer::_setLayer(SPObject *layer) {
+ _layer_release.disconnect();
+ _layer_modified.disconnect();
+ if (_element) {
+ sp_object_unref(_element, nullptr);
+ }
+ _element = layer;
+ if (layer) {
+ sp_object_ref(layer, nullptr);
+ _layer_release = layer->connectRelease(sigc::hide(sigc::bind(sigc::mem_fun(*this, &CurrentLayer::_setLayer), (SPObject *)nullptr)));
+ _layer_modified = layer->connectModified(sigc::hide(sigc::hide(sigc::mem_fun(*this, &CurrentLayer::_emitChanged))));
+ }
+ _emitChanged();
+}
+
+SPObject *StyleSubject::CurrentLayer::_getLayer() const {
+ return _element;
+}
+
+SPObject *StyleSubject::CurrentLayer::_getLayerSList() const {
+ return _element;
+
+}
+
+std::vector<SPObject*> StyleSubject::CurrentLayer::list(){
+ std::vector<SPObject*> list;
+ list.push_back(_element);
+ return list;
+}
+
+Geom::OptRect StyleSubject::CurrentLayer::getBounds(SPItem::BBoxType type) {
+ SPObject *layer = _getLayer();
+ if (layer && SP_IS_ITEM(layer)) {
+ return SP_ITEM(layer)->desktopBounds(type);
+ } else {
+ return Geom::OptRect();
+ }
+}
+
+int StyleSubject::CurrentLayer::queryStyle(SPStyle *query, int property) {
+ std::vector<SPItem*> list;
+ SPObject* i=_getLayerSList();
+ if (i) {
+ list.push_back((SPItem*)i);
+ return sp_desktop_query_style_from_list(list, query, property);
+ } else {
+ return QUERY_STYLE_NOTHING;
+ }
+}
+
+void StyleSubject::CurrentLayer::setCSS(SPCSSAttr *css) {
+ SPObject *layer = _getLayer();
+ if (layer) {
+ sp_desktop_apply_css_recursive(layer, css, true);
+ }
+}
+
+void StyleSubject::CurrentLayer::_afterDesktopSwitch(SPDesktop *desktop) {
+ _layer_switched.disconnect();
+ if (desktop) {
+ _layer_switched = desktop->connectCurrentLayerChanged(sigc::mem_fun(*this, &CurrentLayer::_setLayer));
+ _setLayer(desktop->currentLayer());
+ } else {
+ _setLayer(nullptr);
+ }
+}
+
+}
+}
+}
+
+/*
+ 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-subject.h b/src/ui/widget/style-subject.h
new file mode 100644
index 0000000..c2f2b3f
--- /dev/null
+++ b/src/ui/widget/style-subject.h
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Abstraction for different style widget operands.
+ */
+/*
+ * Copyright (C) 2007 MenTaLguY <mental@rydia.net>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_INKSCAPE_UI_WIDGET_STYLE_SUBJECT_H
+#define SEEN_INKSCAPE_UI_WIDGET_STYLE_SUBJECT_H
+
+#include <boost/optional.hpp>
+#include <2geom/rect.h>
+#include <cstddef>
+#include <sigc++/sigc++.h>
+
+#include "object/sp-item.h"
+#include "object/sp-tag.h"
+#include "object/sp-tag-use.h"
+#include "object/sp-tag-use-reference.h"
+
+class SPDesktop;
+class SPObject;
+class SPCSSAttr;
+class SPStyle;
+
+namespace Inkscape {
+class Selection;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class StyleSubject {
+public:
+ class Selection;
+ class CurrentLayer;
+
+
+ StyleSubject();
+ virtual ~StyleSubject();
+
+ void setDesktop(SPDesktop *desktop);
+ SPDesktop *getDesktop() const { return _desktop; }
+
+ virtual Geom::OptRect getBounds(SPItem::BBoxType type) = 0;
+ virtual int queryStyle(SPStyle *query, int property) = 0;
+ virtual void setCSS(SPCSSAttr *css) = 0;
+ virtual std::vector<SPObject*> list(){return std::vector<SPObject*>();};
+
+ sigc::connection connectChanged(sigc::signal<void>::slot_type slot) {
+ return _changed_signal.connect(slot);
+ }
+
+protected:
+ virtual void _afterDesktopSwitch(SPDesktop */*desktop*/) {}
+ void _emitChanged() { _changed_signal.emit(); }
+ void _emitModified(Inkscape::Selection* selection, guint flags) {
+ // Do not say this object has styles unless it's style has been modified
+ if (flags & (SP_OBJECT_STYLE_MODIFIED_FLAG)) {
+ _emitChanged();
+ }
+ }
+
+private:
+ sigc::signal<void> _changed_signal;
+ SPDesktop *_desktop;
+};
+
+class StyleSubject::Selection : public StyleSubject {
+public:
+ Selection();
+ ~Selection() override;
+
+ Geom::OptRect getBounds(SPItem::BBoxType type) override;
+ int queryStyle(SPStyle *query, int property) override;
+ void setCSS(SPCSSAttr *css) override;
+ std::vector<SPObject*> list() override;
+
+protected:
+ void _afterDesktopSwitch(SPDesktop *desktop) override;
+
+private:
+ Inkscape::Selection *_getSelection() const;
+
+ sigc::connection _sel_changed;
+ sigc::connection _subsel_changed;
+ sigc::connection _sel_modified;
+};
+
+class StyleSubject::CurrentLayer : public StyleSubject {
+public:
+ CurrentLayer();
+ ~CurrentLayer() override;
+
+ Geom::OptRect getBounds(SPItem::BBoxType type) override;
+ int queryStyle(SPStyle *query, int property) override;
+ void setCSS(SPCSSAttr *css) override;
+ std::vector<SPObject*> list() override;
+
+protected:
+ void _afterDesktopSwitch(SPDesktop *desktop) override;
+
+private:
+ SPObject *_getLayer() const;
+ void _setLayer(SPObject *layer);
+ SPObject *_getLayerSList() const;
+
+ sigc::connection _layer_switched;
+ sigc::connection _layer_release;
+ sigc::connection _layer_modified;
+ mutable SPObject* _element;
+};
+
+}
+}
+}
+
+#endif // SEEN_INKSCAPE_UI_WIDGET_STYLE_SUBJECT_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/style-swatch.cpp b/src/ui/widget/style-swatch.cpp
new file mode 100644
index 0000000..734f092
--- /dev/null
+++ b/src/ui/widget/style-swatch.cpp
@@ -0,0 +1,373 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Static style swatch (fill, stroke, opacity).
+ */
+/* Authors:
+ * buliabyak@gmail.com
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2005-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "style-swatch.h"
+
+#include <glibmm/i18n.h>
+#include <gtkmm/grid.h>
+
+#include "inkscape.h"
+#include "verbs.h"
+
+#include "object/sp-linear-gradient.h"
+#include "object/sp-pattern.h"
+#include "object/sp-radial-gradient.h"
+#include "style.h"
+
+#include "helper/action.h"
+
+#include "ui/widget/color-preview.h"
+#include "util/units.h"
+
+#include "widgets/spw-utilities.h"
+#include "widgets/widget-sizes.h"
+
+#include "xml/sp-css-attr.h"
+
+enum {
+ SS_FILL,
+ SS_STROKE
+};
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * Watches whether the tool uses the current style.
+ */
+class StyleSwatch::ToolObserver : public Inkscape::Preferences::Observer {
+public:
+ ToolObserver(Glib::ustring const &path, StyleSwatch &ss) :
+ Observer(path),
+ _style_swatch(ss)
+ {}
+ void notify(Inkscape::Preferences::Entry const &val) override;
+private:
+ StyleSwatch &_style_swatch;
+};
+
+/**
+ * Watches for changes in the observed style pref.
+ */
+class StyleSwatch::StyleObserver : public Inkscape::Preferences::Observer {
+public:
+ StyleObserver(Glib::ustring const &path, StyleSwatch &ss) :
+ Observer(path),
+ _style_swatch(ss)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ this->notify(prefs->getEntry(path));
+ }
+ void notify(Inkscape::Preferences::Entry const &val) override {
+ SPCSSAttr *css = val.getInheritedStyle();
+ _style_swatch.setStyle(css);
+ sp_repr_css_attr_unref(css);
+ }
+private:
+ StyleSwatch &_style_swatch;
+};
+
+void StyleSwatch::ToolObserver::notify(Inkscape::Preferences::Entry const &val)
+{
+ bool usecurrent = val.getBool();
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (_style_swatch._style_obs) delete _style_swatch._style_obs;
+
+ if (usecurrent) {
+ _style_swatch._style_obs = new StyleObserver("/desktop/style", _style_swatch);
+
+ // If desktop's last-set style is empty, a tool uses its own fixed style even if set to use
+ // last-set (so long as it's empty). To correctly show this, we get the tool's style
+ // if the desktop's style is empty.
+ SPCSSAttr *css = prefs->getStyle("/desktop/style");
+ if (!css->attributeList()) {
+ SPCSSAttr *css2 = prefs->getInheritedStyle(_style_swatch._tool_path + "/style");
+ _style_swatch.setStyle(css2);
+ sp_repr_css_attr_unref(css2);
+ }
+ sp_repr_css_attr_unref(css);
+ } else {
+ _style_swatch._style_obs = new StyleObserver(_style_swatch._tool_path + "/style", _style_swatch);
+ }
+ prefs->addObserver(*_style_swatch._style_obs);
+}
+
+StyleSwatch::StyleSwatch(SPCSSAttr *css, gchar const *main_tip)
+ :
+ _desktop(nullptr),
+ _verb_t(0),
+ _css(nullptr),
+ _tool_obs(nullptr),
+ _style_obs(nullptr),
+ _table(Gtk::manage(new Gtk::Grid())),
+ _sw_unit(nullptr)
+{
+ set_name("StyleSwatch");
+
+ _label[SS_FILL].set_markup(_("Fill:"));
+ _label[SS_STROKE].set_markup(_("Stroke:"));
+
+ for (int i = SS_FILL; i <= SS_STROKE; i++) {
+ _label[i].set_halign(Gtk::ALIGN_START);
+ _label[i].set_valign(Gtk::ALIGN_CENTER);
+ _label[i].set_margin_top(0);
+ _label[i].set_margin_bottom(0);
+ _label[i].set_margin_start(0);
+ _label[i].set_margin_end(0);
+
+ _color_preview[i] = new Inkscape::UI::Widget::ColorPreview (0);
+ }
+
+ _opacity_value.set_halign(Gtk::ALIGN_START);
+ _opacity_value.set_valign(Gtk::ALIGN_CENTER);
+ _opacity_value.set_margin_top(0);
+ _opacity_value.set_margin_bottom(0);
+ _opacity_value.set_margin_start(0);
+ _opacity_value.set_margin_end(0);
+
+ _table->set_column_spacing(2);
+ _table->set_row_spacing(0);
+
+ _stroke.pack_start(_place[SS_STROKE]);
+ _stroke_width_place.add(_stroke_width);
+ _stroke.pack_start(_stroke_width_place, Gtk::PACK_SHRINK);
+
+ _opacity_place.add(_opacity_value);
+
+ _table->attach(_label[SS_FILL], 0, 0, 1, 1);
+ _table->attach(_label[SS_STROKE], 0, 1, 1, 1);
+ _table->attach(_place[SS_FILL], 1, 0, 1, 1);
+ _table->attach(_stroke, 1, 1, 1, 1);
+ _table->attach(_opacity_place, 2, 0, 1, 2);
+
+ _swatch.add(*_table);
+ pack_start(_swatch, true, true, 0);
+
+ set_size_request (STYLE_SWATCH_WIDTH, -1);
+
+ setStyle (css);
+
+ _swatch.signal_button_press_event().connect(sigc::mem_fun(*this, &StyleSwatch::on_click));
+
+ if (main_tip)
+ {
+ _swatch.set_tooltip_text(main_tip);
+ }
+}
+
+void StyleSwatch::setClickVerb(sp_verb_t verb_t) {
+ _verb_t = verb_t;
+}
+
+void StyleSwatch::setDesktop(SPDesktop *desktop) {
+ _desktop = desktop;
+}
+
+bool
+StyleSwatch::on_click(GdkEventButton */*event*/)
+{
+ if (this->_desktop && this->_verb_t != SP_VERB_NONE) {
+ Inkscape::Verb *verb = Inkscape::Verb::get(this->_verb_t);
+ SPAction *action = verb->get_action(Inkscape::ActionContext((Inkscape::UI::View::View *) this->_desktop));
+ sp_action_perform (action, nullptr);
+ return true;
+ }
+ return false;
+}
+
+StyleSwatch::~StyleSwatch()
+{
+ if (_css)
+ sp_repr_css_attr_unref (_css);
+
+ for (int i = SS_FILL; i <= SS_STROKE; i++) {
+ delete _color_preview[i];
+ }
+
+ if (_style_obs) delete _style_obs;
+ if (_tool_obs) delete _tool_obs;
+}
+
+void
+StyleSwatch::setWatchedTool(const char *path, bool synthesize)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (_tool_obs) {
+ delete _tool_obs;
+ _tool_obs = nullptr;
+ }
+
+ if (path) {
+ _tool_path = path;
+ _tool_obs = new ToolObserver(_tool_path + "/usecurrent", *this);
+ prefs->addObserver(*_tool_obs);
+ } else {
+ _tool_path = "";
+ }
+
+ // hack until there is a real synthesize events function for prefs,
+ // which shouldn't be hard to write once there is sufficient need for it
+ if (synthesize && _tool_obs) {
+ _tool_obs->notify(prefs->getEntry(_tool_path + "/usecurrent"));
+ }
+}
+
+
+void StyleSwatch::setStyle(SPCSSAttr *css)
+{
+ if (_css)
+ sp_repr_css_attr_unref (_css);
+
+ if (!css)
+ return;
+
+ _css = sp_repr_css_attr_new();
+ sp_repr_css_merge(_css, css);
+
+ Glib::ustring css_string;
+ sp_repr_css_write_string (_css, css_string);
+
+ SPStyle style(SP_ACTIVE_DOCUMENT);
+ if (!css_string.empty()) {
+ style.mergeString(css_string.c_str());
+ }
+ setStyle (&style);
+}
+
+void StyleSwatch::setStyle(SPStyle *query)
+{
+ _place[SS_FILL].remove();
+ _place[SS_STROKE].remove();
+
+ bool has_stroke = true;
+
+ for (int i = SS_FILL; i <= SS_STROKE; i++) {
+ Gtk::EventBox *place = &(_place[i]);
+
+ SPIPaint *paint;
+ if (i == SS_FILL) {
+ paint = &(query->fill);
+ } else {
+ paint = &(query->stroke);
+ }
+
+ if (paint->set && paint->isPaintserver()) {
+ SPPaintServer *server = (i == SS_FILL)? SP_STYLE_FILL_SERVER (query) : SP_STYLE_STROKE_SERVER (query);
+
+ if (SP_IS_LINEARGRADIENT (server)) {
+ _value[i].set_markup(_("L Gradient"));
+ place->add(_value[i]);
+ place->set_tooltip_text((i == SS_FILL)? (_("Linear gradient fill")) : (_("Linear gradient stroke")));
+ } else if (SP_IS_RADIALGRADIENT (server)) {
+ _value[i].set_markup(_("R Gradient"));
+ place->add(_value[i]);
+ place->set_tooltip_text((i == SS_FILL)? (_("Radial gradient fill")) : (_("Radial gradient stroke")));
+ } else if (SP_IS_PATTERN (server)) {
+ _value[i].set_markup(_("Pattern"));
+ place->add(_value[i]);
+ place->set_tooltip_text((i == SS_FILL)? (_("Pattern fill")) : (_("Pattern stroke")));
+ }
+
+ } else if (paint->set && paint->isColor()) {
+ guint32 color = paint->value.color.toRGBA32( SP_SCALE24_TO_FLOAT ((i == SS_FILL)? query->fill_opacity.value : query->stroke_opacity.value) );
+ ((Inkscape::UI::Widget::ColorPreview*)_color_preview[i])->setRgba32 (color);
+ _color_preview[i]->show_all();
+ place->add(*_color_preview[i]);
+ gchar *tip;
+ if (i == SS_FILL) {
+ tip = g_strdup_printf (_("Fill: %06x/%.3g"), color >> 8, SP_RGBA32_A_F(color));
+ } else {
+ tip = g_strdup_printf (_("Stroke: %06x/%.3g"), color >> 8, SP_RGBA32_A_F(color));
+ }
+ place->set_tooltip_text(tip);
+ g_free (tip);
+ } else if (paint->set && paint->isNone()) {
+ _value[i].set_markup(C_("Fill and stroke", "<i>None</i>"));
+ place->add(_value[i]);
+ place->set_tooltip_text((i == SS_FILL)? (C_("Fill and stroke", "No fill")) : (C_("Fill and stroke", "No stroke")));
+ if (i == SS_STROKE) has_stroke = false;
+ } else if (!paint->set) {
+ _value[i].set_markup(_("<b>Unset</b>"));
+ place->add(_value[i]);
+ place->set_tooltip_text((i == SS_FILL)? (_("Unset fill")) : (_("Unset stroke")));
+ if (i == SS_STROKE) has_stroke = false;
+ }
+ }
+
+// Now query stroke_width
+ if (has_stroke) {
+ double w;
+ if (_sw_unit) {
+ w = Inkscape::Util::Quantity::convert(query->stroke_width.computed, "px", _sw_unit);
+ } else {
+ w = query->stroke_width.computed;
+ }
+
+ {
+ gchar *str = g_strdup_printf(" %.3g", w);
+ _stroke_width.set_markup(str);
+ g_free (str);
+ }
+ {
+ gchar *str = g_strdup_printf(_("Stroke width: %.5g%s"),
+ w,
+ _sw_unit? _sw_unit->abbr.c_str() : "px");
+ _stroke_width_place.set_tooltip_text(str);
+ g_free (str);
+ }
+ } else {
+ _stroke_width_place.set_tooltip_text("");
+ _stroke_width.set_markup("");
+ _stroke_width.set_has_tooltip(false);
+ }
+
+ gdouble op = SP_SCALE24_TO_FLOAT(query->opacity.value);
+ if (op != 1) {
+ {
+ gchar *str;
+ str = g_strdup_printf(_("O: %2.0f"), (op*100.0));
+ _opacity_value.set_markup (str);
+ g_free (str);
+ }
+ {
+ gchar *str = g_strdup_printf(_("Opacity: %2.1f %%"), (op*100.0));
+ _opacity_place.set_tooltip_text(str);
+ g_free (str);
+ }
+ } else {
+ _opacity_place.set_tooltip_text("");
+ _opacity_value.set_markup("");
+ _opacity_value.set_has_tooltip(false);
+ }
+
+ show_all();
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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-swatch.h b/src/ui/widget/style-swatch.h
new file mode 100644
index 0000000..4c7dc51
--- /dev/null
+++ b/src/ui/widget/style-swatch.h
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Static style swatch (fill, stroke, opacity)
+ */
+/* Authors:
+ * buliabyak@gmail.com
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2005-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_CURRENT_STYLE_H
+#define INKSCAPE_UI_CURRENT_STYLE_H
+
+#include <gtkmm/box.h>
+#include <gtkmm/label.h>
+#include <gtkmm/eventbox.h>
+#include <gtkmm/enums.h>
+
+#include "desktop.h"
+#include "preferences.h"
+
+class SPStyle;
+class SPCSSAttr;
+
+namespace Gtk {
+class Grid;
+}
+
+namespace Inkscape {
+
+namespace Util {
+ class Unit;
+}
+
+namespace UI {
+namespace Widget {
+
+class StyleSwatch : public Gtk::HBox
+{
+public:
+ StyleSwatch (SPCSSAttr *attr, gchar const *main_tip);
+
+ ~StyleSwatch() override;
+
+ void setStyle(SPStyle *style);
+ void setStyle(SPCSSAttr *attr);
+ SPCSSAttr *getStyle();
+
+ void setWatchedTool (const char *path, bool synthesize);
+
+ void setClickVerb(sp_verb_t verb_t);
+ void setDesktop(SPDesktop *desktop);
+ bool on_click(GdkEventButton *event);
+
+private:
+ class ToolObserver;
+ class StyleObserver;
+
+ SPDesktop *_desktop;
+ sp_verb_t _verb_t;
+ SPCSSAttr *_css;
+ ToolObserver *_tool_obs;
+ StyleObserver *_style_obs;
+ Glib::ustring _tool_path;
+
+ Gtk::EventBox _swatch;
+
+ Gtk::Grid *_table;
+
+ Gtk::Label _label[2];
+ Gtk::EventBox _place[2];
+ Gtk::EventBox _opacity_place;
+ Gtk::Label _value[2];
+ Gtk::Label _opacity_value;
+ Gtk::Widget *_color_preview[2];
+ Glib::ustring __color[2];
+ Gtk::HBox _stroke;
+ Gtk::EventBox _stroke_width_place;
+ Gtk::Label _stroke_width;
+
+ Inkscape::Util::Unit *_sw_unit;
+
+friend class ToolObserver;
+};
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_BUTTON_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/text.cpp b/src/ui/widget/text.cpp
new file mode 100644
index 0000000..656ec45
--- /dev/null
+++ b/src/ui/widget/text.cpp
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Carl Hetherington <inkscape@carlh.net>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ *
+ * Copyright (C) 2004 Carl Hetherington
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "text.h"
+#include <gtkmm/entry.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+Text::Text(Glib::ustring const &label, Glib::ustring const &tooltip,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Labelled(label, tooltip, new Gtk::Entry(), suffix, icon, mnemonic),
+ setProgrammatically(false)
+{
+}
+
+Glib::ustring const Text::getText() const
+{
+ g_assert(_widget != nullptr);
+ return static_cast<Gtk::Entry*>(_widget)->get_text();
+}
+
+void Text::setText(Glib::ustring const text)
+{
+ g_assert(_widget != nullptr);
+ setProgrammatically = true; // callback is supposed to reset back, if it cares
+ static_cast<Gtk::Entry*>(_widget)->set_text(text); // FIXME: set correctly
+}
+
+Glib::SignalProxy0<void> Text::signal_activate()
+{
+ return static_cast<Gtk::Entry*>(_widget)->signal_activate();
+}
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/text.h b/src/ui/widget/text.h
new file mode 100644
index 0000000..87c9357
--- /dev/null
+++ b/src/ui/widget/text.h
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Carl Hetherington <inkscape@carlh.net>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ *
+ * Copyright (C) 2004 Carl Hetherington
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_TEXT_H
+#define INKSCAPE_UI_WIDGET_TEXT_H
+
+#include "labelled.h"
+
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * A labelled text box, with optional icon or suffix, for entering arbitrary number values.
+ */
+class Text : public Labelled
+{
+public:
+
+ /**
+ * Construct a Text Widget.
+ *
+ * @param label Label.
+ * @param suffix Suffix, placed after the widget (defaults to "").
+ * @param icon Icon filename, placed before the label (defaults to "").
+ * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to false).
+ */
+ Text(Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ /**
+ * Get the text in the entry.
+ */
+ Glib::ustring const getText() const;
+
+ /**
+ * Sets the text of the text entry.
+ */
+ void setText(Glib::ustring const text);
+
+ void update();
+
+ /**
+ * Signal raised when the spin button's value changes.
+ */
+ Glib::SignalProxy0<void> signal_activate();
+
+ bool setProgrammatically; // true if the value was set by setValue, not changed by the user;
+ // if a callback checks it, it must reset it back to false
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_TEXT_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/tolerance-slider.cpp b/src/ui/widget/tolerance-slider.cpp
new file mode 100644
index 0000000..b1b28a7
--- /dev/null
+++ b/src/ui/widget/tolerance-slider.cpp
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ralf Stephan <ralf@ark.in-berlin.de>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "tolerance-slider.h"
+
+#include "registry.h"
+
+#include <gtkmm/adjustment.h>
+#include <gtkmm/box.h>
+#include <gtkmm/label.h>
+#include <gtkmm/radiobutton.h>
+#include <gtkmm/scale.h>
+
+#include "inkscape.h"
+#include "document.h"
+#include "document-undo.h"
+#include "desktop.h"
+
+#include "object/sp-namedview.h"
+
+#include "svg/stringstream.h"
+
+#include "xml/repr.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+//===================================================
+
+//---------------------------------------------------
+
+
+
+//====================================================
+
+ToleranceSlider::ToleranceSlider(const Glib::ustring& label1, const Glib::ustring& label2, const Glib::ustring& label3, const Glib::ustring& tip1, const Glib::ustring& tip2, const Glib::ustring& tip3, const Glib::ustring& key, Registry& wr)
+: _vbox(nullptr)
+{
+ init(label1, label2, label3, tip1, tip2, tip3, key, wr);
+}
+
+ToleranceSlider::~ToleranceSlider()
+{
+ if (_vbox) delete _vbox;
+ _scale_changed_connection.disconnect();
+}
+
+void ToleranceSlider::init (const Glib::ustring& label1, const Glib::ustring& label2, const Glib::ustring& label3, const Glib::ustring& tip1, const Glib::ustring& tip2, const Glib::ustring& tip3, const Glib::ustring& key, Registry& wr)
+{
+ // hbox = label + slider
+ //
+ // e.g.
+ //
+ // snap distance |-------X---| 37
+
+ // vbox = checkbutton
+ // +
+ // hbox
+
+ _vbox = new Gtk::VBox;
+ _hbox = Gtk::manage(new Gtk::HBox);
+
+ Gtk::Label *theLabel1 = Gtk::manage(new Gtk::Label(label1));
+ theLabel1->set_use_underline();
+ theLabel1->set_halign(Gtk::ALIGN_START);
+ theLabel1->set_valign(Gtk::ALIGN_CENTER);
+ // align the label with the checkbox text above by indenting 22 px.
+ _hbox->pack_start(*theLabel1, Gtk::PACK_EXPAND_WIDGET, 22);
+
+ _hscale = Gtk::manage(new Gtk::Scale(Gtk::ORIENTATION_HORIZONTAL));
+ _hscale->set_range(1.0, 51.0);
+
+ theLabel1->set_mnemonic_widget (*_hscale);
+ _hscale->set_draw_value (true);
+ _hscale->set_value_pos (Gtk::POS_RIGHT);
+ _hscale->set_size_request (100, -1);
+ _old_val = 10;
+ _hscale->set_value (_old_val);
+ _hscale->set_tooltip_text (tip1);
+ _hbox->add (*_hscale);
+
+
+ Gtk::Label *theLabel2 = Gtk::manage(new Gtk::Label(label2));
+ theLabel2->set_use_underline();
+ Gtk::Label *theLabel3 = Gtk::manage(new Gtk::Label(label3));
+ theLabel3->set_use_underline();
+ _button1 = Gtk::manage(new Gtk::RadioButton);
+ _radio_button_group = _button1->get_group();
+ _button2 = Gtk::manage(new Gtk::RadioButton);
+ _button2->set_group(_radio_button_group);
+ _button1->set_tooltip_text (tip2);
+ _button2->set_tooltip_text (tip3);
+ _button1->add (*theLabel3);
+ _button1->set_halign(Gtk::ALIGN_START);
+ _button1->set_valign(Gtk::ALIGN_CENTER);
+ _button2->add (*theLabel2);
+ _button2->set_halign(Gtk::ALIGN_START);
+ _button2->set_valign(Gtk::ALIGN_CENTER);
+
+ _vbox->add (*_button1);
+ _vbox->add (*_button2);
+ // Here we need some extra pixels to get the vertical spacing right. Why?
+ _vbox->pack_end(*_hbox, true, true, 3); // add 3 px.
+ _key = key;
+ _scale_changed_connection = _hscale->signal_value_changed().connect (sigc::mem_fun (*this, &ToleranceSlider::on_scale_changed));
+ _btn_toggled_connection = _button2->signal_toggled().connect (sigc::mem_fun (*this, &ToleranceSlider::on_toggled));
+ _wr = &wr;
+ _vbox->show_all_children();
+}
+
+void ToleranceSlider::setValue (double val)
+{
+ auto adj = _hscale->get_adjustment();
+
+ adj->set_lower (1.0);
+ adj->set_upper (51.0);
+ adj->set_step_increment (1.0);
+
+ if (val > 9999.9) // magic value 10000.0
+ {
+ _button1->set_active (true);
+ _button2->set_active (false);
+ _hbox->set_sensitive (false);
+ val = 50.0;
+ }
+ else
+ {
+ _button1->set_active (false);
+ _button2->set_active (true);
+ _hbox->set_sensitive (true);
+ }
+ _hscale->set_value (val);
+ _hbox->show_all();
+}
+
+void ToleranceSlider::setLimits (double theMin, double theMax)
+{
+ _hscale->set_range (theMin, theMax);
+ _hscale->get_adjustment()->set_step_increment (1);
+}
+
+void ToleranceSlider::on_scale_changed()
+{
+ update (_hscale->get_value());
+}
+
+void ToleranceSlider::on_toggled()
+{
+ if (!_button2->get_active())
+ {
+ _old_val = _hscale->get_value();
+ _hbox->set_sensitive (false);
+ _hbox->show_all();
+ setValue (10000.0);
+ update (10000.0);
+ }
+ else
+ {
+ _hbox->set_sensitive (true);
+ _hbox->show_all();
+ setValue (_old_val);
+ update (_old_val);
+ }
+}
+
+void ToleranceSlider::update (double val)
+{
+ if (_wr->isUpdating())
+ return;
+
+ SPDesktop *dt = SP_ACTIVE_DESKTOP;
+ if (!dt)
+ return;
+
+ Inkscape::SVGOStringStream os;
+ os << val;
+
+ _wr->setUpdating (true);
+
+ SPDocument *doc = dt->getDocument();
+ bool saved = DocumentUndo::getUndoSensitive(doc);
+ DocumentUndo::setUndoSensitive(doc, false);
+ Inkscape::XML::Node *repr = dt->getNamedView()->getRepr();
+ repr->setAttribute(_key, os.str());
+ DocumentUndo::setUndoSensitive(doc, saved);
+
+ doc->setModifiedSinceSave();
+
+ _wr->setUpdating (false);
+}
+
+
+} // namespace Dialog
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/tolerance-slider.h b/src/ui/widget/tolerance-slider.h
new file mode 100644
index 0000000..1c4af1d
--- /dev/null
+++ b/src/ui/widget/tolerance-slider.h
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ralf Stephan <ralf@ark.in-berlin.de>
+ *
+ * Copyright (C) 2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_TOLERANCE_SLIDER__H_
+#define INKSCAPE_UI_WIDGET_TOLERANCE_SLIDER__H_
+
+#include <gtkmm/radiobuttongroup.h>
+
+namespace Gtk {
+class RadioButton;
+class Scale;
+class VBox;
+class HBox;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class Registry;
+
+/**
+ * Implementation of tolerance slider widget.
+ * This widget is part of the Document properties dialog.
+ */
+class ToleranceSlider {
+public:
+ ToleranceSlider(const Glib::ustring& label1,
+ const Glib::ustring& label2,
+ const Glib::ustring& label3,
+ const Glib::ustring& tip1,
+ const Glib::ustring& tip2,
+ const Glib::ustring& tip3,
+ const Glib::ustring& key,
+ Registry& wr);
+ ~ToleranceSlider();
+ void setValue (double);
+ void setLimits (double, double);
+ Gtk::VBox* _vbox;
+private:
+ void init (const Glib::ustring& label1,
+ const Glib::ustring& label2,
+ const Glib::ustring& label3,
+ const Glib::ustring& tip1,
+ const Glib::ustring& tip2,
+ const Glib::ustring& tip3,
+ const Glib::ustring& key,
+ Registry& wr);
+
+protected:
+ void on_scale_changed();
+ void on_toggled();
+ void update (double val);
+ Gtk::HBox *_hbox;
+ Gtk::Scale *_hscale;
+ Gtk::RadioButtonGroup _radio_button_group;
+ Gtk::RadioButton *_button1;
+ Gtk::RadioButton *_button2;
+ Registry *_wr;
+ Glib::ustring _key;
+ sigc::connection _scale_changed_connection;
+ sigc::connection _btn_toggled_connection;
+ double _old_val;
+};
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_TOLERANCE_SLIDER__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/unit-menu.cpp b/src/ui/widget/unit-menu.cpp
new file mode 100644
index 0000000..aaf565f
--- /dev/null
+++ b/src/ui/widget/unit-menu.cpp
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Bryce Harrington <bryce@bryceharrington.org>
+ *
+ * Copyright (C) 2004 Bryce Harrington
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cmath>
+
+#include "unit-menu.h"
+
+using Inkscape::Util::unit_table;
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+UnitMenu::UnitMenu() : _type(UNIT_TYPE_NONE)
+{
+ set_active(0);
+ gtk_widget_add_events(GTK_WIDGET(gobj()), GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK);
+ signal_scroll_event().connect(sigc::mem_fun(*this, &UnitMenu::on_scroll_event));
+}
+
+UnitMenu::~UnitMenu() = default;
+
+bool UnitMenu::setUnitType(UnitType unit_type)
+{
+ // Expand the unit widget with unit entries from the unit table
+ UnitTable::UnitMap m = unit_table.units(unit_type);
+
+ for (auto & i : m) {
+ append(i.first);
+ }
+ _type = unit_type;
+ set_active_text(unit_table.primary(unit_type));
+
+ return true;
+}
+
+bool UnitMenu::resetUnitType(UnitType unit_type)
+{
+ remove_all();
+
+ return setUnitType(unit_type);
+}
+
+void UnitMenu::addUnit(Unit const& u)
+{
+ unit_table.addUnit(u, false);
+ append(u.abbr);
+}
+
+Unit const * UnitMenu::getUnit() const
+{
+ if (get_active_text() == "") {
+ g_assert(_type != UNIT_TYPE_NONE);
+ return unit_table.getUnit(unit_table.primary(_type));
+ }
+ return unit_table.getUnit(get_active_text());
+}
+
+bool UnitMenu::setUnit(Glib::ustring const & unit)
+{
+ // TODO: Determine if 'unit' is available in the dropdown.
+ // If not, return false
+
+ set_active_text(unit);
+ return true;
+}
+
+Glib::ustring UnitMenu::getUnitAbbr() const
+{
+ if (get_active_text() == "") {
+ return "";
+ }
+ return getUnit()->abbr;
+}
+
+UnitType UnitMenu::getUnitType() const
+{
+ return getUnit()->type;
+}
+
+double UnitMenu::getUnitFactor() const
+{
+ return getUnit()->factor;
+}
+
+int UnitMenu::getDefaultDigits() const
+{
+ return getUnit()->defaultDigits();
+}
+
+double UnitMenu::getDefaultStep() const
+{
+ int factor_digits = -1*int(log10(getUnit()->factor));
+ return pow(10.0, factor_digits);
+}
+
+double UnitMenu::getDefaultPage() const
+{
+ return 10 * getDefaultStep();
+}
+
+double UnitMenu::getConversion(Glib::ustring const &new_unit_abbr, Glib::ustring const &old_unit_abbr) const
+{
+ double old_factor = getUnit()->factor;
+ if (old_unit_abbr != "no_unit") {
+ old_factor = unit_table.getUnit(old_unit_abbr)->factor;
+ }
+ Unit const * new_unit = unit_table.getUnit(new_unit_abbr);
+
+ // Catch the case of zero or negative unit factors (error!)
+ if (old_factor < 0.0000001 ||
+ new_unit->factor < 0.0000001) {
+ // TODO: Should we assert here?
+ return 0.00;
+ }
+
+ return old_factor / new_unit->factor;
+}
+
+bool UnitMenu::isAbsolute() const
+{
+ return getUnitType() != UNIT_TYPE_DIMENSIONLESS;
+}
+
+bool UnitMenu::isRadial() const
+{
+ return getUnitType() == UNIT_TYPE_RADIAL;
+}
+
+bool UnitMenu::on_scroll_event(GdkEventScroll *event) { return false; }
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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/unit-menu.h b/src/ui/widget/unit-menu.h
new file mode 100644
index 0000000..b8e3ab7
--- /dev/null
+++ b/src/ui/widget/unit-menu.h
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Bryce Harrington <bryce@bryceharrington.org>
+ *
+ * Copyright (C) 2004 Bryce Harrington
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_UNIT_H
+#define INKSCAPE_UI_WIDGET_UNIT_H
+
+#include <gtkmm/comboboxtext.h>
+#include "util/units.h"
+
+using namespace Inkscape::Util;
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * A drop down menu for choosing unit types.
+ */
+class UnitMenu : public Gtk::ComboBoxText
+{
+public:
+
+ /**
+ * Construct a UnitMenu
+ */
+ UnitMenu();
+
+ ~UnitMenu() override;
+
+ /**
+ * Adds the unit type to the widget. This extracts the corresponding
+ * units from the unit map matching the given type, and appends them
+ * to the dropdown widget. It causes the primary unit for the given
+ * unit_type to be selected.
+ */
+ bool setUnitType(UnitType unit_type);
+
+ /**
+ * Removes all unit entries, then adds the unit type to the widget.
+ * This extracts the corresponding
+ * units from the unit map matching the given type, and appends them
+ * to the dropdown widget. It causes the primary unit for the given
+ * unit_type to be selected.
+ */
+ bool resetUnitType(UnitType unit_type);
+
+ /**
+ * Adds a unit, possibly user-defined, to the menu.
+ */
+ void addUnit(Unit const& u);
+
+ /**
+ * Sets the dropdown widget to the given unit abbreviation.
+ * Returns true if the unit was selectable, false if not
+ * (i.e., if the unit was not present in the widget).
+ */
+ bool setUnit(Glib::ustring const &unit);
+
+ /**
+ * Returns the Unit object corresponding to the current selection
+ * in the dropdown widget.
+ */
+ Unit const * getUnit() const;
+
+ /**
+ * Returns the abbreviated unit name of the selected unit.
+ */
+ Glib::ustring getUnitAbbr() const;
+
+ /**
+ * Returns the UnitType of the selected unit.
+ */
+ UnitType getUnitType() const;
+
+ /**
+ * Returns the unit factor for the selected unit.
+ */
+ double getUnitFactor() const;
+
+ /**
+ * Returns the recommended number of digits for displaying
+ * numbers of this unit type.
+ */
+ int getDefaultDigits() const;
+
+ /**
+ * Returns the recommended step size in spin buttons
+ * displaying units of this type.
+ */
+ double getDefaultStep() const;
+
+ /**
+ * Returns the recommended page size (when hitting pgup/pgdn)
+ * in spin buttons displaying units of this type.
+ */
+ double getDefaultPage() const;
+
+ /**
+ * Returns the conversion factor required to convert values
+ * of the currently selected unit into units of type
+ * new_unit_abbr.
+ */
+ double getConversion(Glib::ustring const &new_unit_abbr, Glib::ustring const &old_unit_abbr = "no_unit") const;
+
+ /**
+ * Returns true if the selected unit is not dimensionless
+ * (false for %, true for px, pt, cm, etc).
+ */
+ bool isAbsolute() const;
+
+ /**
+ * Returns true if the selected unit is radial (deg or rad).
+ */
+ bool isRadial() const;
+
+protected:
+ UnitType _type;
+ /**
+ * block scroll from widget if is inside a scrolled window.
+ */
+ bool on_scroll_event(GdkEventScroll *event) override;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_UNIT_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/unit-tracker.cpp b/src/ui/widget/unit-tracker.cpp
new file mode 100644
index 0000000..40d5ccf
--- /dev/null
+++ b/src/ui/widget/unit-tracker.cpp
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::UI::Widget::UnitTracker
+ * Simple mediator to synchronize changes to unit menus
+ *
+ * Authors:
+ * Jon A. Cruz <jon@joncruz.org>
+ * Matthew Petroff <matthew@mpetroff.net>
+ *
+ * Copyright (C) 2007 Jon A. Cruz
+ * Copyright (C) 2013 Matthew Petroff
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <algorithm>
+#include <iostream>
+
+#include "unit-tracker.h"
+
+#include "combo-tool-item.h"
+
+#define COLUMN_STRING 0
+
+using Inkscape::Util::UnitTable;
+using Inkscape::Util::unit_table;
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+UnitTracker::UnitTracker(UnitType unit_type) :
+ _active(0),
+ _isUpdating(false),
+ _activeUnit(nullptr),
+ _activeUnitInitialized(false),
+ _store(nullptr),
+ _priorValues()
+{
+ UnitTable::UnitMap m = unit_table.units(unit_type);
+
+ ComboToolItemColumns columns;
+ _store = Gtk::ListStore::create(columns);
+ Gtk::TreeModel::Row row;
+
+ for (auto & m_iter : m) {
+
+ Glib::ustring unit = m_iter.first;
+
+ row = *(_store->append());
+ row[columns.col_label ] = unit;
+ row[columns.col_value ] = unit;
+ row[columns.col_tooltip ] = ("");
+ row[columns.col_icon ] = "NotUsed";
+ row[columns.col_sensitive] = true;
+ }
+
+ // Why?
+ gint count = _store->children().size();
+ if ((count > 0) && (_active > count)) {
+ _setActive(--count);
+ } else {
+ _setActive(_active);
+ }
+}
+
+UnitTracker::~UnitTracker()
+{
+ _combo_list.clear();
+
+ // Unhook weak references to GtkAdjustments
+ for (auto i : _adjList) {
+ g_object_weak_unref(G_OBJECT(i), _adjustmentFinalizedCB, this);
+ }
+ _adjList.clear();
+}
+
+bool UnitTracker::isUpdating() const
+{
+ return _isUpdating;
+}
+
+Inkscape::Util::Unit const * UnitTracker::getActiveUnit() const
+{
+ return _activeUnit;
+}
+
+void UnitTracker::changeLabel(Glib::ustring new_label, gint pos, bool onlylabel)
+{
+ ComboToolItemColumns columns;
+ _store->children()[pos][columns.col_label] = new_label;
+ if (!onlylabel) {
+ _store->children()[pos][columns.col_value] = new_label;
+ }
+}
+
+void UnitTracker::setActiveUnit(Inkscape::Util::Unit const *unit)
+{
+ if (unit) {
+
+ ComboToolItemColumns columns;
+ int index = 0;
+ for (auto& row: _store->children() ) {
+ Glib::ustring storedUnit = row[columns.col_value];
+ if (!unit->abbr.compare (storedUnit)) {
+ _setActive (index);
+ break;
+ }
+ index++;
+ }
+ }
+}
+
+void UnitTracker::setActiveUnitByAbbr(gchar const *abbr)
+{
+ Inkscape::Util::Unit const *u = unit_table.getUnit(abbr);
+ setActiveUnit(u);
+}
+
+void UnitTracker::addAdjustment(GtkAdjustment *adj)
+{
+ if (std::find(_adjList.begin(),_adjList.end(),adj) == _adjList.end()) {
+ g_object_weak_ref(G_OBJECT(adj), _adjustmentFinalizedCB, this);
+ _adjList.push_back(adj);
+ } else {
+ std::cerr << "UnitTracker::addAjustment: Adjustment already added!" << std::endl;
+ }
+}
+
+void UnitTracker::addUnit(Inkscape::Util::Unit const *u)
+{
+ ComboToolItemColumns columns;
+
+ Gtk::TreeModel::Row row;
+ row = *(_store->append());
+ row[columns.col_label ] = u ? u->abbr.c_str() : "";
+ row[columns.col_value ] = u ? u->abbr.c_str() : "";
+ row[columns.col_tooltip ] = ("");
+ row[columns.col_icon ] = "NotUsed";
+ row[columns.col_sensitive] = true;
+}
+
+void UnitTracker::prependUnit(Inkscape::Util::Unit const *u)
+{
+ ComboToolItemColumns columns;
+
+ Gtk::TreeModel::Row row;
+ row = *(_store->prepend());
+ row[columns.col_label ] = u ? u->abbr.c_str() : "";
+ row[columns.col_value ] = u ? u->abbr.c_str() : "";
+ row[columns.col_tooltip ] = ("");
+ row[columns.col_icon ] = "NotUsed";
+ row[columns.col_sensitive] = true;
+
+ /* Re-shuffle our default selection here (_active gets out of sync) */
+ setActiveUnit(_activeUnit);
+
+}
+
+void UnitTracker::setFullVal(GtkAdjustment *adj, gdouble val)
+{
+ _priorValues[adj] = val;
+}
+
+ComboToolItem *
+UnitTracker::create_tool_item(Glib::ustring const &label,
+ Glib::ustring const &tooltip)
+{
+ auto combo = ComboToolItem::create(label, tooltip, "NotUsed", _store);
+ combo->set_active(_active);
+ combo->signal_changed().connect(sigc::mem_fun(*this, &UnitTracker::_unitChangedCB));
+ combo->set_data("unit-tracker", this);
+ _combo_list.push_back(combo);
+ return combo;
+}
+
+void UnitTracker::_unitChangedCB(int active)
+{
+ _setActive(active);
+}
+
+void UnitTracker::_adjustmentFinalizedCB(gpointer data, GObject *where_the_object_was)
+{
+ if (data && where_the_object_was) {
+ UnitTracker *self = reinterpret_cast<UnitTracker *>(data);
+ self->_adjustmentFinalized(where_the_object_was);
+ }
+}
+
+void UnitTracker::_adjustmentFinalized(GObject *where_the_object_was)
+{
+ GtkAdjustment* adj = (GtkAdjustment*)(where_the_object_was);
+ auto it = std::find(_adjList.begin(),_adjList.end(), adj);
+ if (it != _adjList.end()) {
+ _adjList.erase(it);
+ } else {
+ g_warning("Received a finalization callback for unknown object %p", where_the_object_was);
+ }
+}
+
+void UnitTracker::_setActive(gint active)
+{
+ if ( active != _active || !_activeUnitInitialized ) {
+ gint oldActive = _active;
+
+ if (_store) {
+
+ // Find old and new units
+ ComboToolItemColumns columns;
+ int index = 0;
+ Glib::ustring oldAbbr( "NotFound" );
+ Glib::ustring newAbbr( "NotFound" );
+ for (auto& row: _store->children() ) {
+ if (index == _active) {
+ oldAbbr = row[columns.col_value];
+ }
+ if (index == active) {
+ newAbbr = row[columns.col_value];
+ }
+ if (newAbbr != "NotFound" && oldAbbr != "NotFound") break;
+ ++index;
+ }
+
+ if (oldAbbr != "NotFound") {
+
+ if (newAbbr != "NotFound") {
+ Inkscape::Util::Unit const *oldUnit = unit_table.getUnit(oldAbbr);
+ Inkscape::Util::Unit const *newUnit = unit_table.getUnit(newAbbr);
+ _activeUnit = newUnit;
+
+ if (!_adjList.empty()) {
+ _fixupAdjustments(oldUnit, newUnit);
+ }
+ } else {
+ std::cerr << "UnitTracker::_setActive: Did not find new unit: " << active << std::endl;
+ }
+
+ } else {
+ std::cerr << "UnitTracker::_setActive: Did not find old unit: " << oldActive
+ << " new: " << active << std::endl;
+ }
+ }
+ _active = active;
+
+ for (auto combo : _combo_list) {
+ if(combo) combo->set_active(active);
+ }
+
+ _activeUnitInitialized = true;
+ }
+}
+
+void UnitTracker::_fixupAdjustments(Inkscape::Util::Unit const *oldUnit, Inkscape::Util::Unit const *newUnit)
+{
+ _isUpdating = true;
+ for ( auto adj : _adjList ) {
+ gdouble oldVal = gtk_adjustment_get_value(adj);
+ gdouble val = oldVal;
+
+ if ( (oldUnit->type != Inkscape::Util::UNIT_TYPE_DIMENSIONLESS)
+ && (newUnit->type == Inkscape::Util::UNIT_TYPE_DIMENSIONLESS) )
+ {
+ val = newUnit->factor * 100;
+ _priorValues[adj] = Inkscape::Util::Quantity::convert(oldVal, oldUnit, "px");
+ } else if ( (oldUnit->type == Inkscape::Util::UNIT_TYPE_DIMENSIONLESS)
+ && (newUnit->type != Inkscape::Util::UNIT_TYPE_DIMENSIONLESS) )
+ {
+ if (_priorValues.find(adj) != _priorValues.end()) {
+ val = Inkscape::Util::Quantity::convert(_priorValues[adj], "px", newUnit);
+ }
+ } else {
+ val = Inkscape::Util::Quantity::convert(oldVal, oldUnit, newUnit);
+ }
+
+ gtk_adjustment_set_value(adj, val);
+ }
+ _isUpdating = false;
+}
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ 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 :
diff --git a/src/ui/widget/unit-tracker.h b/src/ui/widget/unit-tracker.h
new file mode 100644
index 0000000..b85da06
--- /dev/null
+++ b/src/ui/widget/unit-tracker.h
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::UI::Widget::UnitTracker
+ * Simple mediator to synchronize changes to unit menus
+ *
+ * Authors:
+ * Jon A. Cruz <jon@joncruz.org>
+ * Matthew Petroff <matthew@mpetroff.net>
+ *
+ * Copyright (C) 2007 Jon A. Cruz
+ * Copyright (C) 2013 Matthew Petroff
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_UNIT_TRACKER_H
+#define INKSCAPE_UI_WIDGET_UNIT_TRACKER_H
+
+#include <map>
+#include <vector>
+
+#include <gtkmm/liststore.h>
+
+#include "util/units.h"
+
+using Inkscape::Util::Unit;
+using Inkscape::Util::UnitType;
+
+typedef struct _GObject GObject;
+typedef struct _GtkAdjustment GtkAdjustment;
+typedef struct _GtkListStore GtkListStore;
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+class ComboToolItem;
+
+class UnitTracker {
+public:
+ UnitTracker(UnitType unit_type);
+ virtual ~UnitTracker();
+
+ bool isUpdating() const;
+
+ void setActiveUnit(Inkscape::Util::Unit const *unit);
+ void setActiveUnitByAbbr(gchar const *abbr);
+ Inkscape::Util::Unit const * getActiveUnit() const;
+
+ void addUnit(Inkscape::Util::Unit const *u);
+ void addAdjustment(GtkAdjustment *adj);
+ void prependUnit(Inkscape::Util::Unit const *u);
+ void setFullVal(GtkAdjustment *adj, gdouble val);
+ void changeLabel(Glib::ustring new_label, gint pos, bool onlylabel = false);
+
+ ComboToolItem *create_tool_item(Glib::ustring const &label,
+ Glib::ustring const &tooltip);
+
+protected:
+ UnitType _type;
+
+private:
+ // Callbacks
+ void _unitChangedCB(int active);
+ static void _adjustmentFinalizedCB(gpointer data, GObject *where_the_object_was);
+
+ void _setActive(gint index);
+ void _fixupAdjustments(Inkscape::Util::Unit const *oldUnit, Inkscape::Util::Unit const *newUnit);
+
+ // Cleanup
+ void _adjustmentFinalized(GObject *where_the_object_was);
+
+ gint _active;
+ bool _isUpdating;
+ Inkscape::Util::Unit const *_activeUnit;
+ bool _activeUnitInitialized;
+
+ Glib::RefPtr<Gtk::ListStore> _store;
+ std::vector<ComboToolItem *> _combo_list;
+ std::vector<GtkAdjustment*> _adjList;
+ std::map <GtkAdjustment *, gdouble> _priorValues;
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_UNIT_TRACKER_H