summaryrefslogtreecommitdiffstats
path: root/src/extension/prefdialog
diff options
context:
space:
mode:
Diffstat (limited to 'src/extension/prefdialog')
-rw-r--r--src/extension/prefdialog/parameter-bool.cpp141
-rw-r--r--src/extension/prefdialog/parameter-bool.h77
-rw-r--r--src/extension/prefdialog/parameter-color.cpp146
-rw-r--r--src/extension/prefdialog/parameter-color.h77
-rw-r--r--src/extension/prefdialog/parameter-float.cpp191
-rw-r--r--src/extension/prefdialog/parameter-float.h79
-rw-r--r--src/extension/prefdialog/parameter-int.cpp189
-rw-r--r--src/extension/prefdialog/parameter-int.h74
-rw-r--r--src/extension/prefdialog/parameter-notebook.cpp285
-rw-r--r--src/extension/prefdialog/parameter-notebook.h86
-rw-r--r--src/extension/prefdialog/parameter-optiongroup.cpp351
-rw-r--r--src/extension/prefdialog/parameter-optiongroup.h103
-rw-r--r--src/extension/prefdialog/parameter-path.cpp268
-rw-r--r--src/extension/prefdialog/parameter-path.h75
-rw-r--r--src/extension/prefdialog/parameter-string.cpp228
-rw-r--r--src/extension/prefdialog/parameter-string.h66
-rw-r--r--src/extension/prefdialog/parameter.cpp306
-rw-r--r--src/extension/prefdialog/parameter.h162
-rw-r--r--src/extension/prefdialog/prefdialog.cpp237
-rw-r--r--src/extension/prefdialog/prefdialog.h91
-rw-r--r--src/extension/prefdialog/widget-box.cpp115
-rw-r--r--src/extension/prefdialog/widget-box.h60
-rw-r--r--src/extension/prefdialog/widget-image.cpp87
-rw-r--r--src/extension/prefdialog/widget-image.h61
-rw-r--r--src/extension/prefdialog/widget-label.cpp121
-rw-r--r--src/extension/prefdialog/widget-label.h65
-rw-r--r--src/extension/prefdialog/widget-separator.cpp43
-rw-r--r--src/extension/prefdialog/widget-separator.h53
-rw-r--r--src/extension/prefdialog/widget-spacer.cpp62
-rw-r--r--src/extension/prefdialog/widget-spacer.h60
-rw-r--r--src/extension/prefdialog/widget.cpp178
-rw-r--r--src/extension/prefdialog/widget.h149
32 files changed, 4286 insertions, 0 deletions
diff --git a/src/extension/prefdialog/parameter-bool.cpp b/src/extension/prefdialog/parameter-bool.cpp
new file mode 100644
index 0000000..609e727
--- /dev/null
+++ b/src/extension/prefdialog/parameter-bool.cpp
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-bool.h"
+
+#include <gtkmm/box.h>
+#include <gtkmm/checkbutton.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+#include "preferences.h"
+
+namespace Inkscape {
+namespace Extension {
+
+ParamBool::ParamBool(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // get value
+ if (xml->firstChild()) {
+ const char *value = xml->firstChild()->content();
+ if (value) {
+ if (!strcmp(value, "true")) {
+ _value = true;
+ } else if (!strcmp(value, "false")) {
+ _value = false;
+ } else {
+ g_warning("Invalid default value ('%s') for parameter '%s' in extension '%s'",
+ value, _name, _extension->get_id());
+ }
+ }
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getBool(pref_name(), _value);
+}
+
+bool ParamBool::set(bool in)
+{
+ _value = in;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool(pref_name(), _value);
+
+ return _value;
+}
+
+bool ParamBool::get() const
+{
+ return _value;
+}
+
+/**
+ * A check button which is Param aware. It works with the
+ * parameter to change it's value as the check button changes
+ * value.
+ */
+class ParamBoolCheckButton : public Gtk::CheckButton {
+public:
+ /**
+ * Initialize the check button.
+ * This function sets the value of the checkbox to be that of the
+ * parameter, and then sets up a callback to \c on_toggle.
+ *
+ * @param param Which parameter to adjust on changing the check button
+ */
+ ParamBoolCheckButton(ParamBool *param, char *label, sigc::signal<void> *changeSignal)
+ : Gtk::CheckButton(label)
+ , _pref(param)
+ , _changeSignal(changeSignal) {
+ this->set_active(_pref->get());
+ this->signal_toggled().connect(sigc::mem_fun(this, &ParamBoolCheckButton::on_toggle));
+ return;
+ }
+
+ /**
+ * A function to respond to the check box changing.
+ * Adjusts the value of the preference to match that in the check box.
+ */
+ void on_toggle ();
+
+private:
+ /** Param to change. */
+ ParamBool *_pref;
+ sigc::signal<void> *_changeSignal;
+};
+
+void ParamBoolCheckButton::on_toggle()
+{
+ _pref->set(this->get_active());
+ if (_changeSignal != nullptr) {
+ _changeSignal->emit();
+ }
+ return;
+}
+
+std::string ParamBool::value_to_string() const
+{
+ if (_value) {
+ return "true";
+ }
+ return "false";
+}
+
+Gtk::Widget *ParamBool::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ auto hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING));
+ hbox->set_homogeneous(false);
+
+ ParamBoolCheckButton * checkbox = Gtk::manage(new ParamBoolCheckButton(this, _text, changeSignal));
+ checkbox->show();
+ hbox->pack_start(*checkbox, false, false);
+
+ hbox->show();
+
+ return dynamic_cast<Gtk::Widget *>(hbox);
+}
+
+} /* namespace Extension */
+} /* 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/extension/prefdialog/parameter-bool.h b/src/extension/prefdialog/parameter-bool.h
new file mode 100644
index 0000000..52fe06a
--- /dev/null
+++ b/src/extension/prefdialog/parameter-bool.h
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INK_EXTENSION_PARAMBOOL_H
+#define SEEN_INK_EXTENSION_PARAMBOOL_H
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+
+namespace Gtk {
+class Widget;
+}
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+
+namespace Extension {
+
+/**
+ * A boolean parameter.
+ */
+class ParamBool : public InxParameter {
+public:
+ ParamBool(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ /**
+ * Returns the current state/value.
+ */
+ bool get() const;
+
+ /**
+ * A function to set the state/value.
+ * This function sets the internal value, but it also sets the value
+ * in the preferences structure. To put it in the right place pref_name() is used.
+ *
+ * @param in The value to set to
+ */
+ bool set(bool in);
+
+ /**
+ * Creates a bool check button for a bool parameter.
+ * Builds a hbox with a label and a check button in it.
+ */
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ /**
+ * Appends 'true' or 'false'.
+ * @todo investigate. Returning a value that can then be appended would probably work better/safer.
+ */
+ std::string value_to_string() const override;
+
+private:
+ /** Internal value. */
+ bool _value = true;
+};
+
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // SEEN_INK_EXTENSION_PARAMBOOL_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/extension/prefdialog/parameter-color.cpp b/src/extension/prefdialog/parameter-color.cpp
new file mode 100644
index 0000000..b262afd
--- /dev/null
+++ b/src/extension/prefdialog/parameter-color.cpp
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl>
+ * Christopher Brown <audiere@gmail.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-color.h"
+
+#include <iostream>
+#include <sstream>
+
+#include <gtkmm/box.h>
+#include <gtkmm/colorbutton.h>
+#include <gtkmm/label.h>
+
+#include "color.h"
+#include "preferences.h"
+
+#include "extension/extension.h"
+
+#include "ui/widget/color-notebook.h"
+
+#include "xml/node.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+ParamColor::ParamColor(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // get value
+ unsigned int _value = 0x000000ff; // default to black
+ if (xml->firstChild()) {
+ const char *value = xml->firstChild()->content();
+ if (value) {
+ _value = strtoul(value, nullptr, 0);
+ }
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getUInt(pref_name(), _value);
+
+ _color.setValue(_value);
+
+ _color_changed = _color.signal_changed.connect(sigc::mem_fun(this, &ParamColor::_onColorChanged));
+ // TODO: SelectedColor does not properly emit signal_changed after dragging, so we also need the following
+ _color_released = _color.signal_released.connect(sigc::mem_fun(this, &ParamColor::_onColorChanged));
+
+ // parse appearance
+ if (_appearance) {
+ if (!strcmp(_appearance, "colorbutton")) {
+ _mode = COLOR_BUTTON;
+ } else {
+ g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'",
+ _appearance, _name, _extension->get_id());
+ }
+ }
+}
+
+ParamColor::~ParamColor()
+{
+ _color_changed.disconnect();
+ _color_released.disconnect();
+}
+
+unsigned int ParamColor::set(unsigned int in)
+{
+ _color.setValue(in);
+
+ return in;
+}
+
+Gtk::Widget *ParamColor::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ if (changeSignal) {
+ _changeSignal = new sigc::signal<void>(*changeSignal);
+ }
+
+ Gtk::Box *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING));
+ if (_mode == COLOR_BUTTON) {
+ Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START));
+ label->show();
+ hbox->pack_start(*label, true, true);
+
+ Gdk::RGBA rgba;
+ rgba.set_red_u (((_color.value() >> 24) & 255) << 8);
+ rgba.set_green_u(((_color.value() >> 16) & 255) << 8);
+ rgba.set_blue_u (((_color.value() >> 8) & 255) << 8);
+ rgba.set_alpha_u(((_color.value() >> 0) & 255) << 8);
+
+ // TODO: It would be nicer to have a custom Gtk::ColorButton() implementation here,
+ // that wraps an Inkscape::UI::Widget::ColorNotebook into a new dialog
+ _color_button = Gtk::manage(new Gtk::ColorButton(rgba));
+ _color_button->set_title(_text);
+ _color_button->set_use_alpha();
+ _color_button->show();
+ hbox->pack_end(*_color_button, false, false);
+
+ _color_button->signal_color_set().connect(sigc::mem_fun(this, &ParamColor::_onColorButtonChanged));
+ } else {
+ Gtk::Widget *selector = Gtk::manage(new Inkscape::UI::Widget::ColorNotebook(_color));
+ hbox->pack_start(*selector, true, true, 0);
+ selector->show();
+ }
+ hbox->show();
+ return hbox;
+
+}
+
+void ParamColor::_onColorChanged()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setUInt(pref_name(), _color.value());
+
+ if (_changeSignal)
+ _changeSignal->emit();
+}
+
+void ParamColor::_onColorButtonChanged()
+{
+ Gdk::RGBA rgba = _color_button->get_rgba();
+ unsigned int value = ((rgba.get_red_u() >> 8) << 24) +
+ ((rgba.get_green_u() >> 8) << 16) +
+ ((rgba.get_blue_u() >> 8) << 8) +
+ ((rgba.get_alpha_u() >> 8) << 0);
+ set(value);
+}
+
+std::string ParamColor::value_to_string() const
+{
+ char value_string[16];
+ snprintf(value_string, 16, "%u", _color.value());
+ return value_string;
+}
+
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/prefdialog/parameter-color.h b/src/extension/prefdialog/parameter-color.h
new file mode 100644
index 0000000..4fef4b6
--- /dev/null
+++ b/src/extension/prefdialog/parameter-color.h
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INK_EXTENSION_PARAMCOLOR_H__
+#define SEEN_INK_EXTENSION_PARAMCOLOR_H__
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+#include "ui/selected-color.h"
+
+namespace Gtk {
+class Widget;
+class ColorButton;
+}
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+
+namespace Extension {
+
+class ParamColor : public InxParameter {
+public:
+ enum AppearanceMode {
+ DEFAULT, COLOR_BUTTON
+ };
+
+ ParamColor(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+ ~ParamColor() override;
+
+ /** Returns \c _value, with a \i const to protect it. */
+ unsigned int get() const { return _color.value(); }
+
+ unsigned int set(unsigned int in);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ std::string value_to_string() const override;
+
+ sigc::signal<void> *_changeSignal;
+
+private:
+ void _onColorChanged();
+ void _onColorButtonChanged();
+
+ /** Internal value of this parameter */
+ Inkscape::UI::SelectedColor _color;
+
+ sigc::connection _color_changed;
+ sigc::connection _color_released;
+
+ Gtk::ColorButton *_color_button;
+
+ /** appearance mode **/
+ AppearanceMode _mode = DEFAULT;
+}; // class ParamColor
+
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // SEEN_INK_EXTENSION_PARAMCOLOR_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/extension/prefdialog/parameter-float.cpp b/src/extension/prefdialog/parameter-float.cpp
new file mode 100644
index 0000000..7d8c843
--- /dev/null
+++ b/src/extension/prefdialog/parameter-float.cpp
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-float.h"
+
+#include <iomanip>
+
+#include <gtkmm/adjustment.h>
+#include <gtkmm/box.h>
+
+#include "preferences.h"
+
+#include "extension/extension.h"
+
+#include "ui/widget/spin-scale.h"
+#include "ui/widget/spinbutton.h"
+
+#include "xml/node.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+ParamFloat::ParamFloat(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // get value
+ if (xml->firstChild()) {
+ const char *value = xml->firstChild()->content();
+ if (value) {
+ _value = g_ascii_strtod(value, nullptr);
+ }
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getDouble(pref_name(), _value);
+
+ // parse and apply limits
+ const char *min = xml->attribute("min");
+ if (min) {
+ _min = g_ascii_strtod(min, nullptr);
+ }
+
+ const char *max = xml->attribute("max");
+ if (max) {
+ _max = g_ascii_strtod(max, nullptr);
+ }
+
+ if (_value < _min) {
+ _value = _min;
+ }
+
+ if (_value > _max) {
+ _value = _max;
+ }
+
+ // parse precision
+ const char *precision = xml->attribute("precision");
+ if (precision != nullptr) {
+ _precision = strtol(precision, nullptr, 0);
+ }
+
+
+ // parse appearance
+ if (_appearance) {
+ if (!strcmp(_appearance, "full")) {
+ _mode = FULL;
+ } else {
+ g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'",
+ _appearance, _name, _extension->get_id());
+ }
+ }
+}
+
+/**
+ * A function to set the \c _value.
+ *
+ * This function sets the internal value, but it also sets the value
+ * in the preferences structure. To put it in the right place \c pref_name() is used.
+ *
+ * @param in The value to set to.
+ */
+double ParamFloat::set(double in)
+{
+ _value = in;
+ if (_value > _max) {
+ _value = _max;
+ }
+ if (_value < _min) {
+ _value = _min;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble(pref_name(), _value);
+
+ return _value;
+}
+
+std::string ParamFloat::value_to_string() const
+{
+ static constexpr auto digits10 = std::numeric_limits<double>::digits10; // number of decimal digits that are ensured to be precise
+ return Glib::ustring::format(std::setprecision(digits10), _value);
+}
+
+/** A class to make an adjustment that uses Extension params. */
+class ParamFloatAdjustment : public Gtk::Adjustment {
+ /** The parameter to adjust. */
+ ParamFloat *_pref;
+ sigc::signal<void> *_changeSignal;
+public:
+ /** Make the adjustment using an extension and the string
+ describing the parameter. */
+ ParamFloatAdjustment(ParamFloat *param, sigc::signal<void> *changeSignal)
+ : Gtk::Adjustment(0.0, param->min(), param->max(), 0.1, 1.0, 0)
+ , _pref(param)
+ , _changeSignal(changeSignal) {
+ this->set_value(_pref->get());
+ this->signal_value_changed().connect(sigc::mem_fun(this, &ParamFloatAdjustment::val_changed));
+ return;
+ };
+
+ void val_changed ();
+}; /* class ParamFloatAdjustment */
+
+/**
+ * A function to respond to the value_changed signal from the adjustment.
+ *
+ * This function just grabs the value from the adjustment and writes
+ * it to the parameter. Very simple, but yet beautiful.
+ */
+void ParamFloatAdjustment::val_changed()
+{
+ _pref->set(this->get_value());
+ if (_changeSignal != nullptr) {
+ _changeSignal->emit();
+ }
+ return;
+}
+
+/**
+ * Creates a Float Adjustment for a float parameter.
+ *
+ * Builds a hbox with a label and a float adjustment in it.
+ */
+Gtk::Widget *ParamFloat::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Box *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING));
+
+ auto pfa = new ParamFloatAdjustment(this, changeSignal);
+ Glib::RefPtr<Gtk::Adjustment> fadjust(pfa);
+
+ if (_mode == FULL) {
+
+ Glib::ustring text;
+ if (_text != nullptr)
+ text = _text;
+ UI::Widget::SpinScale *scale = Gtk::manage(new UI::Widget::SpinScale(text, fadjust, _precision));
+ scale->set_size_request(400, -1);
+ scale->show();
+ hbox->pack_start(*scale, true, true);
+
+ }
+ else if (_mode == DEFAULT) {
+
+ Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START));
+ label->show();
+ hbox->pack_start(*label, true, true);
+
+ auto spin = Gtk::manage(new Inkscape::UI::Widget::SpinButton(fadjust, 0.1, _precision));
+ spin->show();
+ hbox->pack_start(*spin, false, false);
+ }
+
+ hbox->show();
+
+ return dynamic_cast<Gtk::Widget *>(hbox);
+}
+
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/parameter-float.h b/src/extension/prefdialog/parameter-float.h
new file mode 100644
index 0000000..a74470a
--- /dev/null
+++ b/src/extension/prefdialog/parameter-float.h
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INK_EXTENSION_PARAMFLOAT_H_SEEN
+#define INK_EXTENSION_PARAMFLOAT_H_SEEN
+
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+
+namespace Gtk {
+class Widget;
+}
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+
+namespace Extension {
+
+class ParamFloat : public InxParameter {
+public:
+ enum AppearanceMode {
+ DEFAULT, FULL
+ };
+
+ ParamFloat(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ /** Returns \c _value. */
+ double get() const { return _value; }
+
+ double set(double in);
+
+ double max () { return _max; }
+
+ double min () { return _min; }
+
+ double precision () { return _precision; }
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ std::string value_to_string() const override;
+
+private:
+ /** Internal value. */
+ double _value = 0;
+
+ /** limits */
+ // TODO: do these defaults make sense or should we be unbounded by default?
+ double _min = 0;
+ double _max = 10;
+
+ /** numeric precision (i.e. number of digits) */
+ int _precision = 1;
+
+ /** appearance mode **/
+ AppearanceMode _mode = DEFAULT;
+};
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* INK_EXTENSION_PARAMFLOAT_H_SEEN */
+
+/*
+ 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/extension/prefdialog/parameter-int.cpp b/src/extension/prefdialog/parameter-int.cpp
new file mode 100644
index 0000000..c94b75b
--- /dev/null
+++ b/src/extension/prefdialog/parameter-int.cpp
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-int.h"
+
+#include <gtkmm/adjustment.h>
+#include <gtkmm/box.h>
+
+#include "preferences.h"
+
+#include "extension/extension.h"
+
+#include "ui/widget/spinbutton.h"
+#include "ui/widget/spin-scale.h"
+
+#include "xml/node.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+
+ParamInt::ParamInt(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // get value
+ if (xml->firstChild()) {
+ const char *value = xml->firstChild()->content();
+ if (value) {
+ _value = strtol(value, nullptr, 0);
+ }
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getInt(pref_name(), _value);
+
+ // parse and apply limits
+ const char *min = xml->attribute("min");
+ if (min) {
+ _min = strtol(min, nullptr, 0);
+ }
+
+ const char *max = xml->attribute("max");
+ if (max) {
+ _max = strtol(max, nullptr, 0);
+ }
+
+ if (_value < _min) {
+ _value = _min;
+ }
+
+ if (_value > _max) {
+ _value = _max;
+ }
+
+ // parse appearance
+ if (_appearance) {
+ if (!strcmp(_appearance, "full")) {
+ _mode = FULL;
+ } else {
+ g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'",
+ _appearance, _name, _extension->get_id());
+ }
+ }
+}
+
+/**
+ * A function to set the \c _value.
+ * This function sets the internal value, but it also sets the value
+ * in the preferences structure. To put it in the right place \c pref_name() is used.
+ *
+ * @param in The value to set to.
+ */
+int ParamInt::set(int in)
+{
+ _value = in;
+ if (_value > _max) {
+ _value = _max;
+ }
+ if (_value < _min) {
+ _value = _min;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt(pref_name(), _value);
+
+ return _value;
+}
+
+/** A class to make an adjustment that uses Extension params. */
+class ParamIntAdjustment : public Gtk::Adjustment {
+ /** The parameter to adjust. */
+ ParamInt *_pref;
+ sigc::signal<void> *_changeSignal;
+public:
+ /** Make the adjustment using an extension and the string describing the parameter. */
+ ParamIntAdjustment(ParamInt *param, sigc::signal<void> *changeSignal)
+ : Gtk::Adjustment(0.0, param->min(), param->max(), 1.0, 10.0, 0)
+ , _pref(param)
+ , _changeSignal(changeSignal)
+ {
+ this->set_value(_pref->get());
+ this->signal_value_changed().connect(sigc::mem_fun(this, &ParamIntAdjustment::val_changed));
+ };
+
+ void val_changed ();
+}; /* class ParamIntAdjustment */
+
+/**
+ * A function to respond to the value_changed signal from the adjustment.
+ *
+ * This function just grabs the value from the adjustment and writes
+ * it to the parameter. Very simple, but yet beautiful.
+ */
+void ParamIntAdjustment::val_changed()
+{
+ _pref->set((int)this->get_value());
+ if (_changeSignal != nullptr) {
+ _changeSignal->emit();
+ }
+}
+
+/**
+ * Creates a Int Adjustment for a int parameter.
+ *
+ * Builds a hbox with a label and a int adjustment in it.
+ */
+Gtk::Widget *
+ParamInt::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Box *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING));
+
+ auto pia = new ParamIntAdjustment(this, changeSignal);
+ Glib::RefPtr<Gtk::Adjustment> fadjust(pia);
+
+ if (_mode == FULL) {
+
+ Glib::ustring text;
+ if (_text != nullptr)
+ text = _text;
+ UI::Widget::SpinScale *scale = Gtk::manage(new UI::Widget::SpinScale(text, fadjust, 0));
+ scale->set_size_request(400, -1);
+ scale->show();
+ hbox->pack_start(*scale, true, true);
+ } else if (_mode == DEFAULT) {
+ Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START));
+ label->show();
+ hbox->pack_start(*label, true, true);
+
+ auto spin = Gtk::manage(new Inkscape::UI::Widget::SpinButton(fadjust, 1.0, 0));
+ spin->show();
+ hbox->pack_start(*spin, false, false);
+ }
+
+ hbox->show();
+
+ return dynamic_cast<Gtk::Widget *>(hbox);
+}
+
+std::string ParamInt::value_to_string() const
+{
+ char value_string[32];
+ snprintf(value_string, 32, "%d", _value);
+ return value_string;
+}
+
+} // namespace Extension
+} // 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/extension/prefdialog/parameter-int.h b/src/extension/prefdialog/parameter-int.h
new file mode 100644
index 0000000..da43eb7
--- /dev/null
+++ b/src/extension/prefdialog/parameter-int.h
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INK_EXTENSION_PARAMINT_H_SEEN
+#define INK_EXTENSION_PARAMINT_H_SEEN
+
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+
+namespace Gtk {
+class Widget;
+}
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+
+namespace Extension {
+
+class ParamInt : public InxParameter {
+public:
+ enum AppearanceMode {
+ DEFAULT, FULL
+ };
+
+ ParamInt(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ /** Returns \c _value. */
+ int get() const { return _value; }
+
+ int set(int in);
+
+ int max () { return _max; }
+
+ int min () { return _min; }
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ std::string value_to_string() const override;
+
+private:
+ /** Internal value. */
+ int _value = 0;
+
+ /** limits */
+ // TODO: do these defaults make sense or should we be unbounded by default?
+ int _min = 0;
+ int _max = 10;
+
+ /** appearance mode **/
+ AppearanceMode _mode = DEFAULT;
+};
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* INK_EXTENSION_PARAMINT_H_SEEN */
+
+/*
+ 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/extension/prefdialog/parameter-notebook.cpp b/src/extension/prefdialog/parameter-notebook.cpp
new file mode 100644
index 0000000..2f431db
--- /dev/null
+++ b/src/extension/prefdialog/parameter-notebook.cpp
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ * Notebook and NotebookPage parameters for extensions.
+ */
+
+/*
+ * Authors:
+ * Johan Engelen <johan@shouraizou.nl>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2006 Author
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-notebook.h"
+
+#include <unordered_set>
+
+#include <gtkmm/box.h>
+#include <gtkmm/notebook.h>
+
+#include "preferences.h"
+
+#include "extension/extension.h"
+
+#include "xml/node.h"
+
+namespace Inkscape {
+namespace Extension {
+
+
+ParamNotebook::ParamNotebookPage::ParamNotebookPage(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // Read XML tree of page and parse parameters
+ if (xml) {
+ Inkscape::XML::Node *child_repr = xml->firstChild();
+ while (child_repr) {
+ const char *chname = child_repr->name();
+ if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) {
+ chname += strlen(INKSCAPE_EXTENSION_NS);
+ }
+ if (chname[0] == '_') { // allow leading underscore in tag names for backwards-compatibility
+ chname++;
+ }
+
+ if (InxWidget::is_valid_widget_name(chname)) {
+ InxWidget *widget = InxWidget::make(child_repr, _extension);
+ if (widget) {
+ _children.push_back(widget);
+ }
+ } else if (child_repr->type() == XML::NodeType::ELEMENT_NODE) {
+ g_warning("Invalid child element ('%s') in notebook page in extension '%s'.",
+ chname, _extension->get_id());
+ } else if (child_repr->type() != XML::NodeType::COMMENT_NODE){
+ g_warning("Invalid child element found in notebook page in extension '%s'.", _extension->get_id());
+ }
+
+ child_repr = child_repr->next();
+ }
+ }
+}
+
+
+/**
+ * Creates a notebookpage widget for a notebook.
+ *
+ * Builds a notebook page (a vbox) and puts parameters on it.
+ */
+Gtk::Widget *ParamNotebook::ParamNotebookPage::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Box * vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
+ vbox->set_border_width(GUI_BOX_MARGIN);
+ vbox->set_spacing(GUI_BOX_SPACING);
+
+ // add parameters onto page (if any)
+ for (auto child : _children) {
+ Gtk::Widget *child_widget = child->get_widget(changeSignal);
+ if (child_widget) {
+ int indent = child->get_indent();
+ child_widget->set_margin_start(indent *GUI_INDENTATION);
+ vbox->pack_start(*child_widget, false, true, 0); // fill=true does not have an effect here, but allows the
+ // child to choose to expand by setting hexpand/vexpand
+
+ const char *tooltip = child->get_tooltip();
+ if (tooltip) {
+ child_widget->set_tooltip_text(tooltip);
+ }
+ }
+ }
+
+ vbox->show();
+
+ return dynamic_cast<Gtk::Widget *>(vbox);
+}
+
+/** End ParamNotebookPage **/
+
+
+
+/** ParamNotebook **/
+
+ParamNotebook::ParamNotebook(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // Read XML tree to add pages (allow _page for backwards compatibility)
+ if (xml) {
+ Inkscape::XML::Node *child_repr = xml->firstChild();
+ while (child_repr) {
+ const char *chname = child_repr->name();
+ if (chname && (!strcmp(chname, INKSCAPE_EXTENSION_NS "page") ||
+ !strcmp(chname, INKSCAPE_EXTENSION_NS "_page") )) {
+ ParamNotebookPage *page;
+ page = new ParamNotebookPage(child_repr, ext);
+
+ if (page) {
+ _children.push_back(page);
+ }
+ } else if (child_repr->type() == XML::NodeType::ELEMENT_NODE) {
+ g_warning("Invalid child element ('%s') for parameter '%s' in extension '%s'. Expected 'page'.",
+ chname, _name, _extension->get_id());
+ } else if (child_repr->type() != XML::NodeType::COMMENT_NODE){
+ g_warning("Invalid child element found in parameter '%s' in extension '%s'. Expected 'page'.",
+ _name, _extension->get_id());
+ }
+ child_repr = child_repr->next();
+ }
+ }
+ if (_children.empty()) {
+ g_warning("No (valid) pages for parameter '%s' in extension '%s'", _name, _extension->get_id());
+ }
+
+ // check for duplicate page names
+ std::unordered_set<std::string> names;
+ for (auto child : _children) {
+ ParamNotebookPage *page = static_cast<ParamNotebookPage *>(child);
+ auto ret = names.emplace(page->_name);
+ if (!ret.second) {
+ g_warning("Duplicate page name ('%s') for parameter '%s' in extension '%s'.",
+ page->_name, _name, _extension->get_id());
+ }
+ }
+
+ // get value (initialize with value of first page if pref is empty)
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getString(pref_name());
+
+ if (_value.empty()) {
+ if (!_children.empty()) {
+ ParamNotebookPage *first_page = dynamic_cast<ParamNotebookPage *>(_children[0]);
+ _value = first_page->_name;
+ }
+ }
+}
+
+
+/**
+ * A function to set the \c _value.
+ *
+ * This function sets the internal value, but it also sets the value
+ * in the preferences structure. To put it in the right place \c pref_name() is used.
+ *
+ * @param in The number of the page to set as new value.
+ */
+const Glib::ustring& ParamNotebook::set(const int in)
+{
+ int i = in < _children.size() ? in : _children.size()-1;
+ ParamNotebookPage *page = dynamic_cast<ParamNotebookPage *>(_children[i]);
+
+ if (page) {
+ _value = page->_name;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString(pref_name(), _value);
+ }
+
+ return _value;
+}
+
+std::string ParamNotebook::value_to_string() const
+{
+ return _value.raw();
+}
+
+
+/** A special category of Gtk::Notebook to handle notebook parameters. */
+class NotebookWidget : public Gtk::Notebook {
+private:
+ ParamNotebook *_pref;
+public:
+ /**
+ * Build a notebookpage preference for the given parameter.
+ * @param pref Where to get the string (pagename) from, and where to put it when it changes.
+ */
+ NotebookWidget(ParamNotebook *pref)
+ : Gtk::Notebook()
+ , _pref(pref)
+ , activated(false)
+ {
+ // don't have to set the correct page: this is done in ParamNotebook::get_widget hook function
+ this->signal_switch_page().connect(sigc::mem_fun(this, &NotebookWidget::changed_page));
+ }
+
+ void changed_page(Gtk::Widget *page, guint pagenum);
+
+ bool activated;
+};
+
+/**
+ * Respond to the selected page of notebook changing.
+ * This function responds to the changing by reporting it to
+ * ParamNotebook. The change is only reported when the notebook
+ * is actually visible. This to exclude 'fake' changes when the
+ * notebookpages are added or removed.
+ */
+void NotebookWidget::changed_page(Gtk::Widget * /*page*/, guint pagenum)
+{
+ if (get_visible()) {
+ _pref->set((int)pagenum);
+ }
+}
+
+/**
+ * Creates a Notebook widget for a notebook parameter.
+ *
+ * Builds a notebook and puts pages in it.
+ */
+Gtk::Widget *ParamNotebook::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ NotebookWidget *notebook = Gtk::manage(new NotebookWidget(this));
+
+ // add pages (if any) and switch to previously selected page
+ int current_page = -1;
+ int selected_page = -1;
+ for (auto child : _children) {
+ ParamNotebookPage *page = dynamic_cast<ParamNotebookPage *>(child);
+ g_assert(child); // A ParamNotebook has only children of type ParamNotebookPage.
+ // If we receive a non-page child here something is very wrong!
+ current_page++;
+
+ Gtk::Widget *page_widget = page->get_widget(changeSignal);
+
+ Glib::ustring page_text = page->_text;
+ if (page->_translatable != NO) { // translate unless explicitly marked untranslatable
+ page_text = page->get_translation(page_text.c_str());
+ }
+
+ notebook->append_page(*page_widget, page_text);
+
+ if (_value == page->_name) {
+ selected_page = current_page;
+ }
+ }
+ if (selected_page >= 0) {
+ notebook->set_current_page(selected_page);
+ }
+
+ notebook->show();
+
+ return static_cast<Gtk::Widget *>(notebook);
+}
+
+
+} // namespace Extension
+} // 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/extension/prefdialog/parameter-notebook.h b/src/extension/prefdialog/parameter-notebook.h
new file mode 100644
index 0000000..b64e5c6
--- /dev/null
+++ b/src/extension/prefdialog/parameter-notebook.h
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INK_EXTENSION_PARAMNOTEBOOK_H_SEEN
+#define INK_EXTENSION_PARAMNOTEBOOK_H_SEEN
+
+/** \file
+ * Notebook parameter for extensions.
+ */
+
+/*
+ * Author:
+ * Johan Engelen <johan@shouraizou.nl>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2006 Author
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+
+#include <vector>
+
+#include <glibmm/ustring.h>
+
+
+namespace Gtk {
+class Widget;
+}
+
+
+namespace Inkscape {
+namespace Extension {
+
+class Extension;
+
+
+/** A class to represent a notebook parameter of an extension. */
+class ParamNotebook : public InxParameter {
+private:
+ /** Internal value. */
+ Glib::ustring _value;
+
+ /**
+ * A class to represent the pages of a notebook parameter of an extension.
+ */
+ class ParamNotebookPage : public InxParameter {
+ friend class ParamNotebook;
+ public:
+ ParamNotebookPage(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ // ParamNotebookPage is not a real parameter (it has no value), so make sure it does not return one
+ std::string value_to_string() const override { return ""; };
+ }; /* class ParamNotebookPage */
+
+public:
+ ParamNotebook(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ std::string value_to_string() const override;
+
+ const Glib::ustring& get() { return _value; }
+ const Glib::ustring& set(const int in);
+}; /* class ParamNotebook */
+
+
+
+
+
+} // namespace Extension
+} // namespace Inkscape
+
+#endif /* INK_EXTENSION_PARAMNOTEBOOK_H_SEEN */
+
+/*
+ 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/extension/prefdialog/parameter-optiongroup.cpp b/src/extension/prefdialog/parameter-optiongroup.cpp
new file mode 100644
index 0000000..4652788
--- /dev/null
+++ b/src/extension/prefdialog/parameter-optiongroup.cpp
@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *extension parameter for options with multiple predefined value choices
+ *
+ * Currently implemented as either Gtk::RadioButton or Gtk::ComboBoxText
+ */
+
+/*
+ * Author:
+ * Johan Engelen <johan@shouraizou.nl>
+ *
+ * Copyright (C) 2006-2007 Johan Engelen
+ * Copyright (C) 2008 Jon A. Cruz
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-optiongroup.h"
+
+#include <unordered_set>
+
+#include <gtkmm/box.h>
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/radiobutton.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+#include "preferences.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+ParamOptionGroup::ParamOptionGroup(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // Read valid optiongroup choices from XML tree, i,e.
+ // - <option> elements
+ // - <item> elements (for backwards-compatibility with params of type enum)
+ // - underscored variants of both (for backwards-compatibility)
+ if (xml) {
+ Inkscape::XML::Node *child_repr = xml->firstChild();
+ while (child_repr) {
+ const char *chname = child_repr->name();
+ if (chname && (!strcmp(chname, INKSCAPE_EXTENSION_NS "option") ||
+ !strcmp(chname, INKSCAPE_EXTENSION_NS "_option") ||
+ !strcmp(chname, INKSCAPE_EXTENSION_NS "item") ||
+ !strcmp(chname, INKSCAPE_EXTENSION_NS "_item")) ) {
+ child_repr->setAttribute("name", "option"); // TODO: hack to allow options to be parameters
+ child_repr->setAttribute("gui-text", "option"); // TODO: hack to allow options to be parameters
+ ParamOptionGroupOption *param = new ParamOptionGroupOption(child_repr, ext, this);
+ choices.push_back(param);
+ } else if (child_repr->type() == XML::NodeType::ELEMENT_NODE) {
+ g_warning("Invalid child element ('%s') for parameter '%s' in extension '%s'. Expected 'option'.",
+ chname, _name, _extension->get_id());
+ } else if (child_repr->type() != XML::NodeType::COMMENT_NODE){
+ g_warning("Invalid child element found in parameter '%s' in extension '%s'. Expected 'option'.",
+ _name, _extension->get_id());
+ }
+ child_repr = child_repr->next();
+ }
+ }
+ if (choices.empty()) {
+ g_warning("No (valid) choices for parameter '%s' in extension '%s'", _name, _extension->get_id());
+ }
+
+ // check for duplicate option texts and values
+ std::unordered_set<std::string> texts;
+ std::unordered_set<std::string> values;
+ for (auto choice : choices) {
+ auto ret1 = texts.emplace(choice->_text.raw());
+ if (!ret1.second) {
+ g_warning("Duplicate option text ('%s') for parameter '%s' in extension '%s'.",
+ choice->_text.c_str(), _name, _extension->get_id());
+ }
+ auto ret2 = values.emplace(choice->_value.raw());
+ if (!ret2.second) {
+ g_warning("Duplicate option value ('%s') for parameter '%s' in extension '%s'.",
+ choice->_value.c_str(), _name, _extension->get_id());
+ }
+ }
+
+ // get value (initialize with value of first choice if pref is empty)
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getString(pref_name());
+
+ if (_value.empty()) {
+ if (!choices.empty()) {
+ _value = choices[0]->_value;
+ }
+ }
+
+ // parse appearance
+ // (we support "combo" and "radio"; "minimal" is for backwards-compatibility)
+ if (_appearance) {
+ if (!strcmp(_appearance, "combo") || !strcmp(_appearance, "minimal")) {
+ _mode = COMBOBOX;
+ } else if (!strcmp(_appearance, "radio")) {
+ _mode = RADIOBUTTON;
+ } else {
+ g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'",
+ _appearance, _name, _extension->get_id());
+ }
+ }
+}
+
+ParamOptionGroup::~ParamOptionGroup ()
+{
+ // destroy choice strings
+ for (auto choice : choices) {
+ delete choice;
+ }
+}
+
+
+/**
+ * A function to set the \c _value.
+ *
+ * This function sets ONLY the internal value, but it also sets the value
+ * in the preferences structure. To put it in the right place \c pref_name() is used.
+ *
+ * @param in The value to set.
+ */
+const Glib::ustring& ParamOptionGroup::set(Glib::ustring in)
+{
+ if (contains(in)) {
+ _value = in;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString(pref_name(), _value.c_str());
+ } else {
+ g_warning("Could not set value ('%s') for parameter '%s' in extension '%s'. Not a valid choice.",
+ in.c_str(), _name, _extension->get_id());
+ }
+
+ return _value;
+}
+
+bool ParamOptionGroup::contains(const Glib::ustring text) const
+{
+ for (auto choice : choices) {
+ if (choice->_value == text) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+std::string ParamOptionGroup::value_to_string() const
+{
+ return _value.raw();
+}
+
+/**
+ * Returns the value for the options label parameter
+ */
+Glib::ustring ParamOptionGroup::value_from_label(const Glib::ustring label)
+{
+ Glib::ustring value;
+
+ for (auto choice : choices) {
+ if (choice->_text == label) {
+ value = choice->_value;
+ break;
+ }
+ }
+
+ return value;
+}
+
+
+
+/** A special RadioButton class to use in ParamOptionGroup. */
+class RadioWidget : public Gtk::RadioButton {
+private:
+ ParamOptionGroup *_pref;
+ sigc::signal<void> *_changeSignal;
+public:
+ RadioWidget(Gtk::RadioButtonGroup& group, const Glib::ustring& label,
+ ParamOptionGroup *pref, sigc::signal<void> *changeSignal)
+ : Gtk::RadioButton(group, label)
+ , _pref(pref)
+ , _changeSignal(changeSignal)
+ {
+ add_changesignal();
+ };
+
+ void add_changesignal() {
+ this->signal_toggled().connect(sigc::mem_fun(this, &RadioWidget::changed));
+ };
+
+ void changed();
+};
+
+/**
+ * Respond to the selected radiobutton changing.
+ *
+ * This function responds to the radiobutton selection changing by grabbing the value
+ * from the text box and putting it in the parameter.
+ */
+void RadioWidget::changed()
+{
+ if (this->get_active()) {
+ Glib::ustring value = _pref->value_from_label(this->get_label());
+ _pref->set(value.c_str());
+ }
+
+ if (_changeSignal) {
+ _changeSignal->emit();
+ }
+}
+
+
+/** A special ComboBoxText class to use in ParamOptionGroup. */
+class ComboWidget : public Gtk::ComboBoxText {
+private:
+ ParamOptionGroup *_pref;
+ sigc::signal<void> *_changeSignal;
+
+public:
+ ComboWidget(ParamOptionGroup *pref, sigc::signal<void> *changeSignal)
+ : _pref(pref)
+ , _changeSignal(changeSignal)
+ {
+ this->signal_changed().connect(sigc::mem_fun(this, &ComboWidget::changed));
+ }
+
+ ~ComboWidget() override = default;
+
+ void changed();
+};
+
+void ComboWidget::changed()
+{
+ if (_pref) {
+ Glib::ustring value = _pref->value_from_label(get_active_text());
+ _pref->set(value.c_str());
+ }
+
+ if (_changeSignal) {
+ _changeSignal->emit();
+ }
+}
+
+
+
+/**
+ * Creates the widget for the optiongroup parameter.
+ */
+Gtk::Widget *ParamOptionGroup::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ auto hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING));
+
+ Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START));
+ hbox->pack_start(*label, false, false);
+
+ if (_mode == COMBOBOX) {
+ ComboWidget *combo = Gtk::manage(new ComboWidget(this, changeSignal));
+
+ for (auto choice : choices) {
+ combo->append(choice->_text);
+ if (choice->_value == _value) {
+ combo->set_active_text(choice->_text);
+ }
+ }
+
+ if (combo->get_active_row_number() == -1) {
+ combo->set_active(0);
+ }
+
+ hbox->pack_end(*combo, false, false);
+ } else if (_mode == RADIOBUTTON) {
+ label->set_valign(Gtk::ALIGN_START); // align label and first radio
+
+ Gtk::Box *radios = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0));
+ Gtk::RadioButtonGroup group;
+
+ for (auto choice : choices) {
+ RadioWidget *radio = Gtk::manage(new RadioWidget(group, choice->_text, this, changeSignal));
+ radios->pack_start(*radio, true, true);
+ if (choice->_value == _value) {
+ radio->set_active();
+ }
+ }
+
+ hbox->pack_end(*radios, false, false);
+ }
+
+ hbox->show_all();
+ return static_cast<Gtk::Widget *>(hbox);
+}
+
+
+ParamOptionGroup::ParamOptionGroupOption::ParamOptionGroupOption(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext,
+ const Inkscape::Extension::ParamOptionGroup *parent)
+ : InxParameter(xml, ext)
+{
+ // get content (=label) of option and translate it
+ const char *text = nullptr;
+ if (xml->firstChild()) {
+ text = xml->firstChild()->content();
+ }
+ if (text) {
+ if (_translatable != NO) { // translate unless explicitly marked untranslatable
+ _text = get_translation(text);
+ } else {
+ _text = text;
+ }
+ } else {
+ g_warning("Missing content in option of parameter '%s' in extension '%s'.",
+ parent->_name, _extension->get_id());
+ }
+
+ // get string value of option
+ const char *value = xml->attribute("value");
+ if (value) {
+ _value = value;
+ } else {
+ if (text) {
+ const char *name = xml->name();
+ if (!strcmp(name, INKSCAPE_EXTENSION_NS "item") || !strcmp(name, INKSCAPE_EXTENSION_NS "_item")) {
+ _value = text; // use untranslated UI text as value (for backwards-compatibility)
+ } else {
+ _value = _text; // use translated UI text as value
+ }
+ } else {
+ g_warning("Missing value for option '%s' of parameter '%s' in extension '%s'.",
+ _text.c_str(), parent->_name, _extension->get_id());
+ }
+ }
+}
+
+
+
+} /* namespace Extension */
+} /* 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 :
diff --git a/src/extension/prefdialog/parameter-optiongroup.h b/src/extension/prefdialog/parameter-optiongroup.h
new file mode 100644
index 0000000..533b131
--- /dev/null
+++ b/src/extension/prefdialog/parameter-optiongroup.h
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INK_EXTENSION_PARAMOPTIONGROUP_H_SEEN
+#define INK_EXTENSION_PARAMOPTIONGROUP_H_SEEN
+
+/** \file
+ * extension parameter for options with multiple predefined value choices
+ *
+ * Currently implemented as either Gtk::RadioButton or Gtk::ComboBoxText
+ */
+
+/*
+ * Authors:
+ * Johan Engelen <johan@shouraizou.nl>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2006-2007 Johan Engelen
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+
+#include <vector>
+
+#include <glibmm/ustring.h>
+
+namespace Gtk {
+class Widget;
+}
+
+namespace Inkscape {
+namespace Extension {
+
+class Extension;
+
+
+
+// \brief A class to represent an optiongroup (option with multiple predefined value choices) parameter of an extension
+class ParamOptionGroup : public InxParameter {
+public:
+ enum AppearanceMode {
+ RADIOBUTTON, COMBOBOX
+ };
+
+ ParamOptionGroup(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+ ~ParamOptionGroup() override;
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ std::string value_to_string() const override;
+
+ Glib::ustring value_from_label(const Glib::ustring label);
+
+ const Glib::ustring& get() const { return _value; }
+
+ const Glib::ustring& set(const Glib::ustring in);
+
+ /**
+ * @returns true if text is a valid choice for this option group
+ * @param text string value to check (this is an actual option value, not the user-visible option name!)
+ */
+ bool contains(const Glib::ustring text) const;
+
+private:
+ /** \brief Internal value. */
+ Glib::ustring _value;
+
+ /** appearance mode **/
+ AppearanceMode _mode = RADIOBUTTON;
+
+ /* For internal use only. */
+ class ParamOptionGroupOption : public InxParameter {
+ friend class ParamOptionGroup;
+ public:
+ ParamOptionGroupOption(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext,
+ const Inkscape::Extension::ParamOptionGroup *parent);
+ private:
+ Glib::ustring _value;
+ Glib::ustring _text;
+ };
+
+ std::vector<ParamOptionGroupOption *> choices; /**< List of available options for the option group */
+}; /* class ParamOptionGroup */
+
+
+
+
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /*INK_EXTENSION_PARAMOPTIONGROUP_H_SEEN */
+
+/*
+ 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/extension/prefdialog/parameter-path.cpp b/src/extension/prefdialog/parameter-path.cpp
new file mode 100644
index 0000000..83b54fc
--- /dev/null
+++ b/src/extension/prefdialog/parameter-path.cpp
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Path parameter for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-path.h"
+
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/algorithm/string/join.hpp>
+
+#include <glibmm/i18n.h>
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+#include <glibmm/regex.h>
+
+#include <gtkmm/box.h>
+#include <gtkmm/button.h>
+#include <gtkmm/dialog.h>
+#include <gtkmm/entry.h>
+#include <gtkmm/filechoosernative.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+#include "preferences.h"
+
+namespace Inkscape {
+namespace Extension {
+
+ParamPath::ParamPath(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // get value
+ const char *value = nullptr;
+ if (xml->firstChild()) {
+ value = xml->firstChild()->content();
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getString(pref_name()).raw();
+
+ if (_value.empty() && value) {
+ _value = value;
+ }
+
+ // parse selection mode
+ const char *mode = xml->attribute("mode");
+ if (mode) {
+ if (!strcmp(mode, "file")) {
+ // this is the default
+ } else if (!strcmp(mode, "files")) {
+ _select_multiple = true;
+ } else if (!strcmp(mode, "folder")) {
+ _mode = FOLDER;
+ } else if (!strcmp(mode, "folders")) {
+ _mode = FOLDER;
+ _select_multiple = true;
+ } else if (!strcmp(mode, "file_new")) {
+ _mode = FILE_NEW;
+ } else if (!strcmp(mode, "folder_new")) {
+ _mode = FOLDER_NEW;
+ } else {
+ g_warning("Invalid value ('%s') for mode of parameter '%s' in extension '%s'",
+ mode, _name, _extension->get_id());
+ }
+ }
+
+ // parse filetypes
+ const char *filetypes = xml->attribute("filetypes");
+ if (filetypes) {
+ _filetypes = Glib::Regex::split_simple("," , filetypes);
+ }
+}
+
+/**
+ * A function to set the \c _value.
+ *
+ * This function sets the internal value, but it also sets the value
+ * in the preferences structure. To put it in the right place \c pref_name() is used.
+ *
+ * @param in The value to set to.
+ */
+const std::string& ParamPath::set(const std::string &in)
+{
+ _value = in;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString(pref_name(), _value);
+
+ return _value;
+}
+
+std::string ParamPath::value_to_string() const
+{
+ if (!Glib::path_is_absolute(_value) && !_value.empty()) {
+ return Glib::build_filename(_extension->get_base_directory(), _value);
+ } else {
+ return _value;
+ }
+}
+
+
+/** A special type of Gtk::Entry to handle path parameters. */
+class ParamPathEntry : public Gtk::Entry {
+private:
+ ParamPath *_pref;
+ sigc::signal<void> *_changeSignal;
+public:
+ /**
+ * Build a string preference for the given parameter.
+ * @param pref Where to get the string from, and where to put it
+ * when it changes.
+ */
+ ParamPathEntry(ParamPath *pref, sigc::signal<void> *changeSignal)
+ : Gtk::Entry()
+ , _pref(pref)
+ , _changeSignal(changeSignal)
+ {
+ this->set_text(_pref->get());
+ this->signal_changed().connect(sigc::mem_fun(this, &ParamPathEntry::changed_text));
+ };
+ void changed_text();
+};
+
+
+/**
+ * Respond to the text box changing.
+ *
+ * This function responds to the box changing by grabbing the value
+ * from the text box and putting it in the parameter.
+ */
+void ParamPathEntry::changed_text()
+{
+ auto data = this->get_text();
+ _pref->set(data.c_str());
+ if (_changeSignal != nullptr) {
+ _changeSignal->emit();
+ }
+}
+
+/**
+ * Creates a text box for the string parameter.
+ *
+ * Builds a hbox with a label and a text box in it.
+ */
+Gtk::Widget *ParamPath::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Box *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING));
+ Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START));
+ label->show();
+ hbox->pack_start(*label, false, false);
+
+ ParamPathEntry *textbox = Gtk::manage(new ParamPathEntry(this, changeSignal));
+ textbox->show();
+ hbox->pack_start(*textbox, true, true);
+ _entry = textbox;
+
+ Gtk::Button *button = Gtk::manage(new Gtk::Button("…"));
+ button->show();
+ hbox->pack_end(*button, false, false);
+ button->signal_clicked().connect(sigc::mem_fun(*this, &ParamPath::on_button_clicked));
+
+ hbox->show();
+
+ return dynamic_cast<Gtk::Widget *>(hbox);
+}
+
+/**
+ * Create and show the file chooser dialog when the "…" button is clicked
+ * Then set the value of the ParamPathEntry holding the current value accordingly
+ */
+void ParamPath::on_button_clicked()
+{
+ // set-up action and dialog title according to 'mode'
+ Gtk::FileChooserAction action;
+ std::string dialog_title;
+ if (_mode == FILE) {
+ // pick the "save" variants here - otherwise the dialog will only accept existing files
+ action = Gtk::FILE_CHOOSER_ACTION_OPEN;
+ if (_select_multiple) {
+ dialog_title = _("Select existing files");
+ } else {
+ dialog_title = _("Select existing file");
+ }
+ } else if (_mode == FOLDER) {
+ // pick the "create" variant here - otherwise the dialog will only accept existing folders
+ action = Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER;
+ if (_select_multiple) {
+ dialog_title = _("Select existing folders");
+ } else {
+ dialog_title = _("Select existing folder");
+ }
+ } else if (_mode == FILE_NEW) {
+ action = Gtk::FILE_CHOOSER_ACTION_SAVE;
+ dialog_title = _("Choose file name");
+ } else if (_mode == FOLDER_NEW) {
+ action = Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER;
+ dialog_title = _("Choose folder name");
+ } else {
+ g_assert_not_reached();
+ }
+
+ // create file chooser dialog
+ auto file_chooser = Gtk::FileChooserNative::create(dialog_title + "…", action, _("Select"));
+ file_chooser->set_select_multiple(_select_multiple);
+ file_chooser->set_do_overwrite_confirmation(true);
+ file_chooser->set_create_folders(true);
+
+ // set FileFilter according to 'filetype' attribute
+ if (!_filetypes.empty() && _mode != FOLDER && _mode != FOLDER_NEW) {
+ Glib::RefPtr<Gtk::FileFilter> file_filter = Gtk::FileFilter::create();
+
+ for (auto filetype : _filetypes) {
+ file_filter->add_pattern(Glib::ustring::compose("*.%1", filetype));
+ }
+
+ std::string filter_name = boost::algorithm::join(_filetypes, "+");
+ boost::algorithm::to_upper(filter_name);
+ file_filter->set_name(filter_name);
+
+ file_chooser->add_filter(file_filter);
+ }
+
+ // set current file/folder suitable for current value
+ // (use basepath of first filename; relative paths are considered relative to .inx file's location)
+ if (!_value.empty()) {
+ std::string first_filename = _value.substr(0, _value.find("|"));
+
+ if (!Glib::path_is_absolute(first_filename)) {
+ first_filename = Glib::build_filename(_extension->get_base_directory(), first_filename);
+ }
+
+ std::string dirname = Glib::path_get_dirname(first_filename);
+ if (Glib::file_test(dirname, Glib::FILE_TEST_IS_DIR)) {
+ file_chooser->set_current_folder(dirname);
+ }
+
+ if(_mode == FILE_NEW || _mode == FOLDER_NEW) {
+ file_chooser->set_current_name(Glib::path_get_basename(first_filename));
+ } else {
+ if (Glib::file_test(first_filename, Glib::FILE_TEST_EXISTS)) {
+ // TODO: This does not seem to work (at least on Windows)
+ // file_chooser->set_filename(first_filename);
+ }
+ }
+ }
+
+ // show dialog and parse result
+ int res = file_chooser->run();
+ if (res == Gtk::ResponseType::RESPONSE_ACCEPT) {
+ std::vector<std::string> filenames = file_chooser->get_filenames();
+ std::string filenames_joined = boost::algorithm::join(filenames, "|");
+ _entry->set_text(filenames_joined); // let the ParamPathEntry handle the rest (including setting the preference)
+ }
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/parameter-path.h b/src/extension/prefdialog/parameter-path.h
new file mode 100644
index 0000000..0fbf911
--- /dev/null
+++ b/src/extension/prefdialog/parameter-path.h
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Path parameter for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_EXTENSION_PARAM_PATH_H_SEEN
+#define INK_EXTENSION_PARAM_PATH_H_SEEN
+
+#include "parameter.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+class ParamPathEntry;
+
+class ParamPath : public InxParameter {
+public:
+ ParamPath(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ /** \brief Returns \c _value, with a \i const to protect it. */
+ const std::string& get() const { return _value; }
+ const std::string& set(const std::string &in);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ std::string value_to_string() const override;
+
+private:
+ enum Mode {
+ FILE, FOLDER, FILE_NEW, FOLDER_NEW
+ };
+
+ /** \brief Internal value. */
+ std::string _value;
+
+ /** selection mode for the file chooser: files or folders? */
+ Mode _mode = FILE;
+
+ /** selection mode for the file chooser: multiple items? */
+ bool _select_multiple = false;
+
+ /** filetypes that should be selectable in file chooser */
+ std::vector<std::string> _filetypes;
+
+ /** pointer to the parameters text entry
+ * keep this around, so we can update the value accordingly in \a on_button_clicked() */
+ ParamPathEntry *_entry;
+
+ void on_button_clicked();
+};
+
+
+} // namespace Extension
+} // namespace Inkscape
+
+#endif /* INK_EXTENSION_PARAM_PATH_H_SEEN */
+
+/*
+ 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/extension/prefdialog/parameter-string.cpp b/src/extension/prefdialog/parameter-string.cpp
new file mode 100644
index 0000000..708731a
--- /dev/null
+++ b/src/extension/prefdialog/parameter-string.cpp
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-string.h"
+
+#include <gtkmm/box.h>
+#include <gtkmm/entry.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/textview.h>
+#include <glibmm/regex.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+#include "preferences.h"
+
+namespace Inkscape {
+namespace Extension {
+
+ParamString::ParamString(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // get value
+ const char *value = nullptr;
+ if (xml->firstChild()) {
+ value = xml->firstChild()->content();
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getString(pref_name());
+
+ if (_value.empty() && value) {
+ _value = value;
+ }
+
+ // translate value
+ if (!_value.empty()) {
+ if (_translatable == YES) { // translate only if explicitly marked translatable
+ _value = get_translation(_value.c_str());
+ }
+ }
+
+ // max-length
+ const char *max_length = xml->attribute("max-length");
+ if (!max_length) {
+ max_length = xml->attribute("max_length"); // backwards-compatibility with old name (underscore)
+ }
+ if (max_length) {
+ _max_length = strtoul(max_length, nullptr, 0);
+ }
+
+ // parse appearance
+ if (_appearance) {
+ if (!strcmp(_appearance, "multiline")) {
+ _mode = MULTILINE;
+ } else {
+ g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'",
+ _appearance, _name, _extension->get_id());
+ }
+ }
+}
+
+/**
+ * A function to set the \c _value.
+ *
+ * This function sets the internal value, but it also sets the value
+ * in the preferences structure. To put it in the right place \c pref_name() is used.
+ *
+ * To copy the data into _value the old memory must be free'd first.
+ * It is important to note that \c g_free handles \c NULL just fine. Then
+ * the passed in value is duplicated using \c g_strdup().
+ *
+ * @param in The value to set to.
+ */
+const Glib::ustring& ParamString::set(const Glib::ustring in)
+{
+ _value = in;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString(pref_name(), _value);
+
+ return _value;
+}
+
+std::string ParamString::value_to_string() const
+{
+ return _value.raw();
+}
+
+
+
+/** A special type of Gtk::Entry to handle string parameters. */
+class ParamStringEntry : public Gtk::Entry {
+private:
+ ParamString *_pref;
+ sigc::signal<void> *_changeSignal;
+public:
+ /**
+ * Build a string preference for the given parameter.
+ * @param pref Where to get the string from, and where to put it
+ * when it changes.
+ */
+ ParamStringEntry(ParamString *pref, sigc::signal<void> *changeSignal)
+ : Gtk::Entry()
+ , _pref(pref)
+ , _changeSignal(changeSignal)
+ {
+ this->set_text(_pref->get());
+ this->set_max_length(_pref->getMaxLength()); //Set the max length - default zero means no maximum
+ this->signal_changed().connect(sigc::mem_fun(this, &ParamStringEntry::changed_text));
+ };
+ void changed_text();
+};
+
+
+/**
+ * Respond to the text box changing.
+ *
+ * This function responds to the box changing by grabbing the value
+ * from the text box and putting it in the parameter.
+ */
+void ParamStringEntry::changed_text()
+{
+ Glib::ustring data = this->get_text();
+ _pref->set(data.c_str());
+ if (_changeSignal != nullptr) {
+ _changeSignal->emit();
+ }
+}
+
+
+
+/** A special type of Gtk::TextView to handle multiline string parameters. */
+class ParamMultilineStringEntry : public Gtk::TextView {
+private:
+ ParamString *_pref;
+ sigc::signal<void> *_changeSignal;
+public:
+ /**
+ * Build a string preference for the given parameter.
+ * @param pref Where to get the string from, and where to put it
+ * when it changes.
+ */
+ ParamMultilineStringEntry(ParamString *pref, sigc::signal<void> *changeSignal)
+ : Gtk::TextView()
+ , _pref(pref)
+ , _changeSignal(changeSignal)
+ {
+ // replace literal '\n' with actual newlines for multiline strings
+ Glib::ustring value = Glib::Regex::create("\\\\n")->replace_literal(_pref->get(), 0, "\n", (Glib::RegexMatchFlags)0);
+
+ this->get_buffer()->set_text(value);
+ this->get_buffer()->signal_changed().connect(sigc::mem_fun(this, &ParamMultilineStringEntry::changed_text));
+ };
+ void changed_text();
+};
+
+/**
+ * Respond to the text box changing.
+ *
+ * This function responds to the box changing by grabbing the value
+ * from the text box and putting it in the parameter.
+ */
+void ParamMultilineStringEntry::changed_text()
+{
+ Glib::ustring data = this->get_buffer()->get_text();
+
+ // always store newlines as literal '\n'
+ data = Glib::Regex::create("\n")->replace_literal(data, 0, "\\n", (Glib::RegexMatchFlags)0);
+
+ _pref->set(data.c_str());
+ if (_changeSignal != nullptr) {
+ _changeSignal->emit();
+ }
+}
+
+
+
+/**
+ * Creates a text box for the string parameter.
+ *
+ * Builds a hbox with a label and a text box in it.
+ */
+Gtk::Widget *ParamString::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Box *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING));
+
+ Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START));
+ label->show();
+ box->pack_start(*label, false, false);
+
+ if (_mode == MULTILINE) {
+ box->set_orientation(Gtk::ORIENTATION_VERTICAL);
+
+ Gtk::ScrolledWindow *textarea = Gtk::manage(new Gtk::ScrolledWindow());
+ textarea->set_vexpand();
+ textarea->set_shadow_type(Gtk::SHADOW_IN);
+
+ ParamMultilineStringEntry *entry = Gtk::manage(new ParamMultilineStringEntry(this, changeSignal));
+ entry->show();
+
+ textarea->add(*entry);
+ textarea->show();
+
+ box->pack_start(*textarea, true, true);
+ } else {
+ Gtk::Widget *entry = Gtk::manage(new ParamStringEntry(this, changeSignal));
+ entry->show();
+
+ box->pack_start(*entry, true, true);
+ }
+
+ box->show();
+
+ return dynamic_cast<Gtk::Widget *>(box);
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/parameter-string.h b/src/extension/prefdialog/parameter-string.h
new file mode 100644
index 0000000..3af8311
--- /dev/null
+++ b/src/extension/prefdialog/parameter-string.h
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INK_EXTENSION_PARAMSTRING_H_SEEN
+#define INK_EXTENSION_PARAMSTRING_H_SEEN
+
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+
+#include <glibmm/ustring.h>
+
+
+namespace Inkscape {
+namespace Extension {
+
+class ParamString : public InxParameter {
+public:
+ enum AppearanceMode {
+ DEFAULT, MULTILINE
+ };
+
+ ParamString(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ /** \brief Returns \c _value, with a \i const to protect it. */
+ const Glib::ustring& get() const { return _value; }
+ const Glib::ustring& set(const Glib::ustring in);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ std::string value_to_string() const override;
+
+ void setMaxLength(int maxLength) { _max_length = maxLength; }
+ int getMaxLength() { return _max_length; }
+
+private:
+ /** \brief Internal value. */
+ Glib::ustring _value;
+
+ /** appearance mode **/
+ AppearanceMode _mode = DEFAULT;
+
+ /** \brief Maximum length of the string in characters (zero meaning unlimited). */
+ int _max_length = 0;
+};
+
+
+} // namespace Extension
+} // namespace Inkscape
+
+#endif /* INK_EXTENSION_PARAMSTRING_H_SEEN */
+
+/*
+ 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/extension/prefdialog/parameter.cpp b/src/extension/prefdialog/parameter.cpp
new file mode 100644
index 0000000..b8d8386
--- /dev/null
+++ b/src/extension/prefdialog/parameter.cpp
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Parameters for extensions.
+ */
+/* Author:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2005-2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstring>
+#include <list>
+
+#include <glibmm/i18n.h>
+#include <sigc++/sigc++.h>
+
+#include "parameter.h"
+#include "parameter-bool.h"
+#include "parameter-color.h"
+#include "parameter-float.h"
+#include "parameter-int.h"
+#include "parameter-notebook.h"
+#include "parameter-optiongroup.h"
+#include "parameter-path.h"
+#include "parameter-string.h"
+#include "widget.h"
+#include "widget-label.h"
+
+#include "extension/extension.h"
+
+#include "object/sp-defs.h"
+
+#include "ui/widget/color-notebook.h"
+
+#include "xml/node.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+
+// Re-implement ParamDescription for backwards-compatibility, deriving from both, WidgetLabel and InxParameter.
+// TODO: Should go away eventually...
+class ParamDescription : public virtual WidgetLabel, public virtual InxParameter {
+public:
+ ParamDescription(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : WidgetLabel(xml, ext)
+ , InxParameter(xml, ext)
+ {}
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override
+ {
+ return this->WidgetLabel::get_widget(changeSignal);
+ }
+
+ // Well, no, I don't have a value! That's why I should not be an InxParameter!
+ std::string value_to_string() const override { return ""; }
+};
+
+
+
+InxParameter *InxParameter::make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext)
+{
+ InxParameter *param = nullptr;
+
+ try {
+ const char *type = in_repr->attribute("type");
+ if (!type) {
+ // we can't create a parameter without type
+ g_warning("Parameter without type in extension '%s'.", in_ext->get_id());
+ } else if(!strcmp(type, "bool") || !strcmp(type, "boolean")) { // support "boolean" for backwards-compatibility
+ param = new ParamBool(in_repr, in_ext);
+ } else if (!strcmp(type, "int")) {
+ param = new ParamInt(in_repr, in_ext);
+ } else if (!strcmp(type, "float")) {
+ param = new ParamFloat(in_repr, in_ext);
+ } else if (!strcmp(type, "string")) {
+ param = new ParamString(in_repr, in_ext);
+ } else if (!strcmp(type, "path")) {
+ param = new ParamPath(in_repr, in_ext);
+ } else if (!strcmp(type, "description")) {
+ // support deprecated "description" for backwards-compatibility
+ in_repr->setAttribute("gui-text", "description"); // TODO: hack to allow descriptions to be parameters
+ param = new ParamDescription(in_repr, in_ext);
+ } else if (!strcmp(type, "notebook")) {
+ in_repr->setAttribute("gui-text", "notebook"); // notebooks have no 'gui-text' (but Parameters need one)
+ param = new ParamNotebook(in_repr, in_ext);
+ } else if (!strcmp(type, "optiongroup")) {
+ param = new ParamOptionGroup(in_repr, in_ext);
+ } else if (!strcmp(type, "enum")) { // support deprecated "enum" for backwards-compatibility
+ in_repr->setAttribute("appearance", "combo");
+ param = new ParamOptionGroup(in_repr, in_ext);
+ } else if (!strcmp(type, "color")) {
+ param = new ParamColor(in_repr, in_ext);
+ } else {
+ g_warning("Unknown parameter type ('%s') in extension '%s'", type, in_ext->get_id());
+ }
+ } catch (const param_no_name&) {
+ } catch (const param_no_text&) {
+ }
+
+ // Note: param could equal nullptr
+ return param;
+}
+
+bool InxParameter::get_bool() const
+{
+ ParamBool const *boolpntr = dynamic_cast<ParamBool const *>(this);
+ if (!boolpntr) {
+ throw param_not_bool_param();
+ }
+ return boolpntr->get();
+}
+
+int InxParameter::get_int() const
+{
+ ParamInt const *intpntr = dynamic_cast<ParamInt const *>(this);
+ if (!intpntr) {
+ // This allows option groups to contain integers. Consider just using this.
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ return prefs->getInt(this->pref_name());
+ }
+ return intpntr->get();
+}
+
+double InxParameter::get_float() const
+{
+ ParamFloat const *floatpntr = dynamic_cast<ParamFloat const *>(this);
+ if (!floatpntr) {
+ throw param_not_float_param();
+ }
+ return floatpntr->get();
+}
+
+const char *InxParameter::get_string() const
+{
+ ParamString const *stringpntr = dynamic_cast<ParamString const *>(this);
+ if (!stringpntr) {
+ throw param_not_string_param();
+ }
+ return stringpntr->get().c_str();
+}
+
+const char *InxParameter::get_optiongroup() const
+{
+ ParamOptionGroup const *param = dynamic_cast<ParamOptionGroup const *>(this);
+ if (!param) {
+ throw param_not_optiongroup_param();
+ }
+ return param->get().c_str();
+}
+
+bool InxParameter::get_optiongroup_contains(const char *value) const
+{
+ ParamOptionGroup const *param = dynamic_cast<ParamOptionGroup const *>(this);
+ if (!param) {
+ throw param_not_optiongroup_param();
+ }
+ return param->contains(value);
+}
+
+unsigned int InxParameter::get_color() const
+{
+ ParamColor const *param = dynamic_cast<ParamColor const *>(this);
+ if (!param) {
+ throw param_not_color_param();
+ }
+ return param->get();
+}
+
+bool InxParameter::set_bool(bool in)
+{
+ ParamBool * boolpntr = dynamic_cast<ParamBool *>(this);
+ if (boolpntr == nullptr)
+ throw param_not_bool_param();
+ return boolpntr->set(in);
+}
+
+int InxParameter::set_int(int in)
+{
+ ParamInt *intpntr = dynamic_cast<ParamInt *>(this);
+ if (intpntr == nullptr)
+ throw param_not_int_param();
+ return intpntr->set(in);
+}
+
+double InxParameter::set_float(double in)
+{
+ ParamFloat * floatpntr;
+ floatpntr = dynamic_cast<ParamFloat *>(this);
+ if (floatpntr == nullptr)
+ throw param_not_float_param();
+ return floatpntr->set(in);
+}
+
+const char *InxParameter::set_string(const char *in)
+{
+ ParamString * stringpntr = dynamic_cast<ParamString *>(this);
+ if (stringpntr == nullptr)
+ throw param_not_string_param();
+ return stringpntr->set(in).c_str();
+}
+
+const char *InxParameter::set_optiongroup(const char *in)
+{
+ ParamOptionGroup *param = dynamic_cast<ParamOptionGroup *>(this);
+ if (!param) {
+ throw param_not_optiongroup_param();
+ }
+ return param->set(in).c_str();
+}
+
+unsigned int InxParameter::set_color(unsigned int in)
+{
+ ParamColor*param = dynamic_cast<ParamColor *>(this);
+ if (param == nullptr)
+ throw param_not_color_param();
+ return param->set(in);
+}
+
+
+InxParameter::InxParameter(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *ext)
+ : InxWidget(in_repr, ext)
+{
+ // name (mandatory for all parameters)
+ const char *name = in_repr->attribute("name");
+ if (name) {
+ _name = g_strstrip(g_strdup(name));
+ }
+ if (!_name || !strcmp(_name, "")) {
+ g_warning("Parameter without name in extension '%s'.", _extension->get_id());
+ throw param_no_name();
+ }
+
+ // gui-text
+ const char *gui_text = in_repr->attribute("gui-text");
+ if (!gui_text) {
+ gui_text = in_repr->attribute("_gui-text"); // backwards-compatibility with underscored variants
+ }
+ if (gui_text) {
+ if (_translatable != NO) { // translate unless explicitly marked untranslatable
+ gui_text = get_translation(gui_text);
+ }
+ _text = g_strdup(gui_text);
+ }
+ if (!_text && !_hidden) {
+ g_warning("Parameter '%s' in extension '%s' is visible but does not have a 'gui-text'.",
+ _name, _extension->get_id());
+ throw param_no_text();
+ }
+
+ // gui-description (optional)
+ const char *gui_description = in_repr->attribute("gui-description");
+ if (!gui_description) {
+ gui_description = in_repr->attribute("_gui-description"); // backwards-compatibility with underscored variants
+ }
+ if (gui_description) {
+ if (_translatable != NO) { // translate unless explicitly marked untranslatable
+ gui_description = get_translation(gui_description);
+ }
+ _description = g_strdup(gui_description);
+ }
+}
+
+InxParameter::~InxParameter()
+{
+ g_free(_name);
+ _name = nullptr;
+
+ g_free(_text);
+ _text = nullptr;
+
+ g_free(_description);
+ _description = nullptr;
+}
+
+Glib::ustring InxParameter::pref_name() const
+{
+ return Glib::ustring::compose("/extensions/%1.%2", _extension->get_id(), _name);
+}
+
+std::string InxParameter::value_to_string() const
+{
+ // if we end up here we're missing a definition of ::string() in one of the subclasses
+ g_critical("InxParameter::value_to_string called from parameter '%s' in extension '%s'", _name, _extension->get_id());
+ g_assert_not_reached();
+ return "";
+}
+
+} // namespace Extension
+} // 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/extension/prefdialog/parameter.h b/src/extension/prefdialog/parameter.h
new file mode 100644
index 0000000..3ae68a3
--- /dev/null
+++ b/src/extension/prefdialog/parameter.h
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Parameters for extensions.
+ */
+/* Authors:
+ * Ted Gould <ted@gould.cx>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2005-2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_EXTENSION_PARAM_H__
+#define SEEN_INK_EXTENSION_PARAM_H__
+
+#include "widget.h"
+
+
+namespace Glib {
+class ustring;
+}
+
+
+namespace Inkscape {
+namespace Extension {
+
+/**
+ * A class to represent the parameter of an extension.
+ *
+ * This is really a super class that allows them to abstract all
+ * the different types of parameters into some that can be passed
+ * around. There is also a few functions that are used by all the
+ * different parameters.
+ */
+class InxParameter : public InxWidget {
+public:
+ InxParameter(Inkscape::XML::Node *in_repr,
+ Inkscape::Extension::Extension *ext);
+
+ ~InxParameter() override;
+
+ /** Wrapper to cast to the object and use its function. */
+ bool get_bool() const;
+
+ /** Wrapper to cast to the object and use it's function. */
+ int get_int() const;
+
+ /** Wrapper to cast to the object and use it's function. */
+ double get_float() const;
+
+ /** Wrapper to cast to the object and use it's function. */
+ const char *get_string() const;
+
+ /** Wrapper to cast to the object and use it's function. */
+ const char *get_optiongroup() const;
+ bool get_optiongroup_contains(const char *value) const;
+
+ /** Wrapper to cast to the object and use it's function. */
+ unsigned int get_color() const;
+
+ /** Wrapper to cast to the object and use it's function. */
+ bool set_bool(bool in);
+
+ /** Wrapper to cast to the object and use it's function. */
+ int set_int(int in);
+
+ /** Wrapper to cast to the object and use it's function. */
+ double set_float(double in);
+
+ /** Wrapper to cast to the object and use it's function. */
+ const char *set_string(const char *in);
+
+ /** Wrapper to cast to the object and use it's function. */
+ const char *set_optiongroup(const char *in);
+
+ /** Wrapper to cast to the object and use it's function. */
+ unsigned int set_color(unsigned int in);
+
+ char const *name() const { return _name; }
+
+ /**
+ * Creates a new extension parameter for usage in a prefdialog.
+ *
+ * The type of widget created is parsed from the XML representation passed in,
+ * and the suitable subclass constructor is called.
+ *
+ * Called from base-class method of the same name.
+ *
+ * @param in_repr The XML representation describing the widget.
+ * @param in_ext The extension the widget belongs to.
+ * @return a pointer to a new Widget if applicable, null otherwise..
+ */
+ static InxParameter *make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext);
+
+ const char *get_tooltip() const override { return _description; }
+
+ /**
+ * Gets the current value of the parameter in a string form.
+ *
+ * @return String representation of the parameter's value.
+ *
+ * \internal Must be implemented by all derived classes.
+ * Unfortunately it seems we can't make this a pure virtual function,
+ * as InxParameter is not supposed to be abstract.
+ */
+ virtual std::string value_to_string() const;
+
+ /** Recommended spacing between the widgets making up a single Parameter (e.g. label and input) (in px) */
+ const static int GUI_PARAM_WIDGETS_SPACING = 4;
+
+
+ /** An error class for when a parameter is called on a type it is not */
+ class param_no_name {};
+ class param_no_text {};
+ class param_not_bool_param {};
+ class param_not_color_param {};
+ class param_not_float_param {};
+ class param_not_int_param {};
+ class param_not_optiongroup_param {};
+ class param_not_string_param {};
+
+
+protected:
+ /** The name of this parameter. */
+ char *_name = nullptr;
+
+ /** Parameter text to show as the GUI label. */
+ char *_text = nullptr;
+
+ /** Extended description of the parameter (currently shown as tooltip on hover). */
+ char *_description = nullptr;
+
+
+ /* **** member functions **** */
+
+ /**
+ * Build preference name for the current parameter.
+ *
+ * Returns a preference name that can be used with setters and getters from Inkscape::Preferences.
+ * The name is assembled from a fixed root ("/extensions/"), extension ID and parameter name.
+ *
+ * @return: Preference name
+ */
+ Glib::ustring pref_name() const;
+};
+
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // SEEN_INK_EXTENSION_PARAM_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/extension/prefdialog/prefdialog.cpp b/src/extension/prefdialog/prefdialog.cpp
new file mode 100644
index 0000000..4c96e4d
--- /dev/null
+++ b/src/extension/prefdialog/prefdialog.cpp
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2005-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "prefdialog.h"
+
+#include <gtkmm/checkbutton.h>
+#include <gtkmm/separator.h>
+#include <glibmm/i18n.h>
+
+#include "ui/dialog-events.h"
+#include "xml/repr.h"
+
+// Used to get SP_ACTIVE_DESKTOP
+#include "inkscape.h"
+#include "document.h"
+#include "document-undo.h"
+
+#include "extension/effect.h"
+#include "extension/execution-env.h"
+#include "extension/implementation/implementation.h"
+
+#include "parameter.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+
+/** \brief Creates a new preference dialog for extension preferences
+ \param name Name of the Extension whose dialog this is (should already be translated)
+ \param controls The extension specific widgets in the dialog
+
+ This function initializes the dialog with the name of the extension
+ in the title. It adds a few buttons and sets up handlers for
+ them. It also places the passed-in widgets into the dialog.
+*/
+PrefDialog::PrefDialog (Glib::ustring name, Gtk::Widget * controls, Effect * effect) :
+ Gtk::Dialog(name, true),
+ _name(name),
+ _button_ok(nullptr),
+ _button_cancel(nullptr),
+ _button_preview(nullptr),
+ _param_preview(nullptr),
+ _effect(effect),
+ _exEnv(nullptr)
+{
+ this->set_default_size(0,0); // we want the window to be as small as possible instead of clobbering up space
+
+ Gtk::Box *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
+ if (controls == nullptr) {
+ if (_effect == nullptr) {
+ std::cout << "AH!!! No controls and no effect!!!" << std::endl;
+ return;
+ }
+ controls = _effect->get_imp()->prefs_effect(_effect, SP_ACTIVE_DESKTOP, &_signal_param_change, nullptr);
+ _signal_param_change.connect(sigc::mem_fun(this, &PrefDialog::param_change));
+ }
+
+ hbox->pack_start(*controls, true, true, 0);
+ hbox->show();
+
+ this->get_content_area()->pack_start(*hbox, true, true, 0);
+
+ _button_cancel = add_button(_effect == nullptr ? _("_Cancel") : _("_Close"), Gtk::RESPONSE_CANCEL);
+ _button_ok = add_button(_effect == nullptr ? _("_OK") : _("_Apply"), Gtk::RESPONSE_OK);
+ set_default_response(Gtk::RESPONSE_OK);
+ _button_ok->grab_focus();
+
+ if (_effect != nullptr && !_effect->no_live_preview) {
+ if (_param_preview == nullptr) {
+ XML::Document * doc = sp_repr_read_mem(live_param_xml, strlen(live_param_xml), nullptr);
+ if (doc == nullptr) {
+ std::cout << "Error encountered loading live parameter XML !!!" << std::endl;
+ return;
+ }
+ _param_preview = InxParameter::make(doc->root(), _effect);
+ }
+
+ auto sep = Gtk::manage(new Gtk::Separator());
+ sep->show();
+
+ this->get_content_area()->pack_start(*sep, false, false, InxWidget::GUI_BOX_SPACING);
+
+ hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
+ hbox->set_border_width(InxWidget::GUI_BOX_MARGIN);
+ _button_preview = _param_preview->get_widget(&_signal_preview);
+ _button_preview->show();
+ hbox->pack_start(*_button_preview, true, true, 0);
+ hbox->show();
+
+ this->get_content_area()->pack_start(*hbox, false, false, 0);
+
+ Gtk::Box *hbox = dynamic_cast<Gtk::Box *>(_button_preview);
+ if (hbox != nullptr) {
+ _checkbox_preview = dynamic_cast<Gtk::CheckButton *>(hbox->get_children().front());
+ }
+
+ preview_toggle();
+ _signal_preview.connect(sigc::mem_fun(this, &PrefDialog::preview_toggle));
+ }
+
+ // Set window modality for effects that don't use live preview
+ if (_effect != nullptr && _effect->no_live_preview) {
+ set_modal(false);
+ }
+
+ return;
+}
+
+PrefDialog::~PrefDialog ( )
+{
+ if (_param_preview != nullptr) {
+ delete _param_preview;
+ _param_preview = nullptr;
+ }
+
+ if (_exEnv != nullptr) {
+ _exEnv->cancel();
+ delete _exEnv;
+ _exEnv = nullptr;
+ _effect->set_execution_env(_exEnv);
+ }
+
+ if (_effect != nullptr) {
+ _effect->set_pref_dialog(nullptr);
+ }
+ return;
+}
+
+void
+PrefDialog::preview_toggle () {
+ SPDocument *document = SP_ACTIVE_DOCUMENT;
+ bool modified = document->isModifiedSinceSave();
+ if(_param_preview->get_bool()) {
+ if (_exEnv == nullptr) {
+ set_modal(true);
+ _exEnv = new ExecutionEnv(_effect, SP_ACTIVE_DESKTOP, nullptr, false, false);
+ _effect->set_execution_env(_exEnv);
+ _exEnv->run();
+ }
+ } else {
+ set_modal(false);
+ if (_exEnv != nullptr) {
+ _exEnv->cancel();
+ _exEnv->undo();
+ _exEnv->reselect();
+ delete _exEnv;
+ _exEnv = nullptr;
+ _effect->set_execution_env(_exEnv);
+ }
+ }
+ document->setModifiedSinceSave(modified);
+}
+
+void
+PrefDialog::param_change () {
+ if (_exEnv != nullptr) {
+ if (!_effect->loaded()) {
+ _effect->set_state(Extension::STATE_LOADED);
+ }
+ _timersig.disconnect();
+ _timersig = Glib::signal_timeout().connect(sigc::mem_fun(this, &PrefDialog::param_timer_expire),
+ 250, /* ms */
+ Glib::PRIORITY_DEFAULT_IDLE);
+ }
+
+ return;
+}
+
+bool
+PrefDialog::param_timer_expire () {
+ if (_exEnv != nullptr) {
+ _exEnv->cancel();
+ _exEnv->undo();
+ _exEnv->reselect();
+ _exEnv->run();
+ }
+
+ return false;
+}
+
+void
+PrefDialog::on_response (int signal) {
+ if (signal == Gtk::RESPONSE_OK) {
+ if (_exEnv == nullptr) {
+ if (_effect != nullptr) {
+ _effect->effect(SP_ACTIVE_DESKTOP);
+ } else {
+ // Shutdown run()
+ return;
+ }
+ } else {
+ if (_exEnv->wait()) {
+ _exEnv->commit();
+ } else {
+ _exEnv->undo();
+ _exEnv->reselect();
+ }
+ delete _exEnv;
+ _exEnv = nullptr;
+ _effect->set_execution_env(_exEnv);
+ }
+ }
+
+ if (_param_preview != nullptr) {
+ _checkbox_preview->set_active(false);
+ }
+
+ if ((signal == Gtk::RESPONSE_CANCEL || signal == Gtk::RESPONSE_DELETE_EVENT) && _effect != nullptr) {
+ delete this;
+ }
+ return;
+}
+
+#include "extension/internal/clear-n_.h"
+
+const char * PrefDialog::live_param_xml = "<param name=\"__live_effect__\" type=\"bool\" gui-text=\"" N_("Live preview") "\" gui-description=\"" N_("Is the effect previewed live on canvas?") "\">false</param>";
+
+}; }; /* namespace Inkscape, Extension */
+
+/*
+ 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/extension/prefdialog/prefdialog.h b/src/extension/prefdialog/prefdialog.h
new file mode 100644
index 0000000..9fe8acd
--- /dev/null
+++ b/src/extension/prefdialog/prefdialog.h
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2005,2007-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_EXTENSION_DIALOG_H__
+#define INKSCAPE_EXTENSION_DIALOG_H__
+
+#include <gtkmm/dialog.h>
+#include <glibmm/value.h>
+#include <glibmm/ustring.h>
+
+namespace Gtk {
+class CheckButton;
+}
+
+namespace Inkscape {
+namespace Extension {
+class Effect;
+class ExecutionEnv;
+class InxParameter;
+
+/** \brief A class to represent the preferences for an extension */
+class PrefDialog : public Gtk::Dialog {
+ /** \brief Name of the extension */
+ Glib::ustring _name;
+
+ /** \brief A pointer to the OK button */
+ Gtk::Button *_button_ok;
+ /** \brief A pointer to the CANCEL button */
+ Gtk::Button *_button_cancel;
+
+ /** \brief Button to control live preview */
+ Gtk::Widget *_button_preview;
+ /** \brief Checkbox for the preview */
+ Gtk::CheckButton *_checkbox_preview;
+
+ /** \brief Parameter to control live preview */
+ InxParameter *_param_preview;
+
+ /** \brief XML to define the live effects parameter on the dialog */
+ static const char * live_param_xml;
+
+ /** \brief Signal that the user is changing the live effect state */
+ sigc::signal<void> _signal_preview;
+ /** \brief Signal that one of the parameters change */
+ sigc::signal<void> _signal_param_change;
+
+ /** \brief If this is the preferences for an effect, the effect
+ that we're working with. */
+ Effect *_effect;
+ /** \brief If we're executing in preview mode here is the execution
+ environment for the effect. */
+ ExecutionEnv *_exEnv;
+
+ /** \brief The timer used to make it so that parameters don't respond
+ directly and allows for changes. */
+ sigc::connection _timersig;
+
+ void preview_toggle();
+ void param_change();
+ bool param_timer_expire();
+ void on_response (int signal) override;
+
+public:
+ PrefDialog (Glib::ustring name,
+ Gtk::Widget * controls = nullptr,
+ Effect * effect = nullptr);
+ ~PrefDialog () override;
+};
+
+
+};}; /* namespace Inkscape, Extension */
+
+#endif /* INKSCAPE_EXTENSION_DIALOG_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/extension/prefdialog/widget-box.cpp b/src/extension/prefdialog/widget-box.cpp
new file mode 100644
index 0000000..12c7b9e
--- /dev/null
+++ b/src/extension/prefdialog/widget-box.cpp
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Box widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "widget-box.h"
+
+#include <gtkmm/box.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+
+
+WidgetBox::WidgetBox(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxWidget(xml, ext)
+{
+ // Decide orientation based on tagname (hbox vs. vbox)
+ const char *tagname = xml->name();
+ if (!strncmp(tagname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) {
+ tagname += strlen(INKSCAPE_EXTENSION_NS);
+ }
+ if (!strcmp(tagname, "hbox")) {
+ _orientation = HORIZONTAL;
+ } else if (!strcmp(tagname, "vbox")) {
+ _orientation = VERTICAL;
+ } else {
+ g_assert_not_reached();
+ }
+
+ // Read XML tree of box and parse child widgets
+ if (xml) {
+ Inkscape::XML::Node *child_repr = xml->firstChild();
+ while (child_repr) {
+ const char *chname = child_repr->name();
+ if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) {
+ chname += strlen(INKSCAPE_EXTENSION_NS);
+ }
+ if (chname[0] == '_') { // allow leading underscore in tag names for backwards-compatibility
+ chname++;
+ }
+
+ if (InxWidget::is_valid_widget_name(chname)) {
+ InxWidget *widget = InxWidget::make(child_repr, _extension);
+ if (widget) {
+ _children.push_back(widget);
+ }
+ } else if (child_repr->type() == XML::NodeType::ELEMENT_NODE) {
+ g_warning("Invalid child element ('%s') in box widget in extension '%s'.",
+ chname, _extension->get_id());
+ } else if (child_repr->type() != XML::NodeType::COMMENT_NODE){
+ g_warning("Invalid child element found in box widget in extension '%s'.", _extension->get_id());
+ }
+
+ child_repr = child_repr->next();
+ }
+ }
+}
+
+Gtk::Widget *WidgetBox::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Orientation orientation;
+ if (_orientation == HORIZONTAL) {
+ orientation = Gtk::ORIENTATION_HORIZONTAL;
+ } else {
+ orientation = Gtk::ORIENTATION_VERTICAL;
+ }
+
+ Gtk::Box *box = Gtk::manage(new Gtk::Box(orientation));
+ // box->set_border_width(GUI_BOX_MARGIN); // leave at zero for now, so box is purely for layouting (not grouping)
+ // revisit this later, possibly implementing GtkFrame or similar
+ box->set_spacing(GUI_BOX_SPACING);
+
+ if (_orientation == HORIZONTAL) {
+ box->set_vexpand(false);
+ } else {
+ box->set_hexpand(false);
+ }
+
+ // add child widgets onto page (if any)
+ for (auto child : _children) {
+ Gtk::Widget *child_widget = child->get_widget(changeSignal);
+ if (child_widget) {
+ int indent = child->get_indent();
+ child_widget->set_margin_start(indent * GUI_INDENTATION);
+ box->pack_start(*child_widget, false, true, 0); // fill=true does not have an effect here, but allows the
+ // child to choose to expand by setting hexpand/vexpand
+
+ const char *tooltip = child->get_tooltip();
+ if (tooltip) {
+ child_widget->set_tooltip_text(tooltip);
+ }
+ }
+ }
+
+ box->show();
+
+ return dynamic_cast<Gtk::Widget *>(box);
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/widget-box.h b/src/extension/prefdialog/widget-box.h
new file mode 100644
index 0000000..6fd7b90
--- /dev/null
+++ b/src/extension/prefdialog/widget-box.h
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Box widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_EXTENSION_WIDGET_BOX_H
+#define SEEN_INK_EXTENSION_WIDGET_BOX_H
+
+#include "widget.h"
+
+#include <glibmm/ustring.h>
+
+namespace Gtk {
+ class Widget;
+}
+
+namespace Inkscape {
+namespace Xml {
+ class Node;
+}
+
+namespace Extension {
+
+/** \brief A box widget */
+class WidgetBox : public InxWidget {
+public:
+ WidgetBox(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+private:
+ enum Orientation {
+ HORIZONTAL, VERTICAL
+ };
+
+ /** Layout orientation of the box (default is vertical) **/
+ Orientation _orientation = VERTICAL;
+};
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* SEEN_INK_EXTENSION_WIDGET_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 :
diff --git a/src/extension/prefdialog/widget-image.cpp b/src/extension/prefdialog/widget-image.cpp
new file mode 100644
index 0000000..4f5e11d
--- /dev/null
+++ b/src/extension/prefdialog/widget-image.cpp
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Image widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "widget-image.h"
+
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+#include <gtkmm/image.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+
+
+WidgetImage::WidgetImage(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxWidget(xml, ext)
+{
+ std::string image_path;
+
+ // get path to image
+ const char *content = nullptr;
+ if (xml->firstChild()) {
+ content = xml->firstChild()->content();
+ }
+ if (content) {
+ image_path = content;
+ } else {
+ g_warning("Missing path for image widget in extension '%s'.", _extension->get_id());
+ return;
+ }
+
+ // make sure path is absolute (relative paths are relative to .inx file's location)
+ if (!Glib::path_is_absolute(image_path)) {
+ image_path = Glib::build_filename(_extension->get_base_directory(), image_path);
+ }
+
+ // check if image exists
+ if (Glib::file_test(image_path, Glib::FILE_TEST_IS_REGULAR)) {
+ _image_path = image_path;
+ } else {
+ g_warning("Image file ('%s') not found for image widget in extension '%s'.",
+ image_path.c_str(), _extension->get_id());
+ }
+
+ // parse width/height attributes
+ const char *width = xml->attribute("width");
+ const char *height = xml->attribute("height");
+ if (width && height) {
+ _width = strtoul(width, nullptr, 0);
+ _height = strtoul(height, nullptr, 0);
+ }
+}
+
+/** \brief Create a label for the description */
+Gtk::Widget *WidgetImage::get_widget(sigc::signal<void> * /*changeSignal*/)
+{
+ if (_hidden || _image_path.empty()) {
+ return nullptr;
+ }
+
+ Gtk::Image *image = Gtk::manage(new Gtk::Image(_image_path));
+
+ // resize if requested
+ if (_width && _height) {
+ Glib::RefPtr<Gdk::Pixbuf> pixbuf = image->get_pixbuf();
+ pixbuf = pixbuf->scale_simple(_width, _height, Gdk::INTERP_BILINEAR);
+ image->set(pixbuf);
+ }
+
+ image->show();
+
+ return dynamic_cast<Gtk::Widget *>(image);
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/widget-image.h b/src/extension/prefdialog/widget-image.h
new file mode 100644
index 0000000..95dd4d5
--- /dev/null
+++ b/src/extension/prefdialog/widget-image.h
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Image widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_EXTENSION_WIDGET_IMAGE_H
+#define SEEN_INK_EXTENSION_WIDGET_IMAGE_H
+
+#include "widget.h"
+
+#include <string>
+
+namespace Gtk {
+ class Widget;
+}
+
+namespace Inkscape {
+namespace Xml {
+ class Node;
+}
+
+namespace Extension {
+
+/** \brief A label widget */
+class WidgetImage : public InxWidget {
+public:
+ WidgetImage(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+private:
+ /** \brief Path to image file (relative paths are relative to the .inx file location). */
+ std::string _image_path;
+
+ /** desired width of image when rendered on screen (in px) */
+ unsigned int _width = 0;
+ /** desired height of image when rendered on screen (in px) */
+ unsigned int _height = 0;
+};
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* SEEN_INK_EXTENSION_WIDGET_IMAGE_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/extension/prefdialog/widget-label.cpp b/src/extension/prefdialog/widget-label.cpp
new file mode 100644
index 0000000..56ba0c1
--- /dev/null
+++ b/src/extension/prefdialog/widget-label.cpp
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Description widget for extensions
+ *//*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2005-2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "widget-label.h"
+
+#include <gtkmm/box.h>
+#include <gtkmm/label.h>
+#include <glibmm/markup.h>
+#include <glibmm/regex.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+
+
+WidgetLabel::WidgetLabel(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxWidget(xml, ext)
+{
+ // construct the text content by concatenating all (non-empty) text nodes,
+ // removing all other nodes (e.g. comment nodes) and replacing <extension:br> elements with "<br/>"
+ Inkscape::XML::Node * cur_child = xml->firstChild();
+ while (cur_child != nullptr) {
+ if (cur_child->type() == XML::NodeType::TEXT_NODE && cur_child->content() != nullptr) {
+ _value += cur_child->content();
+ } else if (cur_child->type() == XML::NodeType::ELEMENT_NODE && !g_strcmp0(cur_child->name(), "extension:br")) {
+ _value += "<br/>";
+ }
+ cur_child = cur_child->next();
+ }
+
+ // do replacements in the source string to account for the attribute xml:space="preserve"
+ // (those should match replacements potentially performed by xgettext to allow for proper translation)
+ if (g_strcmp0(xml->attribute("xml:space"), "preserve") == 0) {
+ // xgettext copies the source string verbatim in this case, so no changes needed
+ } else {
+ // remove all whitespace from start/end of string and replace intermediate whitespace with a single space
+ _value = Glib::Regex::create("^\\s+|\\s+$")->replace_literal(_value, 0, "", (Glib::RegexMatchFlags)0);
+ _value = Glib::Regex::create("\\s+")->replace_literal(_value, 0, " ", (Glib::RegexMatchFlags)0);
+ }
+
+ // translate value
+ if (!_value.empty()) {
+ if (_translatable != NO) { // translate unless explicitly marked untranslatable
+ _value = get_translation(_value.c_str());
+ }
+ }
+
+ // finally replace all remaining <br/> with a real newline character
+ _value = Glib::Regex::create("<br/>")->replace_literal(_value, 0, "\n", (Glib::RegexMatchFlags)0);
+
+ // parse appearance
+ if (_appearance) {
+ if (!strcmp(_appearance, "header")) {
+ _mode = HEADER;
+ } else if (!strcmp(_appearance, "url")) {
+ _mode = URL;
+ } else {
+ g_warning("Invalid value ('%s') for appearance of label widget in extension '%s'",
+ _appearance, _extension->get_id());
+ }
+ }
+}
+
+/** \brief Create a label for the description */
+Gtk::Widget *WidgetLabel::get_widget(sigc::signal<void> * /*changeSignal*/)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Glib::ustring newtext = _value;
+
+ Gtk::Label *label = Gtk::manage(new Gtk::Label());
+ if (_mode == HEADER) {
+ label->set_markup(Glib::ustring("<b>") + Glib::Markup::escape_text(newtext) + Glib::ustring("</b>"));
+ label->set_margin_top(5);
+ label->set_margin_bottom(5);
+ } else if (_mode == URL) {
+ Glib::ustring escaped_url = Glib::Markup::escape_text(newtext);
+ label->set_markup(Glib::ustring::compose("<a href='%1'>%1</a>", escaped_url));
+ } else {
+ label->set_text(newtext);
+ }
+ label->set_line_wrap();
+ label->set_xalign(0);
+
+ // TODO: Ugly "fix" for gtk3 width/height calculation of labels.
+ // - If not applying any limits long labels will make the window grow horizontally until it uses up
+ // most of the available space (i.e. most of the screen area) which is ridiculously wide.
+ // - By using "set_default_size(0,0)" in prefidalog.cpp we tell the window to shrink as much as possible,
+ // however this can result in a much too narrow dialog instead and a lot of unnecessary wrapping.
+ // - Here we set a lower limit of GUI_MAX_LINE_LENGTH characters per line that long texts will always use.
+ // This means texts can not shrink anymore (they can still grow, though) and it's also necessary
+ // to prevent https://bugzilla.gnome.org/show_bug.cgi?id=773572
+ int len = newtext.length();
+ label->set_width_chars(len > GUI_MAX_LINE_LENGTH ? GUI_MAX_LINE_LENGTH : len);
+
+ label->show();
+
+ Gtk::Box *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
+ hbox->pack_start(*label, true, true);
+ hbox->show();
+
+ return hbox;
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/widget-label.h b/src/extension/prefdialog/widget-label.h
new file mode 100644
index 0000000..1c16550
--- /dev/null
+++ b/src/extension/prefdialog/widget-label.h
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Description widget for extensions
+ *//*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2005-2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_EXTENSION_WIDGET_LABEL_H
+#define SEEN_INK_EXTENSION_WIDGET_LABEL_H
+
+#include "widget.h"
+
+#include <glibmm/ustring.h>
+
+namespace Gtk {
+ class Widget;
+}
+
+namespace Inkscape {
+namespace Xml {
+ class Node;
+}
+
+namespace Extension {
+
+/** \brief A label widget */
+class WidgetLabel : public InxWidget {
+public:
+ enum AppearanceMode {
+ DEFAULT, HEADER, URL
+ };
+
+ WidgetLabel(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+private:
+ /** \brief Internal value. */
+ Glib::ustring _value;
+
+ /** appearance mode **/
+ AppearanceMode _mode = DEFAULT;
+};
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* SEEN_INK_EXTENSION_WIDGET_LABEL_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/extension/prefdialog/widget-separator.cpp b/src/extension/prefdialog/widget-separator.cpp
new file mode 100644
index 0000000..d78894b
--- /dev/null
+++ b/src/extension/prefdialog/widget-separator.cpp
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Separator widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "widget-separator.h"
+
+#include <gtkmm/separator.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+
+
+WidgetSeparator::WidgetSeparator(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxWidget(xml, ext)
+{
+}
+
+/** \brief Create a label for the description */
+Gtk::Widget *WidgetSeparator::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Separator *separator = Gtk::manage(new Gtk::Separator());
+ separator->show();
+
+ return dynamic_cast<Gtk::Widget *>(separator);
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/widget-separator.h b/src/extension/prefdialog/widget-separator.h
new file mode 100644
index 0000000..9533aba
--- /dev/null
+++ b/src/extension/prefdialog/widget-separator.h
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Separator widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_EXTENSION_WIDGET_SEPARATOR_H
+#define SEEN_INK_EXTENSION_WIDGET_SEPARATOR_H
+
+#include "widget.h"
+
+#include <glibmm/ustring.h>
+
+namespace Gtk {
+ class Widget;
+}
+
+namespace Inkscape {
+namespace Xml {
+ class Node;
+}
+
+namespace Extension {
+
+/** \brief A separator widget */
+class WidgetSeparator : public InxWidget {
+public:
+ WidgetSeparator(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+};
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* SEEN_INK_EXTENSION_WIDGET_SEPARATOR_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/extension/prefdialog/widget-spacer.cpp b/src/extension/prefdialog/widget-spacer.cpp
new file mode 100644
index 0000000..8ffa6ac
--- /dev/null
+++ b/src/extension/prefdialog/widget-spacer.cpp
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Spacer widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "widget-spacer.h"
+
+#include <gtkmm/box.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+
+
+WidgetSpacer::WidgetSpacer(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxWidget(xml, ext)
+{
+ // get size
+ const char *size = xml->attribute("size");
+ if (size) {
+ _size = strtol(size, nullptr, 0);
+ if (_size == 0) {
+ if (!strcmp(size, "expand")) {
+ _expand = true;
+ } else {
+ g_warning("Invalid value ('%s') for size spacer in extension '%s'", size, _extension->get_id());
+ }
+ }
+ }
+}
+
+/** \brief Create a label for the description */
+Gtk::Widget *WidgetSpacer::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Box *spacer = Gtk::manage(new Gtk::Box());
+ spacer->set_border_width(_size/2);
+
+ if (_expand) {
+ spacer->set_hexpand();
+ spacer->set_vexpand();
+ }
+
+ spacer->show();
+
+ return dynamic_cast<Gtk::Widget *>(spacer);
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/widget-spacer.h b/src/extension/prefdialog/widget-spacer.h
new file mode 100644
index 0000000..467b5f9
--- /dev/null
+++ b/src/extension/prefdialog/widget-spacer.h
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Spacer widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_EXTENSION_WIDGET_SPACER_H
+#define SEEN_INK_EXTENSION_WIDGET_SPACER_H
+
+#include "widget.h"
+
+#include <glibmm/ustring.h>
+
+namespace Gtk {
+ class Widget;
+}
+
+namespace Inkscape {
+namespace Xml {
+ class Node;
+}
+
+namespace Extension {
+
+/** \brief A separator widget */
+class WidgetSpacer : public InxWidget {
+public:
+ WidgetSpacer(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+private:
+ /** size of the spacer in px */
+ int _size = GUI_BOX_MARGIN;
+
+ /** should the spacer be flexible and expand? */
+ bool _expand = false;
+};
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* SEEN_INK_EXTENSION_WIDGET_SPACER_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/extension/prefdialog/widget.cpp b/src/extension/prefdialog/widget.cpp
new file mode 100644
index 0000000..18639c5
--- /dev/null
+++ b/src/extension/prefdialog/widget.cpp
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Parameters for extensions.
+ *//*
+ * Author:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+#include "widget.h"
+#include "widget-box.h"
+#include "widget-image.h"
+#include "widget-label.h"
+#include "widget-separator.h"
+#include "widget-spacer.h"
+
+#include <algorithm>
+#include <cstring>
+
+#include <sigc++/sigc++.h>
+
+#include "extension/extension.h"
+
+#include "xml/node.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+InxWidget *InxWidget::make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext)
+{
+ InxWidget *widget = nullptr;
+
+ const char *name = in_repr->name();
+ if (!strncmp(name, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) {
+ name += strlen(INKSCAPE_EXTENSION_NS);
+ }
+ if (name[0] == '_') { // allow leading underscore in tag names for backwards-compatibility
+ name++;
+ }
+
+ // decide on widget type based on tag name
+ // keep in sync with list of names supported in InxWidget::is_valid_widget_name() below
+ if (!name) {
+ // we can't create a widget without name
+ g_warning("InxWidget without name in extension '%s'.", in_ext->get_id());
+ } else if (!strcmp(name, "hbox") || !strcmp(name, "vbox")) {
+ widget = new WidgetBox(in_repr, in_ext);
+ } else if (!strcmp(name, "image")) {
+ widget = new WidgetImage(in_repr, in_ext);
+ } else if (!strcmp(name, "label")) {
+ widget = new WidgetLabel(in_repr, in_ext);
+ } else if (!strcmp(name, "separator")) {
+ widget = new WidgetSeparator(in_repr, in_ext);
+ } else if (!strcmp(name, "spacer")) {
+ widget = new WidgetSpacer(in_repr, in_ext);
+ } else if (!strcmp(name, "param")) {
+ widget = InxParameter::make(in_repr, in_ext);
+ } else {
+ g_warning("Unknown widget name ('%s') in extension '%s'", name, in_ext->get_id());
+ }
+
+ // Note: widget could equal nullptr
+ return widget;
+}
+
+bool InxWidget::is_valid_widget_name(const char *name)
+{
+ // keep in sync with names supported in InxWidget::make() above
+ static const std::vector<std::string> valid_names =
+ {"hbox", "vbox", "image", "label", "separator", "spacer", "param"};
+
+ if (std::find(valid_names.begin(), valid_names.end(), name) != valid_names.end()) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+InxWidget::InxWidget(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *ext)
+ : _extension(ext)
+{
+ // translatable (optional)
+ const char *translatable = in_repr->attribute("translatable");
+ if (translatable) {
+ if (!strcmp(translatable, "yes")) {
+ _translatable = YES;
+ } else if (!strcmp(translatable, "no")) {
+ _translatable = NO;
+ } else {
+ g_warning("Invalid value ('%s') for translatable attribute of widget '%s' in extension '%s'",
+ translatable, in_repr->name(), _extension->get_id());
+ }
+ }
+
+ // context (optional)
+ const char *context = in_repr->attribute("context");
+ if (!context) {
+ context = in_repr->attribute("msgctxt"); // backwards-compatibility with previous name
+ }
+ if (context) {
+ _context = g_strdup(context);
+ }
+
+ // gui-hidden (optional)
+ const char *gui_hidden = in_repr->attribute("gui-hidden");
+ if (gui_hidden != nullptr) {
+ if (strcmp(gui_hidden, "true") == 0) {
+ _hidden = true;
+ }
+ }
+
+ // indent (optional)
+ const char *indent = in_repr->attribute("indent");
+ if (indent != nullptr) {
+ _indent = strtol(indent, nullptr, 0);
+ }
+
+ // appearance (optional, does not apply to all parameters)
+ const char *appearance = in_repr->attribute("appearance");
+ if (appearance) {
+ _appearance = g_strdup(appearance);
+ }
+}
+
+InxWidget::~InxWidget()
+{
+ for (auto child : _children) {
+ delete child;
+ }
+
+ g_free(_context);
+ _context = nullptr;
+
+ g_free(_appearance);
+ _appearance = nullptr;
+}
+
+Gtk::Widget *
+InxWidget::get_widget(sigc::signal<void> * /*changeSignal*/)
+{
+ // if we end up here we're missing a definition of ::get_widget() in one of the subclasses
+ g_critical("InxWidget::get_widget called from widget of type '%s' in extension '%s'",
+ typeid(this).name(), _extension->get_id());
+ g_assert_not_reached();
+ return nullptr;
+}
+
+const char *InxWidget::get_translation(const char* msgid) {
+ return _extension->get_translation(msgid, _context);
+}
+
+void InxWidget::get_widgets(std::vector<InxWidget *> &list)
+{
+ list.push_back(this);
+ for (auto child : _children) {
+ child->get_widgets(list);
+ }
+}
+
+} // namespace Extension
+} // 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/extension/prefdialog/widget.h b/src/extension/prefdialog/widget.h
new file mode 100644
index 0000000..843655c
--- /dev/null
+++ b/src/extension/prefdialog/widget.h
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Base class for extension widgets.
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_EXTENSION_WIDGET_H
+#define SEEN_INK_EXTENSION_WIDGET_H
+
+#include <string>
+#include <vector>
+
+#include <sigc++/sigc++.h>
+
+namespace Gtk {
+class Widget;
+}
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+
+namespace Extension {
+
+class Extension;
+
+
+/**
+ * Base class to represent all widgets of an extension (including parameters)
+ */
+class InxWidget {
+public:
+ InxWidget(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext);
+
+ virtual ~InxWidget();
+
+ /**
+ * Creates a new extension widget for usage in a prefdialog.
+ *
+ * The type of widget created is parsed from the XML representation passed in,
+ * and the suitable subclass constructor is called.
+ *
+ * For specialized widget types (like parameters) we defer to the subclass function of the same name.
+ *
+ * @param in_repr The XML representation describing the widget.
+ * @param in_ext The extension the widget belongs to.
+ * @return a pointer to a new Widget if applicable, null otherwise..
+ */
+ static InxWidget *make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext);
+
+ /** Checks if name is a valid widget name, i.e. a widget can be constructed from it using make() */
+ static bool is_valid_widget_name(const char *name);
+
+ /** Return the instance's GTK::Widget representation for usage in a GUI
+ *
+ * @param changeSignal Can be used to subscribe to parameter changes.
+ * Will be emitted whenever a parameter value changes.
+ *
+ * @teturn A Gtk::Widget for the \a InxWidget. \c nullptr if the widget is hidden.
+ */
+ virtual Gtk::Widget *get_widget(sigc::signal<void> *changeSignal);
+
+ virtual const char *get_tooltip() const { return nullptr; } // tool-tips are exclusive to InxParameters for now
+
+ /** Indicates if the widget is hidden or not */
+ bool get_hidden() const { return _hidden; }
+
+ /** Indentation level of the widget */
+ int get_indent() const { return _indent; }
+
+
+ /**
+ * Recursively construct a list containing the current widget and all of it's child widgets (if it has any)
+ *
+ * @param list Reference to a vector of pointers to \a InxWidget that will be appended with the new \a InxWidgets
+ */
+ virtual void get_widgets(std::vector<InxWidget *> &list);
+
+
+ /** Recommended margin of boxes containing multiple widgets (in px) */
+ const static int GUI_BOX_MARGIN = 10;
+ /** Recommended spacing between multiple widgets packed into a box (in px) */
+ const static int GUI_BOX_SPACING = 4;
+ /** Recommended indentation width of widgets(in px) */
+ const static int GUI_INDENTATION = 12;
+ /** Recommended maximum line length for wrapping textual wdgets (in chars) */
+ const static int GUI_MAX_LINE_LENGTH = 60;
+
+protected:
+ enum Translatable {
+ UNSET, YES, NO
+ };
+
+ /** Which extension is this Widget attached to. */
+ Inkscape::Extension::Extension *_extension = nullptr;
+
+ /** Child widgets of this widget (might be empty if there are none) */
+ std::vector<InxWidget *> _children;
+
+ /** Whether the widget is visible. */
+ bool _hidden = false;
+
+ /** Indentation level of the widget. */
+ int _indent = 0;
+
+ /** Appearance of the widget (not used by all widgets). */
+ char *_appearance = nullptr;
+
+ /** Is widget translatable? */
+ Translatable _translatable = UNSET;
+
+ /** context for translation of translatable strings. */
+ char *_context = nullptr;
+
+
+ /* **** member functions **** */
+
+ /** gets the gettext translation for msgid
+ *
+ * Handles translation domain of the extension and message context of the widget internally
+ *
+ * @param msgid String to translate
+ * @return Translated string
+ */
+ const char *get_translation(const char* msgid);
+};
+
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // SEEN_INK_EXTENSION_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 :