diff options
Diffstat (limited to 'src/extension/prefdialog')
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 : |