summaryrefslogtreecommitdiffstats
path: root/src/ui/toolbar
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
commitcca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch)
tree146f39ded1c938019e1ed42d30923c2ac9e86789 /src/ui/toolbar
parentInitial commit. (diff)
downloadinkscape-upstream.tar.xz
inkscape-upstream.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/ui/toolbar')
-rw-r--r--src/ui/toolbar/arc-toolbar.cpp559
-rw-r--r--src/ui/toolbar/arc-toolbar.h116
-rw-r--r--src/ui/toolbar/box3d-toolbar.cpp428
-rw-r--r--src/ui/toolbar/box3d-toolbar.h108
-rw-r--r--src/ui/toolbar/calligraphy-toolbar.cpp628
-rw-r--r--src/ui/toolbar/calligraphy-toolbar.h105
-rw-r--r--src/ui/toolbar/connector-toolbar.cpp431
-rw-r--r--src/ui/toolbar/connector-toolbar.h93
-rw-r--r--src/ui/toolbar/dropper-toolbar.cpp117
-rw-r--r--src/ui/toolbar/dropper-toolbar.h70
-rw-r--r--src/ui/toolbar/eraser-toolbar.cpp352
-rw-r--r--src/ui/toolbar/eraser-toolbar.h101
-rw-r--r--src/ui/toolbar/gradient-toolbar.cpp1173
-rw-r--r--src/ui/toolbar/gradient-toolbar.h105
-rw-r--r--src/ui/toolbar/lpe-toolbar.cpp417
-rw-r--r--src/ui/toolbar/lpe-toolbar.h101
-rw-r--r--src/ui/toolbar/marker-toolbar.cpp34
-rw-r--r--src/ui/toolbar/marker-toolbar.h31
-rw-r--r--src/ui/toolbar/measure-toolbar.cpp448
-rw-r--r--src/ui/toolbar/measure-toolbar.h91
-rw-r--r--src/ui/toolbar/mesh-toolbar.cpp613
-rw-r--r--src/ui/toolbar/mesh-toolbar.h97
-rw-r--r--src/ui/toolbar/node-toolbar.cpp663
-rw-r--r--src/ui/toolbar/node-toolbar.h116
-rw-r--r--src/ui/toolbar/page-toolbar.cpp351
-rw-r--r--src/ui/toolbar/page-toolbar.h91
-rw-r--r--src/ui/toolbar/paintbucket-toolbar.cpp220
-rw-r--r--src/ui/toolbar/paintbucket-toolbar.h72
-rw-r--r--src/ui/toolbar/pencil-toolbar.cpp692
-rw-r--r--src/ui/toolbar/pencil-toolbar.h111
-rw-r--r--src/ui/toolbar/rect-toolbar.cpp416
-rw-r--r--src/ui/toolbar/rect-toolbar.h115
-rw-r--r--src/ui/toolbar/select-toolbar.cpp631
-rw-r--r--src/ui/toolbar/select-toolbar.h94
-rw-r--r--src/ui/toolbar/spiral-toolbar.cpp295
-rw-r--r--src/ui/toolbar/spiral-toolbar.h98
-rw-r--r--src/ui/toolbar/spray-toolbar.cpp541
-rw-r--r--src/ui/toolbar/spray-toolbar.h107
-rw-r--r--src/ui/toolbar/star-toolbar.cpp569
-rw-r--r--src/ui/toolbar/star-toolbar.h108
-rw-r--r--src/ui/toolbar/text-toolbar.cpp2576
-rw-r--r--src/ui/toolbar/text-toolbar.h150
-rw-r--r--src/ui/toolbar/toolbar.cpp84
-rw-r--r--src/ui/toolbar/toolbar.h66
-rw-r--r--src/ui/toolbar/tweak-toolbar.cpp346
-rw-r--r--src/ui/toolbar/tweak-toolbar.h89
-rw-r--r--src/ui/toolbar/zoom-toolbar.cpp67
-rw-r--r--src/ui/toolbar/zoom-toolbar.h62
48 files changed, 14948 insertions, 0 deletions
diff --git a/src/ui/toolbar/arc-toolbar.cpp b/src/ui/toolbar/arc-toolbar.cpp
new file mode 100644
index 0000000..b3493e8
--- /dev/null
+++ b/src/ui/toolbar/arc-toolbar.cpp
@@ -0,0 +1,559 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Arc aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "arc-toolbar.h"
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/radiotoolbutton.h>
+#include <gtkmm/separatortoolitem.h>
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "mod360.h"
+#include "selection.h"
+
+#include "object/sp-ellipse.h"
+#include "object/sp-namedview.h"
+
+#include "ui/icon-names.h"
+#include "ui/tools/arc-tool.h"
+#include "ui/widget/canvas.h"
+#include "ui/widget/combo-tool-item.h"
+#include "ui/widget/label-tool-item.h"
+#include "ui/widget/spinbutton.h"
+#include "ui/widget/spin-button-tool-item.h"
+#include "ui/widget/unit-tracker.h"
+
+#include "widgets/widget-sizes.h"
+
+#include "xml/node-event-vector.h"
+
+using Inkscape::UI::Widget::UnitTracker;
+using Inkscape::DocumentUndo;
+using Inkscape::Util::Quantity;
+using Inkscape::Util::unit_table;
+
+
+static Inkscape::XML::NodeEventVector arc_tb_repr_events = {
+ nullptr, /* child_added */
+ nullptr, /* child_removed */
+ Inkscape::UI::Toolbar::ArcToolbar::event_attr_changed,
+ nullptr, /* content_changed */
+ nullptr /* order_changed */
+};
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+ArcToolbar::ArcToolbar(SPDesktop *desktop) :
+ Toolbar(desktop),
+ _tracker(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR)),
+ _freeze(false),
+ _repr(nullptr)
+{
+ auto init_units = desktop->getNamedView()->display_units;
+ _tracker->setActiveUnit(init_units);
+ auto prefs = Inkscape::Preferences::get();
+
+ {
+ _mode_item = Gtk::manage(new UI::Widget::LabelToolItem(_("<b>New:</b>")));
+ _mode_item->set_use_markup(true);
+ add(*_mode_item);
+ }
+
+ /* Radius X */
+ {
+ std::vector<double> values = {1, 2, 3, 5, 10, 20, 50, 100, 200, 500};
+ auto rx_val = prefs->getDouble("/tools/shapes/arc/rx", 0);
+ rx_val = Quantity::convert(rx_val, "px", init_units);
+
+ _rx_adj = Gtk::Adjustment::create(rx_val, 0, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
+ _rx_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("arc-rx", _("Rx:"), _rx_adj));
+ _rx_item->set_tooltip_text(_("Horizontal radius of the circle, ellipse, or arc"));
+ _rx_item->set_custom_numeric_menu_data(values);
+ _tracker->addAdjustment(_rx_adj->gobj());
+ _rx_item->get_spin_button()->addUnitTracker(_tracker);
+ _rx_item->set_focus_widget(desktop->canvas);
+ _rx_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &ArcToolbar::value_changed),
+ _rx_adj, "rx"));
+ _rx_item->set_sensitive(false);
+ add(*_rx_item);
+ }
+
+ /* Radius Y */
+ {
+ std::vector<double> values = {1, 2, 3, 5, 10, 20, 50, 100, 200, 500};
+ auto ry_val = prefs->getDouble("/tools/shapes/arc/ry", 0);
+ ry_val = Quantity::convert(ry_val, "px", init_units);
+
+ _ry_adj = Gtk::Adjustment::create(ry_val, 0, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
+ _ry_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("arc-ry", _("Ry:"), _ry_adj));
+ _ry_item->set_tooltip_text(_("Vertical radius of the circle, ellipse, or arc"));
+ _ry_item->set_custom_numeric_menu_data(values);
+ _tracker->addAdjustment(_ry_adj->gobj());
+ _ry_item->get_spin_button()->addUnitTracker(_tracker);
+ _ry_item->set_focus_widget(desktop->canvas);
+ _ry_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &ArcToolbar::value_changed),
+ _ry_adj, "ry"));
+ _ry_item->set_sensitive(false);
+ add(*_ry_item);
+ }
+
+ // add the units menu
+ {
+ auto unit_menu = _tracker->create_tool_item(_("Units"), ("") );
+ add(*unit_menu);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Start */
+ {
+ auto start_val = prefs->getDouble("/tools/shapes/arc/start", 0.0);
+ _start_adj = Gtk::Adjustment::create(start_val, -360.0, 360.0, 1.0, 10.0);
+ auto eact = Gtk::manage(new UI::Widget::SpinButtonToolItem("arc-start", _("Start:"), _start_adj));
+ eact->set_tooltip_text(_("The angle (in degrees) from the horizontal to the arc's start point"));
+ eact->set_focus_widget(desktop->canvas);
+ add(*eact);
+ }
+
+ /* End */
+ {
+ auto end_val = prefs->getDouble("/tools/shapes/arc/end", 0.0);
+ _end_adj = Gtk::Adjustment::create(end_val, -360.0, 360.0, 1.0, 10.0);
+ auto eact = Gtk::manage(new UI::Widget::SpinButtonToolItem("arc-end", _("End:"), _end_adj));
+ eact->set_tooltip_text(_("The angle (in degrees) from the horizontal to the arc's end point"));
+ eact->set_focus_widget(desktop->canvas);
+ add(*eact);
+ }
+ _start_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &ArcToolbar::startend_value_changed),
+ _start_adj, "start", _end_adj));
+ _end_adj->signal_value_changed().connect( sigc::bind(sigc::mem_fun(*this, &ArcToolbar::startend_value_changed),
+ _end_adj, "end", _start_adj));
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Arc: Slice, Arc, Chord */
+ {
+ Gtk::RadioToolButton::Group type_group;
+
+ auto slice_btn = Gtk::manage(new Gtk::RadioToolButton(_("Slice")));
+ slice_btn->set_tooltip_text(_("Switch to slice (closed shape with two radii)"));
+ slice_btn->set_icon_name(INKSCAPE_ICON("draw-ellipse-segment"));
+ _type_buttons.push_back(slice_btn);
+
+ auto arc_btn = Gtk::manage(new Gtk::RadioToolButton(_("Arc (Open)")));
+ arc_btn->set_tooltip_text(_("Switch to arc (unclosed shape)"));
+ arc_btn->set_icon_name(INKSCAPE_ICON("draw-ellipse-arc"));
+ _type_buttons.push_back(arc_btn);
+
+ auto chord_btn = Gtk::manage(new Gtk::RadioToolButton(_("Chord")));
+ chord_btn->set_tooltip_text(_("Switch to chord (closed shape)"));
+ chord_btn->set_icon_name(INKSCAPE_ICON("draw-ellipse-chord"));
+ _type_buttons.push_back(chord_btn);
+
+ slice_btn->set_group(type_group);
+ arc_btn->set_group(type_group);
+ chord_btn->set_group(type_group);
+
+ gint type = prefs->getInt("/tools/shapes/arc/arc_type", 0);
+ _type_buttons[type]->set_active();
+
+ int btn_index = 0;
+ for (auto btn : _type_buttons)
+ {
+ btn->set_sensitive();
+ btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &ArcToolbar::type_changed), btn_index++));
+ add(*btn);
+ }
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Make Whole */
+ {
+ _make_whole = Gtk::manage(new Gtk::ToolButton(_("Make whole")));
+ _make_whole->set_tooltip_text(_("Make the shape a whole ellipse, not arc or segment"));
+ _make_whole->set_icon_name(INKSCAPE_ICON("draw-ellipse-whole"));
+ _make_whole->signal_clicked().connect(sigc::mem_fun(*this, &ArcToolbar::defaults));
+ add(*_make_whole);
+ _make_whole->set_sensitive(true);
+ }
+
+ _single = true;
+ // sensitivize make whole and open checkbox
+ {
+ sensitivize( _start_adj->get_value(), _end_adj->get_value() );
+ }
+
+ desktop->connectEventContextChanged(sigc::mem_fun(*this, &ArcToolbar::check_ec));
+
+ show_all();
+}
+
+ArcToolbar::~ArcToolbar()
+{
+ if(_repr) {
+ _repr->removeListenerByData(this);
+ GC::release(_repr);
+ _repr = nullptr;
+ }
+}
+
+GtkWidget *
+ArcToolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = new ArcToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+void
+ArcToolbar::value_changed(Glib::RefPtr<Gtk::Adjustment>& adj,
+ gchar const *value_name)
+{
+ // Per SVG spec "a [radius] value of zero disables rendering of the element".
+ // However our implementation does not allow a setting of zero in the UI (not even in the XML editor)
+ // and ugly things happen if it's forced here, so better leave the properties untouched.
+ if (!adj->get_value()) {
+ return;
+ }
+
+ Unit const *unit = _tracker->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+
+ SPDocument* document = _desktop->getDocument();
+
+ if (DocumentUndo::getUndoSensitive(document)) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble(Glib::ustring("/tools/shapes/arc/") + value_name,
+ Quantity::convert(adj->get_value(), unit, "px"));
+ }
+
+ // quit if run by the attr_changed listener
+ if (_freeze || _tracker->isUpdating()) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ bool modmade = false;
+ Inkscape::Selection *selection = _desktop->getSelection();
+ auto itemlist= selection->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ SPItem *item = *i;
+ if (SP_IS_GENERICELLIPSE(item)) {
+
+ SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
+
+ if (!strcmp(value_name, "rx")) {
+ ge->setVisibleRx(Quantity::convert(adj->get_value(), unit, "px"));
+ } else {
+ ge->setVisibleRy(Quantity::convert(adj->get_value(), unit, "px"));
+ }
+
+ ge->normalize();
+ ge->updateRepr();
+ ge->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+
+ modmade = true;
+ }
+ }
+
+ if (modmade) {
+ DocumentUndo::done(_desktop->getDocument(), _("Ellipse: Change radius"), INKSCAPE_ICON("draw-ellipse"));
+ }
+
+ _freeze = false;
+}
+
+void
+ArcToolbar::startend_value_changed(Glib::RefPtr<Gtk::Adjustment>& adj,
+ gchar const *value_name,
+ Glib::RefPtr<Gtk::Adjustment>& other_adj)
+{
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble(Glib::ustring("/tools/shapes/arc/") + value_name, adj->get_value());
+ }
+
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ gchar* namespaced_name = g_strconcat("sodipodi:", value_name, nullptr);
+
+ bool modmade = false;
+ auto itemlist= _desktop->getSelection()->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ SPItem *item = *i;
+ if (SP_IS_GENERICELLIPSE(item)) {
+
+ SPGenericEllipse *ge = SP_GENERICELLIPSE(item);
+
+ if (!strcmp(value_name, "start")) {
+ ge->start = (adj->get_value() * M_PI)/ 180;
+ } else {
+ ge->end = (adj->get_value() * M_PI)/ 180;
+ }
+
+ ge->normalize();
+ ge->updateRepr();
+ ge->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+
+ modmade = true;
+ }
+ }
+
+ g_free(namespaced_name);
+
+ sensitivize( adj->get_value(), other_adj->get_value() );
+
+ if (modmade) {
+ DocumentUndo::maybeDone(_desktop->getDocument(), value_name, _("Arc: Change start/end"), INKSCAPE_ICON("draw-ellipse"));
+ }
+
+ _freeze = false;
+}
+
+void
+ArcToolbar::type_changed( int type )
+{
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/tools/shapes/arc/arc_type", type);
+ }
+
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ Glib::ustring arc_type = "slice";
+ bool open = false;
+ switch (type) {
+ case 0:
+ arc_type = "slice";
+ open = false;
+ break;
+ case 1:
+ arc_type = "arc";
+ open = true;
+ break;
+ case 2:
+ arc_type = "chord";
+ open = true; // For backward compat, not truly open but chord most like arc.
+ break;
+ default:
+ std::cerr << "sp_arctb_type_changed: bad arc type: " << type << std::endl;
+ }
+
+ bool modmade = false;
+ auto itemlist= _desktop->getSelection()->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ SPItem *item = *i;
+ if (SP_IS_GENERICELLIPSE(item)) {
+ Inkscape::XML::Node *repr = item->getRepr();
+ repr->setAttribute("sodipodi:open", (open?"true":nullptr) );
+ repr->setAttribute("sodipodi:arc-type", arc_type);
+ item->updateRepr();
+ modmade = true;
+ }
+ }
+
+ if (modmade) {
+ DocumentUndo::done(_desktop->getDocument(), _("Arc: Changed arc type"), INKSCAPE_ICON("draw-ellipse"));
+ }
+
+ _freeze = false;
+}
+
+void
+ArcToolbar::defaults()
+{
+ _start_adj->set_value(0.0);
+ _end_adj->set_value(0.0);
+
+ if(_desktop->canvas) _desktop->canvas->grab_focus();
+}
+
+void
+ArcToolbar::sensitivize( double v1, double v2 )
+{
+ if (v1 == 0 && v2 == 0) {
+ if (_single) { // only for a single selected ellipse (for now)
+ for (auto btn : _type_buttons) btn->set_sensitive(false);
+ _make_whole->set_sensitive(false);
+ }
+ } else {
+ for (auto btn : _type_buttons) btn->set_sensitive(true);
+ _make_whole->set_sensitive(true);
+ }
+}
+
+void
+ArcToolbar::check_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec)
+{
+ if (SP_IS_ARC_CONTEXT(ec)) {
+ _changed = _desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &ArcToolbar::selection_changed));
+ selection_changed(desktop->getSelection());
+ } else {
+ if (_changed) {
+ _changed.disconnect();
+ if(_repr) {
+ _repr->removeListenerByData(this);
+ Inkscape::GC::release(_repr);
+ _repr = nullptr;
+ }
+ }
+ }
+}
+
+void
+ArcToolbar::selection_changed(Inkscape::Selection *selection)
+{
+ int n_selected = 0;
+ Inkscape::XML::Node *repr = nullptr;
+
+ if ( _repr ) {
+ _item = nullptr;
+ _repr->removeListenerByData(this);
+ GC::release(_repr);
+ _repr = nullptr;
+ }
+
+ SPItem *item = nullptr;
+
+ for(auto i : selection->items()){
+ if (SP_IS_GENERICELLIPSE(i)) {
+ n_selected++;
+ item = i;
+ repr = item->getRepr();
+ }
+ }
+
+ _single = false;
+ if (n_selected == 0) {
+ _mode_item->set_markup(_("<b>New:</b>"));
+ } else if (n_selected == 1) {
+ _single = true;
+ _mode_item->set_markup(_("<b>Change:</b>"));
+ _rx_item->set_sensitive(true);
+ _ry_item->set_sensitive(true);
+
+ if (repr) {
+ _repr = repr;
+ _item = item;
+ Inkscape::GC::anchor(_repr);
+ _repr->addListener(&arc_tb_repr_events, this);
+ _repr->synthesizeEvents(&arc_tb_repr_events, this);
+ }
+ } else {
+ // FIXME: implement averaging of all parameters for multiple selected
+ //gtk_label_set_markup(GTK_LABEL(l), _("<b>Average:</b>"));
+ _mode_item->set_markup(_("<b>Change:</b>"));
+ sensitivize( 1, 0 );
+ }
+}
+
+void
+ArcToolbar::event_attr_changed(Inkscape::XML::Node *repr, gchar const * /*name*/,
+ gchar const * /*old_value*/, gchar const * /*new_value*/,
+ bool /*is_interactive*/, gpointer data)
+{
+ auto toolbar = reinterpret_cast<ArcToolbar *>(data);
+
+ // quit if run by the _changed callbacks
+ if (toolbar->_freeze) {
+ return;
+ }
+
+ // in turn, prevent callbacks from responding
+ toolbar->_freeze = true;
+
+ if (toolbar->_item && SP_IS_GENERICELLIPSE(toolbar->_item)) {
+ SPGenericEllipse *ge = SP_GENERICELLIPSE(toolbar->_item);
+
+ Unit const *unit = toolbar->_tracker->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+
+ gdouble rx = ge->getVisibleRx();
+ gdouble ry = ge->getVisibleRy();
+ toolbar->_rx_adj->set_value(Quantity::convert(rx, "px", unit));
+ toolbar->_ry_adj->set_value(Quantity::convert(ry, "px", unit));
+ }
+
+ gdouble start = repr->getAttributeDouble("sodipodi:start", 0.0);;
+ gdouble end = repr->getAttributeDouble("sodipodi:end", 0.0);
+
+ toolbar->_start_adj->set_value(mod360((start * 180)/M_PI));
+ toolbar->_end_adj->set_value(mod360((end * 180)/M_PI));
+
+ toolbar->sensitivize(toolbar->_start_adj->get_value(), toolbar->_end_adj->get_value());
+
+ char const *arctypestr = nullptr;
+ arctypestr = repr->attribute("sodipodi:arc-type");
+ if (!arctypestr) { // For old files.
+ char const *openstr = nullptr;
+ openstr = repr->attribute("sodipodi:open");
+ arctypestr = (openstr ? "arc" : "slice");
+ }
+
+ if (!strcmp(arctypestr,"slice")) {
+ toolbar->_type_buttons[0]->set_active();
+ } else if (!strcmp(arctypestr,"arc")) {
+ toolbar->_type_buttons[1]->set_active();
+ } else {
+ toolbar->_type_buttons[2]->set_active();
+ }
+
+ toolbar->_freeze = false;
+}
+
+}
+}
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/arc-toolbar.h b/src/ui/toolbar/arc-toolbar.h
new file mode 100644
index 0000000..b0b0450
--- /dev/null
+++ b/src/ui/toolbar/arc-toolbar.h
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_ARC_TOOLBAR_H
+#define SEEN_ARC_TOOLBAR_H
+
+/**
+ * @file
+ * 3d box aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+#include <gtkmm/adjustment.h>
+
+class SPDesktop;
+class SPItem;
+
+namespace Gtk {
+class RadioToolButton;
+class ToolButton;
+}
+
+namespace Inkscape {
+class Selection;
+
+namespace XML {
+class Node;
+}
+
+namespace UI {
+namespace Tools {
+class ToolBase;
+}
+
+namespace Widget {
+class LabelToolItem;
+class SpinButtonToolItem;
+class UnitTracker;
+}
+
+namespace Toolbar {
+class ArcToolbar : public Toolbar {
+private:
+ UI::Widget::UnitTracker *_tracker;
+
+ UI::Widget::SpinButtonToolItem *_rx_item;
+ UI::Widget::SpinButtonToolItem *_ry_item;
+
+ UI::Widget::LabelToolItem *_mode_item;
+
+ std::vector<Gtk::RadioToolButton *> _type_buttons;
+ Gtk::ToolButton *_make_whole;
+
+ Glib::RefPtr<Gtk::Adjustment> _rx_adj;
+ Glib::RefPtr<Gtk::Adjustment> _ry_adj;
+ Glib::RefPtr<Gtk::Adjustment> _start_adj;
+ Glib::RefPtr<Gtk::Adjustment> _end_adj;
+
+ bool _freeze;
+ bool _single;
+
+ XML::Node *_repr;
+ SPItem *_item;
+
+ void value_changed(Glib::RefPtr<Gtk::Adjustment>& adj,
+ gchar const *value_name);
+ void startend_value_changed(Glib::RefPtr<Gtk::Adjustment>& adj,
+ gchar const *value_name,
+ Glib::RefPtr<Gtk::Adjustment>& other_adj);
+ void type_changed( int type );
+ void defaults();
+ void sensitivize( double v1, double v2 );
+ void check_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec);
+ void selection_changed(Inkscape::Selection *selection);
+
+ sigc::connection _changed;
+
+protected:
+ ArcToolbar(SPDesktop *desktop);
+ ~ArcToolbar() override;
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+ static void event_attr_changed(Inkscape::XML::Node *repr,
+ gchar const *name,
+ gchar const *old_value,
+ gchar const *new_value,
+ bool is_interactive,
+ gpointer data);
+};
+
+}
+}
+}
+
+#endif /* !SEEN_ARC_TOOLBAR_H */
diff --git a/src/ui/toolbar/box3d-toolbar.cpp b/src/ui/toolbar/box3d-toolbar.cpp
new file mode 100644
index 0000000..e594240
--- /dev/null
+++ b/src/ui/toolbar/box3d-toolbar.cpp
@@ -0,0 +1,428 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * 3d box aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "box3d-toolbar.h"
+
+#include <glibmm/i18n.h>
+#include <gtkmm/adjustment.h>
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "document.h"
+#include "selection.h"
+
+#include "object/box3d.h"
+#include "object/persp3d.h"
+
+#include "ui/icon-names.h"
+#include "ui/tools/box3d-tool.h"
+#include "ui/widget/canvas.h"
+#include "ui/widget/spin-button-tool-item.h"
+
+#include "xml/node-event-vector.h"
+
+using Inkscape::DocumentUndo;
+
+static Inkscape::XML::NodeEventVector box3d_persp_tb_repr_events =
+{
+ nullptr, /* child_added */
+ nullptr, /* child_removed */
+ Inkscape::UI::Toolbar::Box3DToolbar::event_attr_changed,
+ nullptr, /* content_changed */
+ nullptr /* order_changed */
+};
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+Box3DToolbar::Box3DToolbar(SPDesktop *desktop)
+ : Toolbar(desktop),
+ _repr(nullptr),
+ _freeze(false)
+{
+ auto prefs = Inkscape::Preferences::get();
+ auto document = desktop->getDocument();
+ auto persp_impl = document->getCurrentPersp3DImpl();
+
+ /* Angle X */
+ {
+ std::vector<double> values = {-90, -60, -30, 0, 30, 60, 90};
+ auto angle_x_val = prefs->getDouble("/tools/shapes/3dbox/box3d_angle_x", 30);
+ _angle_x_adj = Gtk::Adjustment::create(angle_x_val, -360.0, 360.0, 1.0, 10.0);
+ _angle_x_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("box3d-angle-x", _("Angle X:"), _angle_x_adj));
+ // TRANSLATORS: PL is short for 'perspective line'
+ _angle_x_item->set_tooltip_text(_("Angle of PLs in X direction"));
+ _angle_x_item->set_custom_numeric_menu_data(values);
+ _angle_x_item->set_focus_widget(desktop->canvas);
+ _angle_x_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &Box3DToolbar::angle_value_changed),
+ _angle_x_adj, Proj::X));
+ add(*_angle_x_item);
+ }
+
+ if (!persp_impl || !Persp3D::VP_is_finite(persp_impl, Proj::X)) {
+ _angle_x_item->set_sensitive(true);
+ } else {
+ _angle_x_item->set_sensitive(false);
+ }
+
+ /* VP X state */
+ {
+ // TRANSLATORS: VP is short for 'vanishing point'
+ _vp_x_state_item = add_toggle_button(_("State of VP in X direction"),
+ _("Toggle VP in X direction between 'finite' and 'infinite' (=parallel)"));
+ _vp_x_state_item->set_icon_name(INKSCAPE_ICON("perspective-parallel"));
+ _vp_x_state_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &Box3DToolbar::vp_state_changed), Proj::X));
+ _angle_x_item->set_sensitive( !prefs->getBool("/tools/shapes/3dbox/vp_x_state", true) );
+ _vp_x_state_item->set_active( prefs->getBool("/tools/shapes/3dbox/vp_x_state", true) );
+ }
+
+ /* Angle Y */
+ {
+ auto angle_y_val = prefs->getDouble("/tools/shapes/3dbox/box3d_angle_y", 30);
+ _angle_y_adj = Gtk::Adjustment::create(angle_y_val, -360.0, 360.0, 1.0, 10.0);
+ _angle_y_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("box3d-angle-y", _("Angle Y:"), _angle_y_adj));
+ // TRANSLATORS: PL is short for 'perspective line'
+ _angle_y_item->set_tooltip_text(_("Angle of PLs in Y direction"));
+ std::vector<double> values = {-90, -60, -30, 0, 30, 60, 90};
+ _angle_y_item->set_custom_numeric_menu_data(values);
+ _angle_y_item->set_focus_widget(desktop->canvas);
+ _angle_y_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &Box3DToolbar::angle_value_changed),
+ _angle_y_adj, Proj::Y));
+ add(*_angle_y_item);
+ }
+
+ if (!persp_impl || !Persp3D::VP_is_finite(persp_impl, Proj::Y)) {
+ _angle_y_item->set_sensitive(true);
+ } else {
+ _angle_y_item->set_sensitive(false);
+ }
+
+ /* VP Y state */
+ {
+ // TRANSLATORS: VP is short for 'vanishing point'
+ _vp_y_state_item = add_toggle_button(_("State of VP in Y direction"),
+ _("Toggle VP in Y direction between 'finite' and 'infinite' (=parallel)"));
+ _vp_y_state_item->set_icon_name(INKSCAPE_ICON("perspective-parallel"));
+ _vp_y_state_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &Box3DToolbar::vp_state_changed), Proj::Y));
+ _angle_y_item->set_sensitive( !prefs->getBool("/tools/shapes/3dbox/vp_y_state", true) );
+ _vp_y_state_item->set_active( prefs->getBool("/tools/shapes/3dbox/vp_y_state", true) );
+ }
+
+ /* Angle Z */
+ {
+ auto angle_z_val = prefs->getDouble("/tools/shapes/3dbox/box3d_angle_z", 30);
+ _angle_z_adj = Gtk::Adjustment::create(angle_z_val, -360.0, 360.0, 1.0, 10.0);
+ _angle_z_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("box3d-angle-z", _("Angle Z:"), _angle_z_adj));
+ // TRANSLATORS: PL is short for 'perspective line'
+ _angle_z_item->set_tooltip_text(_("Angle of PLs in Z direction"));
+ std::vector<double> values = {-90, -60, -30, 0, 30, 60, 90};
+ _angle_z_item->set_custom_numeric_menu_data(values);
+ _angle_z_item->set_focus_widget(desktop->canvas);
+ _angle_z_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &Box3DToolbar::angle_value_changed),
+ _angle_z_adj, Proj::Z));
+ add(*_angle_z_item);
+ }
+
+ if (!persp_impl || !Persp3D::VP_is_finite(persp_impl, Proj::Z)) {
+ _angle_z_item->set_sensitive(true);
+ } else {
+ _angle_z_item->set_sensitive(false);
+ }
+
+ /* VP Z state */
+ {
+ // TRANSLATORS: VP is short for 'vanishing point'
+ _vp_z_state_item = add_toggle_button(_("State of VP in Z direction"),
+ _("Toggle VP in Z direction between 'finite' and 'infinite' (=parallel)"));
+ _vp_z_state_item->set_icon_name(INKSCAPE_ICON("perspective-parallel"));
+ _vp_z_state_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &Box3DToolbar::vp_state_changed), Proj::Z));
+ _angle_z_item->set_sensitive(!prefs->getBool("/tools/shapes/3dbox/vp_z_state", true));
+ _vp_z_state_item->set_active( prefs->getBool("/tools/shapes/3dbox/vp_z_state", true) );
+ }
+
+ desktop->connectEventContextChanged(sigc::mem_fun(*this, &Box3DToolbar::check_ec));
+
+ show_all();
+}
+
+GtkWidget *
+Box3DToolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = new Box3DToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+void
+Box3DToolbar::angle_value_changed(Glib::RefPtr<Gtk::Adjustment> &adj,
+ Proj::Axis axis)
+{
+ SPDocument *document = _desktop->getDocument();
+
+ // quit if run by the attr_changed or selection changed listener
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ std::list<Persp3D *> sel_persps = _desktop->getSelection()->perspList();
+ if (sel_persps.empty()) {
+ // this can happen when the document is created; we silently ignore it
+ return;
+ }
+ Persp3D *persp = sel_persps.front();
+
+ persp->perspective_impl->tmat.set_infinite_direction (axis,
+ adj->get_value());
+ persp->updateRepr();
+
+ // TODO: use the correct axis here, too
+ DocumentUndo::maybeDone(document, "perspangle", _("3D Box: Change perspective (angle of infinite axis)"), INKSCAPE_ICON("draw-cuboid"));
+
+ _freeze = false;
+}
+
+void
+Box3DToolbar::vp_state_changed(Proj::Axis axis)
+{
+ // TODO: Take all selected perspectives into account
+ auto sel_persps = _desktop->getSelection()->perspList();
+ if (sel_persps.empty()) {
+ // this can happen when the document is created; we silently ignore it
+ return;
+ }
+ Persp3D *persp = sel_persps.front();
+
+ Gtk::ToggleToolButton *btn = nullptr;
+
+ switch(axis) {
+ case Proj::X:
+ btn = _vp_x_state_item;
+ break;
+ case Proj::Y:
+ btn = _vp_y_state_item;
+ break;
+ case Proj::Z:
+ btn = _vp_z_state_item;
+ break;
+ default:
+ return;
+ }
+
+ bool set_infinite = btn->get_active();
+ persp->set_VP_state (axis, set_infinite ? Proj::VP_INFINITE : Proj::VP_FINITE);
+}
+
+void
+Box3DToolbar::check_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec)
+{
+ if (SP_IS_BOX3D_CONTEXT(ec)) {
+ _changed = desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &Box3DToolbar::selection_changed));
+ selection_changed(desktop->getSelection());
+ } else {
+ if (_changed)
+ _changed.disconnect();
+
+ if (_repr) { // remove old listener
+ _repr->removeListenerByData(this);
+ Inkscape::GC::release(_repr);
+ _repr = nullptr;
+ }
+ }
+}
+
+Box3DToolbar::~Box3DToolbar()
+{
+ if (_repr) { // remove old listener
+ _repr->removeListenerByData(this);
+ Inkscape::GC::release(_repr);
+ _repr = nullptr;
+ }
+}
+
+/**
+ * \param selection Should not be NULL.
+ */
+// FIXME: This should rather be put into persp3d-reference.cpp or something similar so that it reacts upon each
+// Change of the perspective, and not of the current selection (but how to refer to the toolbar then?)
+void
+Box3DToolbar::selection_changed(Inkscape::Selection *selection)
+{
+ // Here the following should be done: If all selected boxes have finite VPs in a certain direction,
+ // disable the angle entry fields for this direction (otherwise entering a value in them should only
+ // update the perspectives with infinite VPs and leave the other ones untouched).
+
+ Inkscape::XML::Node *persp_repr = nullptr;
+
+ if (_repr) { // remove old listener
+ _repr->removeListenerByData(this);
+ Inkscape::GC::release(_repr);
+ _repr = nullptr;
+ }
+
+ SPItem *item = selection->singleItem();
+ SPBox3D *box = dynamic_cast<SPBox3D *>(item);
+ if (box) {
+ // FIXME: Also deal with multiple selected boxes
+ Persp3D *persp = box->get_perspective();
+ if (!persp) {
+ g_warning("Box has no perspective set!");
+ return;
+ }
+ persp_repr = persp->getRepr();
+ if (persp_repr) {
+ _repr = persp_repr;
+ Inkscape::GC::anchor(_repr);
+ _repr->addListener(&box3d_persp_tb_repr_events, this);
+ _repr->synthesizeEvents(&box3d_persp_tb_repr_events, this);
+
+ selection->document()->setCurrentPersp3D(Persp3D::get_from_repr(_repr));
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString("/tools/shapes/3dbox/persp", _repr->attribute("id"));
+
+ _freeze = true;
+ resync_toolbar(_repr);
+ _freeze = false;
+ }
+ }
+}
+
+void
+Box3DToolbar::resync_toolbar(Inkscape::XML::Node *persp_repr)
+{
+ if (!persp_repr) {
+ g_print ("No perspective given to box3d_resync_toolbar().\n");
+ return;
+ }
+
+ Persp3D *persp = Persp3D::get_from_repr(persp_repr);
+ if (!persp) {
+ // Hmm, is it an error if this happens?
+ return;
+ }
+ set_button_and_adjustment(persp, Proj::X,
+ _angle_x_adj,
+ _angle_x_item,
+ _vp_x_state_item);
+ set_button_and_adjustment(persp, Proj::Y,
+ _angle_y_adj,
+ _angle_y_item,
+ _vp_y_state_item);
+ set_button_and_adjustment(persp, Proj::Z,
+ _angle_z_adj,
+ _angle_z_item,
+ _vp_z_state_item);
+}
+
+void
+Box3DToolbar::set_button_and_adjustment(Persp3D *persp,
+ Proj::Axis axis,
+ Glib::RefPtr<Gtk::Adjustment>& adj,
+ UI::Widget::SpinButtonToolItem *spin_btn,
+ Gtk::ToggleToolButton *toggle_btn)
+{
+ // TODO: Take all selected perspectives into account but don't touch the state button if not all of them
+ // have the same state (otherwise a call to box3d_vp_z_state_changed() is triggered and the states
+ // are reset).
+ bool is_infinite = !Persp3D::VP_is_finite(persp->perspective_impl, axis);
+
+ if (is_infinite) {
+ toggle_btn->set_active(true);
+ spin_btn->set_sensitive(true);
+
+ double angle = persp->get_infinite_angle(axis);
+ if (angle != Geom::infinity()) { // FIXME: We should catch this error earlier (don't show the spinbutton at all)
+ adj->set_value(normalize_angle(angle));
+ }
+ } else {
+ toggle_btn->set_active(false);
+ spin_btn->set_sensitive(false);
+ }
+}
+
+void
+Box3DToolbar::event_attr_changed(Inkscape::XML::Node *repr,
+ gchar const * /*name*/,
+ gchar const * /*old_value*/,
+ gchar const * /*new_value*/,
+ bool /*is_interactive*/,
+ gpointer data)
+{
+ auto toolbar = reinterpret_cast<Box3DToolbar*>(data);
+
+ // quit if run by the attr_changed or selection changed listener
+ if (toolbar->_freeze) {
+ return;
+ }
+
+ // set freeze so that it can be caught in box3d_angle_z_value_changed() (to avoid calling
+ // SPDocumentUndo::maybeDone() when the document is undo insensitive)
+ toolbar->_freeze = true;
+
+ // TODO: Only update the appropriate part of the toolbar
+// if (!strcmp(name, "inkscape:vp_z")) {
+ toolbar->resync_toolbar(repr);
+// }
+
+ Persp3D *persp = Persp3D::get_from_repr(repr);
+ if (persp) {
+ persp->update_box_reprs();
+ }
+
+ toolbar->_freeze = false;
+}
+
+/**
+ * \brief normalize angle so that it lies in the interval [0,360]
+ *
+ * TODO: Isn't there something in 2Geom or cmath that does this?
+ */
+double
+Box3DToolbar::normalize_angle(double a) {
+ double angle = a + ((int) (a/360.0))*360;
+ if (angle < 0) {
+ angle += 360.0;
+ }
+ return angle;
+}
+
+}
+}
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/box3d-toolbar.h b/src/ui/toolbar/box3d-toolbar.h
new file mode 100644
index 0000000..8ba7872
--- /dev/null
+++ b/src/ui/toolbar/box3d-toolbar.h
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_BOX3D_TOOLBAR_H
+#define SEEN_BOX3D_TOOLBAR_H
+
+/**
+ * @file
+ * 3d box aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+#include "axis-manip.h"
+
+namespace Gtk {
+class Adjustment;
+}
+
+class Persp3D;
+class SPDesktop;
+
+namespace Inkscape {
+class Selection;
+
+namespace XML {
+class Node;
+}
+
+namespace UI {
+namespace Widget {
+class SpinButtonToolItem;
+}
+
+namespace Tools {
+class ToolBase;
+}
+
+namespace Toolbar {
+class Box3DToolbar : public Toolbar {
+private:
+ UI::Widget::SpinButtonToolItem *_angle_x_item;
+ UI::Widget::SpinButtonToolItem *_angle_y_item;
+ UI::Widget::SpinButtonToolItem *_angle_z_item;
+
+ Glib::RefPtr<Gtk::Adjustment> _angle_x_adj;
+ Glib::RefPtr<Gtk::Adjustment> _angle_y_adj;
+ Glib::RefPtr<Gtk::Adjustment> _angle_z_adj;
+
+ Gtk::ToggleToolButton *_vp_x_state_item;
+ Gtk::ToggleToolButton *_vp_y_state_item;
+ Gtk::ToggleToolButton *_vp_z_state_item;
+
+ XML::Node *_repr;
+ bool _freeze;
+
+ void angle_value_changed(Glib::RefPtr<Gtk::Adjustment> &adj,
+ Proj::Axis axis);
+ void vp_state_changed(Proj::Axis axis);
+ void check_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec);
+ void selection_changed(Inkscape::Selection *selection);
+ void resync_toolbar(Inkscape::XML::Node *persp_repr);
+ void set_button_and_adjustment(Persp3D *persp,
+ Proj::Axis axis,
+ Glib::RefPtr<Gtk::Adjustment>& adj,
+ UI::Widget::SpinButtonToolItem *spin_btn,
+ Gtk::ToggleToolButton *toggle_btn);
+ double normalize_angle(double a);
+
+ sigc::connection _changed;
+
+protected:
+ Box3DToolbar(SPDesktop *desktop);
+ ~Box3DToolbar() override;
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+ static void event_attr_changed(Inkscape::XML::Node *repr,
+ gchar const *name,
+ gchar const *old_value,
+ gchar const *new_value,
+ bool is_interactive,
+ gpointer data);
+
+};
+}
+}
+}
+#endif /* !SEEN_BOX3D_TOOLBAR_H */
diff --git a/src/ui/toolbar/calligraphy-toolbar.cpp b/src/ui/toolbar/calligraphy-toolbar.cpp
new file mode 100644
index 0000000..05af968
--- /dev/null
+++ b/src/ui/toolbar/calligraphy-toolbar.cpp
@@ -0,0 +1,628 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Calligraphy aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "calligraphy-toolbar.h"
+
+#include <glibmm/i18n.h>
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/separatortoolitem.h>
+
+#include "desktop.h"
+
+#include "ui/dialog/calligraphic-profile-rename.h"
+#include "ui/icon-names.h"
+#include "ui/simple-pref-pusher.h"
+#include "ui/widget/canvas.h"
+#include "ui/widget/combo-tool-item.h"
+#include "ui/widget/spin-button-tool-item.h"
+#include "ui/widget/unit-tracker.h"
+
+using Inkscape::UI::Widget::UnitTracker;
+using Inkscape::Util::Quantity;
+using Inkscape::Util::Unit;
+using Inkscape::Util::unit_table;
+
+std::vector<Glib::ustring> get_presets_list() {
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ std::vector<Glib::ustring> presets = prefs->getAllDirs("/tools/calligraphic/preset");
+
+ return presets;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+
+CalligraphyToolbar::CalligraphyToolbar(SPDesktop *desktop)
+ : Toolbar(desktop)
+ , _tracker(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR))
+ , _presets_blocked(false)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _tracker->prependUnit(unit_table.getUnit("px"));
+ _tracker->changeLabel("%", 0, true);
+ if (prefs->getBool("/tools/calligraphic/abs_width")) {
+ _tracker->setActiveUnitByLabel(prefs->getString("/tools/calligraphic/unit"));
+ }
+
+ /*calligraphic profile */
+ {
+ _profile_selector_combo = Gtk::manage(new Gtk::ComboBoxText());
+ _profile_selector_combo->set_tooltip_text(_("Choose a preset"));
+
+ build_presets_list();
+
+ auto profile_selector_ti = Gtk::manage(new Gtk::ToolItem());
+ profile_selector_ti->add(*_profile_selector_combo);
+ add(*profile_selector_ti);
+
+ _profile_selector_combo->signal_changed().connect(sigc::mem_fun(*this, &CalligraphyToolbar::change_profile));
+ }
+
+ /*calligraphic profile editor */
+ {
+ auto profile_edit_item = Gtk::manage(new Gtk::ToolButton(_("Add/Edit Profile")));
+ profile_edit_item->set_tooltip_text(_("Add or edit calligraphic profile"));
+ profile_edit_item->set_icon_name(INKSCAPE_ICON("document-properties"));
+ profile_edit_item->signal_clicked().connect(sigc::mem_fun(*this, &CalligraphyToolbar::edit_profile));
+ add(*profile_edit_item);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ /* Width */
+ std::vector<Glib::ustring> labels = {_("(hairline)"), "", "", "", _("(default)"), "", "", "", "", _("(broad stroke)")};
+ std::vector<double> values = { 1, 3, 5, 10, 15, 20, 30, 50, 75, 100};
+ auto width_val = prefs->getDouble("/tools/calligraphic/width", 15.118);
+ Unit const *unit = unit_table.getUnit(prefs->getString("/tools/calligraphic/unit"));
+ _width_adj = Gtk::Adjustment::create(Quantity::convert(width_val, "px", unit), 0.001, 100, 1.0, 10.0);
+ auto width_item =
+ Gtk::manage(new UI::Widget::SpinButtonToolItem("calligraphy-width", _("Width:"), _width_adj, 0.001, 3));
+ width_item->set_tooltip_text(_("The width of the calligraphic pen (relative to the visible canvas area)"));
+ width_item->set_custom_numeric_menu_data(values, labels);
+ width_item->set_focus_widget(desktop->canvas);
+ _width_adj->signal_value_changed().connect(sigc::mem_fun(*this, &CalligraphyToolbar::width_value_changed));
+ _widget_map["width"] = G_OBJECT(_width_adj->gobj());
+ add(*width_item);
+ _tracker->addAdjustment(_width_adj->gobj());
+ width_item->set_sensitive(true);
+ }
+
+ /* Unit Menu */
+ {
+ auto unit_menu_ti = _tracker->create_tool_item(_("Units"), "");
+ add(*unit_menu_ti);
+ unit_menu_ti->signal_changed_after().connect(sigc::mem_fun(*this, &CalligraphyToolbar::unit_changed));
+ }
+
+ /* Use Pressure button */
+ {
+ _usepressure = add_toggle_button(_("Pressure"),
+ _("Use the pressure of the input device to alter the width of the pen"));
+ _usepressure->set_icon_name(INKSCAPE_ICON("draw-use-pressure"));
+ _widget_map["usepressure"] = G_OBJECT(_usepressure->gobj());
+ _usepressure_pusher.reset(new SimplePrefPusher(_usepressure, "/tools/calligraphic/usepressure"));
+ _usepressure->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &CalligraphyToolbar::on_pref_toggled),
+ _usepressure,
+ "/tools/calligraphic/usepressure"));
+ }
+
+ /* Trace Background button */
+ {
+ _tracebackground = add_toggle_button(_("Trace Background"),
+ _("Trace the lightness of the background by the width of the pen (white - minimum width, black - maximum width)"));
+ _tracebackground->set_icon_name(INKSCAPE_ICON("draw-trace-background"));
+ _widget_map["tracebackground"] = G_OBJECT(_tracebackground->gobj());
+ _tracebackground_pusher.reset(new SimplePrefPusher(_tracebackground, "/tools/calligraphic/tracebackground"));
+ _tracebackground->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &CalligraphyToolbar::on_pref_toggled),
+ _tracebackground,
+ "/tools/calligraphic/tracebackground"));
+ }
+
+ {
+ /* Thinning */
+ std::vector<Glib::ustring> labels = {_("(speed blows up stroke)"), "", "", _("(slight widening)"), _("(constant width)"), _("(slight thinning, default)"), "", "", _("(speed deflates stroke)")};
+ std::vector<double> values = { -100, -40, -20, -10, 0, 10, 20, 40, 100};
+ auto thinning_val = prefs->getDouble("/tools/calligraphic/thinning", 10);
+ _thinning_adj = Gtk::Adjustment::create(thinning_val, -100, 100, 1, 10.0);
+ auto thinning_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("calligraphy-thinning", _("Thinning:"), _thinning_adj, 1, 0));
+ thinning_item->set_tooltip_text(("How much velocity thins the stroke (> 0 makes fast strokes thinner, < 0 makes them broader, 0 makes width independent of velocity)"));
+ thinning_item->set_custom_numeric_menu_data(values, labels);
+ thinning_item->set_focus_widget(desktop->canvas);
+ _thinning_adj->signal_value_changed().connect(sigc::mem_fun(*this, &CalligraphyToolbar::velthin_value_changed));
+ _widget_map["thinning"] = G_OBJECT(_thinning_adj->gobj());
+ add(*thinning_item);
+ thinning_item->set_sensitive(true);
+ }
+
+ {
+ /* Mass */
+ std::vector<Glib::ustring> labels = {_("(no inertia)"), _("(slight smoothing, default)"), _("(noticeable lagging)"), "", "", _("(maximum inertia)")};
+ std::vector<double> values = { 0.0, 2, 10, 20, 50, 100};
+ auto mass_val = prefs->getDouble("/tools/calligraphic/mass", 2.0);
+ _mass_adj = Gtk::Adjustment::create(mass_val, 0.0, 100, 1, 10.0);
+ auto mass_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("calligraphy-mass", _("Mass:"), _mass_adj, 1, 0));
+ mass_item->set_tooltip_text(_("Increase to make the pen drag behind, as if slowed by inertia"));
+ mass_item->set_custom_numeric_menu_data(values, labels);
+ mass_item->set_focus_widget(desktop->canvas);
+ _mass_adj->signal_value_changed().connect(sigc::mem_fun(*this, &CalligraphyToolbar::mass_value_changed));
+ _widget_map["mass"] = G_OBJECT(_mass_adj->gobj());
+ add(*mass_item);
+ mass_item->set_sensitive(true);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ /* Angle */
+ std::vector<Glib::ustring> labels = {_("(left edge up)"), "", "", _("(horizontal)"), _("(default)"), "", _("(right edge up)")};
+ std::vector<double> values = { -90, -60, -30, 0, 30, 60, 90};
+ auto angle_val = prefs->getDouble("/tools/calligraphic/angle", 30);
+ _angle_adj = Gtk::Adjustment::create(angle_val, -90.0, 90.0, 1.0, 10.0);
+ _angle_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("calligraphy-angle", _("Angle:"), _angle_adj, 1, 0));
+ _angle_item->set_tooltip_text(_("The angle of the pen's nib (in degrees; 0 = horizontal; has no effect if fixation = 0)"));
+ _angle_item->set_custom_numeric_menu_data(values, labels);
+ _angle_item->set_focus_widget(desktop->canvas);
+ _angle_adj->signal_value_changed().connect(sigc::mem_fun(*this, &CalligraphyToolbar::angle_value_changed));
+ _widget_map["angle"] = G_OBJECT(_angle_adj->gobj());
+ add(*_angle_item);
+ _angle_item->set_sensitive(true);
+ }
+
+ /* Use Tilt button */
+ {
+ _usetilt = add_toggle_button(_("Tilt"),
+ _("Use the tilt of the input device to alter the angle of the pen's nib"));
+ _usetilt->set_icon_name(INKSCAPE_ICON("draw-use-tilt"));
+ _widget_map["usetilt"] = G_OBJECT(_usetilt->gobj());
+ _usetilt_pusher.reset(new SimplePrefPusher(_usetilt, "/tools/calligraphic/usetilt"));
+ _usetilt->signal_toggled().connect(sigc::mem_fun(*this, &CalligraphyToolbar::tilt_state_changed));
+ _angle_item->set_sensitive(!prefs->getBool("/tools/calligraphic/usetilt", true));
+ _usetilt->set_active(prefs->getBool("/tools/calligraphic/usetilt", true));
+ }
+
+ {
+ /* Fixation */
+ std::vector<Glib::ustring> labels = {_("(perpendicular to stroke, \"brush\")"), "", "", "", _("(almost fixed, default)"), _("(fixed by Angle, \"pen\")")};
+ std::vector<double> values = { 0, 20, 40, 60, 90, 100};
+ auto flatness_val = prefs->getDouble("/tools/calligraphic/flatness", -90);
+ _fixation_adj = Gtk::Adjustment::create(flatness_val, -100.0, 100.0, 1.0, 10.0);
+ auto flatness_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("calligraphy-fixation", _("Fixation:"), _fixation_adj, 1, 0));
+ flatness_item->set_tooltip_text(_("Angle behavior (0 = nib always perpendicular to stroke direction, 100 = fixed angle, -100 = fixed angle in opposite direction)"));
+ flatness_item->set_custom_numeric_menu_data(values, labels);
+ flatness_item->set_focus_widget(desktop->canvas);
+ _fixation_adj->signal_value_changed().connect(sigc::mem_fun(*this, &CalligraphyToolbar::flatness_value_changed));
+ _widget_map["flatness"] = G_OBJECT(_fixation_adj->gobj());
+ add(*flatness_item);
+ flatness_item->set_sensitive(true);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ /* Cap Rounding */
+ std::vector<Glib::ustring> labels = {_("(blunt caps, default)"), _("(slightly bulging)"), "", "", _("(approximately round)"), _("(long protruding caps)")};
+ std::vector<double> values = { 0, 0.3, 0.5, 1.0, 1.4, 5.0};
+ auto cap_rounding_val = prefs->getDouble("/tools/calligraphic/cap_rounding", 0.0);
+ _cap_rounding_adj = Gtk::Adjustment::create(cap_rounding_val, 0.0, 5.0, 0.01, 0.1);
+ auto cap_rounding_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("calligraphy-cap-rounding", _("Caps:"), _cap_rounding_adj, 0.01, 2));
+
+ // TRANSLATORS: "cap" means "end" (both start and finish) here
+ cap_rounding_item->set_tooltip_text(_("Increase to make caps at the ends of strokes protrude more (0 = no caps, 1 = round caps)"));
+ cap_rounding_item->set_custom_numeric_menu_data(values, labels);
+ cap_rounding_item->set_focus_widget(desktop->canvas);
+ _cap_rounding_adj->signal_value_changed().connect(sigc::mem_fun(*this, &CalligraphyToolbar::cap_rounding_value_changed));
+ _widget_map["cap_rounding"] = G_OBJECT(_cap_rounding_adj->gobj());
+ add(*cap_rounding_item);
+ cap_rounding_item->set_sensitive(true);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ /* Tremor */
+ std::vector<Glib::ustring> labels = {_("(smooth line)"), _("(slight tremor)"), _("(noticeable tremor)"), "", "", _("(maximum tremor)")};
+ std::vector<double> values = { 0, 10, 20, 40, 60, 100};
+ auto tremor_val = prefs->getDouble("/tools/calligraphic/tremor", 0.0);
+ _tremor_adj = Gtk::Adjustment::create(tremor_val, 0.0, 100, 1, 10.0);
+ auto tremor_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("calligraphy-tremor", _("Tremor:"), _tremor_adj, 1, 0));
+ tremor_item->set_tooltip_text(_("Increase to make strokes rugged and trembling"));
+ tremor_item->set_custom_numeric_menu_data(values, labels);
+ tremor_item->set_focus_widget(desktop->canvas);
+ _tremor_adj->signal_value_changed().connect(sigc::mem_fun(*this, &CalligraphyToolbar::tremor_value_changed));
+ _widget_map["tremor"] = G_OBJECT(_tremor_adj->gobj());
+ add(*tremor_item);
+ tremor_item->set_sensitive(true);
+ }
+
+ {
+ /* Wiggle */
+ std::vector<Glib::ustring> labels = {_("(no wiggle)"), _("(slight deviation)"), "", "", _("(wild waves and curls)")};
+ std::vector<double> values = { 0, 20, 40, 60, 100};
+ auto wiggle_val = prefs->getDouble("/tools/calligraphic/wiggle", 0.0);
+ _wiggle_adj = Gtk::Adjustment::create(wiggle_val, 0.0, 100, 1, 10.0);
+ auto wiggle_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("calligraphy-wiggle", _("Wiggle:"), _wiggle_adj, 1, 0));
+ wiggle_item->set_tooltip_text(_("Increase to make the pen waver and wiggle"));
+ wiggle_item->set_custom_numeric_menu_data(values, labels);
+ wiggle_item->set_focus_widget(desktop->canvas);
+ _wiggle_adj->signal_value_changed().connect(sigc::mem_fun(*this, &CalligraphyToolbar::wiggle_value_changed));
+ _widget_map["wiggle"] = G_OBJECT(_wiggle_adj->gobj());
+ add(*wiggle_item);
+ wiggle_item->set_sensitive(true);
+ }
+
+ show_all();
+}
+
+GtkWidget *
+CalligraphyToolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = new CalligraphyToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+void
+CalligraphyToolbar::width_value_changed()
+{
+ Unit const *unit = _tracker->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/calligraphic/abs_width", _tracker->getCurrentLabel() != "%");
+ prefs->setDouble("/tools/calligraphic/width", Quantity::convert(_width_adj->get_value(), unit, "px"));
+ update_presets_list();
+}
+
+void
+CalligraphyToolbar::velthin_value_changed()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setDouble("/tools/calligraphic/thinning", _thinning_adj->get_value() );
+ update_presets_list();
+}
+
+void
+CalligraphyToolbar::angle_value_changed()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/calligraphic/angle", _angle_adj->get_value() );
+ update_presets_list();
+}
+
+void
+CalligraphyToolbar::flatness_value_changed()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/calligraphic/flatness", _fixation_adj->get_value() );
+ update_presets_list();
+}
+
+void
+CalligraphyToolbar::cap_rounding_value_changed()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/calligraphic/cap_rounding", _cap_rounding_adj->get_value() );
+ update_presets_list();
+}
+
+void
+CalligraphyToolbar::tremor_value_changed()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/calligraphic/tremor", _tremor_adj->get_value() );
+ update_presets_list();
+}
+
+void
+CalligraphyToolbar::wiggle_value_changed()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/calligraphic/wiggle", _wiggle_adj->get_value() );
+ update_presets_list();
+}
+
+void
+CalligraphyToolbar::mass_value_changed()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/calligraphic/mass", _mass_adj->get_value() );
+ update_presets_list();
+}
+
+void
+CalligraphyToolbar::on_pref_toggled(Gtk::ToggleToolButton *item,
+ const Glib::ustring& path)
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setBool(path, item->get_active());
+ update_presets_list();
+}
+
+void
+CalligraphyToolbar::update_presets_list()
+{
+ if (_presets_blocked) {
+ return;
+ }
+
+ auto prefs = Inkscape::Preferences::get();
+ auto presets = get_presets_list();
+
+ int index = 1; // 0 is for no preset.
+ for (auto i = presets.begin(); i != presets.end(); ++i, ++index) {
+ bool match = true;
+
+ auto preset = prefs->getAllEntries(*i);
+ for (auto & j : preset) {
+ Glib::ustring entry_name = j.getEntryName();
+ if (entry_name == "id" || entry_name == "name") {
+ continue;
+ }
+
+ void *widget = _widget_map[entry_name.data()];
+ if (widget) {
+ if (GTK_IS_ADJUSTMENT(widget)) {
+ double v = j.getDouble();
+ GtkAdjustment* adj = static_cast<GtkAdjustment *>(widget);
+ //std::cout << "compared adj " << attr_name << gtk_adjustment_get_value(adj) << " to " << v << "\n";
+ if (fabs(gtk_adjustment_get_value(adj) - v) > 1e-6) {
+ match = false;
+ break;
+ }
+ } else if (GTK_IS_TOGGLE_TOOL_BUTTON(widget)) {
+ bool v = j.getBool();
+ auto toggle = GTK_TOGGLE_TOOL_BUTTON(widget);
+ //std::cout << "compared toggle " << attr_name << gtk_toggle_action_get_active(toggle) << " to " << v << "\n";
+ if ( static_cast<bool>(gtk_toggle_tool_button_get_active(toggle)) != v ) {
+ match = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (match) {
+ // newly added item is at the same index as the
+ // save command, so we need to change twice for it to take effect
+ _profile_selector_combo->set_active(0);
+ _profile_selector_combo->set_active(index);
+ return;
+ }
+ }
+
+ // no match found
+ _profile_selector_combo->set_active(0);
+}
+
+void
+CalligraphyToolbar::tilt_state_changed()
+{
+ _angle_item->set_sensitive(!_usetilt->get_active());
+ on_pref_toggled(_usetilt, "/tools/calligraphic/usetilt");
+}
+
+void
+CalligraphyToolbar::build_presets_list()
+{
+ _presets_blocked = true;
+
+ _profile_selector_combo->remove_all();
+ _profile_selector_combo->append(_("No preset"));
+
+ // iterate over all presets to populate the list
+ auto prefs = Inkscape::Preferences::get();
+ auto presets = get_presets_list();
+
+ for (auto & preset : presets) {
+ Glib::ustring preset_name = prefs->getString(preset + "/name");
+
+ if (!preset_name.empty()) {
+ _profile_selector_combo->append(_(preset_name.data()));
+ }
+ }
+
+ _presets_blocked = false;
+
+ update_presets_list();
+}
+
+void
+CalligraphyToolbar::change_profile()
+{
+ auto mode = _profile_selector_combo->get_active_row_number();
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (_presets_blocked) {
+ return;
+ }
+
+ // mode is one-based so we subtract 1
+ std::vector<Glib::ustring> presets = get_presets_list();
+
+ Glib::ustring preset_path = "";
+ if (mode - 1 < presets.size()) {
+ preset_path = presets.at(mode - 1);
+ }
+
+ if (!preset_path.empty()) {
+ _presets_blocked = true; //temporarily block the selector so no one will updadte it while we're reading it
+
+ std::vector<Inkscape::Preferences::Entry> preset = prefs->getAllEntries(preset_path);
+
+ // Shouldn't this be std::map?
+ for (auto & i : preset) {
+ Glib::ustring entry_name = i.getEntryName();
+ if (entry_name == "id" || entry_name == "name") {
+ continue;
+ }
+ void *widget = _widget_map[entry_name.data()];
+ if (widget) {
+ if (GTK_IS_ADJUSTMENT(widget)) {
+ GtkAdjustment* adj = static_cast<GtkAdjustment *>(widget);
+ gtk_adjustment_set_value(adj, i.getDouble());
+ //std::cout << "set adj " << attr_name << " to " << v << "\n";
+ } else if (GTK_IS_TOGGLE_TOOL_BUTTON(widget)) {
+ auto toggle = GTK_TOGGLE_TOOL_BUTTON(widget);
+ gtk_toggle_tool_button_set_active(toggle, i.getBool());
+ //std::cout << "set toggle " << attr_name << " to " << v << "\n";
+ } else {
+ g_warning("Unknown widget type for preset: %s\n", entry_name.data());
+ }
+ } else {
+ g_warning("Bad key found in a preset record: %s\n", entry_name.data());
+ }
+ }
+ _presets_blocked = false;
+ }
+}
+
+void
+CalligraphyToolbar::edit_profile()
+{
+ save_profile(nullptr);
+}
+
+void CalligraphyToolbar::unit_changed(int /* NotUsed */)
+{
+ Unit const *unit = _tracker->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/calligraphic/abs_width", _tracker->getCurrentLabel() != "%");
+ prefs->setDouble("/tools/calligraphic/width",
+ CLAMP(prefs->getDouble("/tools/calligraphic/width"), Quantity::convert(0.001, unit, "px"),
+ Quantity::convert(100, unit, "px")));
+ prefs->setString("/tools/calligraphic/unit", unit->abbr);
+}
+
+void CalligraphyToolbar::save_profile(GtkWidget * /*widget*/)
+{
+ using Inkscape::UI::Dialog::CalligraphicProfileRename;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (! _desktop) {
+ return;
+ }
+
+ if (_presets_blocked) {
+ return;
+ }
+
+ Glib::ustring current_profile_name = _profile_selector_combo->get_active_text();
+
+ if (current_profile_name == _("No preset")) {
+ current_profile_name = "";
+ }
+
+ CalligraphicProfileRename::show(_desktop, current_profile_name);
+ if ( !CalligraphicProfileRename::applied()) {
+ // dialog cancelled
+ update_presets_list();
+ return;
+ }
+ Glib::ustring new_profile_name = CalligraphicProfileRename::getProfileName();
+
+ if (new_profile_name.empty()) {
+ // empty name entered
+ update_presets_list ();
+ return;
+ }
+
+ _presets_blocked = true;
+
+ // If there's a preset with the given name, find it and set save_path appropriately
+ auto presets = get_presets_list();
+ int total_presets = presets.size();
+ int new_index = -1;
+ Glib::ustring save_path; // profile pref path without a trailing slash
+
+ int temp_index = 0;
+ for (std::vector<Glib::ustring>::iterator i = presets.begin(); i != presets.end(); ++i, ++temp_index) {
+ Glib::ustring name = prefs->getString(*i + "/name");
+ if (!name.empty() && (new_profile_name == name || current_profile_name == name)) {
+ new_index = temp_index;
+ save_path = *i;
+ break;
+ }
+ }
+
+ if ( CalligraphicProfileRename::deleted() && new_index != -1) {
+ prefs->remove(save_path);
+ _presets_blocked = false;
+ build_presets_list();
+ return;
+ }
+
+ if (new_index == -1) {
+ // no preset with this name, create
+ new_index = total_presets + 1;
+ gchar *profile_id = g_strdup_printf("/dcc%d", new_index);
+ save_path = Glib::ustring("/tools/calligraphic/preset") + profile_id;
+ g_free(profile_id);
+ }
+
+ for (auto map_item : _widget_map) {
+ auto widget_name = map_item.first;
+ auto widget = map_item.second;
+
+ if (widget) {
+ if (GTK_IS_ADJUSTMENT(widget)) {
+ GtkAdjustment* adj = GTK_ADJUSTMENT(widget);
+ prefs->setDouble(save_path + "/" + widget_name, gtk_adjustment_get_value(adj));
+ //std::cout << "wrote adj " << widget_name << ": " << v << "\n";
+ } else if (GTK_IS_TOGGLE_TOOL_BUTTON(widget)) {
+ auto toggle = GTK_TOGGLE_TOOL_BUTTON(widget);
+ prefs->setBool(save_path + "/" + widget_name, gtk_toggle_tool_button_get_active(toggle));
+ //std::cout << "wrote tog " << widget_name << ": " << v << "\n";
+ } else {
+ g_warning("Unknown widget type for preset: %s\n", widget_name.c_str());
+ }
+ } else {
+ g_warning("Bad key when writing preset: %s\n", widget_name.c_str());
+ }
+ }
+ prefs->setString(save_path + "/name", new_profile_name);
+
+ _presets_blocked = true;
+ build_presets_list();
+}
+
+}
+}
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/calligraphy-toolbar.h b/src/ui/toolbar/calligraphy-toolbar.h
new file mode 100644
index 0000000..88f22ad
--- /dev/null
+++ b/src/ui/toolbar/calligraphy-toolbar.h
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_CALLIGRAPHY_TOOLBAR_H
+#define SEEN_CALLIGRAPHY_TOOLBAR_H
+
+/**
+ * @file
+ * Calligraphy aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+#include <gtkmm/adjustment.h>
+
+class SPDesktop;
+
+namespace Gtk {
+class ComboBoxText;
+}
+
+namespace Inkscape {
+namespace UI {
+class SimplePrefPusher;
+
+namespace Widget {
+class SpinButtonToolItem;
+class UnitTracker;
+}
+
+namespace Toolbar {
+class CalligraphyToolbar : public Toolbar {
+private:
+ UI::Widget::UnitTracker *_tracker;
+ bool _presets_blocked;
+
+ UI::Widget::SpinButtonToolItem *_angle_item;
+ Gtk::ComboBoxText *_profile_selector_combo;
+
+ std::map<Glib::ustring, GObject *> _widget_map;
+
+ Glib::RefPtr<Gtk::Adjustment> _width_adj;
+ Glib::RefPtr<Gtk::Adjustment> _mass_adj;
+ Glib::RefPtr<Gtk::Adjustment> _wiggle_adj;
+ Glib::RefPtr<Gtk::Adjustment> _angle_adj;
+ Glib::RefPtr<Gtk::Adjustment> _thinning_adj;
+ Glib::RefPtr<Gtk::Adjustment> _tremor_adj;
+ Glib::RefPtr<Gtk::Adjustment> _fixation_adj;
+ Glib::RefPtr<Gtk::Adjustment> _cap_rounding_adj;
+ Gtk::ToggleToolButton *_usepressure;
+ Gtk::ToggleToolButton *_tracebackground;
+ Gtk::ToggleToolButton *_usetilt;
+
+ std::unique_ptr<SimplePrefPusher> _tracebackground_pusher;
+ std::unique_ptr<SimplePrefPusher> _usepressure_pusher;
+ std::unique_ptr<SimplePrefPusher> _usetilt_pusher;
+
+ void width_value_changed();
+ void velthin_value_changed();
+ void angle_value_changed();
+ void flatness_value_changed();
+ void cap_rounding_value_changed();
+ void tremor_value_changed();
+ void wiggle_value_changed();
+ void mass_value_changed();
+ void build_presets_list();
+ void change_profile();
+ void save_profile(GtkWidget *widget);
+ void edit_profile();
+ void update_presets_list();
+ void tilt_state_changed();
+ void unit_changed(int not_used);
+ void on_pref_toggled(Gtk::ToggleToolButton *item,
+ const Glib::ustring& path);
+
+protected:
+ CalligraphyToolbar(SPDesktop *desktop);
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+};
+
+}
+}
+}
+
+#endif /* !SEEN_CALLIGRAPHY_TOOLBAR_H */
diff --git a/src/ui/toolbar/connector-toolbar.cpp b/src/ui/toolbar/connector-toolbar.cpp
new file mode 100644
index 0000000..305698b
--- /dev/null
+++ b/src/ui/toolbar/connector-toolbar.cpp
@@ -0,0 +1,431 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Connector aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "connector-toolbar.h"
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/separatortoolitem.h>
+
+#include "conn-avoid-ref.h"
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "enums.h"
+#include "layer-manager.h"
+#include "selection.h"
+
+#include "object/algorithms/graphlayout.h"
+#include "object/sp-namedview.h"
+#include "object/sp-path.h"
+
+#include "ui/icon-names.h"
+#include "ui/tools/connector-tool.h"
+#include "ui/widget/canvas.h"
+#include "ui/widget/spin-button-tool-item.h"
+
+#include "xml/node-event-vector.h"
+
+using Inkscape::DocumentUndo;
+
+static Inkscape::XML::NodeEventVector connector_tb_repr_events = {
+ nullptr, /* child_added */
+ nullptr, /* child_removed */
+ Inkscape::UI::Toolbar::ConnectorToolbar::event_attr_changed,
+ nullptr, /* content_changed */
+ nullptr /* order_changed */
+};
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+ConnectorToolbar::ConnectorToolbar(SPDesktop *desktop)
+ : Toolbar(desktop),
+ _freeze(false),
+ _repr(nullptr)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ {
+ auto avoid_item = Gtk::manage(new Gtk::ToolButton(_("Avoid")));
+ avoid_item->set_tooltip_text(_("Make connectors avoid selected objects"));
+ avoid_item->set_icon_name(INKSCAPE_ICON("connector-avoid"));
+ avoid_item->signal_clicked().connect(sigc::mem_fun(*this, &ConnectorToolbar::path_set_avoid));
+ add(*avoid_item);
+ }
+
+ {
+ auto ignore_item = Gtk::manage(new Gtk::ToolButton(_("Ignore")));
+ ignore_item->set_tooltip_text(_("Make connectors ignore selected objects"));
+ ignore_item->set_icon_name(INKSCAPE_ICON("connector-ignore"));
+ ignore_item->signal_clicked().connect(sigc::mem_fun(*this, &ConnectorToolbar::path_set_ignore));
+ add(*ignore_item);
+ }
+
+ // Orthogonal connectors toggle button
+ {
+ _orthogonal = add_toggle_button(_("Orthogonal"),
+ _("Make connector orthogonal or polyline"));
+ _orthogonal->set_icon_name(INKSCAPE_ICON("connector-orthogonal"));
+
+ bool tbuttonstate = prefs->getBool("/tools/connector/orthogonal");
+ _orthogonal->set_active(( tbuttonstate ? TRUE : FALSE ));
+ _orthogonal->signal_toggled().connect(sigc::mem_fun(*this, &ConnectorToolbar::orthogonal_toggled));
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ // Curvature spinbox
+ auto curvature_val = prefs->getDouble("/tools/connector/curvature", defaultConnCurvature);
+ _curvature_adj = Gtk::Adjustment::create(curvature_val, 0, 100, 1.0, 10.0);
+ auto curvature_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("inkscape:connector-curvature", _("Curvature:"), _curvature_adj, 1, 0));
+ curvature_item->set_tooltip_text(_("The amount of connectors curvature"));
+ curvature_item->set_focus_widget(desktop->canvas);
+ _curvature_adj->signal_value_changed().connect(sigc::mem_fun(*this, &ConnectorToolbar::curvature_changed));
+ add(*curvature_item);
+
+ // Spacing spinbox
+ auto spacing_val = prefs->getDouble("/tools/connector/spacing", defaultConnSpacing);
+ _spacing_adj = Gtk::Adjustment::create(spacing_val, 0, 100, 1.0, 10.0);
+ auto spacing_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("inkscape:connector-spacing", _("Spacing:"), _spacing_adj, 1, 0));
+ spacing_item->set_tooltip_text(_("The amount of space left around objects by auto-routing connectors"));
+ spacing_item->set_focus_widget(desktop->canvas);
+ _spacing_adj->signal_value_changed().connect(sigc::mem_fun(*this, &ConnectorToolbar::spacing_changed));
+ add(*spacing_item);
+
+ // Graph (connector network) layout
+ {
+ auto graph_item = Gtk::manage(new Gtk::ToolButton(_("Graph")));
+ graph_item->set_tooltip_text(_("Nicely arrange selected connector network"));
+ graph_item->set_icon_name(INKSCAPE_ICON("distribute-graph"));
+ graph_item->signal_clicked().connect(sigc::mem_fun(*this, &ConnectorToolbar::graph_layout));
+ add(*graph_item);
+ }
+
+ // Default connector length spinbox
+ auto length_val = prefs->getDouble("/tools/connector/length", 100);
+ _length_adj = Gtk::Adjustment::create(length_val, 10, 1000, 10.0, 100.0);
+ auto length_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("inkscape:connector-length", _("Length:"), _length_adj, 1, 0));
+ length_item->set_tooltip_text(_("Ideal length for connectors when layout is applied"));
+ length_item->set_focus_widget(desktop->canvas);
+ _length_adj->signal_value_changed().connect(sigc::mem_fun(*this, &ConnectorToolbar::length_changed));
+ add(*length_item);
+
+ // Directed edges toggle button
+ {
+ _directed_item = add_toggle_button(_("Downwards"),
+ _("Make connectors with end-markers (arrows) point downwards"));
+ _directed_item->set_icon_name(INKSCAPE_ICON("distribute-graph-directed"));
+
+ bool tbuttonstate = prefs->getBool("/tools/connector/directedlayout");
+ _directed_item->set_active(tbuttonstate ? TRUE : FALSE);
+
+ _directed_item->signal_toggled().connect(sigc::mem_fun(*this, &ConnectorToolbar::directed_graph_layout_toggled));
+ desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &ConnectorToolbar::selection_changed));
+ }
+
+ // Avoid overlaps toggle button
+ {
+ _overlap_item = add_toggle_button(_("Remove overlaps"),
+ _("Do not allow overlapping shapes"));
+ _overlap_item->set_icon_name(INKSCAPE_ICON("distribute-remove-overlaps"));
+
+ bool tbuttonstate = prefs->getBool("/tools/connector/avoidoverlaplayout");
+ _overlap_item->set_active(tbuttonstate ? TRUE : FALSE);
+
+ _overlap_item->signal_toggled().connect(sigc::mem_fun(*this, &ConnectorToolbar::nooverlaps_graph_layout_toggled));
+ }
+
+ // Code to watch for changes to the connector-spacing attribute in
+ // the XML.
+ Inkscape::XML::Node *repr = desktop->namedview->getRepr();
+ g_assert(repr != nullptr);
+
+ if(_repr) {
+ _repr->removeListenerByData(this);
+ Inkscape::GC::release(_repr);
+ _repr = nullptr;
+ }
+
+ if (repr) {
+ _repr = repr;
+ Inkscape::GC::anchor(_repr);
+ _repr->addListener(&connector_tb_repr_events, this);
+ _repr->synthesizeEvents(&connector_tb_repr_events, this);
+ }
+
+ show_all();
+}
+
+GtkWidget *
+ConnectorToolbar::create( SPDesktop *desktop)
+{
+ auto toolbar = new ConnectorToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+} // end of ConnectorToolbar::prep()
+
+void
+ConnectorToolbar::path_set_avoid()
+{
+ Inkscape::UI::Tools::cc_selection_set_avoid(_desktop, true);
+}
+
+void
+ConnectorToolbar::path_set_ignore()
+{
+ Inkscape::UI::Tools::cc_selection_set_avoid(_desktop, false);
+}
+
+void
+ConnectorToolbar::orthogonal_toggled()
+{
+ auto doc = _desktop->getDocument();
+
+ if (!DocumentUndo::getUndoSensitive(doc)) {
+ return;
+ }
+
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent callbacks from responding
+ _freeze = true;
+
+ bool is_orthog = _orthogonal->get_active();
+ gchar orthog_str[] = "orthogonal";
+ gchar polyline_str[] = "polyline";
+ gchar *value = is_orthog ? orthog_str : polyline_str ;
+
+ bool modmade = false;
+ auto itemlist= _desktop->getSelection()->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ SPItem *item = *i;
+
+ if (Inkscape::UI::Tools::cc_item_is_connector(item)) {
+ item->setAttribute( "inkscape:connector-type", value);
+ item->getAvoidRef().handleSettingChange();
+ modmade = true;
+ }
+ }
+
+ if (!modmade) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/connector/orthogonal", is_orthog);
+ } else {
+
+ DocumentUndo::done(doc, is_orthog ? _("Set connector type: orthogonal"): _("Set connector type: polyline"), INKSCAPE_ICON("draw-connector"));
+ }
+
+ _freeze = false;
+}
+
+void
+ConnectorToolbar::curvature_changed()
+{
+ SPDocument *doc = _desktop->getDocument();
+
+ if (!DocumentUndo::getUndoSensitive(doc)) {
+ return;
+ }
+
+
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent callbacks from responding
+ _freeze = true;
+
+ auto newValue = _curvature_adj->get_value();
+ gchar value[G_ASCII_DTOSTR_BUF_SIZE];
+ g_ascii_dtostr(value, G_ASCII_DTOSTR_BUF_SIZE, newValue);
+
+ bool modmade = false;
+ auto itemlist= _desktop->getSelection()->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ SPItem *item = *i;
+
+ if (Inkscape::UI::Tools::cc_item_is_connector(item)) {
+ item->setAttribute( "inkscape:connector-curvature", value);
+ item->getAvoidRef().handleSettingChange();
+ modmade = true;
+ }
+ }
+
+ if (!modmade) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble(Glib::ustring("/tools/connector/curvature"), newValue);
+ }
+ else {
+ DocumentUndo::done(doc, _("Change connector curvature"), INKSCAPE_ICON("draw-connector"));
+ }
+
+ _freeze = false;
+}
+
+void
+ConnectorToolbar::spacing_changed()
+{
+ SPDocument *doc = _desktop->getDocument();
+
+ if (!DocumentUndo::getUndoSensitive(doc)) {
+ return;
+ }
+
+ Inkscape::XML::Node *repr = _desktop->namedview->getRepr();
+
+ if ( !repr->attribute("inkscape:connector-spacing") &&
+ ( _spacing_adj->get_value() == defaultConnSpacing )) {
+ // Don't need to update the repr if the attribute doesn't
+ // exist and it is being set to the default value -- as will
+ // happen at startup.
+ return;
+ }
+
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ repr->setAttributeCssDouble("inkscape:connector-spacing", _spacing_adj->get_value());
+ _desktop->namedview->updateRepr();
+ bool modmade = false;
+
+ std::vector<SPItem *> items;
+ items = get_avoided_items(items, _desktop->layerManager().currentRoot(), _desktop);
+ for (auto item : items) {
+ Geom::Affine m = Geom::identity();
+ avoid_item_move(&m, item);
+ modmade = true;
+ }
+
+ if(modmade) {
+ DocumentUndo::done(doc, _("Change connector spacing"), INKSCAPE_ICON("draw-connector"));
+ }
+ _freeze = false;
+}
+
+void
+ConnectorToolbar::graph_layout()
+{
+ assert(_desktop);
+ if (!_desktop) {
+ return;
+ }
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ // hack for clones, see comment in align-and-distribute.cpp
+ int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+ prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
+
+ auto tmp = _desktop->getSelection()->items();
+ std::vector<SPItem *> vec(tmp.begin(), tmp.end());
+ graphlayout(vec);
+
+ prefs->setInt("/options/clonecompensation/value", saved_compensation);
+
+ DocumentUndo::done(_desktop->getDocument(), _("Arrange connector network"), INKSCAPE_ICON("dialog-align-and-distribute"));
+}
+
+void
+ConnectorToolbar::length_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble("/tools/connector/length", _length_adj->get_value());
+}
+
+void
+ConnectorToolbar::directed_graph_layout_toggled()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/connector/directedlayout", _directed_item->get_active());
+}
+
+void
+ConnectorToolbar::selection_changed(Inkscape::Selection *selection)
+{
+ SPItem *item = selection->singleItem();
+ if (SP_IS_PATH(item))
+ {
+ gdouble curvature = SP_PATH(item)->connEndPair.getCurvature();
+ bool is_orthog = SP_PATH(item)->connEndPair.isOrthogonal();
+ _orthogonal->set_active(is_orthog);
+ _curvature_adj->set_value(curvature);
+ }
+
+}
+
+void
+ConnectorToolbar::nooverlaps_graph_layout_toggled()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/connector/avoidoverlaplayout",
+ _overlap_item->get_active());
+}
+
+void
+ConnectorToolbar::event_attr_changed(Inkscape::XML::Node *repr,
+ gchar const *name,
+ gchar const * /*old_value*/,
+ gchar const * /*new_value*/,
+ bool /*is_interactive*/,
+ gpointer data)
+{
+ auto toolbar = reinterpret_cast<ConnectorToolbar *>(data);
+
+ if ( !toolbar->_freeze
+ && (strcmp(name, "inkscape:connector-spacing") == 0) ) {
+ gdouble spacing = repr->getAttributeDouble("inkscape:connector-spacing", defaultConnSpacing);
+
+ toolbar->_spacing_adj->set_value(spacing);
+
+ if (toolbar->_desktop->canvas) {
+ toolbar->_desktop->canvas->grab_focus();
+ }
+ }
+}
+
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/connector-toolbar.h b/src/ui/toolbar/connector-toolbar.h
new file mode 100644
index 0000000..66df79e
--- /dev/null
+++ b/src/ui/toolbar/connector-toolbar.h
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_CONNECTOR_TOOLBAR_H
+#define SEEN_CONNECTOR_TOOLBAR_H
+
+/**
+ * @file
+ * Connector aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+#include <gtkmm/adjustment.h>
+
+class SPDesktop;
+
+namespace Gtk {
+class ToolButton;
+}
+
+namespace Inkscape {
+class Selection;
+
+namespace XML {
+class Node;
+}
+
+namespace UI {
+namespace Toolbar {
+class ConnectorToolbar : public Toolbar {
+private:
+ Gtk::ToggleToolButton *_orthogonal;
+ Gtk::ToggleToolButton *_directed_item;
+ Gtk::ToggleToolButton *_overlap_item;
+
+ Glib::RefPtr<Gtk::Adjustment> _curvature_adj;
+ Glib::RefPtr<Gtk::Adjustment> _spacing_adj;
+ Glib::RefPtr<Gtk::Adjustment> _length_adj;
+
+ bool _freeze;
+
+ Inkscape::XML::Node *_repr;
+
+ void path_set_avoid();
+ void path_set_ignore();
+ void orthogonal_toggled();
+ void graph_layout();
+ void directed_graph_layout_toggled();
+ void nooverlaps_graph_layout_toggled();
+ void curvature_changed();
+ void spacing_changed();
+ void length_changed();
+ void selection_changed(Inkscape::Selection *selection);
+
+protected:
+ ConnectorToolbar(SPDesktop *desktop);
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+
+ static void event_attr_changed(Inkscape::XML::Node *repr,
+ gchar const *name,
+ gchar const * /*old_value*/,
+ gchar const * /*new_value*/,
+ bool /*is_interactive*/,
+ gpointer data);
+};
+
+}
+}
+}
+
+#endif /* !SEEN_CONNECTOR_TOOLBAR_H */
diff --git a/src/ui/toolbar/dropper-toolbar.cpp b/src/ui/toolbar/dropper-toolbar.cpp
new file mode 100644
index 0000000..83a18c3
--- /dev/null
+++ b/src/ui/toolbar/dropper-toolbar.cpp
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Dropper aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/i18n.h>
+
+#include "dropper-toolbar.h"
+#include "document-undo.h"
+#include "preferences.h"
+#include "desktop.h"
+
+#include "ui/widget/canvas.h" // Grab focus
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+
+void DropperToolbar::on_pick_alpha_button_toggled()
+{
+ auto active = _pick_alpha_button->get_active();
+
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setInt( "/tools/dropper/pick", active );
+
+ _set_alpha_button->set_sensitive(active);
+ _desktop->canvas->grab_focus();
+}
+
+void DropperToolbar::on_set_alpha_button_toggled()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setBool( "/tools/dropper/setalpha", _set_alpha_button->get_active( ) );
+ _desktop->canvas->grab_focus();
+}
+
+/*
+ * TODO: Would like to add swatch of current color.
+ * TODO: Add queue of last 5 or so colors selected with new swatches so that
+ * can drag and drop places. Will provide a nice mixing palette.
+ */
+DropperToolbar::DropperToolbar(SPDesktop *desktop)
+ : Toolbar(desktop)
+{
+ // Add widgets to toolbar
+ add_label(_("Opacity:"));
+ _pick_alpha_button = add_toggle_button(_("Pick"),
+ _("Pick both the color and the alpha (transparency) under cursor; "
+ "otherwise, pick only the visible color premultiplied by alpha"));
+ _set_alpha_button = add_toggle_button(_("Assign"),
+ _("If alpha was picked, assign it to selection "
+ "as fill or stroke transparency"));
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ // Set initial state of widgets
+ auto pickAlpha = prefs->getInt( "/tools/dropper/pick", 1 );
+ auto setAlpha = prefs->getBool( "/tools/dropper/setalpha", true);
+
+ _pick_alpha_button->set_active(pickAlpha);
+ _set_alpha_button->set_active(setAlpha);
+
+ // Make sure the set-alpha button is disabled if we're not picking alpha
+ _set_alpha_button->set_sensitive(pickAlpha);
+
+ // Connect signal handlers
+ auto pick_alpha_button_toggled_cb = sigc::mem_fun(*this, &DropperToolbar::on_pick_alpha_button_toggled);
+ auto set_alpha_button_toggled_cb = sigc::mem_fun(*this, &DropperToolbar::on_set_alpha_button_toggled);
+
+ _pick_alpha_button->signal_toggled().connect(pick_alpha_button_toggled_cb);
+ _set_alpha_button->signal_toggled().connect(set_alpha_button_toggled_cb);
+
+ show_all();
+}
+
+GtkWidget *
+DropperToolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = Gtk::manage(new DropperToolbar(desktop));
+ return GTK_WIDGET(toolbar->gobj());
+}
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/dropper-toolbar.h b/src/ui/toolbar/dropper-toolbar.h
new file mode 100644
index 0000000..c8aa42f
--- /dev/null
+++ b/src/ui/toolbar/dropper-toolbar.h
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_DROPPER_TOOLBAR_H
+#define SEEN_DROPPER_TOOLBAR_H
+
+/**
+ * @file
+ * Dropper aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+
+/**
+ * \brief A toolbar for controlling the dropper tool
+ */
+class DropperToolbar : public Toolbar {
+private:
+ // Tool widgets
+ Gtk::ToggleToolButton *_pick_alpha_button; ///< Control whether to pick opacity
+ Gtk::ToggleToolButton *_set_alpha_button; ///< Control whether to set opacity
+
+ // Event handlers
+ void on_pick_alpha_button_toggled();
+ void on_set_alpha_button_toggled();
+
+protected:
+ DropperToolbar(SPDesktop *desktop);
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+};
+}
+}
+}
+#endif /* !SEEN_DROPPER_TOOLBAR_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/eraser-toolbar.cpp b/src/ui/toolbar/eraser-toolbar.cpp
new file mode 100644
index 0000000..33487f4
--- /dev/null
+++ b/src/ui/toolbar/eraser-toolbar.cpp
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Erasor aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "eraser-toolbar.h"
+
+#include <array>
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/radiotoolbutton.h>
+#include <gtkmm/separatortoolitem.h>
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "ui/icon-names.h"
+#include "ui/simple-pref-pusher.h"
+#include "ui/tools/eraser-tool.h"
+
+#include "ui/widget/canvas.h" // Focus widget
+#include "ui/widget/spin-button-tool-item.h"
+
+using Inkscape::DocumentUndo;
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+EraserToolbar::EraserToolbar(SPDesktop *desktop)
+ : Toolbar(desktop),
+ _freeze(false)
+{
+ auto prefs = Inkscape::Preferences::get();
+ gint const eraser_mode = prefs->getInt("/tools/eraser/mode", _modeAsInt(Tools::DEFAULT_ERASER_MODE));
+ // Mode
+ {
+ add_label(_("Mode:"));
+
+ Gtk::RadioToolButton::Group mode_group;
+
+ std::vector<Gtk::RadioToolButton *> mode_buttons;
+
+ auto delete_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Delete")));
+ delete_btn->set_tooltip_text(_("Delete objects touched by eraser"));
+ delete_btn->set_icon_name(INKSCAPE_ICON("draw-eraser-delete-objects"));
+ mode_buttons.push_back(delete_btn);
+
+ auto cut_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Cut")));
+ cut_btn->set_tooltip_text(_("Cut out from paths and shapes"));
+ cut_btn->set_icon_name(INKSCAPE_ICON("path-difference"));
+ mode_buttons.push_back(cut_btn);
+
+ auto clip_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Clip")));
+ clip_btn->set_tooltip_text(_("Clip from objects"));
+ clip_btn->set_icon_name(INKSCAPE_ICON("path-intersection"));
+ mode_buttons.push_back(clip_btn);
+
+ mode_buttons[eraser_mode]->set_active();
+
+ int btn_index = 0;
+
+ for (auto btn : mode_buttons)
+ {
+ add(*btn);
+ btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &EraserToolbar::mode_changed), btn_index++));
+ }
+ }
+
+ _separators.push_back(Gtk::manage(new Gtk::SeparatorToolItem()));
+ add(*_separators.back());
+
+ /* Width */
+ {
+ std::vector<Glib::ustring> labels = {_("(no width)"), _("(hairline)"), "", "", "", _("(default)"), "", "", "", "", _("(broad stroke)")};
+ std::vector<double> values = { 0, 1, 3, 5, 10, 15, 20, 30, 50, 75, 100};
+ auto width_val = prefs->getDouble("/tools/eraser/width", 15);
+ _width_adj = Gtk::Adjustment::create(width_val, 0, 100, 1.0, 10.0);
+ _width = Gtk::manage(new UI::Widget::SpinButtonToolItem("eraser-width", _("Width:"), _width_adj, 1, 0));
+ _width->set_tooltip_text(_("The width of the eraser pen (relative to the visible canvas area)"));
+ _width->set_focus_widget(desktop->canvas);
+ _width->set_custom_numeric_menu_data(values, labels);
+ _width_adj->signal_value_changed().connect(sigc::mem_fun(*this, &EraserToolbar::width_value_changed));
+ // TODO: Allow SpinButtonToolItem to display as a slider
+ add(*_width);
+ _width->set_sensitive(true);
+ }
+
+ /* Use Pressure button */
+ {
+ _usepressure = add_toggle_button(_("Eraser Pressure"),
+ _("Use the pressure of the input device to alter the width of the pen"));
+ _usepressure->set_icon_name(INKSCAPE_ICON("draw-use-pressure"));
+ _pressure_pusher.reset(new UI::SimplePrefPusher(_usepressure, "/tools/eraser/usepressure"));
+ _usepressure->signal_toggled().connect(sigc::mem_fun(*this, &EraserToolbar::usepressure_toggled));
+ }
+
+ _separators.push_back(Gtk::manage(new Gtk::SeparatorToolItem()));
+ add(*_separators.back());
+
+ /* Thinning */
+ {
+ std::vector<Glib::ustring> labels = {_("(speed blows up stroke)"), "", "", _("(slight widening)"), _("(constant width)"), _("(slight thinning, default)"), "", "", _("(speed deflates stroke)")};
+ std::vector<double> values = { -100, -40, -20, -10, 0, 10, 20, 40, 100};
+ auto thinning_val = prefs->getDouble("/tools/eraser/thinning", 10);
+ _thinning_adj = Gtk::Adjustment::create(thinning_val, -100, 100, 1, 10.0);
+ _thinning = Gtk::manage(new UI::Widget::SpinButtonToolItem("eraser-thinning", _("Thinning:"), _thinning_adj, 1, 0));
+ _thinning->set_tooltip_text(_("How much velocity thins the stroke (> 0 makes fast strokes thinner, < 0 makes them broader, 0 makes width independent of velocity)"));
+ _thinning->set_custom_numeric_menu_data(values, labels);
+ _thinning->set_focus_widget(desktop->canvas);
+ _thinning_adj->signal_value_changed().connect(sigc::mem_fun(*this, &EraserToolbar::velthin_value_changed));
+ add(*_thinning);
+ _thinning->set_sensitive(true);
+ }
+
+ _separators.push_back(Gtk::manage(new Gtk::SeparatorToolItem()));
+ add(*_separators.back());
+
+ /* Cap Rounding */
+ {
+ std::vector<Glib::ustring> labels = {_("(blunt caps, default)"), _("(slightly bulging)"), "", "", _("(approximately round)"), _("(long protruding caps)")};
+ std::vector<double> values = { 0, 0.3, 0.5, 1.0, 1.4, 5.0};
+ auto cap_rounding_val = prefs->getDouble("/tools/eraser/cap_rounding", 0.0);
+ _cap_rounding_adj = Gtk::Adjustment::create(cap_rounding_val, 0.0, 5.0, 0.01, 0.1);
+ // TRANSLATORS: "cap" means "end" (both start and finish) here
+ _cap_rounding = Gtk::manage(new UI::Widget::SpinButtonToolItem("eraser-cap-rounding", _("Caps:"), _cap_rounding_adj, 0.01, 2));
+ _cap_rounding->set_tooltip_text(_("Increase to make caps at the ends of strokes protrude more (0 = no caps, 1 = round caps)"));
+ _cap_rounding->set_custom_numeric_menu_data(values, labels);
+ _cap_rounding->set_focus_widget(desktop->canvas);
+ _cap_rounding_adj->signal_value_changed().connect(sigc::mem_fun(*this, &EraserToolbar::cap_rounding_value_changed));
+ add(*_cap_rounding);
+ _cap_rounding->set_sensitive(true);
+ }
+
+ _separators.push_back(Gtk::manage(new Gtk::SeparatorToolItem()));
+ add(*_separators.back());
+
+ /* Tremor */
+ {
+ std::vector<Glib::ustring> labels = {_("(smooth line)"), _("(slight tremor)"), _("(noticeable tremor)"), "", "", _("(maximum tremor)")};
+ std::vector<double> values = { 0, 10, 20, 40, 60, 100};
+ auto tremor_val = prefs->getDouble("/tools/eraser/tremor", 0.0);
+ _tremor_adj = Gtk::Adjustment::create(tremor_val, 0.0, 100, 1, 10.0);
+ _tremor = Gtk::manage(new UI::Widget::SpinButtonToolItem("eraser-tremor", _("Tremor:"), _tremor_adj, 1, 0));
+ _tremor->set_tooltip_text(_("Increase to make strokes rugged and trembling"));
+ _tremor->set_custom_numeric_menu_data(values, labels);
+ _tremor->set_focus_widget(desktop->canvas);
+ _tremor_adj->signal_value_changed().connect(sigc::mem_fun(*this, &EraserToolbar::tremor_value_changed));
+
+ // TODO: Allow slider appearance
+ add(*_tremor);
+ _tremor->set_sensitive(true);
+ }
+
+ _separators.push_back(Gtk::manage(new Gtk::SeparatorToolItem()));
+ add(*_separators.back());
+
+ /* Mass */
+ {
+ std::vector<Glib::ustring> labels = {_("(no inertia)"), _("(slight smoothing, default)"), _("(noticeable lagging)"), "", "", _("(maximum inertia)")};
+ std::vector<double> values = { 0.0, 2, 10, 20, 50, 100};
+ auto mass_val = prefs->getDouble("/tools/eraser/mass", 10.0);
+ _mass_adj = Gtk::Adjustment::create(mass_val, 0.0, 100, 1, 10.0);
+ _mass = Gtk::manage(new UI::Widget::SpinButtonToolItem("eraser-mass", _("Mass:"), _mass_adj, 1, 0));
+ _mass->set_tooltip_text(_("Increase to make the eraser drag behind, as if slowed by inertia"));
+ _mass->set_custom_numeric_menu_data(values, labels);
+ _mass->set_focus_widget(desktop->canvas);
+ _mass_adj->signal_value_changed().connect(sigc::mem_fun(*this, &EraserToolbar::mass_value_changed));
+ // TODO: Allow slider appearance
+ add(*_mass);
+ _mass->set_sensitive(true);
+ }
+
+ _separators.push_back(Gtk::manage(new Gtk::SeparatorToolItem()));
+ add(*_separators.back());
+
+ /* Overlap */
+ {
+ _split = add_toggle_button(_("Break apart cut items"),
+ _("Break apart cut items"));
+ _split->set_icon_name(INKSCAPE_ICON("distribute-randomize"));
+ _split->set_active( prefs->getBool("/tools/eraser/break_apart", false) );
+ _split->signal_toggled().connect(sigc::mem_fun(*this, &EraserToolbar::toggle_break_apart));
+ }
+
+ show_all();
+
+ set_eraser_mode_visibility(eraser_mode);
+}
+
+GtkWidget *
+EraserToolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = new EraserToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+/**
+ * @brief Computes the integer value representing eraser mode
+ * @param mode A mode of the eraser tool, from the enum EraserToolMode
+ * @return the integer to be stored in the prefs as the selected mode
+ */
+guint EraserToolbar::_modeAsInt(Inkscape::UI::Tools::EraserToolMode mode)
+{
+ using namespace Inkscape::UI::Tools;
+
+ if (mode == EraserToolMode::DELETE) {
+ return 0;
+ } else if (mode == EraserToolMode::CUT) {
+ return 1;
+ } else if (mode == EraserToolMode::CLIP) {
+ return 2;
+ } else {
+ return _modeAsInt(DEFAULT_ERASER_MODE);
+ }
+}
+
+void
+EraserToolbar::mode_changed(int mode)
+{
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt( "/tools/eraser/mode", mode );
+ }
+
+ set_eraser_mode_visibility(mode);
+
+ // only take action if run by the attr_changed listener
+ if (!_freeze) {
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ /*
+ if ( eraser_mode != ERASER_MODE_DELETE ) {
+ } else {
+ }
+ */
+ // TODO finish implementation
+
+ _freeze = false;
+ }
+}
+
+void
+EraserToolbar::set_eraser_mode_visibility(const guint eraser_mode)
+{
+ using namespace Inkscape::UI::Tools;
+
+ _split->set_visible(eraser_mode == _modeAsInt(EraserToolMode::CUT));
+
+ const gboolean visibility = (eraser_mode != _modeAsInt(EraserToolMode::DELETE));
+
+ const std::array<Gtk::Widget *, 6> arr = {_cap_rounding,
+ _mass,
+ _thinning,
+ _tremor,
+ _usepressure,
+ _width};
+ for (auto widget : arr) {
+ widget->set_visible(visibility);
+ }
+
+ for (auto separator : _separators) {
+ separator->set_visible(visibility);
+ }
+}
+
+void
+EraserToolbar::width_value_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/eraser/width", _width_adj->get_value() );
+}
+
+void
+EraserToolbar::mass_value_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/eraser/mass", _mass_adj->get_value() );
+}
+
+void
+EraserToolbar::velthin_value_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble("/tools/eraser/thinning", _thinning_adj->get_value() );
+}
+
+void
+EraserToolbar::cap_rounding_value_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/eraser/cap_rounding", _cap_rounding_adj->get_value() );
+}
+
+void
+EraserToolbar::tremor_value_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/eraser/tremor", _tremor_adj->get_value() );
+}
+
+void
+EraserToolbar::toggle_break_apart()
+{
+ auto prefs = Inkscape::Preferences::get();
+ bool active = _split->get_active();
+ prefs->setBool("/tools/eraser/break_apart", active);
+}
+
+void
+EraserToolbar::usepressure_toggled()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/eraser/usepressure", _usepressure->get_active());
+}
+
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/eraser-toolbar.h b/src/ui/toolbar/eraser-toolbar.h
new file mode 100644
index 0000000..c993e46
--- /dev/null
+++ b/src/ui/toolbar/eraser-toolbar.h
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_ERASOR_TOOLBAR_H
+#define SEEN_ERASOR_TOOLBAR_H
+
+/**
+ * @file
+ * Erasor aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+#include <gtkmm/adjustment.h>
+
+class SPDesktop;
+
+namespace Gtk {
+class SeparatorToolItem;
+}
+
+namespace Inkscape {
+namespace UI {
+class SimplePrefPusher;
+
+namespace Tools {
+enum class EraserToolMode;
+extern EraserToolMode const DEFAULT_ERASER_MODE;
+} // namespace Tools
+
+namespace Widget {
+class SpinButtonToolItem;
+} // namespace Widget
+
+namespace Toolbar {
+class EraserToolbar : public Toolbar {
+private:
+ UI::Widget::SpinButtonToolItem *_width;
+ UI::Widget::SpinButtonToolItem *_mass;
+ UI::Widget::SpinButtonToolItem *_thinning;
+ UI::Widget::SpinButtonToolItem *_cap_rounding;
+ UI::Widget::SpinButtonToolItem *_tremor;
+
+ Gtk::ToggleToolButton *_usepressure;
+ Gtk::ToggleToolButton *_split;
+
+ Glib::RefPtr<Gtk::Adjustment> _width_adj;
+ Glib::RefPtr<Gtk::Adjustment> _mass_adj;
+ Glib::RefPtr<Gtk::Adjustment> _thinning_adj;
+ Glib::RefPtr<Gtk::Adjustment> _cap_rounding_adj;
+ Glib::RefPtr<Gtk::Adjustment> _tremor_adj;
+
+ std::unique_ptr<SimplePrefPusher> _pressure_pusher;
+
+ std::vector<Gtk::SeparatorToolItem *> _separators;
+
+ bool _freeze;
+
+ static guint _modeAsInt(Inkscape::UI::Tools::EraserToolMode mode);
+ void mode_changed(int mode);
+ void set_eraser_mode_visibility(const guint eraser_mode);
+ void width_value_changed();
+ void mass_value_changed();
+ void velthin_value_changed();
+ void cap_rounding_value_changed();
+ void tremor_value_changed();
+ static void update_presets_list(gpointer data);
+ void toggle_break_apart();
+ void usepressure_toggled();
+
+protected:
+ EraserToolbar(SPDesktop *desktop);
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+};
+
+}
+}
+}
+
+#endif /* !SEEN_ERASOR_TOOLBAR_H */
diff --git a/src/ui/toolbar/gradient-toolbar.cpp b/src/ui/toolbar/gradient-toolbar.cpp
new file mode 100644
index 0000000..d2faeb1
--- /dev/null
+++ b/src/ui/toolbar/gradient-toolbar.cpp
@@ -0,0 +1,1173 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gradient aux toolbar
+ *
+ * Authors:
+ * bulia byak <bulia@dr.com>
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2007 Johan Engelen
+ * Copyright (C) 2005 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/radiotoolbutton.h>
+#include <gtkmm/separatortoolitem.h>
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "document.h"
+#include "gradient-chemistry.h"
+#include "gradient-drag.h"
+#include "gradient-toolbar.h"
+#include "selection.h"
+
+#include "object/sp-defs.h"
+#include "object/sp-linear-gradient.h"
+#include "object/sp-radial-gradient.h"
+#include "object/sp-stop.h"
+#include "style.h"
+
+#include "ui/icon-names.h"
+#include "ui/tools/gradient-tool.h"
+#include "ui/util.h"
+#include "ui/widget/canvas.h"
+#include "ui/widget/color-preview.h"
+#include "ui/widget/combo-tool-item.h"
+#include "ui/widget/gradient-image.h"
+#include "ui/widget/spin-button-tool-item.h"
+#include "ui/widget/gradient-vector-selector.h"
+
+using Inkscape::DocumentUndo;
+using Inkscape::UI::Tools::ToolBase;
+
+static bool blocked = false;
+
+void gr_apply_gradient_to_item( SPItem *item, SPGradient *gr, SPGradientType initialType, Inkscape::PaintTarget initialMode, Inkscape::PaintTarget mode )
+{
+ SPStyle *style = item->style;
+ bool isFill = (mode == Inkscape::FOR_FILL);
+ if (style
+ && (isFill ? style->fill.isPaintserver() : style->stroke.isPaintserver())
+ //&& SP_IS_GRADIENT(isFill ? style->getFillPaintServer() : style->getStrokePaintServer()) ) {
+ && (isFill ? SP_IS_GRADIENT(style->getFillPaintServer()) : SP_IS_GRADIENT(style->getStrokePaintServer())) ) {
+ SPPaintServer *server = isFill ? style->getFillPaintServer() : style->getStrokePaintServer();
+ if ( SP_IS_LINEARGRADIENT(server) ) {
+ sp_item_set_gradient(item, gr, SP_GRADIENT_TYPE_LINEAR, mode);
+ } else if ( SP_IS_RADIALGRADIENT(server) ) {
+ sp_item_set_gradient(item, gr, SP_GRADIENT_TYPE_RADIAL, mode);
+ }
+ }
+ else if (initialMode == mode)
+ {
+ sp_item_set_gradient(item, gr, initialType, mode);
+ }
+}
+
+/**
+Applies gradient vector gr to the gradients attached to the selected dragger of drag, or if none,
+to all objects in selection. If there was no previous gradient on an item, uses gradient type and
+fill/stroke setting from preferences to create new default (linear: left/right; radial: centered)
+gradient.
+*/
+void gr_apply_gradient(Inkscape::Selection *selection, GrDrag *drag, SPGradient *gr)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ SPGradientType initialType = static_cast<SPGradientType>(prefs->getInt("/tools/gradient/newgradient", SP_GRADIENT_TYPE_LINEAR));
+ Inkscape::PaintTarget initialMode = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE;
+
+ // GRADIENTFIXME: make this work for multiple selected draggers.
+
+ // First try selected dragger
+ if (drag && !drag->selected.empty()) {
+ GrDragger *dragger = *(drag->selected.begin());
+ for(auto draggable : dragger->draggables) { //for all draggables of dragger
+ gr_apply_gradient_to_item(draggable->item, gr, initialType, initialMode, draggable->fill_or_stroke);
+ }
+ return;
+ }
+
+ // If no drag or no dragger selected, act on selection
+ auto itemlist= selection->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ gr_apply_gradient_to_item(*i, gr, initialType, initialMode, initialMode);
+ }
+}
+
+int gr_vector_list(Glib::RefPtr<Gtk::ListStore> store, SPDesktop *desktop,
+ bool selection_empty, SPGradient *gr_selected, bool gr_multi)
+{
+ int selected = -1;
+
+ if (!blocked) {
+ std::cerr << "gr_vector_list: should be blocked!" << std::endl;
+ }
+
+ // Get list of gradients in document.
+ SPDocument *document = desktop->getDocument();
+ std::vector<SPObject *> gl;
+ std::vector<SPObject *> gradients = document->getResourceList( "gradient" );
+ for (auto gradient : gradients) {
+ SPGradient *grad = SP_GRADIENT(gradient);
+ if ( grad->hasStops() && !grad->isSolid() ) {
+ gl.push_back(gradient);
+ }
+ }
+
+ store->clear();
+
+ Inkscape::UI::Widget::ComboToolItemColumns columns;
+ Gtk::TreeModel::Row row;
+
+ if (gl.empty()) {
+ // The document has no gradients
+
+ row = *(store->append());
+ row[columns.col_label ] = _("No gradient");
+ row[columns.col_tooltip ] = "";
+ row[columns.col_icon ] = "NotUsed";
+ row[columns.col_data ] = nullptr;
+ row[columns.col_sensitive] = true;
+
+ } else if (selection_empty) {
+ // Document has gradients, but nothing is currently selected.
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Nothing Selected");
+ row[columns.col_tooltip ] = "";
+ row[columns.col_icon ] = "NotUsed";
+ row[columns.col_data ] = nullptr;
+ row[columns.col_sensitive] = true;
+
+ } else {
+
+ if (gr_selected == nullptr) {
+ row = *(store->append());
+ row[columns.col_label ] = _("No gradient");
+ row[columns.col_tooltip ] = "";
+ row[columns.col_icon ] = "NotUsed";
+ row[columns.col_data ] = nullptr;
+ row[columns.col_sensitive] = true;
+ }
+
+ if (gr_multi) {
+ row = *(store->append());
+ row[columns.col_label ] = _("Multiple gradients");
+ row[columns.col_tooltip ] = "";
+ row[columns.col_icon ] = "NotUsed";
+ row[columns.col_data ] = nullptr;
+ row[columns.col_sensitive] = true;
+ }
+
+ int idx = 0;
+ for (auto it : gl) {
+ SPGradient *gradient = SP_GRADIENT(it);
+
+ Glib::ustring label = gr_prepare_label(gradient);
+ Glib::RefPtr<Gdk::Pixbuf> pixbuf = sp_gradient_to_pixbuf_ref(gradient, 64, 16);
+
+ row = *(store->append());
+ row[columns.col_label ] = label;
+ row[columns.col_tooltip ] = "";
+ row[columns.col_icon ] = "NotUsed";
+ row[columns.col_pixbuf ] = pixbuf;
+ row[columns.col_data ] = gradient;
+ row[columns.col_sensitive] = true;
+
+ if (gradient == gr_selected) {
+ selected = idx;
+ }
+ idx ++;
+ }
+
+ if (gr_multi) {
+ selected = 0; // This will show "Multiple Gradients"
+ }
+ }
+
+ return selected;
+}
+
+/*
+ * Get the gradient of the selected desktop item
+ * This is gradient containing the repeat settings, not the underlying "getVector" href linked gradient.
+ */
+void gr_get_dt_selected_gradient(Inkscape::Selection *selection, SPGradient *&gr_selected)
+{
+ SPGradient *gradient = nullptr;
+
+ auto itemlist= selection->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ SPItem *item = *i;// get the items gradient, not the getVector() version
+ SPStyle *style = item->style;
+ SPPaintServer *server = nullptr;
+
+ if (style && (style->fill.isPaintserver())) {
+ server = item->style->getFillPaintServer();
+ }
+ if (style && (style->stroke.isPaintserver())) {
+ server = item->style->getStrokePaintServer();
+ }
+
+ if ( SP_IS_GRADIENT(server) ) {
+ gradient = SP_GRADIENT(server);
+ }
+ }
+
+ if (gradient && gradient->isSolid()) {
+ gradient = nullptr;
+ }
+
+ if (gradient) {
+ gr_selected = gradient;
+ }
+}
+
+/*
+ * Get the current selection and dragger status from the desktop
+ */
+void gr_read_selection( Inkscape::Selection *selection,
+ GrDrag *drag,
+ SPGradient *&gr_selected,
+ bool &gr_multi,
+ SPGradientSpread &spr_selected,
+ bool &spr_multi )
+{
+ if (drag && !drag->selected.empty()) {
+ // GRADIENTFIXME: make this work for more than one selected dragger?
+ GrDragger *dragger = *(drag->selected.begin());
+ for(auto draggable : dragger->draggables) { //for all draggables of dragger
+ SPGradient *gradient = sp_item_gradient_get_vector(draggable->item, draggable->fill_or_stroke);
+ SPGradientSpread spread = sp_item_gradient_get_spread(draggable->item, draggable->fill_or_stroke);
+
+ if (gradient && gradient->isSolid()) {
+ gradient = nullptr;
+ }
+
+ if (gradient && (gradient != gr_selected)) {
+ if (gr_selected) {
+ gr_multi = true;
+ } else {
+ gr_selected = gradient;
+ }
+ }
+ if (spread != spr_selected) {
+ if (spr_selected != SP_GRADIENT_SPREAD_UNDEFINED) {
+ spr_multi = true;
+ } else {
+ spr_selected = spread;
+ }
+ }
+ }
+ return;
+ }
+
+ // If no selected dragger, read desktop selection
+ auto itemlist= selection->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ SPItem *item = *i;
+ SPStyle *style = item->style;
+
+ if (style && (style->fill.isPaintserver())) {
+ SPPaintServer *server = item->style->getFillPaintServer();
+ if ( SP_IS_GRADIENT(server) ) {
+ SPGradient *gradient = SP_GRADIENT(server)->getVector();
+ SPGradientSpread spread = SP_GRADIENT(server)->fetchSpread();
+
+ if (gradient && gradient->isSolid()) {
+ gradient = nullptr;
+ }
+
+ if (gradient && (gradient != gr_selected)) {
+ if (gr_selected) {
+ gr_multi = true;
+ } else {
+ gr_selected = gradient;
+ }
+ }
+ if (spread != spr_selected) {
+ if (spr_selected != SP_GRADIENT_SPREAD_UNDEFINED) {
+ spr_multi = true;
+ } else {
+ spr_selected = spread;
+ }
+ }
+ }
+ }
+ if (style && (style->stroke.isPaintserver())) {
+ SPPaintServer *server = item->style->getStrokePaintServer();
+ if ( SP_IS_GRADIENT(server) ) {
+ SPGradient *gradient = SP_GRADIENT(server)->getVector();
+ SPGradientSpread spread = SP_GRADIENT(server)->fetchSpread();
+
+ if (gradient && gradient->isSolid()) {
+ gradient = nullptr;
+ }
+
+ if (gradient && (gradient != gr_selected)) {
+ if (gr_selected) {
+ gr_multi = true;
+ } else {
+ gr_selected = gradient;
+ }
+ }
+ if (spread != spr_selected) {
+ if (spr_selected != SP_GRADIENT_SPREAD_UNDEFINED) {
+ spr_multi = true;
+ } else {
+ spr_selected = spread;
+ }
+ }
+ }
+ }
+ }
+ }
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+GradientToolbar::GradientToolbar(SPDesktop *desktop)
+ : Toolbar(desktop)
+{
+ auto prefs = Inkscape::Preferences::get();
+
+ /* New gradient linear or radial */
+ {
+ add_label(_("New:"));
+
+ Gtk::RadioToolButton::Group new_type_group;
+
+ auto linear_button = Gtk::manage(new Gtk::RadioToolButton(new_type_group, _("linear")));
+ linear_button->set_tooltip_text(_("Create linear gradient"));
+ linear_button->set_icon_name(INKSCAPE_ICON("paint-gradient-linear"));
+ _new_type_buttons.push_back(linear_button);
+
+ auto radial_button = Gtk::manage(new Gtk::RadioToolButton(new_type_group, _("radial")));
+ radial_button->set_tooltip_text(_("Create radial (elliptic or circular) gradient"));
+ radial_button->set_icon_name(INKSCAPE_ICON("paint-gradient-radial"));
+ _new_type_buttons.push_back(radial_button);
+
+ gint mode = prefs->getInt("/tools/gradient/newgradient", SP_GRADIENT_TYPE_LINEAR);
+ _new_type_buttons[ mode == SP_GRADIENT_TYPE_LINEAR ? 0 : 1 ]->set_active(); // linear == 1, radial == 2
+
+ int btn_index = 0;
+ for (auto btn : _new_type_buttons)
+ {
+ btn->set_sensitive(true);
+ btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &GradientToolbar::new_type_changed), btn_index++));
+ add(*btn);
+ }
+ }
+
+ /* New gradient on fill or stroke*/
+ {
+ Gtk::RadioToolButton::Group new_fillstroke_group;
+
+ auto fill_btn = Gtk::manage(new Gtk::RadioToolButton(new_fillstroke_group, _("fill")));
+ fill_btn->set_tooltip_text(_("Create gradient in the fill"));
+ fill_btn->set_icon_name(INKSCAPE_ICON("object-fill"));
+ _new_fillstroke_buttons.push_back(fill_btn);
+
+ auto stroke_btn = Gtk::manage(new Gtk::RadioToolButton(new_fillstroke_group, _("stroke")));
+ stroke_btn->set_tooltip_text(_("Create gradient in the stroke"));
+ stroke_btn->set_icon_name(INKSCAPE_ICON("object-stroke"));
+ _new_fillstroke_buttons.push_back(stroke_btn);
+
+ auto fsmode = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE;
+ _new_fillstroke_buttons[ fsmode == Inkscape::FOR_FILL ? 0 : 1 ]->set_active();
+
+ auto btn_index = 0;
+ for (auto btn : _new_fillstroke_buttons)
+ {
+ btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &GradientToolbar::new_fillstroke_changed), btn_index++));
+ btn->set_sensitive();
+ add(*btn);
+ }
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Gradient Select list*/
+ {
+ UI::Widget::ComboToolItemColumns columns;
+
+ auto store = Gtk::ListStore::create(columns);
+
+ Gtk::TreeModel::Row row;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("No gradient");
+ row[columns.col_tooltip ] = "";
+ row[columns.col_icon ] = "NotUsed";
+ row[columns.col_sensitive] = true;
+
+ _select_cb = UI::Widget::ComboToolItem::create(_("Select"), // Label
+ "", // Tooltip
+ "Not Used", // Icon
+ store ); // Tree store
+
+ _select_cb->use_icon( false );
+ _select_cb->use_pixbuf( true );
+ _select_cb->use_group_label( true );
+ _select_cb->set_active( 0 );
+ _select_cb->set_sensitive( false );
+
+ add(*_select_cb);
+ _select_cb->signal_changed().connect(sigc::mem_fun(*this, &GradientToolbar::gradient_changed));
+ }
+
+ // Gradients Linked toggle
+ {
+ _linked_item = add_toggle_button(_("Link gradients"),
+ _("Link gradients to change all related gradients"));
+ _linked_item->set_icon_name(INKSCAPE_ICON("object-unlocked"));
+ _linked_item->signal_toggled().connect(sigc::mem_fun(*this, &GradientToolbar::linked_changed));
+
+ bool linkedmode = prefs->getBool("/options/forkgradientvectors/value", true);
+ _linked_item->set_active(!linkedmode);
+ }
+
+ /* Reverse */
+ {
+ _stops_reverse_item = Gtk::manage(new Gtk::ToolButton(_("Reverse")));
+ _stops_reverse_item->set_tooltip_text(_("Reverse the direction of the gradient"));
+ _stops_reverse_item->set_icon_name(INKSCAPE_ICON("object-flip-horizontal"));
+ _stops_reverse_item->signal_clicked().connect(sigc::mem_fun(*this, &GradientToolbar::reverse));
+ add(*_stops_reverse_item);
+ _stops_reverse_item->set_sensitive(false);
+ }
+
+ // Gradient Spread type (how a gradient is drawn outside its nominal area)
+ {
+ UI::Widget::ComboToolItemColumns columns;
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+
+ std::vector<gchar*> spread_dropdown_items_list = {
+ const_cast<gchar *>(C_("Gradient repeat type", "None")),
+ _("Reflected"),
+ _("Direct")
+ };
+
+ for (auto item: spread_dropdown_items_list) {
+ Gtk::TreeModel::Row row = *(store->append());
+ row[columns.col_label ] = item;
+ row[columns.col_sensitive] = true;
+ }
+
+ _spread_cb = Gtk::manage(UI::Widget::ComboToolItem::create(_("Repeat"),
+ // TRANSLATORS: for info, see http://www.w3.org/TR/2000/CR-SVG-20000802/pservers.html#LinearGradientSpreadMethodAttribute
+ _("Whether to fill with flat color beyond the ends of the gradient vector "
+ "(spreadMethod=\"pad\"), or repeat the gradient in the same direction "
+ "(spreadMethod=\"repeat\"), or repeat the gradient in alternating opposite "
+ "directions (spreadMethod=\"reflect\")"),
+ "Not Used", store));
+ _spread_cb->use_group_label(true);
+
+ _spread_cb->set_active(0);
+ _spread_cb->set_sensitive(false);
+
+ _spread_cb->signal_changed().connect(sigc::mem_fun(*this, &GradientToolbar::spread_changed));
+ add(*_spread_cb);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Gradient Stop list */
+ {
+ UI::Widget::ComboToolItemColumns columns;
+
+ auto store = Gtk::ListStore::create(columns);
+
+ Gtk::TreeModel::Row row;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("No stops");
+ row[columns.col_tooltip ] = "";
+ row[columns.col_icon ] = "NotUsed";
+ row[columns.col_sensitive] = true;
+
+ _stop_cb =
+ UI::Widget::ComboToolItem::create(_("Stops" ), // Label
+ "", // Tooltip
+ "Not Used", // Icon
+ store ); // Tree store
+
+ _stop_cb->use_icon( false );
+ _stop_cb->use_pixbuf( true );
+ _stop_cb->use_group_label( true );
+ _stop_cb->set_active( 0 );
+ _stop_cb->set_sensitive( false );
+
+ add(*_stop_cb);
+ _stop_cb->signal_changed().connect(sigc::mem_fun(*this, &GradientToolbar::stop_changed));
+ }
+
+ /* Offset */
+ {
+ auto offset_val = prefs->getDouble("/tools/gradient/stopoffset", 0);
+ _offset_adj = Gtk::Adjustment::create(offset_val, 0.0, 1.0, 0.01, 0.1);
+ _offset_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("gradient-stopoffset", C_("Gradient", "Offset:"), _offset_adj, 0.01, 2));
+ _offset_item->set_tooltip_text(_("Offset of selected stop"));
+ _offset_item->set_focus_widget(desktop->canvas);
+ _offset_adj->signal_value_changed().connect(sigc::mem_fun(*this, &GradientToolbar::stop_offset_adjustment_changed));
+ add(*_offset_item);
+ _offset_item->set_sensitive(false);
+ }
+
+ /* Add stop */
+ {
+ _stops_add_item = Gtk::manage(new Gtk::ToolButton(_("Insert new stop")));
+ _stops_add_item->set_tooltip_text(_("Insert new stop"));
+ _stops_add_item->set_icon_name(INKSCAPE_ICON("node-add"));
+ _stops_add_item->signal_clicked().connect(sigc::mem_fun(*this, &GradientToolbar::add_stop));
+ add(*_stops_add_item);
+ _stops_add_item->set_sensitive(false);
+ }
+
+ /* Delete stop */
+ {
+ _stops_delete_item = Gtk::manage(new Gtk::ToolButton(_("Delete stop")));
+ _stops_delete_item->set_tooltip_text(_("Delete stop"));
+ _stops_delete_item->set_icon_name(INKSCAPE_ICON("node-delete"));
+ _stops_delete_item->signal_clicked().connect(sigc::mem_fun(*this, &GradientToolbar::remove_stop));
+ add(*_stops_delete_item);
+ _stops_delete_item->set_sensitive(false);
+ }
+
+ desktop->connectEventContextChanged(sigc::mem_fun(*this, &GradientToolbar::check_ec));
+
+ show_all();
+}
+
+/**
+ * Gradient auxiliary toolbar construction and setup.
+ *
+ */
+GtkWidget *
+GradientToolbar::create(SPDesktop * desktop)
+{
+ auto toolbar = new GradientToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+void
+GradientToolbar::new_type_changed(int mode)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/tools/gradient/newgradient",
+ mode == 0 ? SP_GRADIENT_TYPE_LINEAR : SP_GRADIENT_TYPE_RADIAL);
+}
+
+void
+GradientToolbar::new_fillstroke_changed(int mode)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ Inkscape::PaintTarget fsmode = (mode == 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE;
+ prefs->setInt("/tools/gradient/newfillorstroke", (fsmode == Inkscape::FOR_FILL) ? 1 : 0);
+}
+
+/*
+ * User selected a gradient from the combobox
+ */
+void
+GradientToolbar::gradient_changed(int active)
+{
+ if (blocked) {
+ return;
+ }
+
+ if (active < 0) {
+ return;
+ }
+
+ blocked = true;
+
+ SPGradient *gr = get_selected_gradient();
+
+ if (gr) {
+ gr = sp_gradient_ensure_vector_normalized(gr);
+
+ Inkscape::Selection *selection = _desktop->getSelection();
+ ToolBase *ev = _desktop->getEventContext();
+
+ gr_apply_gradient(selection, ev ? ev->get_drag() : nullptr, gr);
+
+ DocumentUndo::done(_desktop->getDocument(), _("Assign gradient to object"), INKSCAPE_ICON("color-gradient"));
+ }
+
+ blocked = false;
+}
+
+/**
+ * \brief Return gradient selected in menu
+ */
+SPGradient *
+GradientToolbar::get_selected_gradient()
+{
+ int active = _select_cb->get_active();
+
+ auto store = _select_cb->get_store();
+ auto row = store->children()[active];
+ UI::Widget::ComboToolItemColumns columns;
+
+ void* pointer = row[columns.col_data];
+ SPGradient *gr = static_cast<SPGradient *>(pointer);
+
+ return gr;
+}
+
+/**
+ * \brief User selected a spread method from the combobox
+ */
+void
+GradientToolbar::spread_changed(int active)
+{
+ if (blocked) {
+ return;
+ }
+
+ blocked = true;
+
+ Inkscape::Selection *selection = _desktop->getSelection();
+ SPGradient *gradient = nullptr;
+ gr_get_dt_selected_gradient(selection, gradient);
+
+ if (gradient) {
+ SPGradientSpread spread = (SPGradientSpread) active;
+ gradient->setSpread(spread);
+ gradient->updateRepr();
+
+ DocumentUndo::done(_desktop->getDocument(), _("Set gradient repeat"), INKSCAPE_ICON("color-gradient"));
+ }
+
+ blocked = false;
+}
+
+/**
+ * \brief User selected a stop from the combobox
+ */
+void
+GradientToolbar::stop_changed(int active)
+{
+ if (blocked) {
+ return;
+ }
+
+ blocked = true;
+
+ ToolBase *ev = _desktop->getEventContext();
+ SPGradient *gr = get_selected_gradient();
+
+ select_dragger_by_stop(gr, ev);
+
+ blocked = false;
+}
+
+void
+GradientToolbar::select_dragger_by_stop(SPGradient *gradient,
+ ToolBase *ev)
+{
+ if (!blocked) {
+ std::cerr << "select_dragger_by_stop: should be blocked!" << std::endl;
+ }
+
+ if (!ev || !gradient) {
+ return;
+ }
+
+ GrDrag *drag = ev->get_drag();
+ if (!drag) {
+ return;
+ }
+
+ SPStop *stop = get_selected_stop();
+
+ drag->selectByStop(stop, false, true);
+
+ stop_set_offset();
+}
+
+/**
+ * \brief Get stop selected by menu
+ */
+SPStop *
+GradientToolbar::get_selected_stop()
+{
+ int active = _stop_cb->get_active();
+
+ auto store = _stop_cb->get_store();
+ auto row = store->children()[active];
+ UI::Widget::ComboToolItemColumns columns;
+ void* pointer = row[columns.col_data];
+ SPStop *stop = static_cast<SPStop *>(pointer);
+
+ return stop;
+}
+
+/**
+ * Change desktop dragger selection to this stop
+ *
+ * Set the offset widget value (based on which stop is selected)
+ */
+void
+GradientToolbar::stop_set_offset()
+{
+ if (!blocked) {
+ std::cerr << "gr_stop_set_offset: should be blocked!" << std::endl;
+ }
+
+ SPStop *stop = get_selected_stop();
+ if (!stop) {
+ // std::cerr << "gr_stop_set_offset: no stop!" << std::endl;
+ return;
+ }
+
+ if (!_offset_item) {
+ return;
+ }
+ bool isEndStop = false;
+
+ SPStop *prev = nullptr;
+ prev = stop->getPrevStop();
+ if (prev != nullptr ) {
+ _offset_adj->set_lower(prev->offset);
+ } else {
+ isEndStop = true;
+ _offset_adj->set_lower(0);
+ }
+
+ SPStop *next = nullptr;
+ next = stop->getNextStop();
+ if (next != nullptr ) {
+ _offset_adj->set_upper(next->offset);
+ } else {
+ isEndStop = true;
+ _offset_adj->set_upper(1.0);
+ }
+
+ _offset_adj->set_value(stop->offset);
+ _offset_item->set_sensitive( !isEndStop );
+}
+
+/**
+ * \brief User changed the offset
+ */
+void
+GradientToolbar::stop_offset_adjustment_changed()
+{
+ if (blocked) {
+ return;
+ }
+
+ blocked = true;
+
+ SPStop *stop = get_selected_stop();
+ if (stop) {
+ stop->offset = _offset_adj->get_value();
+ stop->getRepr()->setAttributeCssDouble("offset", stop->offset);
+
+ DocumentUndo::maybeDone(stop->document, "gradient:stop:offset", _("Change gradient stop offset"), INKSCAPE_ICON("color-gradient"));
+ }
+
+ blocked = false;
+}
+
+/**
+ * \brief Add stop to gradient
+ */
+void
+GradientToolbar::add_stop()
+{
+ if (!_desktop) {
+ return;
+ }
+
+ auto selection = _desktop->getSelection();
+ if (!selection) {
+ return;
+ }
+
+ auto ev = _desktop->getEventContext();
+ if (auto rc = SP_GRADIENT_CONTEXT(ev)) {
+ rc->add_stops_between_selected_stops();
+ }
+}
+
+/**
+ * \brief Remove stop from vector
+ */
+void
+GradientToolbar::remove_stop()
+{
+ if (!_desktop) {
+ return;
+ }
+
+ auto selection = _desktop->getSelection(); // take from desktop, not from args
+ if (!selection) {
+ return;
+ }
+
+ auto ev = _desktop->getEventContext();
+ GrDrag *drag = nullptr;
+ if (ev) {
+ drag = ev->get_drag();
+ }
+
+ if (drag) {
+ drag->deleteSelected();
+ }
+}
+
+/**
+ * \brief Reverse vector
+ */
+void
+GradientToolbar::reverse()
+{
+ sp_gradient_reverse_selected_gradients(_desktop);
+}
+
+/**
+ * \brief Lock or unlock links
+ */
+void
+GradientToolbar::linked_changed()
+{
+ bool active = _linked_item->get_active();
+ if ( active ) {
+ _linked_item->set_icon_name(INKSCAPE_ICON("object-locked"));
+ } else {
+ _linked_item->set_icon_name(INKSCAPE_ICON("object-unlocked"));
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/options/forkgradientvectors/value", !active);
+}
+
+// lp:1327267
+/**
+ * Checks the current tool and connects gradient aux toolbox signals if it happens to be the gradient tool.
+ * Called every time the current tool changes by signal emission.
+ */
+void
+GradientToolbar::check_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec)
+{
+ if (SP_IS_GRADIENT_CONTEXT(ec)) {
+ Inkscape::Selection *selection = desktop->getSelection();
+ SPDocument *document = desktop->getDocument();
+
+ // connect to selection modified and changed signals
+ _connection_changed = selection->connectChanged(sigc::mem_fun(*this, &GradientToolbar::selection_changed));
+ _connection_modified = selection->connectModified(sigc::mem_fun(*this, &GradientToolbar::selection_modified));
+ _connection_subselection_changed = desktop->connect_gradient_stop_selected([=](void* sender, SPStop* stop){
+ drag_selection_changed(nullptr);
+ });
+
+ // Is this necessary? Couldn't hurt.
+ selection_changed(selection);
+
+ // connect to release and modified signals of the defs (i.e. when someone changes gradient)
+ _connection_defs_release = document->getDefs()->connectRelease(sigc::mem_fun(*this, &GradientToolbar::defs_release));
+ _connection_defs_modified = document->getDefs()->connectModified(sigc::mem_fun(*this, &GradientToolbar::defs_modified));
+ } else {
+ if (_connection_changed)
+ _connection_changed.disconnect();
+ if (_connection_modified)
+ _connection_modified.disconnect();
+ if (_connection_subselection_changed)
+ _connection_subselection_changed.disconnect();
+ if (_connection_defs_release)
+ _connection_defs_release.disconnect();
+ if (_connection_defs_modified)
+ _connection_defs_modified.disconnect();
+ }
+}
+
+/**
+ * Core function, setup all the widgets whenever something changes on the desktop
+ */
+void
+GradientToolbar::selection_changed(Inkscape::Selection * /*selection*/)
+{
+ if (blocked)
+ return;
+
+ blocked = true;
+
+ if (!_desktop) {
+ return;
+ }
+
+ Inkscape::Selection *selection = _desktop->getSelection(); // take from desktop, not from args
+ if (selection) {
+
+ ToolBase *ev = _desktop->getEventContext();
+ GrDrag *drag = nullptr;
+ if (ev) {
+ drag = ev->get_drag();
+ }
+
+ SPGradient *gr_selected = nullptr;
+ SPGradientSpread spr_selected = SP_GRADIENT_SPREAD_UNDEFINED;
+ bool gr_multi = false;
+ bool spr_multi = false;
+
+ gr_read_selection(selection, drag, gr_selected, gr_multi, spr_selected, spr_multi);
+
+ // Gradient selection menu
+ auto store = _select_cb->get_store();
+ int gradient = gr_vector_list (store, _desktop, selection->isEmpty(), gr_selected, gr_multi);
+
+ if (gradient < 0) {
+ // No selection or no gradients
+ _select_cb->set_active( 0 );
+ _select_cb->set_sensitive (false);
+ } else {
+ // Single gradient or multiple gradients
+ _select_cb->set_active( gradient );
+ _select_cb->set_sensitive (true);
+ }
+
+ // Spread menu
+ _spread_cb->set_sensitive( gr_selected && !gr_multi );
+ _spread_cb->set_active( gr_selected ? (int)spr_selected : 0 );
+
+ _stops_add_item->set_sensitive((gr_selected && !gr_multi && drag && !drag->selected.empty()));
+ _stops_delete_item->set_sensitive((gr_selected && !gr_multi && drag && !drag->selected.empty()));
+ _stops_reverse_item->set_sensitive((gr_selected!= nullptr));
+
+ _stop_cb->set_sensitive( gr_selected && !gr_multi);
+
+ update_stop_list (gr_selected, nullptr, gr_multi);
+ select_stop_by_draggers(gr_selected, ev);
+ }
+
+ blocked = false;
+}
+
+/**
+ * \brief Construct stop list
+ */
+int
+GradientToolbar::update_stop_list( SPGradient *gradient, SPStop *new_stop, bool gr_multi)
+{
+ if (!blocked) {
+ std::cerr << "update_stop_list should be blocked!" << std::endl;
+ }
+
+ int selected = -1;
+
+ auto store = _stop_cb->get_store();
+
+ if (!store) {
+ return selected;
+ }
+
+ store->clear();
+
+ UI::Widget::ComboToolItemColumns columns;
+ Gtk::TreeModel::Row row;
+
+ if (!SP_IS_GRADIENT(gradient)) {
+ // No valid gradient
+
+ row = *(store->append());
+ row[columns.col_label ] = _("No gradient");
+ row[columns.col_tooltip ] = "";
+ row[columns.col_icon ] = "NotUsed";
+ row[columns.col_data ] = nullptr;
+ row[columns.col_sensitive] = true;
+
+ } else if (!gradient->hasStops()) {
+ // Has gradient but it has no stops
+
+ row = *(store->append());
+ row[columns.col_label ] = _("No stops in gradient");
+ row[columns.col_tooltip ] = "";
+ row[columns.col_icon ] = "NotUsed";
+ row[columns.col_data ] = nullptr;
+ row[columns.col_sensitive] = true;
+
+ } else {
+ // Gradient has stops
+
+ // Get list of stops
+ for (auto& ochild: gradient->children) {
+ if (SP_IS_STOP(&ochild)) {
+
+ SPStop *stop = SP_STOP(&ochild);
+ Glib::RefPtr<Gdk::Pixbuf> pixbuf = sp_gradstop_to_pixbuf_ref (stop, 32, 16);
+
+ Inkscape::XML::Node *repr = reinterpret_cast<SPItem *>(&ochild)->getRepr();
+ Glib::ustring label = gr_ellipsize_text(repr->attribute("id"), 25);
+
+ row = *(store->append());
+ row[columns.col_label ] = label;
+ row[columns.col_tooltip ] = "";
+ row[columns.col_icon ] = "NotUsed";
+ row[columns.col_pixbuf ] = pixbuf;
+ row[columns.col_data ] = stop;
+ row[columns.col_sensitive] = true;
+ }
+ }
+ }
+
+ if (new_stop != nullptr) {
+ selected = select_stop_in_list (gradient, new_stop);
+ }
+
+ return selected;
+}
+
+/**
+ * \brief Find position of new_stop in menu.
+ */
+int
+GradientToolbar::select_stop_in_list(SPGradient *gradient, SPStop *new_stop)
+{
+ int i = 0;
+ for (auto& ochild: gradient->children) {
+ if (SP_IS_STOP(&ochild)) {
+ if (&ochild == new_stop) {
+ return i;
+ }
+ i++;
+ }
+ }
+ return -1;
+}
+
+/**
+ * \brief Set stop in menu to match stops selected by draggers
+ */
+void
+GradientToolbar::select_stop_by_draggers(SPGradient *gradient, ToolBase *ev)
+{
+ if (!blocked) {
+ std::cerr << "select_stop_by_draggers should be blocked!" << std::endl;
+ }
+
+ if (!ev || !gradient)
+ return;
+
+ SPGradient *vector = gradient->getVector();
+ if (!vector)
+ return;
+
+ GrDrag *drag = ev->get_drag();
+
+ if (!drag || drag->selected.empty()) {
+ _stop_cb->set_active(0);
+ stop_set_offset();
+ return;
+ }
+
+ gint n = 0;
+ SPStop *stop = nullptr;
+ int selected = -1;
+
+ // For all selected draggers
+ for(auto dragger : drag->selected) {
+
+ // For all draggables of dragger
+ for(auto draggable : dragger->draggables) {
+
+ if (draggable->point_type != POINT_RG_FOCUS) {
+ n++;
+ if (n > 1) break;
+ }
+
+ stop = vector->getFirstStop();
+
+ switch (draggable->point_type) {
+ case POINT_LG_MID:
+ case POINT_RG_MID1:
+ case POINT_RG_MID2:
+ stop = sp_get_stop_i(vector, draggable->point_i);
+ break;
+ case POINT_LG_END:
+ case POINT_RG_R1:
+ case POINT_RG_R2:
+ stop = sp_last_stop(vector);
+ break;
+ default:
+ break;
+ }
+ }
+ if (n > 1) break;
+ }
+
+ if (n > 1) {
+ // Multiple stops selected
+ if (_offset_item) {
+ _offset_item->set_sensitive(false);
+ }
+
+ // Stop list always updated first... reinsert "Multiple stops" as first entry.
+ UI::Widget::ComboToolItemColumns columns;
+ auto store = _stop_cb->get_store();
+
+ auto row = *(store->prepend());
+ row[columns.col_label ] = _("Multiple stops");
+ row[columns.col_tooltip ] = "";
+ row[columns.col_icon ] = "NotUsed";
+ row[columns.col_sensitive] = true;
+ selected = 0;
+
+ } else {
+ selected = select_stop_in_list(gradient, stop);
+ }
+
+ if (selected < 0) {
+ _stop_cb->set_active (0);
+ _stop_cb->set_sensitive (false);
+ } else {
+ _stop_cb->set_active (selected);
+ _stop_cb->set_sensitive (true);
+ stop_set_offset();
+ }
+}
+
+void
+GradientToolbar::selection_modified(Inkscape::Selection *selection, guint /*flags*/)
+{
+ selection_changed(selection);
+}
+
+void
+GradientToolbar::drag_selection_changed(gpointer /*dragger*/)
+{
+ selection_changed(nullptr);
+}
+
+void
+GradientToolbar::defs_release(SPObject * /*defs*/)
+{
+ selection_changed(nullptr);
+}
+
+void
+GradientToolbar::defs_modified(SPObject * /*defs*/, guint /*flags*/)
+{
+ selection_changed(nullptr);
+}
+
+}
+}
+}
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/ui/toolbar/gradient-toolbar.h b/src/ui/toolbar/gradient-toolbar.h
new file mode 100644
index 0000000..96beb0f
--- /dev/null
+++ b/src/ui/toolbar/gradient-toolbar.h
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_GRADIENT_TOOLBAR_H
+#define SEEN_GRADIENT_TOOLBAR_H
+
+/*
+ * Gradient aux toolbar
+ *
+ * Authors:
+ * bulia byak <bulia@dr.com>
+ *
+ * Copyright (C) 2005 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+#include <gtkmm/adjustment.h>
+
+class SPDesktop;
+class SPGradient;
+class SPStop;
+class SPObject;
+
+namespace Gtk {
+class ComboBoxText;
+class RadioToolButton;
+class ToolButton;
+class ToolItem;
+}
+
+namespace Inkscape {
+class Selection;
+
+namespace UI {
+namespace Tools {
+class ToolBase;
+}
+
+namespace Widget {
+class ComboToolItem;
+class SpinButtonToolItem;
+}
+
+namespace Toolbar {
+class GradientToolbar : public Toolbar {
+private:
+ std::vector<Gtk::RadioToolButton *> _new_type_buttons;
+ std::vector<Gtk::RadioToolButton *> _new_fillstroke_buttons;
+ UI::Widget::ComboToolItem *_select_cb;
+ UI::Widget::ComboToolItem *_spread_cb;
+ UI::Widget::ComboToolItem *_stop_cb;
+
+ Gtk::ToolButton *_stops_add_item;
+ Gtk::ToolButton *_stops_delete_item;
+ Gtk::ToolButton *_stops_reverse_item;
+ Gtk::ToggleToolButton *_linked_item;
+
+ UI::Widget::SpinButtonToolItem *_offset_item;
+
+ Glib::RefPtr<Gtk::Adjustment> _offset_adj;
+
+ void new_type_changed(int mode);
+ void new_fillstroke_changed(int mode);
+ void gradient_changed(int active);
+ SPGradient * get_selected_gradient();
+ void spread_changed(int active);
+ void stop_changed(int active);
+ void select_dragger_by_stop(SPGradient *gradient,
+ UI::Tools::ToolBase *ev);
+ SPStop * get_selected_stop();
+ void stop_set_offset();
+ void stop_offset_adjustment_changed();
+ void add_stop();
+ void remove_stop();
+ void reverse();
+ void linked_changed();
+ void check_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec);
+ void selection_changed(Inkscape::Selection *selection);
+ int update_stop_list( SPGradient *gradient, SPStop *new_stop, bool gr_multi);
+ int select_stop_in_list(SPGradient *gradient, SPStop *new_stop);
+ void select_stop_by_draggers(SPGradient *gradient, UI::Tools::ToolBase *ev);
+ void selection_modified(Inkscape::Selection *selection, guint flags);
+ void drag_selection_changed(gpointer dragger);
+ void defs_release(SPObject * defs);
+ void defs_modified(SPObject *defs, guint flags);
+
+ sigc::connection _connection_changed;
+ sigc::connection _connection_modified;
+ sigc::connection _connection_subselection_changed;
+ sigc::connection _connection_defs_release;
+ sigc::connection _connection_defs_modified;
+
+protected:
+ GradientToolbar(SPDesktop *desktop);
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+};
+
+}
+}
+}
+
+#endif /* !SEEN_GRADIENT_TOOLBAR_H */
diff --git a/src/ui/toolbar/lpe-toolbar.cpp b/src/ui/toolbar/lpe-toolbar.cpp
new file mode 100644
index 0000000..df45e22
--- /dev/null
+++ b/src/ui/toolbar/lpe-toolbar.cpp
@@ -0,0 +1,417 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * LPE aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "lpe-toolbar.h"
+
+#include <gtkmm/radiotoolbutton.h>
+#include <gtkmm/separatortoolitem.h>
+
+#include "live_effects/lpe-line_segment.h"
+
+#include "ui/dialog/dialog-container.h"
+#include "ui/icon-names.h"
+#include "ui/tools/lpe-tool.h"
+#include "ui/widget/combo-tool-item.h"
+#include "ui/widget/unit-tracker.h"
+
+using Inkscape::UI::Widget::UnitTracker;
+using Inkscape::Util::Unit;
+using Inkscape::Util::Quantity;
+using Inkscape::DocumentUndo;
+using Inkscape::UI::Tools::ToolBase;
+using Inkscape::UI::Tools::LpeTool;
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+LPEToolbar::LPEToolbar(SPDesktop *desktop)
+ : Toolbar(desktop),
+ _tracker(new UnitTracker(Util::UNIT_TYPE_LINEAR)),
+ _freeze(false),
+ _currentlpe(nullptr),
+ _currentlpeitem(nullptr)
+{
+ _tracker->setActiveUnit(_desktop->getNamedView()->display_units);
+
+ auto unit = _tracker->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setString("/tools/lpetool/unit", unit->abbr);
+
+ /* Automatically create a list of LPEs that get added to the toolbar **/
+ {
+ Gtk::RadioToolButton::Group mode_group;
+
+ // The first toggle button represents the state that no subtool is active.
+ auto inactive_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("All inactive")));
+ inactive_mode_btn->set_tooltip_text(_("No geometric tool is active"));
+ inactive_mode_btn->set_icon_name(INKSCAPE_ICON("draw-geometry-inactive"));
+ _mode_buttons.push_back(inactive_mode_btn);
+
+ Inkscape::LivePathEffect::EffectType type;
+ for (int i = 1; i < num_subtools; ++i) { // i == 0 ia INVALIDE_LPE.
+
+ type = lpesubtools[i].type;
+
+ auto btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, Inkscape::LivePathEffect::LPETypeConverter.get_label(type)));
+ btn->set_tooltip_text(_(Inkscape::LivePathEffect::LPETypeConverter.get_label(type).c_str()));
+ btn->set_icon_name(lpesubtools[i].icon_name);
+ _mode_buttons.push_back(btn);
+ }
+
+ int btn_idx = 0;
+ for (auto btn : _mode_buttons) {
+ btn->set_sensitive(true);
+ add(*btn);
+ btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LPEToolbar::mode_changed), btn_idx++));
+ }
+
+ int mode = prefs->getInt("/tools/lpetool/mode", 0);
+ _mode_buttons[mode]->set_active();
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Show limiting bounding box */
+ {
+ _show_bbox_item = add_toggle_button(_("Show limiting bounding box"),
+ _("Show bounding box (used to cut infinite lines)"));
+ _show_bbox_item->set_icon_name(INKSCAPE_ICON("show-bounding-box"));
+ _show_bbox_item->signal_toggled().connect(sigc::mem_fun(*this, &LPEToolbar::toggle_show_bbox));
+ _show_bbox_item->set_active(prefs->getBool( "/tools/lpetool/show_bbox", true ));
+ }
+
+ /* Set limiting bounding box to bbox of current selection */
+ {
+ // TODO: Shouldn't this just be a button (not toggle button)?
+ _bbox_from_selection_item = add_toggle_button(_("Get limiting bounding box from selection"),
+ _("Set limiting bounding box (used to cut infinite lines) to the bounding box of current selection"));
+ _bbox_from_selection_item->set_icon_name(INKSCAPE_ICON("draw-geometry-set-bounding-box"));
+ _bbox_from_selection_item->signal_toggled().connect(sigc::mem_fun(*this, &LPEToolbar::toggle_set_bbox));
+ _bbox_from_selection_item->set_active(false);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Combo box to choose line segment type */
+ {
+ UI::Widget::ComboToolItemColumns columns;
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+
+ std::vector<gchar*> line_segment_dropdown_items_list = {
+ _("Closed"),
+ _("Open start"),
+ _("Open end"),
+ _("Open both")
+ };
+
+ for (auto item: line_segment_dropdown_items_list) {
+ Gtk::TreeModel::Row row = *(store->append());
+ row[columns.col_label ] = item;
+ row[columns.col_sensitive] = true;
+ }
+
+ _line_segment_combo = Gtk::manage(UI::Widget::ComboToolItem::create(_("Line Type"), _("Choose a line segment type"), "Not Used", store));
+ _line_segment_combo->use_group_label(false);
+
+ _line_segment_combo->set_active(0);
+
+ _line_segment_combo->signal_changed().connect(sigc::mem_fun(*this, &LPEToolbar::change_line_segment_type));
+ add(*_line_segment_combo);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Display measuring info for selected items */
+ {
+ _measuring_item = add_toggle_button(_("Display measuring info"),
+ _("Display measuring info for selected items"));
+ _measuring_item->set_icon_name(INKSCAPE_ICON("draw-geometry-show-measuring-info"));
+ _measuring_item->signal_toggled().connect(sigc::mem_fun(*this, &LPEToolbar::toggle_show_measuring_info));
+ _measuring_item->set_active( prefs->getBool( "/tools/lpetool/show_measuring_info", true ) );
+ }
+
+ // Add the units menu
+ {
+ _units_item = _tracker->create_tool_item(_("Units"), ("") );
+ add(*_units_item);
+ _units_item->signal_changed_after().connect(sigc::mem_fun(*this, &LPEToolbar::unit_changed));
+ _units_item->set_sensitive( prefs->getBool("/tools/lpetool/show_measuring_info", true));
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Open LPE dialog (to adapt parameters numerically) */
+ {
+ // TODO: Shouldn't this be a regular Gtk::ToolButton (not toggle)?
+ _open_lpe_dialog_item = add_toggle_button(_("Open LPE dialog"),
+ _("Open LPE dialog (to adapt parameters numerically)"));
+ _open_lpe_dialog_item->set_icon_name(INKSCAPE_ICON("dialog-geometry"));
+ _open_lpe_dialog_item->signal_toggled().connect(sigc::mem_fun(*this, &LPEToolbar::open_lpe_dialog));
+ _open_lpe_dialog_item->set_active(false);
+ }
+
+ desktop->connectEventContextChanged(sigc::mem_fun(*this, &LPEToolbar::watch_ec));
+
+ show_all();
+}
+
+void
+LPEToolbar::set_mode(int mode)
+{
+ _mode_buttons[mode]->set_active();
+}
+
+GtkWidget *
+LPEToolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = new LPEToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+// this is called when the mode is changed via the toolbar (i.e., one of the subtool buttons is pressed)
+void
+LPEToolbar::mode_changed(int mode)
+{
+ using namespace Inkscape::LivePathEffect;
+
+ ToolBase *ec = _desktop->event_context;
+ if (!SP_IS_LPETOOL_CONTEXT(ec)) {
+ return;
+ }
+
+ // only take action if run by the attr_changed listener
+ if (!_freeze) {
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ EffectType type = lpesubtools[mode].type;
+
+ LpeTool *lc = SP_LPETOOL_CONTEXT(_desktop->event_context);
+ bool success = lpetool_try_construction(lc, type);
+ if (success) {
+ // since the construction was already performed, we set the state back to inactive
+ _mode_buttons[0]->set_active();
+ mode = 0;
+ } else {
+ // switch to the chosen subtool
+ SP_LPETOOL_CONTEXT(_desktop->event_context)->mode = type;
+ }
+
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt( "/tools/lpetool/mode", mode );
+ }
+
+ _freeze = false;
+ }
+}
+
+void
+LPEToolbar::toggle_show_bbox() {
+ auto prefs = Inkscape::Preferences::get();
+
+ bool show = _show_bbox_item->get_active();
+ prefs->setBool("/tools/lpetool/show_bbox", show);
+
+ LpeTool *lc = dynamic_cast<LpeTool *>(_desktop->event_context);
+ if (lc) {
+ lpetool_context_reset_limiting_bbox(lc);
+ }
+}
+
+void
+LPEToolbar::toggle_set_bbox()
+{
+ auto selection = _desktop->selection;
+
+ auto bbox = selection->visualBounds();
+
+ if (bbox) {
+ Geom::Point A(bbox->min());
+ Geom::Point B(bbox->max());
+
+ A *= _desktop->doc2dt();
+ B *= _desktop->doc2dt();
+
+ // TODO: should we provide a way to store points in prefs?
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setDouble("/tools/lpetool/bbox_upperleftx", A[Geom::X]);
+ prefs->setDouble("/tools/lpetool/bbox_upperlefty", A[Geom::Y]);
+ prefs->setDouble("/tools/lpetool/bbox_lowerrightx", B[Geom::X]);
+ prefs->setDouble("/tools/lpetool/bbox_lowerrighty", B[Geom::Y]);
+
+ lpetool_context_reset_limiting_bbox(SP_LPETOOL_CONTEXT(_desktop->event_context));
+ }
+
+ _bbox_from_selection_item->set_active(false);
+}
+
+void
+LPEToolbar::change_line_segment_type(int mode)
+{
+ using namespace Inkscape::LivePathEffect;
+
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+ auto line_seg = dynamic_cast<LPELineSegment *>(_currentlpe);
+
+ if (_currentlpeitem && line_seg) {
+ line_seg->end_type.param_set_value(static_cast<Inkscape::LivePathEffect::EndType>(mode));
+ sp_lpe_item_update_patheffect(_currentlpeitem, true, true);
+ }
+
+ _freeze = false;
+}
+
+void
+LPEToolbar::toggle_show_measuring_info()
+{
+ LpeTool *lc = dynamic_cast<LpeTool *>(_desktop->event_context);
+ if (!lc) {
+ return;
+ }
+
+ bool show = _measuring_item->get_active();
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/lpetool/show_measuring_info", show);
+
+ lpetool_show_measuring_info(lc, show);
+
+ _units_item->set_sensitive( show );
+}
+
+void
+LPEToolbar::unit_changed(int /* NotUsed */)
+{
+ Unit const *unit = _tracker->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString("/tools/lpetool/unit", unit->abbr);
+
+ if (SP_IS_LPETOOL_CONTEXT(_desktop->event_context)) {
+ LpeTool *lc = SP_LPETOOL_CONTEXT(_desktop->event_context);
+ lpetool_delete_measuring_items(lc);
+ lpetool_create_measuring_items(lc);
+ }
+}
+
+void
+LPEToolbar::open_lpe_dialog()
+{
+ if (dynamic_cast<LpeTool *>(_desktop->event_context)) {
+ _desktop->getContainer()->new_dialog("LivePathEffect");
+ } else {
+ std::cerr << "LPEToolbar::open_lpe_dialog: LPEToolbar active but current tool is not LPE tool!" << std::endl;
+ }
+ _open_lpe_dialog_item->set_active(false);
+}
+
+void
+LPEToolbar::watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec)
+{
+ if (SP_IS_LPETOOL_CONTEXT(ec)) {
+ // Watch selection
+ c_selection_modified = desktop->getSelection()->connectModified(sigc::mem_fun(*this, &LPEToolbar::sel_modified));
+ c_selection_changed = desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &LPEToolbar::sel_changed));
+ sel_changed(desktop->getSelection());
+ } else {
+ if (c_selection_modified)
+ c_selection_modified.disconnect();
+ if (c_selection_changed)
+ c_selection_changed.disconnect();
+ }
+}
+
+void
+LPEToolbar::sel_modified(Inkscape::Selection *selection, guint /*flags*/)
+{
+ ToolBase *ec = selection->desktop()->event_context;
+ if (SP_IS_LPETOOL_CONTEXT(ec)) {
+ lpetool_update_measuring_items(SP_LPETOOL_CONTEXT(ec));
+ }
+}
+
+void
+LPEToolbar::sel_changed(Inkscape::Selection *selection)
+{
+ using namespace Inkscape::LivePathEffect;
+ ToolBase *ec = selection->desktop()->event_context;
+ if (!SP_IS_LPETOOL_CONTEXT(ec)) {
+ return;
+ }
+ LpeTool *lc = SP_LPETOOL_CONTEXT(ec);
+
+ lpetool_delete_measuring_items(lc);
+ lpetool_create_measuring_items(lc, selection);
+
+ // activate line segment combo box if a single item with LPELineSegment is selected
+ SPItem *item = selection->singleItem();
+ if (item && SP_IS_LPE_ITEM(item) && lpetool_item_has_construction(lc, item)) {
+
+ SPLPEItem *lpeitem = SP_LPE_ITEM(item);
+ Effect* lpe = lpeitem->getCurrentLPE();
+ if (lpe && lpe->effectType() == LINE_SEGMENT) {
+ LPELineSegment *lpels = static_cast<LPELineSegment*>(lpe);
+ _currentlpe = lpe;
+ _currentlpeitem = lpeitem;
+ _line_segment_combo->set_sensitive(true);
+ _line_segment_combo->set_active( lpels->end_type.get_value() );
+ } else {
+ _currentlpe = nullptr;
+ _currentlpeitem = nullptr;
+ _line_segment_combo->set_sensitive(false);
+ }
+
+ } else {
+ _currentlpe = nullptr;
+ _currentlpeitem = nullptr;
+ _line_segment_combo->set_sensitive(false);
+ }
+}
+
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
diff --git a/src/ui/toolbar/lpe-toolbar.h b/src/ui/toolbar/lpe-toolbar.h
new file mode 100644
index 0000000..903d9da
--- /dev/null
+++ b/src/ui/toolbar/lpe-toolbar.h
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_LPE_TOOLBAR_H
+#define SEEN_LPE_TOOLBAR_H
+
+/**
+ * @file
+ * LPE aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+class SPDesktop;
+class SPLPEItem;
+
+namespace Gtk {
+class RadioToolButton;
+}
+
+namespace Inkscape {
+class Selection;
+
+namespace LivePathEffect {
+class Effect;
+}
+
+namespace UI {
+namespace Tools {
+class ToolBase;
+}
+
+namespace Widget {
+class ComboToolItem;
+class UnitTracker;
+}
+
+namespace Toolbar {
+class LPEToolbar : public Toolbar {
+private:
+ std::unique_ptr<UI::Widget::UnitTracker> _tracker;
+ std::vector<Gtk::RadioToolButton *> _mode_buttons;
+ Gtk::ToggleToolButton *_show_bbox_item;
+ Gtk::ToggleToolButton *_bbox_from_selection_item;
+ Gtk::ToggleToolButton *_measuring_item;
+ Gtk::ToggleToolButton *_open_lpe_dialog_item;
+ UI::Widget::ComboToolItem *_line_segment_combo;
+ UI::Widget::ComboToolItem *_units_item;
+
+ bool _freeze;
+
+ LivePathEffect::Effect *_currentlpe;
+ SPLPEItem *_currentlpeitem;
+
+ sigc::connection c_selection_modified;
+ sigc::connection c_selection_changed;
+
+ void mode_changed(int mode);
+ void unit_changed(int not_used);
+ void sel_modified(Inkscape::Selection *selection, guint flags);
+ void sel_changed(Inkscape::Selection *selection);
+ void change_line_segment_type(int mode);
+ void watch_ec(SPDesktop* desktop, UI::Tools::ToolBase* ec);
+
+ void toggle_show_bbox();
+ void toggle_set_bbox();
+ void toggle_show_measuring_info();
+ void open_lpe_dialog();
+
+protected:
+ LPEToolbar(SPDesktop *desktop);
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+ void set_mode(int mode);
+};
+
+}
+}
+}
+
+#endif /* !SEEN_LPE_TOOLBAR_H */
diff --git a/src/ui/toolbar/marker-toolbar.cpp b/src/ui/toolbar/marker-toolbar.cpp
new file mode 100644
index 0000000..d60f2d6
--- /dev/null
+++ b/src/ui/toolbar/marker-toolbar.cpp
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Marker edit mode toolbar - onCanvas marker editing of marker orientation, position, scale
+ *//*
+ * Authors:
+ * see git history
+ * Rachana Podaralla <rpodaralla3@gatech.edu>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/i18n.h>
+#include "marker-toolbar.h"
+#include "document-undo.h"
+#include "preferences.h"
+#include "desktop.h"
+#include "ui/widget/canvas.h"
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+
+MarkerToolbar::MarkerToolbar(SPDesktop *desktop)
+ : Toolbar(desktop)
+{
+}
+
+GtkWidget* MarkerToolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = Gtk::manage(new MarkerToolbar(desktop));
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+}}} \ No newline at end of file
diff --git a/src/ui/toolbar/marker-toolbar.h b/src/ui/toolbar/marker-toolbar.h
new file mode 100644
index 0000000..f5f4d64
--- /dev/null
+++ b/src/ui/toolbar/marker-toolbar.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Marker edit mode toolbar - onCanvas marker editing of marker orientation, position, scale
+ *//*
+ * Authors:
+ * see git history
+ * Rachana Podaralla <rpodaralla3@gatech.edu>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_MARKER_TOOLBAR_H
+#define SEEN_MARKER_TOOLBAR_H
+
+#include "toolbar.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+
+class MarkerToolbar : public Toolbar {
+protected:
+ MarkerToolbar(SPDesktop *desktop);
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+};
+
+}}}
+#endif \ No newline at end of file
diff --git a/src/ui/toolbar/measure-toolbar.cpp b/src/ui/toolbar/measure-toolbar.cpp
new file mode 100644
index 0000000..92ca4c5
--- /dev/null
+++ b/src/ui/toolbar/measure-toolbar.cpp
@@ -0,0 +1,448 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Measure aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "measure-toolbar.h"
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/separatortoolitem.h>
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "message-stack.h"
+#include "object/sp-namedview.h"
+
+#include "ui/icon-names.h"
+#include "ui/tools/measure-tool.h"
+#include "ui/widget/canvas.h"
+#include "ui/widget/combo-tool-item.h"
+#include "ui/widget/label-tool-item.h"
+#include "ui/widget/spin-button-tool-item.h"
+#include "ui/widget/unit-tracker.h"
+
+using Inkscape::UI::Widget::UnitTracker;
+using Inkscape::Util::Unit;
+using Inkscape::DocumentUndo;
+using Inkscape::UI::Tools::MeasureTool;
+
+static MeasureTool *get_measure_tool(SPDesktop *desktop)
+{
+ if (desktop) {
+ return dynamic_cast<MeasureTool *>(desktop->event_context);
+ }
+ return nullptr;
+}
+
+
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+MeasureToolbar::MeasureToolbar(SPDesktop *desktop)
+ : Toolbar(desktop),
+ _tracker(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR))
+{
+ auto prefs = Inkscape::Preferences::get();
+ auto unit = desktop->getNamedView()->getDisplayUnit();
+ _tracker->setActiveUnitByAbbr(prefs->getString("/tools/measure/unit", unit->abbr).c_str());
+
+ /* Font Size */
+ {
+ auto font_size_val = prefs->getDouble("/tools/measure/fontsize", 10.0);
+ _font_size_adj = Gtk::Adjustment::create(font_size_val, 1.0, 36.0, 1.0, 4.0);
+ auto font_size_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("measure-fontsize", _("Font Size:"), _font_size_adj, 0, 2));
+ font_size_item->set_tooltip_text(_("The font size to be used in the measurement labels"));
+ font_size_item->set_focus_widget(desktop->canvas);
+ _font_size_adj->signal_value_changed().connect(sigc::mem_fun(*this, &MeasureToolbar::fontsize_value_changed));
+ add(*font_size_item);
+ }
+
+ /* Precision */
+ {
+ auto precision_val = prefs->getDouble("/tools/measure/precision", 2);
+ _precision_adj = Gtk::Adjustment::create(precision_val, 0, 10, 1, 0);
+ auto precision_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("measure-precision", _("Precision:"), _precision_adj, 0, 0));
+ precision_item->set_tooltip_text(_("Decimal precision of measure"));
+ precision_item->set_focus_widget(desktop->canvas);
+ _precision_adj->signal_value_changed().connect(sigc::mem_fun(*this, &MeasureToolbar::precision_value_changed));
+ add(*precision_item);
+ }
+
+ /* Scale */
+ {
+ auto scale_val = prefs->getDouble("/tools/measure/scale", 100.0);
+ _scale_adj = Gtk::Adjustment::create(scale_val, 0.0, 90000.0, 1.0, 4.0);
+ auto scale_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("measure-scale", _("Scale %:"), _scale_adj, 0, 3));
+ scale_item->set_tooltip_text(_("Scale the results"));
+ scale_item->set_focus_widget(desktop->canvas);
+ _scale_adj->signal_value_changed().connect(sigc::mem_fun(*this, &MeasureToolbar::scale_value_changed));
+ add(*scale_item);
+ }
+
+ /* units label */
+ {
+ auto unit_label = Gtk::manage(new UI::Widget::LabelToolItem(_("Units:")));
+ unit_label->set_tooltip_text(_("The units to be used for the measurements"));
+ unit_label->set_use_markup(true);
+ add(*unit_label);
+ }
+
+ /* units menu */
+ {
+ auto ti = _tracker->create_tool_item(_("Units"), _("The units to be used for the measurements") );
+ ti->signal_changed().connect(sigc::mem_fun(*this, &MeasureToolbar::unit_changed));
+ add(*ti);
+ }
+
+ add(*Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* measure only selected */
+ {
+ _only_selected_item = add_toggle_button(_("Measure only selected"),
+ _("Measure only selected"));
+ _only_selected_item->set_icon_name(INKSCAPE_ICON("snap-bounding-box-center"));
+ _only_selected_item->set_active(prefs->getBool("/tools/measure/only_selected", false));
+ _only_selected_item->signal_toggled().connect(sigc::mem_fun(*this, &MeasureToolbar::toggle_only_selected));
+ }
+
+ /* ignore_1st_and_last */
+ {
+ _ignore_1st_and_last_item = add_toggle_button(_("Ignore first and last"),
+ _("Ignore first and last"));
+ _ignore_1st_and_last_item->set_icon_name(INKSCAPE_ICON("draw-geometry-line-segment"));
+ _ignore_1st_and_last_item->set_active(prefs->getBool("/tools/measure/ignore_1st_and_last", true));
+ _ignore_1st_and_last_item->signal_toggled().connect(sigc::mem_fun(*this, &MeasureToolbar::toggle_ignore_1st_and_last));
+ }
+
+ /* measure in betweens */
+ {
+ _inbetween_item = add_toggle_button(_("Show measures between items"),
+ _("Show measures between items"));
+ _inbetween_item->set_icon_name(INKSCAPE_ICON("distribute-randomize"));
+ _inbetween_item->set_active(prefs->getBool("/tools/measure/show_in_between", true));
+ _inbetween_item->signal_toggled().connect(sigc::mem_fun(*this, &MeasureToolbar::toggle_show_in_between));
+ }
+
+ /* only visible */
+ {
+ _show_hidden_item = add_toggle_button(_("Show hidden intersections"),
+ _("Show hidden intersections"));
+ _show_hidden_item->set_icon_name(INKSCAPE_ICON("object-hidden"));
+ _show_hidden_item->set_active(prefs->getBool("/tools/measure/show_hidden", true));
+ _show_hidden_item->signal_toggled().connect(sigc::mem_fun(*this, &MeasureToolbar::toggle_show_hidden)) ;
+ }
+
+ /* measure only current layer */
+ {
+ _all_layers_item = add_toggle_button(_("Measure all layers"),
+ _("Measure all layers"));
+ _all_layers_item->set_icon_name(INKSCAPE_ICON("dialog-layers"));
+ _all_layers_item->set_active(prefs->getBool("/tools/measure/all_layers", true));
+ _all_layers_item->signal_toggled().connect(sigc::mem_fun(*this, &MeasureToolbar::toggle_all_layers));
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* toggle start end */
+ {
+ _reverse_item = Gtk::manage(new Gtk::ToolButton(_("Reverse measure")));
+ _reverse_item->set_tooltip_text(_("Reverse measure"));
+ _reverse_item->set_icon_name(INKSCAPE_ICON("draw-geometry-mirror"));
+ _reverse_item->signal_clicked().connect(sigc::mem_fun(*this, &MeasureToolbar::reverse_knots));
+ add(*_reverse_item);
+ }
+
+ /* phantom measure */
+ {
+ _to_phantom_item = Gtk::manage(new Gtk::ToolButton(_("Phantom measure")));
+ _to_phantom_item->set_tooltip_text(_("Phantom measure"));
+ _to_phantom_item->set_icon_name(INKSCAPE_ICON("selection-make-bitmap-copy"));
+ _to_phantom_item->signal_clicked().connect(sigc::mem_fun(*this, &MeasureToolbar::to_phantom));
+ add(*_to_phantom_item);
+ }
+
+ /* to guides */
+ {
+ _to_guides_item = Gtk::manage(new Gtk::ToolButton(_("To guides")));
+ _to_guides_item->set_tooltip_text(_("To guides"));
+ _to_guides_item->set_icon_name(INKSCAPE_ICON("guides"));
+ _to_guides_item->signal_clicked().connect(sigc::mem_fun(*this, &MeasureToolbar::to_guides));
+ add(*_to_guides_item);
+ }
+
+ /* to item */
+ {
+ _to_item_item = Gtk::manage(new Gtk::ToolButton(_("Convert to item")));
+ _to_item_item->set_tooltip_text(_("Convert to item"));
+ _to_item_item->set_icon_name(INKSCAPE_ICON("path-reverse"));
+ _to_item_item->signal_clicked().connect(sigc::mem_fun(*this, &MeasureToolbar::to_item));
+ add(*_to_item_item);
+ }
+
+ /* to mark dimensions */
+ {
+ _mark_dimension_item = Gtk::manage(new Gtk::ToolButton(_("Mark Dimension")));
+ _mark_dimension_item->set_tooltip_text(_("Mark Dimension"));
+ _mark_dimension_item->set_icon_name(INKSCAPE_ICON("tool-pointer"));
+ _mark_dimension_item->signal_clicked().connect(sigc::mem_fun(*this, &MeasureToolbar::to_mark_dimension));
+ add(*_mark_dimension_item);
+ }
+
+ /* Offset */
+ {
+ auto offset_val = prefs->getDouble("/tools/measure/offset", 5.0);
+ _offset_adj = Gtk::Adjustment::create(offset_val, 0.0, 90000.0, 1.0, 4.0);
+ auto offset_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("measure-offset", _("Offset:"), _offset_adj, 0, 2));
+ offset_item->set_tooltip_text(_("Mark dimension offset"));
+ offset_item->set_focus_widget(desktop->canvas);
+ _offset_adj->signal_value_changed().connect(sigc::mem_fun(*this, &MeasureToolbar::offset_value_changed));
+ add(*offset_item);
+ }
+
+ show_all();
+}
+
+GtkWidget *
+MeasureToolbar::create(SPDesktop * desktop)
+{
+ auto toolbar = new MeasureToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+} // MeasureToolbar::prep()
+
+void
+MeasureToolbar::fontsize_value_changed()
+{
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble(Glib::ustring("/tools/measure/fontsize"),
+ _font_size_adj->get_value());
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->showCanvasItems();
+ }
+ }
+}
+
+void
+MeasureToolbar::unit_changed(int /* notUsed */)
+{
+ Glib::ustring const unit = _tracker->getActiveUnit()->abbr;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString("/tools/measure/unit", unit);
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->showCanvasItems();
+ }
+}
+
+void
+MeasureToolbar::precision_value_changed()
+{
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt(Glib::ustring("/tools/measure/precision"),
+ _precision_adj->get_value());
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->showCanvasItems();
+ }
+ }
+}
+
+void
+MeasureToolbar::scale_value_changed()
+{
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble(Glib::ustring("/tools/measure/scale"),
+ _scale_adj->get_value());
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->showCanvasItems();
+ }
+ }
+}
+
+void
+MeasureToolbar::offset_value_changed()
+{
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble(Glib::ustring("/tools/measure/offset"),
+ _offset_adj->get_value());
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->showCanvasItems();
+ }
+ }
+}
+
+void
+MeasureToolbar::toggle_only_selected()
+{
+ auto prefs = Inkscape::Preferences::get();
+ bool active = _only_selected_item->get_active();
+ prefs->setBool("/tools/measure/only_selected", active);
+ if ( active ) {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Measures only selected."));
+ } else {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Measure all."));
+ }
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->showCanvasItems();
+ }
+}
+
+void
+MeasureToolbar::toggle_ignore_1st_and_last()
+{
+ auto prefs = Inkscape::Preferences::get();
+ bool active = _ignore_1st_and_last_item->get_active();
+ prefs->setBool("/tools/measure/ignore_1st_and_last", active);
+ if ( active ) {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Start and end measures inactive."));
+ } else {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Start and end measures active."));
+ }
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->showCanvasItems();
+ }
+}
+
+void
+MeasureToolbar::toggle_show_in_between()
+{
+ auto prefs = Inkscape::Preferences::get();
+ bool active = _inbetween_item->get_active();
+ prefs->setBool("/tools/measure/show_in_between", active);
+ if ( active ) {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Compute all elements."));
+ } else {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Compute max length."));
+ }
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->showCanvasItems();
+ }
+}
+
+void
+MeasureToolbar::toggle_show_hidden()
+{
+ auto prefs = Inkscape::Preferences::get();
+ bool active = _show_hidden_item->get_active();
+ prefs->setBool("/tools/measure/show_hidden", active);
+ if ( active ) {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Show all crossings."));
+ } else {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Show visible crossings."));
+ }
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->showCanvasItems();
+ }
+}
+
+void
+MeasureToolbar::toggle_all_layers()
+{
+ auto prefs = Inkscape::Preferences::get();
+ bool active = _all_layers_item->get_active();
+ prefs->setBool("/tools/measure/all_layers", active);
+ if ( active ) {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Use all layers in the measure."));
+ } else {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Use current layer in the measure."));
+ }
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->showCanvasItems();
+ }
+}
+
+void
+MeasureToolbar::reverse_knots()
+{
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->reverseKnots();
+ }
+}
+
+void
+MeasureToolbar::to_phantom()
+{
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->toPhantom();
+ }
+}
+
+void
+MeasureToolbar::to_guides()
+{
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->toGuides();
+ }
+}
+
+void
+MeasureToolbar::to_item()
+{
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->toItem();
+ }
+}
+
+void
+MeasureToolbar::to_mark_dimension()
+{
+ MeasureTool *mt = get_measure_tool(_desktop);
+ if (mt) {
+ mt->toMarkDimension();
+ }
+}
+
+}
+}
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/measure-toolbar.h b/src/ui/toolbar/measure-toolbar.h
new file mode 100644
index 0000000..a922fa1
--- /dev/null
+++ b/src/ui/toolbar/measure-toolbar.h
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_MEASURE_TOOLBAR_H
+#define SEEN_MEASURE_TOOLBAR_H
+
+/**
+ * @file
+ * Measure aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+#include <gtkmm/adjustment.h>
+
+class SPDesktop;
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+class UnitTracker;
+}
+
+namespace Toolbar {
+class MeasureToolbar : public Toolbar {
+private:
+ UI::Widget::UnitTracker *_tracker;
+ Glib::RefPtr<Gtk::Adjustment> _font_size_adj;
+ Glib::RefPtr<Gtk::Adjustment> _precision_adj;
+ Glib::RefPtr<Gtk::Adjustment> _scale_adj;
+ Glib::RefPtr<Gtk::Adjustment> _offset_adj;
+
+ Gtk::ToggleToolButton *_only_selected_item;
+ Gtk::ToggleToolButton *_ignore_1st_and_last_item;
+ Gtk::ToggleToolButton *_inbetween_item;
+ Gtk::ToggleToolButton *_show_hidden_item;
+ Gtk::ToggleToolButton *_all_layers_item;
+
+ Gtk::ToolButton *_reverse_item;
+ Gtk::ToolButton *_to_phantom_item;
+ Gtk::ToolButton *_to_guides_item;
+ Gtk::ToolButton *_to_item_item;
+ Gtk::ToolButton *_mark_dimension_item;
+
+ void fontsize_value_changed();
+ void unit_changed(int notUsed);
+ void precision_value_changed();
+ void scale_value_changed();
+ void offset_value_changed();
+ void toggle_only_selected();
+ void toggle_ignore_1st_and_last();
+ void toggle_show_hidden();
+ void toggle_show_in_between();
+ void toggle_all_layers();
+ void reverse_knots();
+ void to_phantom();
+ void to_guides();
+ void to_item();
+ void to_mark_dimension();
+
+protected:
+ MeasureToolbar(SPDesktop *desktop);
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+};
+
+}
+}
+}
+
+#endif /* !SEEN_MEASURE_TOOLBAR_H */
diff --git a/src/ui/toolbar/mesh-toolbar.cpp b/src/ui/toolbar/mesh-toolbar.cpp
new file mode 100644
index 0000000..a1ed631
--- /dev/null
+++ b/src/ui/toolbar/mesh-toolbar.cpp
@@ -0,0 +1,613 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gradient aux toolbar
+ *
+ * Authors:
+ * bulia byak <bulia@dr.com>
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ * Abhishek Sharma
+ * Tavmjong Bah <tavjong@free.fr>
+ *
+ * Copyright (C) 2012 Tavmjong Bah
+ * Copyright (C) 2007 Johan Engelen
+ * Copyright (C) 2005 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "mesh-toolbar.h"
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/messagedialog.h>
+#include <gtkmm/radiotoolbutton.h>
+#include <gtkmm/separatortoolitem.h>
+
+#include "desktop-style.h"
+#include "desktop.h"
+#include "document-undo.h"
+#include "gradient-chemistry.h"
+#include "gradient-drag.h"
+#include "inkscape.h"
+
+#include "object/sp-defs.h"
+#include "object/sp-mesh-gradient.h"
+#include "object/sp-stop.h"
+#include "style.h"
+
+#include "svg/css-ostringstream.h"
+
+#include "ui/icon-names.h"
+#include "ui/simple-pref-pusher.h"
+#include "ui/tools/gradient-tool.h"
+#include "ui/tools/mesh-tool.h"
+#include "ui/widget/canvas.h"
+#include "ui/widget/color-preview.h"
+#include "ui/widget/combo-tool-item.h"
+#include "ui/widget/gradient-image.h"
+#include "ui/widget/spin-button-tool-item.h"
+
+using Inkscape::DocumentUndo;
+using Inkscape::UI::Tools::MeshTool;
+
+static bool blocked = false;
+
+// Get a list of selected meshes taking into account fill/stroke toggles
+std::vector<SPMeshGradient *> ms_get_dt_selected_gradients(Inkscape::Selection *selection)
+{
+ std::vector<SPMeshGradient *> ms_selected;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool edit_fill = prefs->getBool("/tools/mesh/edit_fill", true);
+ bool edit_stroke = prefs->getBool("/tools/mesh/edit_stroke", true);
+
+ auto itemlist= selection->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ SPItem *item = *i;// get the items gradient, not the getVector() version
+ SPStyle *style = item->style;
+
+ if (style) {
+
+
+ if (edit_fill && style->fill.isPaintserver()) {
+ SPPaintServer *server = item->style->getFillPaintServer();
+ SPMeshGradient *mesh = dynamic_cast<SPMeshGradient *>(server);
+ if (mesh) {
+ ms_selected.push_back(mesh);
+ }
+ }
+
+ if (edit_stroke && style->stroke.isPaintserver()) {
+ SPPaintServer *server = item->style->getStrokePaintServer();
+ SPMeshGradient *mesh = dynamic_cast<SPMeshGradient *>(server);
+ if (mesh) {
+ ms_selected.push_back(mesh);
+ }
+ }
+ }
+
+ }
+ return ms_selected;
+}
+
+
+/*
+ * Get the current selection status from the desktop
+ */
+void ms_read_selection( Inkscape::Selection *selection,
+ SPMeshGradient *&ms_selected,
+ bool &ms_selected_multi,
+ SPMeshType &ms_type,
+ bool &ms_type_multi )
+{
+ ms_selected = nullptr;
+ ms_selected_multi = false;
+ ms_type = SP_MESH_TYPE_COONS;
+ ms_type_multi = false;
+
+ bool first = true;
+
+ // Read desktop selection, taking into account fill/stroke toggles
+ std::vector<SPMeshGradient *> meshes = ms_get_dt_selected_gradients( selection );
+ for (auto & meshe : meshes) {
+ if (first) {
+ ms_selected = meshe;
+ ms_type = meshe->type;
+ first = false;
+ } else {
+ if (ms_selected != meshe) {
+ ms_selected_multi = true;
+ }
+ if (ms_type != meshe->type) {
+ ms_type_multi = true;
+ }
+ }
+ }
+}
+
+
+/*
+ * Callback functions for user actions
+ */
+
+
+/** Temporary hack: Returns the mesh tool in the active desktop.
+ * Will go away during tool refactoring. */
+static MeshTool *get_mesh_tool()
+{
+ MeshTool *tool = nullptr;
+ if (SP_ACTIVE_DESKTOP ) {
+ Inkscape::UI::Tools::ToolBase *ec = SP_ACTIVE_DESKTOP->event_context;
+ if (SP_IS_MESH_CONTEXT(ec)) {
+ tool = static_cast<MeshTool*>(ec);
+ }
+ }
+ return tool;
+}
+
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+MeshToolbar::MeshToolbar(SPDesktop *desktop)
+ : Toolbar(desktop),
+ _edit_fill_pusher(nullptr)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ /* New mesh: normal or conical */
+ {
+ add_label(_("New:"));
+
+ Gtk::RadioToolButton::Group new_type_group;
+
+ auto normal_type_btn = Gtk::manage(new Gtk::RadioToolButton(new_type_group, _("normal")));
+ normal_type_btn->set_tooltip_text(_("Create mesh gradient"));
+ normal_type_btn->set_icon_name(INKSCAPE_ICON("paint-gradient-mesh"));
+ _new_type_buttons.push_back(normal_type_btn);
+
+ auto conical_type_btn = Gtk::manage(new Gtk::RadioToolButton(new_type_group, _("conical")));
+ conical_type_btn->set_tooltip_text(_("Create conical gradient"));
+ conical_type_btn->set_icon_name(INKSCAPE_ICON("paint-gradient-conical"));
+ _new_type_buttons.push_back(conical_type_btn);
+
+ int btn_idx = 0;
+ for (auto btn : _new_type_buttons) {
+ add(*btn);
+ btn->set_sensitive();
+ btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &MeshToolbar::new_geometry_changed), btn_idx++));
+ }
+
+ gint mode = prefs->getInt("/tools/mesh/mesh_geometry", SP_MESH_GEOMETRY_NORMAL);
+ _new_type_buttons[mode]->set_active();
+ }
+
+ /* New gradient on fill or stroke*/
+ {
+ Gtk::RadioToolButton::Group new_fillstroke_group;
+
+ auto fill_button = Gtk::manage(new Gtk::RadioToolButton(new_fillstroke_group, _("fill")));
+ fill_button->set_tooltip_text(_("Create gradient in the fill"));
+ fill_button->set_icon_name(INKSCAPE_ICON("object-fill"));
+ _new_fillstroke_buttons.push_back(fill_button);
+
+ auto stroke_btn = Gtk::manage(new Gtk::RadioToolButton(new_fillstroke_group, _("stroke")));
+ stroke_btn->set_tooltip_text(_("Create gradient in the stroke"));
+ stroke_btn->set_icon_name(INKSCAPE_ICON("object-stroke"));
+ _new_fillstroke_buttons.push_back(stroke_btn);
+
+ int btn_idx = 0;
+ for(auto btn : _new_fillstroke_buttons) {
+ add(*btn);
+ btn->set_sensitive(true);
+ btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &MeshToolbar::new_fillstroke_changed), btn_idx++));
+ }
+
+ gint mode = prefs->getInt("/tools/mesh/newfillorstroke");
+ _new_fillstroke_buttons[mode]->set_active();
+ }
+
+ /* Number of mesh rows */
+ {
+ std::vector<double> values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ auto rows_val = prefs->getDouble("/tools/mesh/mesh_rows", 1);
+ _row_adj = Gtk::Adjustment::create(rows_val, 1, 20, 1, 1);
+ auto row_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("mesh-row", _("Rows:"), _row_adj, 1.0, 0));
+ row_item->set_tooltip_text(_("Number of rows in new mesh"));
+ row_item->set_custom_numeric_menu_data(values);
+ row_item->set_focus_widget(desktop->canvas);
+ _row_adj->signal_value_changed().connect(sigc::mem_fun(*this, &MeshToolbar::row_changed));
+ add(*row_item);
+ row_item->set_sensitive(true);
+ }
+
+ /* Number of mesh columns */
+ {
+ std::vector<double> values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ auto col_val = prefs->getDouble("/tools/mesh/mesh_cols", 1);
+ _col_adj = Gtk::Adjustment::create(col_val, 1, 20, 1, 1);
+ auto col_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("mesh-col", _("Columns:"), _col_adj, 1.0, 0));
+ col_item->set_tooltip_text(_("Number of columns in new mesh"));
+ col_item->set_custom_numeric_menu_data(values);
+ col_item->set_focus_widget(desktop->canvas);
+ _col_adj->signal_value_changed().connect(sigc::mem_fun(*this, &MeshToolbar::col_changed));
+ add(*col_item);
+ col_item->set_sensitive(true);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ // TODO: These were disabled in the UI file. Either activate or delete
+#if 0
+ /* Edit fill mesh */
+ {
+ _edit_fill_item = add_toggle_button(_("Edit Fill"),
+ _("Edit fill mesh"));
+ _edit_fill_item->set_icon_name(INKSCAPE_ICON("object-fill"));
+ _edit_fill_pusher.reset(new UI::SimplePrefPusher(_edit_fill_item, "/tools/mesh/edit_fill"));
+ _edit_fill_item->signal_toggled().connect(sigc::mem_fun(*this, &MeshToolbar::toggle_fill_stroke));
+ }
+
+ /* Edit stroke mesh */
+ {
+ _edit_stroke_item = add_toggle_button(_("Edit Stroke"),
+ _("Edit stroke mesh"));
+ _edit_stroke_item->set_icon_name(INKSCAPE_ICON("object-stroke"));
+ _edit_stroke_pusher.reset(new UI::SimplePrefPusher(_edit_stroke_item, "/tools/mesh/edit_stroke"));
+ _edit_stroke_item->signal_toggled().connect(sigc::mem_fun(*this, &MeshToolbar::toggle_fill_stroke));
+ }
+
+ /* Show/hide side and tensor handles */
+ {
+ auto show_handles_item = add_toggle_button(_("Show Handles"),
+ _("Show handles"));
+ show_handles_item->set_icon_name(INKSCAPE_ICON("show-node-handles"));
+ _show_handles_pusher.reset(new UI::SimplePrefPusher(show_handles_item, "/tools/mesh/show_handles"));
+ show_handles_item->signal_toggled().connect(sigc::mem_fun(*this, &MeshToolbar::toggle_handles));
+ }
+#endif
+
+ desktop->connectEventContextChanged(sigc::mem_fun(*this, &MeshToolbar::watch_ec));
+
+ {
+ auto btn = Gtk::manage(new Gtk::ToolButton(_("Toggle Sides")));
+ btn->set_tooltip_text(_("Toggle selected sides between Beziers and lines."));
+ btn->set_icon_name(INKSCAPE_ICON("node-segment-line"));
+ btn->signal_clicked().connect(sigc::mem_fun(*this, &MeshToolbar::toggle_sides));
+ add(*btn);
+ }
+
+ {
+ auto btn = Gtk::manage(new Gtk::ToolButton(_("Make elliptical")));
+ btn->set_tooltip_text(_("Make selected sides elliptical by changing length of handles. Works best if handles already approximate ellipse."));
+ btn->set_icon_name(INKSCAPE_ICON("node-segment-curve"));
+ btn->signal_clicked().connect(sigc::mem_fun(*this, &MeshToolbar::make_elliptical));
+ add(*btn);
+ }
+
+ {
+ auto btn = Gtk::manage(new Gtk::ToolButton(_("Pick colors:")));
+ btn->set_tooltip_text(_("Pick colors for selected corner nodes from underneath mesh."));
+ btn->set_icon_name(INKSCAPE_ICON("color-picker"));
+ btn->signal_clicked().connect(sigc::mem_fun(*this, &MeshToolbar::pick_colors));
+ add(*btn);
+ }
+
+
+ {
+ auto btn = Gtk::manage(new Gtk::ToolButton(_("Scale mesh to bounding box:")));
+ btn->set_tooltip_text(_("Scale mesh to fit inside bounding box."));
+ btn->set_icon_name(INKSCAPE_ICON("mesh-gradient-fit"));
+ btn->signal_clicked().connect(sigc::mem_fun(*this, &MeshToolbar::fit_mesh));
+ add(*btn);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Warning */
+ {
+ auto btn = Gtk::manage(new Gtk::ToolButton(_("WARNING: Mesh SVG Syntax Subject to Change")));
+ btn->set_tooltip_text(_("WARNING: Mesh SVG Syntax Subject to Change"));
+ btn->set_icon_name(INKSCAPE_ICON("dialog-warning"));
+ add(*btn);
+ btn->signal_clicked().connect(sigc::mem_fun(*this, &MeshToolbar::warning_popup));
+ btn->set_sensitive(true);
+ }
+
+ /* Type */
+ {
+ UI::Widget::ComboToolItemColumns columns;
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+ Gtk::TreeModel::Row row;
+
+ row = *(store->append());
+ row[columns.col_label ] = C_("Type", "Coons");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Bicubic");
+ row[columns.col_sensitive] = true;
+
+ _select_type_item = Gtk::manage(UI::Widget::ComboToolItem::create(_("Smoothing"),
+ // TRANSLATORS: Type of Smoothing. See https://en.wikipedia.org/wiki/Coons_patch
+ _("Coons: no smoothing. Bicubic: smoothing across patch boundaries."),
+ "Not Used", store));
+ _select_type_item->use_group_label(true);
+
+ _select_type_item->set_active(0);
+
+ _select_type_item->signal_changed().connect(sigc::mem_fun(*this, &MeshToolbar::type_changed));
+ add(*_select_type_item);
+ }
+
+ show_all();
+}
+
+/**
+ * Mesh auxiliary toolbar construction and setup.
+ * Don't forget to add to XML in widgets/toolbox.cpp!
+ *
+ */
+GtkWidget *
+MeshToolbar::create(SPDesktop * desktop)
+{
+ auto toolbar = new MeshToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+void
+MeshToolbar::new_geometry_changed(int mode)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/tools/mesh/mesh_geometry", mode);
+}
+
+void
+MeshToolbar::new_fillstroke_changed(int mode)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/tools/mesh/newfillorstroke", mode);
+}
+
+void
+MeshToolbar::row_changed()
+{
+ if (blocked) {
+ return;
+ }
+
+ blocked = TRUE;
+
+ int rows = _row_adj->get_value();
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ prefs->setInt("/tools/mesh/mesh_rows", rows);
+
+ blocked = FALSE;
+}
+
+void
+MeshToolbar::col_changed()
+{
+ if (blocked) {
+ return;
+ }
+
+ blocked = TRUE;
+
+ int cols = _col_adj->get_value();
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ prefs->setInt("/tools/mesh/mesh_cols", cols);
+
+ blocked = FALSE;
+}
+
+void
+MeshToolbar::toggle_fill_stroke()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setBool("tools/mesh/edit_fill", _edit_fill_item->get_active());
+ prefs->setBool("tools/mesh/edit_stroke", _edit_stroke_item->get_active());
+
+ MeshTool *mt = get_mesh_tool();
+ if (mt) {
+ GrDrag *drag = mt->get_drag();
+ drag->updateDraggers();
+ drag->updateLines();
+ drag->updateLevels();
+ selection_changed(nullptr); // Need to update Type widget
+ }
+}
+
+void
+MeshToolbar::toggle_handles()
+{
+ MeshTool *mt = get_mesh_tool();
+ if (mt) {
+ GrDrag *drag = mt->get_drag();
+ drag->refreshDraggers();
+ }
+}
+
+void
+MeshToolbar::watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec)
+{
+ if (SP_IS_MESH_CONTEXT(ec)) {
+ // connect to selection modified and changed signals
+ Inkscape::Selection *selection = desktop->getSelection();
+ SPDocument *document = desktop->getDocument();
+
+ c_selection_changed = selection->connectChanged(sigc::mem_fun(*this, &MeshToolbar::selection_changed));
+ c_selection_modified = selection->connectModified(sigc::mem_fun(*this, &MeshToolbar::selection_modified));
+ c_subselection_changed = desktop->connectToolSubselectionChanged(sigc::mem_fun(*this, &MeshToolbar::drag_selection_changed));
+
+ c_defs_release = document->getDefs()->connectRelease(sigc::mem_fun(*this, &MeshToolbar::defs_release));
+ c_defs_modified = document->getDefs()->connectModified(sigc::mem_fun(*this, &MeshToolbar::defs_modified));
+ selection_changed(selection);
+ } else {
+ if (c_selection_changed)
+ c_selection_changed.disconnect();
+ if (c_selection_modified)
+ c_selection_modified.disconnect();
+ if (c_subselection_changed)
+ c_subselection_changed.disconnect();
+ if (c_defs_release)
+ c_defs_release.disconnect();
+ if (c_defs_modified)
+ c_defs_modified.disconnect();
+ }
+}
+
+void
+MeshToolbar::selection_modified(Inkscape::Selection *selection, guint /*flags*/)
+{
+ selection_changed(selection);
+}
+
+void
+MeshToolbar::drag_selection_changed(gpointer /*dragger*/)
+{
+ selection_changed(nullptr);
+}
+
+void
+MeshToolbar::defs_release(SPObject * /*defs*/)
+{
+ selection_changed(nullptr);
+}
+
+void
+MeshToolbar::defs_modified(SPObject * /*defs*/, guint /*flags*/)
+{
+ selection_changed(nullptr);
+}
+
+/*
+ * Core function, setup all the widgets whenever something changes on the desktop
+ */
+void
+MeshToolbar::selection_changed(Inkscape::Selection * /* selection */)
+{
+ // std::cout << "ms_tb_selection_changed" << std::endl;
+
+ if (blocked)
+ return;
+
+ if (!_desktop) {
+ return;
+ }
+
+ Inkscape::Selection *selection = _desktop->getSelection(); // take from desktop, not from args
+ if (selection) {
+ // ToolBase *ev = sp_desktop_event_context(desktop);
+ // GrDrag *drag = NULL;
+ // if (ev) {
+ // drag = ev->get_drag();
+ // // Hide/show handles?
+ // }
+
+ SPMeshGradient *ms_selected = nullptr;
+ SPMeshType ms_type = SP_MESH_TYPE_COONS;
+ bool ms_selected_multi = false;
+ bool ms_type_multi = false;
+ ms_read_selection( selection, ms_selected, ms_selected_multi, ms_type, ms_type_multi );
+ // std::cout << " type: " << ms_type << std::endl;
+
+ if (_select_type_item) {
+ _select_type_item->set_sensitive(!ms_type_multi);
+ blocked = TRUE;
+ _select_type_item->set_active(ms_type);
+ blocked = FALSE;
+ }
+ }
+}
+
+void
+MeshToolbar::warning_popup()
+{
+ char *msg = _("Mesh gradients are part of SVG 2:\n"
+ "* Syntax may change.\n"
+ "* Web browser implementation is not guaranteed.\n"
+ "\n"
+ "For web: convert to bitmap (Edit->Make bitmap copy).\n"
+ "For print: export to PDF.");
+ Gtk::MessageDialog dialog(msg, false, Gtk::MESSAGE_WARNING,
+ Gtk::BUTTONS_OK, true);
+ dialog.run();
+}
+
+/**
+ * Sets mesh type: Coons, Bicubic
+ */
+void
+MeshToolbar::type_changed(int mode)
+{
+ if (blocked) {
+ return;
+ }
+
+ Inkscape::Selection *selection = _desktop->getSelection();
+ std::vector<SPMeshGradient *> meshes = ms_get_dt_selected_gradients(selection);
+
+ SPMeshType type = (SPMeshType) mode;
+ for (auto & meshe : meshes) {
+ meshe->type = type;
+ meshe->type_set = true;
+ meshe->updateRepr();
+ }
+ if (!meshes.empty() ) {
+ DocumentUndo::done(_desktop->getDocument(), _("Set mesh type"), INKSCAPE_ICON("mesh-gradient"));
+ }
+}
+
+void
+MeshToolbar::toggle_sides()
+{
+ if (MeshTool *mt = get_mesh_tool()) {
+ mt->corner_operation(MG_CORNER_SIDE_TOGGLE);
+ }
+}
+
+void
+MeshToolbar::make_elliptical()
+{
+ if (MeshTool *mt = get_mesh_tool()) {
+ mt->corner_operation(MG_CORNER_SIDE_ARC);
+ }
+}
+
+void
+MeshToolbar::pick_colors()
+{
+ if (MeshTool *mt = get_mesh_tool()) {
+ mt->corner_operation(MG_CORNER_COLOR_PICK);
+ }
+}
+
+void
+MeshToolbar::fit_mesh()
+{
+ if (MeshTool *mt = get_mesh_tool()) {
+ mt->fit_mesh_in_bbox();
+ }
+}
+
+
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/ui/toolbar/mesh-toolbar.h b/src/ui/toolbar/mesh-toolbar.h
new file mode 100644
index 0000000..2df4411
--- /dev/null
+++ b/src/ui/toolbar/mesh-toolbar.h
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_MESH_TOOLBAR_H
+#define SEEN_MESH_TOOLBAR_H
+
+/*
+ * Mesh aux toolbar
+ *
+ * Authors:
+ * bulia byak <bulia@dr.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2012 authors
+ * Copyright (C) 2005 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+#include <gtkmm/adjustment.h>
+
+class SPDesktop;
+class SPObject;
+
+namespace Gtk {
+class RadioToolButton;
+}
+
+namespace Inkscape {
+class Selection;
+
+namespace UI {
+class SimplePrefPusher;
+
+namespace Tools {
+class ToolBase;
+}
+
+namespace Widget {
+class ComboToolItem;
+class SpinButtonToolItem;
+}
+
+namespace Toolbar {
+class MeshToolbar : public Toolbar {
+private:
+ std::vector<Gtk::RadioToolButton *> _new_type_buttons;
+ std::vector<Gtk::RadioToolButton *> _new_fillstroke_buttons;
+ UI::Widget::ComboToolItem *_select_type_item;
+
+ Gtk::ToggleToolButton *_edit_fill_item;
+ Gtk::ToggleToolButton *_edit_stroke_item;
+
+ Glib::RefPtr<Gtk::Adjustment> _row_adj;
+ Glib::RefPtr<Gtk::Adjustment> _col_adj;
+
+ std::unique_ptr<UI::SimplePrefPusher> _edit_fill_pusher;
+ std::unique_ptr<UI::SimplePrefPusher> _edit_stroke_pusher;
+ std::unique_ptr<UI::SimplePrefPusher> _show_handles_pusher;
+
+ sigc::connection c_selection_changed;
+ sigc::connection c_selection_modified;
+ sigc::connection c_subselection_changed;
+ sigc::connection c_defs_release;
+ sigc::connection c_defs_modified;
+
+ void new_geometry_changed(int mode);
+ void new_fillstroke_changed(int mode);
+ void row_changed();
+ void col_changed();
+ void toggle_fill_stroke();
+ void selection_changed(Inkscape::Selection *selection);
+ void toggle_handles();
+ void watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec);
+ void selection_modified(Inkscape::Selection *selection, guint flags);
+ void drag_selection_changed(gpointer dragger);
+ void defs_release(SPObject *defs);
+ void defs_modified(SPObject *defs, guint flags);
+ void warning_popup();
+ void type_changed(int mode);
+ void toggle_sides();
+ void make_elliptical();
+ void pick_colors();
+ void fit_mesh();
+
+protected:
+ MeshToolbar(SPDesktop *desktop);
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+};
+
+}
+}
+}
+
+#endif /* !SEEN_MESH_TOOLBAR_H */
diff --git a/src/ui/toolbar/node-toolbar.cpp b/src/ui/toolbar/node-toolbar.cpp
new file mode 100644
index 0000000..d3be665
--- /dev/null
+++ b/src/ui/toolbar/node-toolbar.cpp
@@ -0,0 +1,663 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Node aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "node-toolbar.h"
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/adjustment.h>
+#include <gtkmm/image.h>
+#include <gtkmm/menutoolbutton.h>
+#include <gtkmm/separatortoolitem.h>
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "inkscape.h"
+#include "selection-chemistry.h"
+
+#include "object/sp-namedview.h"
+
+#include "ui/icon-names.h"
+#include "ui/simple-pref-pusher.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tools/node-tool.h"
+#include "ui/widget/canvas.h"
+#include "ui/widget/combo-tool-item.h"
+#include "ui/widget/spinbutton.h"
+#include "ui/widget/spin-button-tool-item.h"
+#include "ui/widget/unit-tracker.h"
+
+#include "widgets/widget-sizes.h"
+
+using Inkscape::UI::Widget::UnitTracker;
+using Inkscape::Util::Unit;
+using Inkscape::Util::Quantity;
+using Inkscape::DocumentUndo;
+using Inkscape::Util::unit_table;
+using Inkscape::UI::Tools::NodeTool;
+
+/** Temporary hack: Returns the node tool in the active desktop.
+ * Will go away during tool refactoring. */
+static NodeTool *get_node_tool()
+{
+ NodeTool *tool = nullptr;
+ if (SP_ACTIVE_DESKTOP ) {
+ Inkscape::UI::Tools::ToolBase *ec = SP_ACTIVE_DESKTOP->event_context;
+ if (INK_IS_NODE_TOOL(ec)) {
+ tool = static_cast<NodeTool*>(ec);
+ }
+ }
+ return tool;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+
+NodeToolbar::NodeToolbar(SPDesktop *desktop)
+ : Toolbar(desktop),
+ _tracker(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR)),
+ _freeze(false)
+{
+ auto prefs = Inkscape::Preferences::get();
+
+ Unit doc_units = *desktop->getNamedView()->display_units;
+ _tracker->setActiveUnit(&doc_units);
+
+ {
+ auto insert_node_item = Gtk::manage(new Gtk::MenuToolButton());
+ insert_node_item->set_icon_name(INKSCAPE_ICON("node-add"));
+ insert_node_item->set_label(_("Insert node"));
+ insert_node_item->set_tooltip_text(_("Insert new nodes into selected segments"));
+ insert_node_item->signal_clicked().connect(sigc::mem_fun(*this, &NodeToolbar::edit_add));
+
+ auto insert_node_menu = Gtk::manage(new Gtk::Menu());
+
+ {
+ // TODO: Consider moving back to icons in menu?
+ //auto insert_min_x_icon = Gtk::manage(new Gtk::Image());
+ //insert_min_x_icon->set_from_icon_name(INKSCAPE_ICON("node_insert_min_x"), Gtk::ICON_SIZE_MENU);
+ //auto insert_min_x_item = Gtk::manage(new Gtk::MenuItem(*insert_min_x_icon));
+ auto insert_min_x_item = Gtk::manage(new Gtk::MenuItem(_("Insert node at min X")));
+ insert_min_x_item->set_tooltip_text(_("Insert new nodes at min X into selected segments"));
+ insert_min_x_item->signal_activate().connect(sigc::mem_fun(*this, &NodeToolbar::edit_add_min_x));
+ insert_node_menu->append(*insert_min_x_item);
+ }
+ {
+ //auto insert_max_x_icon = Gtk::manage(new Gtk::Image());
+ //insert_max_x_icon->set_from_icon_name(INKSCAPE_ICON("node_insert_max_x"), Gtk::ICON_SIZE_MENU);
+ //auto insert_max_x_item = Gtk::manage(new Gtk::MenuItem(*insert_max_x_icon));
+ auto insert_max_x_item = Gtk::manage(new Gtk::MenuItem(_("Insert node at max X")));
+ insert_max_x_item->set_tooltip_text(_("Insert new nodes at max X into selected segments"));
+ insert_max_x_item->signal_activate().connect(sigc::mem_fun(*this, &NodeToolbar::edit_add_max_x));
+ insert_node_menu->append(*insert_max_x_item);
+ }
+ {
+ //auto insert_min_y_icon = Gtk::manage(new Gtk::Image());
+ //insert_min_y_icon->set_from_icon_name(INKSCAPE_ICON("node_insert_min_y"), Gtk::ICON_SIZE_MENU);
+ //auto insert_min_y_item = Gtk::manage(new Gtk::MenuItem(*insert_min_y_icon));
+ auto insert_min_y_item = Gtk::manage(new Gtk::MenuItem(_("Insert node at min Y")));
+ insert_min_y_item->set_tooltip_text(_("Insert new nodes at min Y into selected segments"));
+ insert_min_y_item->signal_activate().connect(sigc::mem_fun(*this, &NodeToolbar::edit_add_min_y));
+ insert_node_menu->append(*insert_min_y_item);
+ }
+ {
+ //auto insert_max_y_icon = Gtk::manage(new Gtk::Image());
+ //insert_max_y_icon->set_from_icon_name(INKSCAPE_ICON("node_insert_max_y"), Gtk::ICON_SIZE_MENU);
+ //auto insert_max_y_item = Gtk::manage(new Gtk::MenuItem(*insert_max_y_icon));
+ auto insert_max_y_item = Gtk::manage(new Gtk::MenuItem(_("Insert node at max Y")));
+ insert_max_y_item->set_tooltip_text(_("Insert new nodes at max Y into selected segments"));
+ insert_max_y_item->signal_activate().connect(sigc::mem_fun(*this, &NodeToolbar::edit_add_max_y));
+ insert_node_menu->append(*insert_max_y_item);
+ }
+
+ insert_node_menu->show_all();
+ insert_node_item->set_menu(*insert_node_menu);
+ add(*insert_node_item);
+ }
+
+ {
+ auto delete_item = Gtk::manage(new Gtk::ToolButton(_("Delete node")));
+ delete_item->set_tooltip_text(_("Delete selected nodes"));
+ delete_item->set_icon_name(INKSCAPE_ICON("node-delete"));
+ delete_item->signal_clicked().connect(sigc::mem_fun(*this, &NodeToolbar::edit_delete));
+ add(*delete_item);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ auto join_item = Gtk::manage(new Gtk::ToolButton(_("Join nodes")));
+ join_item->set_tooltip_text(_("Join selected nodes"));
+ join_item->set_icon_name(INKSCAPE_ICON("node-join"));
+ join_item->signal_clicked().connect(sigc::mem_fun(*this, &NodeToolbar::edit_join));
+ add(*join_item);
+ }
+
+ {
+ auto break_item = Gtk::manage(new Gtk::ToolButton(_("Break nodes")));
+ break_item->set_tooltip_text(_("Break path at selected nodes"));
+ break_item->set_icon_name(INKSCAPE_ICON("node-break"));
+ break_item->signal_clicked().connect(sigc::mem_fun(*this, &NodeToolbar::edit_break));
+ add(*break_item);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ auto join_segment_item = Gtk::manage(new Gtk::ToolButton(_("Join with segment")));
+ join_segment_item->set_tooltip_text(_("Join selected endnodes with a new segment"));
+ join_segment_item->set_icon_name(INKSCAPE_ICON("node-join-segment"));
+ join_segment_item->signal_clicked().connect(sigc::mem_fun(*this, &NodeToolbar::edit_join_segment));
+ add(*join_segment_item);
+ }
+
+ {
+ auto delete_segment_item = Gtk::manage(new Gtk::ToolButton(_("Delete segment")));
+ delete_segment_item->set_tooltip_text(_("Delete segment between two non-endpoint nodes"));
+ delete_segment_item->set_icon_name(INKSCAPE_ICON("node-delete-segment"));
+ delete_segment_item->signal_clicked().connect(sigc::mem_fun(*this, &NodeToolbar::edit_delete_segment));
+ add(*delete_segment_item);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ auto cusp_item = Gtk::manage(new Gtk::ToolButton(_("Node Cusp")));
+ cusp_item->set_tooltip_text(_("Make selected nodes corner"));
+ cusp_item->set_icon_name(INKSCAPE_ICON("node-type-cusp"));
+ cusp_item->signal_clicked().connect(sigc::mem_fun(*this, &NodeToolbar::edit_cusp));
+ add(*cusp_item);
+ }
+
+ {
+ auto smooth_item = Gtk::manage(new Gtk::ToolButton(_("Node Smooth")));
+ smooth_item->set_tooltip_text(_("Make selected nodes smooth"));
+ smooth_item->set_icon_name(INKSCAPE_ICON("node-type-smooth"));
+ smooth_item->signal_clicked().connect(sigc::mem_fun(*this, &NodeToolbar::edit_smooth));
+ add(*smooth_item);
+ }
+
+ {
+ auto symmetric_item = Gtk::manage(new Gtk::ToolButton(_("Node Symmetric")));
+ symmetric_item->set_tooltip_text(_("Make selected nodes symmetric"));
+ symmetric_item->set_icon_name(INKSCAPE_ICON("node-type-symmetric"));
+ symmetric_item->signal_clicked().connect(sigc::mem_fun(*this, &NodeToolbar::edit_symmetrical));
+ add(*symmetric_item);
+ }
+
+ {
+ auto auto_item = Gtk::manage(new Gtk::ToolButton(_("Node Auto")));
+ auto_item->set_tooltip_text(_("Make selected nodes auto-smooth"));
+ auto_item->set_icon_name(INKSCAPE_ICON("node-type-auto-smooth"));
+ auto_item->signal_clicked().connect(sigc::mem_fun(*this, &NodeToolbar::edit_auto));
+ add(*auto_item);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ auto line_item = Gtk::manage(new Gtk::ToolButton(_("Node Line")));
+ line_item->set_tooltip_text(_("Make selected segments lines"));
+ line_item->set_icon_name(INKSCAPE_ICON("node-segment-line"));
+ line_item->signal_clicked().connect(sigc::mem_fun(*this, &NodeToolbar::edit_toline));
+ add(*line_item);
+ }
+
+ {
+ auto curve_item = Gtk::manage(new Gtk::ToolButton(_("Node Curve")));
+ curve_item->set_tooltip_text(_("Make selected segments curves"));
+ curve_item->set_icon_name(INKSCAPE_ICON("node-segment-curve"));
+ curve_item->signal_clicked().connect(sigc::mem_fun(*this, &NodeToolbar::edit_tocurve));
+ add(*curve_item);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ auto object_to_path_item = Gtk::manage(new Gtk::ToolButton(_("_Object to Path")));
+ object_to_path_item->set_tooltip_text(_("Convert selected object to path"));
+ object_to_path_item->set_icon_name(INKSCAPE_ICON("object-to-path"));
+ // Must use C API until GTK4.
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(object_to_path_item->gobj()), "app.object-to-path");
+ add(*object_to_path_item);
+ }
+
+ {
+ auto stroke_to_path_item = Gtk::manage(new Gtk::ToolButton(_("_Stroke to Path")));
+ stroke_to_path_item->set_tooltip_text(_("Convert selected object's stroke to paths"));
+ stroke_to_path_item->set_icon_name(INKSCAPE_ICON("stroke-to-path"));
+ // Must use C API until GTK4.
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(stroke_to_path_item->gobj()), "app.object-stroke-to-path");
+ add(*stroke_to_path_item);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* X coord of selected node(s) */
+ {
+ std::vector<double> values = {1, 2, 3, 5, 10, 20, 50, 100, 200, 500};
+ auto nodes_x_val = prefs->getDouble("/tools/nodes/Xcoord", 0);
+ _nodes_x_adj = Gtk::Adjustment::create(nodes_x_val, -1e6, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
+ _nodes_x_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("node-x", _("X:"), _nodes_x_adj));
+ _nodes_x_item->set_tooltip_text(_("X coordinate of selected node(s)"));
+ _nodes_x_item->set_custom_numeric_menu_data(values);
+ _tracker->addAdjustment(_nodes_x_adj->gobj());
+ _nodes_x_item->get_spin_button()->addUnitTracker(_tracker.get());
+ _nodes_x_item->set_focus_widget(desktop->canvas);
+ _nodes_x_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &NodeToolbar::value_changed), Geom::X));
+ _nodes_x_item->set_sensitive(false);
+ add(*_nodes_x_item);
+ }
+
+ /* Y coord of selected node(s) */
+ {
+ std::vector<double> values = {1, 2, 3, 5, 10, 20, 50, 100, 200, 500};
+ auto nodes_y_val = prefs->getDouble("/tools/nodes/Ycoord", 0);
+ _nodes_y_adj = Gtk::Adjustment::create(nodes_y_val, -1e6, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
+ _nodes_y_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("node-y", _("Y:"), _nodes_y_adj));
+ _nodes_y_item->set_tooltip_text(_("Y coordinate of selected node(s)"));
+ _nodes_y_item->set_custom_numeric_menu_data(values);
+ _tracker->addAdjustment(_nodes_y_adj->gobj());
+ _nodes_y_item->get_spin_button()->addUnitTracker(_tracker.get());
+ _nodes_y_item->set_focus_widget(desktop->canvas);
+ _nodes_y_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &NodeToolbar::value_changed), Geom::Y));
+ _nodes_y_item->set_sensitive(false);
+ add(*_nodes_y_item);
+ }
+
+ // add the units menu
+ {
+ auto unit_menu = _tracker->create_tool_item(_("Units"), (""));
+ add(*unit_menu);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ _object_edit_clip_path_item = add_toggle_button(_("Edit clipping paths"),
+ _("Show clipping path(s) of selected object(s)"));
+ _object_edit_clip_path_item->set_icon_name(INKSCAPE_ICON("path-clip-edit"));
+ _pusher_edit_clipping_paths.reset(new SimplePrefPusher(_object_edit_clip_path_item, "/tools/nodes/edit_clipping_paths"));
+ _object_edit_clip_path_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &NodeToolbar::on_pref_toggled),
+ _object_edit_clip_path_item,
+ "/tools/nodes/edit_clipping_paths"));
+ }
+
+ {
+ _object_edit_mask_path_item = add_toggle_button(_("Edit masks"),
+ _("Show mask(s) of selected object(s)"));
+ _object_edit_mask_path_item->set_icon_name(INKSCAPE_ICON("path-mask-edit"));
+ _pusher_edit_masks.reset(new SimplePrefPusher(_object_edit_mask_path_item, "/tools/nodes/edit_masks"));
+ _object_edit_mask_path_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &NodeToolbar::on_pref_toggled),
+ _object_edit_mask_path_item,
+ "/tools/nodes/edit_masks"));
+ }
+
+ {
+ _nodes_lpeedit_item = Gtk::manage(new Gtk::ToolButton(N_("Next path effect parameter")));
+ _nodes_lpeedit_item->set_tooltip_text(N_("Show next editable path effect parameter"));
+ _nodes_lpeedit_item->set_icon_name(INKSCAPE_ICON("path-effect-parameter-next"));
+ // Must use C API until GTK4.
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(_nodes_lpeedit_item->gobj()), "win.path-effect-parameter-next");
+ add(*_nodes_lpeedit_item);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ _show_transform_handles_item = add_toggle_button(_("Show Transform Handles"),
+ _("Show transformation handles for selected nodes"));
+ _show_transform_handles_item->set_icon_name(INKSCAPE_ICON("node-transform"));
+ _pusher_show_transform_handles.reset(new UI::SimplePrefPusher(_show_transform_handles_item, "/tools/nodes/show_transform_handles"));
+ _show_transform_handles_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &NodeToolbar::on_pref_toggled),
+ _show_transform_handles_item,
+ "/tools/nodes/show_transform_handles"));
+ }
+
+ {
+ _show_handles_item = add_toggle_button(_("Show Handles"),
+ _("Show Bezier handles of selected nodes"));
+ _show_handles_item->set_icon_name(INKSCAPE_ICON("show-node-handles"));
+ _pusher_show_handles.reset(new UI::SimplePrefPusher(_show_handles_item, "/tools/nodes/show_handles"));
+ _show_handles_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &NodeToolbar::on_pref_toggled),
+ _show_handles_item,
+ "/tools/nodes/show_handles"));
+ }
+
+ {
+ _show_helper_path_item = add_toggle_button(_("Show Outline"),
+ _("Show path outline (without path effects)"));
+ _show_helper_path_item->set_icon_name(INKSCAPE_ICON("show-path-outline"));
+ _pusher_show_outline.reset(new UI::SimplePrefPusher(_show_helper_path_item, "/tools/nodes/show_outline"));
+ _show_helper_path_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &NodeToolbar::on_pref_toggled),
+ _show_helper_path_item,
+ "/tools/nodes/show_outline"));
+ }
+
+ sel_changed(desktop->getSelection());
+ desktop->connectEventContextChanged(sigc::mem_fun(*this, &NodeToolbar::watch_ec));
+
+ show_all();
+}
+
+GtkWidget *
+NodeToolbar::create(SPDesktop *desktop)
+{
+ auto holder = new NodeToolbar(desktop);
+ return GTK_WIDGET(holder->gobj());
+} // NodeToolbar::prep()
+
+void
+NodeToolbar::value_changed(Geom::Dim2 d)
+{
+ auto adj = (d == Geom::X) ? _nodes_x_adj : _nodes_y_adj;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (!_tracker) {
+ return;
+ }
+
+ Unit const *unit = _tracker->getActiveUnit();
+
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ prefs->setDouble(Glib::ustring("/tools/nodes/") + (d == Geom::X ? "x" : "y"),
+ Quantity::convert(adj->get_value(), unit, "px"));
+ }
+
+ // quit if run by the attr_changed listener
+ if (_freeze || _tracker->isUpdating()) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ NodeTool *nt = get_node_tool();
+ if (nt && !nt->_selected_nodes->empty()) {
+ double val = Quantity::convert(adj->get_value(), unit, "px");
+ double oldval = nt->_selected_nodes->pointwiseBounds()->midpoint()[d];
+ Geom::Point delta(0,0);
+ delta[d] = val - oldval;
+ nt->_multipath->move(delta);
+ }
+
+ _freeze = false;
+}
+
+void
+NodeToolbar::sel_changed(Inkscape::Selection *selection)
+{
+ SPItem *item = selection->singleItem();
+ if (item && SP_IS_LPE_ITEM(item)) {
+ if (SP_LPE_ITEM(item)->hasPathEffect()) {
+ _nodes_lpeedit_item->set_sensitive(true);
+ } else {
+ _nodes_lpeedit_item->set_sensitive(false);
+ }
+ } else {
+ _nodes_lpeedit_item->set_sensitive(false);
+ }
+}
+
+void
+NodeToolbar::watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec)
+{
+ if (INK_IS_NODE_TOOL(ec)) {
+ // watch selection
+ c_selection_changed = desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &NodeToolbar::sel_changed));
+ c_selection_modified = desktop->getSelection()->connectModified(sigc::mem_fun(*this, &NodeToolbar::sel_modified));
+ c_subselection_changed = desktop->connect_control_point_selected([=](void* sender, Inkscape::UI::ControlPointSelection* selection) {
+ coord_changed(selection);
+ });
+
+ sel_changed(desktop->getSelection());
+ } else {
+ if (c_selection_changed)
+ c_selection_changed.disconnect();
+ if (c_selection_modified)
+ c_selection_modified.disconnect();
+ if (c_subselection_changed)
+ c_subselection_changed.disconnect();
+ }
+}
+
+void
+NodeToolbar::sel_modified(Inkscape::Selection *selection, guint /*flags*/)
+{
+ sel_changed(selection);
+}
+
+/* is called when the node selection is modified */
+void
+NodeToolbar::coord_changed(Inkscape::UI::ControlPointSelection* selected_nodes) // gpointer /*shape_editor*/)
+{
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ if (!_tracker) {
+ return;
+ }
+ Unit const *unit = _tracker->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+
+ if (!selected_nodes || selected_nodes->empty()) {
+ // no path selected
+ _nodes_x_item->set_sensitive(false);
+ _nodes_y_item->set_sensitive(false);
+ } else {
+ _nodes_x_item->set_sensitive(true);
+ _nodes_y_item->set_sensitive(true);
+ Geom::Coord oldx = Quantity::convert(_nodes_x_adj->get_value(), unit, "px");
+ Geom::Coord oldy = Quantity::convert(_nodes_y_adj->get_value(), unit, "px");
+ Geom::Point mid = selected_nodes->pointwiseBounds()->midpoint();
+
+ if (oldx != mid[Geom::X]) {
+ _nodes_x_adj->set_value(Quantity::convert(mid[Geom::X], "px", unit));
+ }
+ if (oldy != mid[Geom::Y]) {
+ _nodes_y_adj->set_value(Quantity::convert(mid[Geom::Y], "px", unit));
+ }
+ }
+
+ _freeze = false;
+}
+
+void
+NodeToolbar::edit_add()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->insertNodes();
+ }
+}
+
+void
+NodeToolbar::edit_add_min_x()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->insertNodesAtExtrema(Inkscape::UI::PointManipulator::EXTR_MIN_X);
+ }
+}
+
+void
+NodeToolbar::edit_add_max_x()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->insertNodesAtExtrema(Inkscape::UI::PointManipulator::EXTR_MAX_X);
+ }
+}
+
+void
+NodeToolbar::edit_add_min_y()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->insertNodesAtExtrema(Inkscape::UI::PointManipulator::EXTR_MIN_Y);
+ }
+}
+
+void
+NodeToolbar::edit_add_max_y()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->insertNodesAtExtrema(Inkscape::UI::PointManipulator::EXTR_MAX_Y);
+ }
+}
+
+void
+NodeToolbar::edit_delete()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ nt->_multipath->deleteNodes(prefs->getBool("/tools/nodes/delete_preserves_shape", true));
+ }
+}
+
+void
+NodeToolbar::edit_join()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->joinNodes();
+ }
+}
+
+void
+NodeToolbar::edit_break()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->breakNodes();
+ }
+}
+
+void
+NodeToolbar::edit_delete_segment()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->deleteSegments();
+ }
+}
+
+void
+NodeToolbar::edit_join_segment()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->joinSegments();
+ }
+}
+
+void
+NodeToolbar::edit_cusp()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->setNodeType(Inkscape::UI::NODE_CUSP);
+ }
+}
+
+void
+NodeToolbar::edit_smooth()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->setNodeType(Inkscape::UI::NODE_SMOOTH);
+ }
+}
+
+void
+NodeToolbar::edit_symmetrical()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->setNodeType(Inkscape::UI::NODE_SYMMETRIC);
+ }
+}
+
+void
+NodeToolbar::edit_auto()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->setNodeType(Inkscape::UI::NODE_AUTO);
+ }
+}
+
+void
+NodeToolbar::edit_toline()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->setSegmentType(Inkscape::UI::SEGMENT_STRAIGHT);
+ }
+}
+
+void
+NodeToolbar::edit_tocurve()
+{
+ NodeTool *nt = get_node_tool();
+ if (nt) {
+ nt->_multipath->setSegmentType(Inkscape::UI::SEGMENT_CUBIC_BEZIER);
+ }
+}
+
+void
+NodeToolbar::on_pref_toggled(Gtk::ToggleToolButton *item,
+ const Glib::ustring& path)
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setBool(path, item->get_active());
+}
+
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/node-toolbar.h b/src/ui/toolbar/node-toolbar.h
new file mode 100644
index 0000000..9723922
--- /dev/null
+++ b/src/ui/toolbar/node-toolbar.h
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_NODE_TOOLBAR_H
+#define SEEN_NODE_TOOLBAR_H
+
+/**
+ * @file
+ * Node aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+#include "2geom/coord.h"
+
+class SPDesktop;
+
+namespace Inkscape {
+class Selection;
+
+namespace UI {
+class SimplePrefPusher;
+class ControlPointSelection;
+
+namespace Tools {
+class ToolBase;
+}
+
+namespace Widget {
+class SpinButtonToolItem;
+class UnitTracker;
+}
+
+namespace Toolbar {
+class NodeToolbar : public Toolbar {
+private:
+ std::unique_ptr<UI::Widget::UnitTracker> _tracker;
+
+ std::unique_ptr<UI::SimplePrefPusher> _pusher_show_transform_handles;
+ std::unique_ptr<UI::SimplePrefPusher> _pusher_show_handles;
+ std::unique_ptr<UI::SimplePrefPusher> _pusher_show_outline;
+ std::unique_ptr<UI::SimplePrefPusher> _pusher_edit_clipping_paths;
+ std::unique_ptr<UI::SimplePrefPusher> _pusher_edit_masks;
+
+ Gtk::ToggleToolButton *_object_edit_clip_path_item;
+ Gtk::ToggleToolButton *_object_edit_mask_path_item;
+ Gtk::ToggleToolButton *_show_transform_handles_item;
+ Gtk::ToggleToolButton *_show_handles_item;
+ Gtk::ToggleToolButton *_show_helper_path_item;
+
+ Gtk::ToolButton *_nodes_lpeedit_item;
+
+ UI::Widget::SpinButtonToolItem *_nodes_x_item;
+ UI::Widget::SpinButtonToolItem *_nodes_y_item;
+
+ Glib::RefPtr<Gtk::Adjustment> _nodes_x_adj;
+ Glib::RefPtr<Gtk::Adjustment> _nodes_y_adj;
+
+ bool _freeze;
+
+ sigc::connection c_selection_changed;
+ sigc::connection c_selection_modified;
+ sigc::connection c_subselection_changed;
+
+ void value_changed(Geom::Dim2 d);
+ void sel_changed(Inkscape::Selection *selection);
+ void sel_modified(Inkscape::Selection *selection, guint /*flags*/);
+ void coord_changed(Inkscape::UI::ControlPointSelection* selected_nodes);
+ void watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec);
+ void edit_add();
+ void edit_add_min_x();
+ void edit_add_max_x();
+ void edit_add_min_y();
+ void edit_add_max_y();
+ void edit_delete();
+ void edit_join();
+ void edit_break();
+ void edit_join_segment();
+ void edit_delete_segment();
+ void edit_cusp();
+ void edit_smooth();
+ void edit_symmetrical();
+ void edit_auto();
+ void edit_toline();
+ void edit_tocurve();
+ void on_pref_toggled(Gtk::ToggleToolButton *item,
+ const Glib::ustring& path);
+
+protected:
+ NodeToolbar(SPDesktop *desktop);
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+};
+}
+}
+}
+#endif /* !SEEN_SELECT_TOOLBAR_H */
diff --git a/src/ui/toolbar/page-toolbar.cpp b/src/ui/toolbar/page-toolbar.cpp
new file mode 100644
index 0000000..d845872
--- /dev/null
+++ b/src/ui/toolbar/page-toolbar.cpp
@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Page aux toolbar: Temp until we convert all toolbars to ui files with Gio::Actions.
+ */
+/* Authors:
+ * Martin Owens <doctormo@geek-2.com>
+
+ * Copyright (C) 2021 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "page-toolbar.h"
+
+#include <glibmm/i18n.h>
+#include <gtkmm.h>
+#include <regex>
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "document.h"
+#include "io/resource.h"
+#include "object/sp-namedview.h"
+#include "object/sp-page.h"
+#include "ui/icon-names.h"
+#include "ui/tools/pages-tool.h"
+#include "util/paper.h"
+#include "util/units.h"
+
+using Inkscape::IO::Resource::UIS;
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+
+PageToolbar::PageToolbar(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &builder, SPDesktop *desktop)
+ : Gtk::Toolbar(cobject)
+ , _desktop(desktop)
+ , combo_page_sizes(nullptr)
+ , text_page_label(nullptr)
+{
+ builder->get_widget("page_sizes", combo_page_sizes);
+ builder->get_widget("page_label", text_page_label);
+ builder->get_widget("page_pos", label_page_pos);
+ builder->get_widget("page_backward", btn_page_backward);
+ builder->get_widget("page_foreward", btn_page_foreward);
+ builder->get_widget("page_delete", btn_page_delete);
+ builder->get_widget("page_move_objects", btn_move_toggle);
+ builder->get_widget("sep1", sep1);
+
+ if (text_page_label) {
+ text_page_label->signal_changed().connect(sigc::mem_fun(*this, &PageToolbar::labelEdited));
+ }
+
+ if (combo_page_sizes) {
+ combo_page_sizes->signal_changed().connect(sigc::mem_fun(*this, &PageToolbar::sizeChoose));
+ entry_page_sizes = dynamic_cast<Gtk::Entry *>(combo_page_sizes->get_child());
+ if (entry_page_sizes) {
+ entry_page_sizes->set_placeholder_text(_("ex.: 100x100cm"));
+ entry_page_sizes->set_tooltip_text(_("Type in width & height of a page. (ex.: 15x10cm, 10in x 100mm)\n"
+ "or choose preset from dropdown."));
+ entry_page_sizes->signal_activate().connect(sigc::mem_fun(*this, &PageToolbar::sizeChanged));
+ entry_page_sizes->signal_icon_press().connect([=](Gtk::EntryIconPosition, const GdkEventButton*){
+ _document->getPageManager().changeOrientation();
+ DocumentUndo::maybeDone(_document, "page-resize", _("Resize Page"), INKSCAPE_ICON("tool-pages"));
+ setSizeText();
+ });
+ entry_page_sizes->signal_focus_in_event().connect([=](GdkEventFocus* focus){
+ entry_page_sizes->set_text("");
+ return false;
+ });
+ }
+ auto& page_sizes = Inkscape::PaperSize::getPageSizes();
+ for (int i = 0; i < page_sizes.size(); i++) {
+ combo_page_sizes->append(std::to_string(i), page_sizes[i].getDescription(false));
+ }
+ }
+
+ // Watch for when the tool changes
+ _ec_connection = _desktop->connectEventContextChanged(sigc::mem_fun(*this, &PageToolbar::toolChanged));
+ _doc_connection = _desktop->connectDocumentReplaced([=](SPDesktop *desktop, SPDocument *doc) {
+ if (doc) {
+ toolChanged(desktop, desktop->getEventContext());
+ }
+ });
+
+ // Constructed by a builder, so we're going to protect the widget from destruction.
+ this->reference();
+ was_referenced = true;
+}
+
+void PageToolbar::on_parent_changed(Gtk::Widget *)
+{
+ if (was_referenced) {
+ // Undo the gtkbuilder protection now that we have a parent
+ this->unreference();
+ was_referenced = false;
+ }
+}
+
+PageToolbar::~PageToolbar()
+{
+ _ec_connection.disconnect();
+ _doc_connection.disconnect();
+ toolChanged(nullptr, nullptr);
+}
+
+void PageToolbar::toolChanged(SPDesktop *desktop, Inkscape::UI::Tools::ToolBase *ec)
+{
+ // Disconnect previous page changed signal
+ _page_selected.disconnect();
+ _pages_changed.disconnect();
+ _page_modified.disconnect();
+ _document = nullptr;
+
+ if (dynamic_cast<Inkscape::UI::Tools::PagesTool *>(ec)) {
+ // Save the document and page_manager for future use.
+ if ((_document = desktop->getDocument())) {
+ auto &page_manager = _document->getPageManager();
+ // Connect the page changed signal and indicate changed
+ _pages_changed = page_manager.connectPagesChanged(sigc::mem_fun(*this, &PageToolbar::pagesChanged));
+ _page_selected = page_manager.connectPageSelected(sigc::mem_fun(*this, &PageToolbar::selectionChanged));
+ // Update everything now.
+ pagesChanged();
+ }
+ }
+}
+
+void PageToolbar::labelEdited()
+{
+ auto text = text_page_label->get_text();
+ if (auto page = _document->getPageManager().getSelected()) {
+ page->setLabel(text.empty() ? nullptr : text.c_str());
+ DocumentUndo::maybeDone(_document, "page-relabel", _("Relabel Page"), INKSCAPE_ICON("tool-pages"));
+ }
+}
+
+void PageToolbar::sizeChoose()
+{
+ auto &pm = _document->getPageManager();
+ try {
+ auto p_rect = pm.getSelectedPageRect();
+ bool landscape = p_rect.width() > p_rect.height();
+
+ auto page_id = std::stoi(combo_page_sizes->get_active_id());
+ auto& page_sizes = Inkscape::PaperSize::getPageSizes();
+ if (page_id >= 0 && page_id < page_sizes.size()) {
+ auto&& ps = page_sizes[page_id];
+ // Keep page orientation while selecting size
+ auto width = ps.unit->convert(ps.size[landscape], "px");
+ auto height = ps.unit->convert(ps.size[!landscape], "px");
+ pm.resizePage(width, height);
+ setSizeText();
+ DocumentUndo::maybeDone(_document, "page-resize", _("Resize Page"), INKSCAPE_ICON("tool-pages"));
+ }
+ } catch (std::invalid_argument const &e) {
+ // Ignore because user is typing into Entry
+ }
+}
+
+double PageToolbar::_unit_to_size(std::string number, std::string unit_str, std::string backup)
+{
+ // We always support comma, even if not in that particular locale.
+ std::replace(number.begin(), number.end(), ',', '.');
+ double value = std::stod(number);
+
+ // Get the best unit, for example 50x40cm means cm for both
+ if (unit_str.empty() && !backup.empty())
+ unit_str = backup;
+ if (unit_str == "\"")
+ unit_str = "in";
+
+ // Output is always in px as it's the most useful.
+ auto px = Inkscape::Util::unit_table.getUnit("px");
+
+ // Convert from user entered unit to display unit
+ if (!unit_str.empty())
+ return Inkscape::Util::Quantity::convert(value, unit_str, px);
+
+ // Default unit is the document's display unit
+ auto unit = _document->getDisplayUnit();
+ return Inkscape::Util::Quantity::convert(value, unit, px);
+}
+
+/**
+ * A manually typed input size, parse out what we can understand from
+ * the text or ignore it if the text can't be parsed.
+ *
+ * Format: 50cm x 40mm
+ * 20',40"
+ * 30,4-40.2
+ */
+void PageToolbar::sizeChanged()
+{
+ // Parse the size out of the typed text if possible.
+ auto text = std::string(combo_page_sizes->get_active_text());
+ // This does not support negative values, because pages can not be negatively sized.
+ static std::string arg = "([0-9]+[\\.,]?[0-9]*|\\.[0-9]+) ?(px|mm|cm|in|\\\")?";
+ // We can't support × here since it's UTF8 and this doesn't match
+ static std::regex re_size("^ *" + arg + " *([ *Xx,\\-]) *" + arg + " *$");
+
+ std::smatch matches;
+ if (std::regex_match(text, matches, re_size)) {
+ double width = _unit_to_size(matches[1], matches[2], matches[5]);
+ double height = _unit_to_size(matches[4], matches[5], matches[2]);
+ if (width > 0 && height > 0) {
+ _document->getPageManager().resizePage(width, height);
+ }
+ }
+ setSizeText();
+}
+
+/**
+ * Sets the size of the current page into the entry page size.
+ */
+void PageToolbar::setSizeText(SPPage *page)
+{
+ if (!page)
+ page = _document->getPageManager().getSelected();
+
+ auto unit = _document->getDisplayUnit();
+ double width = _document->getWidth().value(unit);
+ double height = _document->getHeight().value(unit);
+ if (page) {
+ auto px = Inkscape::Util::unit_table.getUnit("px");
+ auto rect = page->getDesktopRect();
+ width = px->convert(rect.width(), unit);
+ height = px->convert(rect.height(), unit);
+ }
+ // Orientation button
+ std::string icon = width > height ? "page-landscape" : "page-portrait";
+ if (width == height) {
+ entry_page_sizes->unset_icon(Gtk::ENTRY_ICON_SECONDARY);
+ } else {
+ entry_page_sizes->set_icon_from_icon_name(INKSCAPE_ICON(icon), Gtk::ENTRY_ICON_SECONDARY);
+ }
+
+ if (auto page_size = Inkscape::PaperSize::findPaperSize(width, height, unit)) {
+ entry_page_sizes->set_text(page_size->getDescription(width > height));
+ } else {
+ entry_page_sizes->set_text(Inkscape::PaperSize::toDescription(_("Custom"), width, height, unit));
+ }
+ // Select text if box is currently in focus.
+ if (entry_page_sizes->has_focus()) {
+ entry_page_sizes->select_region(0, -1);
+ }
+}
+
+void PageToolbar::pagesChanged()
+{
+ selectionChanged(_document->getPageManager().getSelected());
+}
+
+void PageToolbar::selectionChanged(SPPage *page)
+{
+ _page_modified.disconnect();
+ auto &page_manager = _document->getPageManager();
+ text_page_label->set_tooltip_text(_("Page label"));
+
+ // Set label widget content with page label.
+ if (page) {
+ text_page_label->set_sensitive(true);
+ text_page_label->set_placeholder_text(page->getDefaultLabel());
+
+ if (auto label = page->label()) {
+ text_page_label->set_text(label);
+ } else {
+ text_page_label->set_text("");
+ }
+
+ // TRANSLATORS: "%1" is replaced with the page we are on, and "%2" is the total number of pages.
+ auto label = Glib::ustring::compose(_("%1/%2"), page->getPagePosition(), page_manager.getPageCount());
+ label_page_pos->set_label(label);
+
+ _page_modified = page->connectModified([=](SPObject *obj, unsigned int flags) {
+ if (auto page = dynamic_cast<SPPage *>(obj)) {
+ // Make sure we don't 'select' on removal of the page
+ if (flags & SP_OBJECT_MODIFIED_FLAG) {
+ selectionChanged(page);
+ }
+ }
+ });
+ } else {
+ text_page_label->set_text("");
+ text_page_label->set_sensitive(false);
+ text_page_label->set_placeholder_text(_("Single Page Document"));
+ label_page_pos->set_label(_("1/-"));
+
+ _page_modified = _document->connectModified([=](guint) {
+ selectionChanged(nullptr);
+ });
+ }
+ if (!page_manager.hasPrevPage() && !page_manager.hasNextPage() && !page) {
+ sep1->set_visible(false);
+ label_page_pos->get_parent()->set_visible(false);
+ btn_page_backward->set_visible(false);
+ btn_page_foreward->set_visible(false);
+ btn_page_delete->set_visible(false);
+ btn_move_toggle->set_sensitive(false);
+ } else {
+ // Set the forward and backward button sensitivities
+ sep1->set_visible(true);
+ label_page_pos->get_parent()->set_visible(true);
+ btn_page_backward->set_visible(true);
+ btn_page_foreward->set_visible(true);
+ btn_page_backward->set_sensitive(page_manager.hasPrevPage());
+ btn_page_foreward->set_sensitive(page_manager.hasNextPage());
+ btn_page_delete->set_visible(true);
+ btn_move_toggle->set_sensitive(true);
+ }
+ setSizeText(page);
+}
+
+GtkWidget *PageToolbar::create(SPDesktop *desktop)
+{
+ Glib::ustring page_toolbar_builder_file = get_filename(UIS, "toolbar-page.ui");
+ PageToolbar *toolbar = nullptr;
+
+ try {
+ auto builder = Gtk::Builder::create_from_file(page_toolbar_builder_file);
+ builder->get_widget_derived("page-toolbar", toolbar, desktop);
+
+ if (!toolbar) {
+ std::cerr << "InkscapeWindow: Failed to load page toolbar!" << std::endl;
+ return nullptr;
+ }
+ // Usually we should be packing this widget into a parent before the builder
+ // is destroyed, but the create method expects a blind widget so this widget
+ // contains a special keep-alive pattern which can be removed when refactoring.
+ } catch (const Glib::Error &ex) {
+ std::cerr << "PageToolbar: " << page_toolbar_builder_file << " file not read! " << ex.what().raw() << std::endl;
+ }
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+
+} // namespace Toolbar
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/page-toolbar.h b/src/ui/toolbar/page-toolbar.h
new file mode 100644
index 0000000..8a2ec08
--- /dev/null
+++ b/src/ui/toolbar/page-toolbar.h
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_PAGE_TOOLBAR_H
+#define SEEN_PAGE_TOOLBAR_H
+
+/**
+ * @file
+ * Page toolbar
+ */
+/* Authors:
+ * Martin Owens <doctormo@geek-2.com>
+ *
+ * Copyright (C) 2021 Martin Owens
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm.h>
+
+#include "toolbar.h"
+
+class SPDesktop;
+class SPDocument;
+class SPPage;
+
+namespace Inkscape {
+class PaperSize;
+namespace UI {
+namespace Tools {
+class ToolBase;
+}
+namespace Toolbar {
+
+class PageToolbar : public Gtk::Toolbar
+{
+public:
+ PageToolbar(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &builder, SPDesktop *desktop);
+ ~PageToolbar() override;
+
+ static GtkWidget *create(SPDesktop *desktop);
+
+protected:
+ void labelEdited();
+ void sizeChoose();
+ void sizeChanged();
+ void setSizeText(SPPage *page = nullptr);
+
+private:
+ SPDesktop *_desktop;
+ SPDocument *_document;
+
+ void toolChanged(SPDesktop *desktop, Inkscape::UI::Tools::ToolBase *ec);
+ void pagesChanged();
+ void selectionChanged(SPPage *page);
+ void on_parent_changed(Gtk::Widget *prev) override;
+
+ sigc::connection _ec_connection;
+ sigc::connection _doc_connection;
+ sigc::connection _pages_changed;
+ sigc::connection _page_selected;
+ sigc::connection _page_modified;
+
+ bool was_referenced;
+ Gtk::ComboBoxText *combo_page_sizes;
+ Gtk::Entry *entry_page_sizes;
+ Gtk::Entry *text_page_label;
+ Gtk::Label *label_page_pos;
+ Gtk::ToolButton *btn_page_backward;
+ Gtk::ToolButton *btn_page_foreward;
+ Gtk::ToolButton *btn_page_delete;
+ Gtk::ToolButton *btn_move_toggle;
+ Gtk::SeparatorToolItem *sep1;
+
+ double _unit_to_size(std::string number, std::string unit_str, std::string backup);
+};
+
+} // namespace Toolbar
+} // namespace UI
+} // namespace Inkscape
+
+#endif /* !SEEN_PAGE_TOOLBAR_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/paintbucket-toolbar.cpp b/src/ui/toolbar/paintbucket-toolbar.cpp
new file mode 100644
index 0000000..41e4ed9
--- /dev/null
+++ b/src/ui/toolbar/paintbucket-toolbar.cpp
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Paint bucket aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "paintbucket-toolbar.h"
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/separatortoolitem.h>
+
+#include "desktop.h"
+
+#include "ui/icon-names.h"
+#include "ui/tools/flood-tool.h"
+#include "ui/widget/canvas.h"
+#include "ui/widget/combo-tool-item.h"
+#include "ui/widget/spinbutton.h"
+#include "ui/widget/spin-button-tool-item.h"
+#include "ui/widget/unit-tracker.h"
+
+using Inkscape::UI::Widget::UnitTracker;
+using Inkscape::Util::unit_table;
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+PaintbucketToolbar::PaintbucketToolbar(SPDesktop *desktop)
+ : Toolbar(desktop),
+ _tracker(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR))
+{
+ auto prefs = Inkscape::Preferences::get();
+
+ // Channel
+ {
+ UI::Widget::ComboToolItemColumns columns;
+
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+
+ for (auto item: Inkscape::UI::Tools::FloodTool::channel_list) {
+ Gtk::TreeModel::Row row = *(store->append());
+ row[columns.col_label ] = _(item.c_str());
+ row[columns.col_sensitive] = true;
+ }
+
+ _channels_item = Gtk::manage(UI::Widget::ComboToolItem::create(_("Fill by"), Glib::ustring(), "Not Used", store));
+ _channels_item->use_group_label(true);
+
+ int channels = prefs->getInt("/tools/paintbucket/channels", 0);
+ _channels_item->set_active(channels);
+
+ _channels_item->signal_changed().connect(sigc::mem_fun(*this, &PaintbucketToolbar::channels_changed));
+ add(*_channels_item);
+ }
+
+ // Spacing spinbox
+ {
+ auto threshold_val = prefs->getDouble("/tools/paintbucket/threshold", 5);
+ _threshold_adj = Gtk::Adjustment::create(threshold_val, 0, 100.0, 1.0, 10.0);
+ auto threshold_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("inkscape:paintbucket-threshold", _("Threshold:"), _threshold_adj, 1, 0));
+ threshold_item->set_tooltip_text(_("The maximum allowed difference between the clicked pixel and the neighboring pixels to be counted in the fill"));
+ threshold_item->set_focus_widget(desktop->canvas);
+ _threshold_adj->signal_value_changed().connect(sigc::mem_fun(*this, &PaintbucketToolbar::threshold_changed));
+ add(*threshold_item);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ // Create the units menu.
+ Glib::ustring stored_unit = prefs->getString("/tools/paintbucket/offsetunits");
+ if (!stored_unit.empty()) {
+ Unit const *u = unit_table.getUnit(stored_unit);
+ _tracker->setActiveUnit(u);
+ }
+
+ // Offset spinbox
+ {
+ auto offset_val = prefs->getDouble("/tools/paintbucket/offset", 0);
+ _offset_adj = Gtk::Adjustment::create(offset_val, -1e4, 1e4, 0.1, 0.5);
+ auto offset_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("inkscape:paintbucket-offset", _("Grow/shrink by:"), _offset_adj, 1, 2));
+ offset_item->set_tooltip_text(_("The amount to grow (positive) or shrink (negative) the created fill path"));
+ _tracker->addAdjustment(_offset_adj->gobj());
+ offset_item->get_spin_button()->addUnitTracker(_tracker);
+ offset_item->set_focus_widget(desktop->canvas);
+ _offset_adj->signal_value_changed().connect(sigc::mem_fun(*this, &PaintbucketToolbar::offset_changed));
+ add(*offset_item);
+ }
+
+ {
+ auto unit_menu = _tracker->create_tool_item(_("Units"), (""));
+ add(*unit_menu);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Auto Gap */
+ {
+ UI::Widget::ComboToolItemColumns columns;
+
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+
+ for (auto item: Inkscape::UI::Tools::FloodTool::gap_list) {
+ Gtk::TreeModel::Row row = *(store->append());
+ row[columns.col_label ] = item;
+ row[columns.col_sensitive] = true;
+ }
+
+ _autogap_item = Gtk::manage(UI::Widget::ComboToolItem::create(_("Close gaps"), Glib::ustring(), "Not Used", store));
+ _autogap_item->use_group_label(true);
+
+ int autogap = prefs->getInt("/tools/paintbucket/autogap", 0);
+ _autogap_item->set_active(autogap);
+
+ _autogap_item->signal_changed().connect(sigc::mem_fun(*this, &PaintbucketToolbar::autogap_changed));
+ add(*_autogap_item);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Reset */
+ {
+ auto reset_button = Gtk::manage(new Gtk::ToolButton(_("Defaults")));
+ reset_button->set_tooltip_text(_("Reset paint bucket parameters to defaults (use Inkscape Preferences > Tools to change defaults)"));
+ reset_button->set_icon_name(INKSCAPE_ICON("edit-clear"));
+ reset_button->signal_clicked().connect(sigc::mem_fun(*this, &PaintbucketToolbar::defaults));
+ add(*reset_button);
+ reset_button->set_sensitive(true);
+ }
+
+ show_all();
+}
+
+GtkWidget *
+PaintbucketToolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = new PaintbucketToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+void
+PaintbucketToolbar::channels_changed(int channels)
+{
+ Inkscape::UI::Tools::FloodTool::set_channels(channels);
+}
+
+void
+PaintbucketToolbar::threshold_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/tools/paintbucket/threshold", (gint)_threshold_adj->get_value());
+}
+
+void
+PaintbucketToolbar::offset_changed()
+{
+ Unit const *unit = _tracker->getActiveUnit();
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ // Don't adjust the offset value because we're saving the
+ // unit and it'll be correctly handled on load.
+ prefs->setDouble("/tools/paintbucket/offset", (gdouble)_offset_adj->get_value());
+
+ g_return_if_fail(unit != nullptr);
+ prefs->setString("/tools/paintbucket/offsetunits", unit->abbr);
+}
+
+void
+PaintbucketToolbar::autogap_changed(int autogap)
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setInt("/tools/paintbucket/autogap", autogap);
+}
+
+void
+PaintbucketToolbar::defaults()
+{
+ // FIXME: make defaults settable via Inkscape Options
+ _threshold_adj->set_value(15);
+ _offset_adj->set_value(0.0);
+
+ _channels_item->set_active(Inkscape::UI::Tools::FLOOD_CHANNELS_RGB);
+ _autogap_item->set_active(0);
+}
+
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/paintbucket-toolbar.h b/src/ui/toolbar/paintbucket-toolbar.h
new file mode 100644
index 0000000..d1b1a77
--- /dev/null
+++ b/src/ui/toolbar/paintbucket-toolbar.h
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_PAINTBUCKET_TOOLBAR_H
+#define SEEN_PAINTBUCKET_TOOLBAR_H
+
+/**
+ * @file
+ * Paintbucket aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+#include <gtkmm/adjustment.h>
+
+class SPDesktop;
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+class UnitTracker;
+class ComboToolItem;
+}
+
+namespace Toolbar {
+class PaintbucketToolbar : public Toolbar {
+private:
+ UI::Widget::ComboToolItem *_channels_item;
+ UI::Widget::ComboToolItem *_autogap_item;
+
+ Glib::RefPtr<Gtk::Adjustment> _threshold_adj;
+ Glib::RefPtr<Gtk::Adjustment> _offset_adj;
+
+ UI::Widget::UnitTracker *_tracker;
+
+ void channels_changed(int channels);
+ void threshold_changed();
+ void offset_changed();
+ void autogap_changed(int autogap);
+ void defaults();
+
+protected:
+ PaintbucketToolbar(SPDesktop *desktop);
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+};
+
+}
+}
+}
+
+#endif /* !SEEN_PAINTBUCKET_TOOLBAR_H */
diff --git a/src/ui/toolbar/pencil-toolbar.cpp b/src/ui/toolbar/pencil-toolbar.cpp
new file mode 100644
index 0000000..95de66a
--- /dev/null
+++ b/src/ui/toolbar/pencil-toolbar.cpp
@@ -0,0 +1,692 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Pencil and pen toolbars
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "pencil-toolbar.h"
+
+#include <glibmm/i18n.h>
+#include <gtkmm.h>
+
+#include "desktop.h"
+#include "display/curve.h"
+#include "live_effects/lpe-bendpath.h"
+#include "live_effects/lpe-bspline.h"
+#include "live_effects/lpe-patternalongpath.h"
+#include "live_effects/lpe-powerstroke.h"
+#include "live_effects/lpe-simplify.h"
+#include "live_effects/lpe-spiro.h"
+#include "live_effects/lpeobject-reference.h"
+#include "live_effects/lpeobject.h"
+#include "object/sp-shape.h"
+#include "selection.h"
+#include "ui/icon-names.h"
+#include "ui/tools/freehand-base.h"
+#include "ui/tools/pen-tool.h"
+#include "ui/tools/pencil-tool.h"
+#include "ui/widget/canvas.h"
+#include "ui/widget/combo-tool-item.h"
+#include "ui/widget/label-tool-item.h"
+#include "ui/widget/spin-button-tool-item.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+PencilToolbar::PencilToolbar(SPDesktop *desktop,
+ bool pencil_mode)
+ : Toolbar(desktop),
+ _tool_is_pencil(pencil_mode)
+{
+ auto prefs = Inkscape::Preferences::get();
+
+ add_freehand_mode_toggle();
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ if (_tool_is_pencil) {
+ /* Use pressure */
+ {
+ _pressure_item = add_toggle_button(_("Use pressure input"), _("Use pressure input"));
+ _pressure_item->set_icon_name(INKSCAPE_ICON("draw-use-pressure"));
+ bool pressure = prefs->getBool("/tools/freehand/pencil/pressure", false);
+ _pressure_item->set_active(pressure);
+ _pressure_item->signal_toggled().connect(sigc::mem_fun(*this, &PencilToolbar::use_pencil_pressure));
+ }
+ /* min pressure */
+ {
+ auto minpressure_val = prefs->getDouble("/tools/freehand/pencil/minpressure", 0);
+ _minpressure_adj = Gtk::Adjustment::create(minpressure_val, 0, 100, 1, 0);
+ _minpressure =
+ Gtk::manage(new UI::Widget::SpinButtonToolItem("pencil-minpressure", _("Min:"), _minpressure_adj, 0, 0));
+ _minpressure->set_tooltip_text(_("Min percent of pressure"));
+ _minpressure->set_focus_widget(desktop->canvas);
+ _minpressure_adj->signal_value_changed().connect(
+ sigc::mem_fun(*this, &PencilToolbar::minpressure_value_changed));
+ add(*_minpressure);
+ }
+ /* max pressure */
+ {
+ auto maxpressure_val = prefs->getDouble("/tools/freehand/pencil/maxpressure", 30);
+ _maxpressure_adj = Gtk::Adjustment::create(maxpressure_val, 0, 100, 1, 0);
+ _maxpressure =
+ Gtk::manage(new UI::Widget::SpinButtonToolItem("pencil-maxpressure", _("Max:"), _maxpressure_adj, 0, 0));
+ _maxpressure->set_tooltip_text(_("Max percent of pressure"));
+ _maxpressure->set_focus_widget(desktop->canvas);
+ _maxpressure_adj->signal_value_changed().connect(
+ sigc::mem_fun(*this, &PencilToolbar::maxpressure_value_changed));
+ add(*_maxpressure);
+ }
+
+ /* powerstoke */
+ add_powerstroke_cap();
+
+ add(*Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Tolerance */
+ {
+ std::vector<Glib::ustring> labels = { _("(many nodes, rough)"), _("(default)"), "", "", "", "",
+ _("(few nodes, smooth)") };
+ std::vector<double> values = { 1, 10, 20, 30, 50, 75, 100 };
+ auto tolerance_val = prefs->getDouble("/tools/freehand/pencil/tolerance", 3.0);
+ _tolerance_adj = Gtk::Adjustment::create(tolerance_val, 0, 100.0, 0.5, 1.0);
+ auto tolerance_item =
+ Gtk::manage(new UI::Widget::SpinButtonToolItem("pencil-tolerance", _("Smoothing:"), _tolerance_adj, 1, 2));
+ tolerance_item->set_tooltip_text(_("How much smoothing (simplifying) is applied to the line"));
+ tolerance_item->set_custom_numeric_menu_data(values, labels);
+ tolerance_item->set_focus_widget(desktop->canvas);
+ _tolerance_adj->signal_value_changed().connect(sigc::mem_fun(*this, &PencilToolbar::tolerance_value_changed));
+ add(*tolerance_item);
+ }
+
+ /* LPE simplify based tolerance */
+ {
+ _simplify = add_toggle_button(_("LPE based interactive simplify"), _("LPE based interactive simplify"));
+ _simplify->set_icon_name(INKSCAPE_ICON("interactive_simplify"));
+ _simplify->set_active(prefs->getInt("/tools/freehand/pencil/simplify", 0));
+ _simplify->signal_toggled().connect(sigc::mem_fun(*this, &PencilToolbar::simplify_lpe));
+ }
+
+ /* LPE simplify flatten */
+ {
+ _flatten_simplify = Gtk::manage(new Gtk::ToolButton(_("LPE simplify flatten")));
+ _flatten_simplify->set_tooltip_text(_("LPE simplify flatten"));
+ _flatten_simplify->set_icon_name(INKSCAPE_ICON("flatten"));
+ _flatten_simplify->signal_clicked().connect(sigc::mem_fun(*this, &PencilToolbar::simplify_flatten));
+ add(*_flatten_simplify);
+ }
+
+ add(*Gtk::manage(new Gtk::SeparatorToolItem()));
+ }
+
+ /* advanced shape options */
+ add_advanced_shape_options();
+
+ show_all();
+
+ // Elements must be hidden after show_all() is called
+ guint freehandMode = prefs->getInt(( _tool_is_pencil ?
+ "/tools/freehand/pencil/freehand-mode" :
+ "/tools/freehand/pen/freehand-mode" ), 0);
+ if (freehandMode != 1 && freehandMode != 2) {
+ _flatten_spiro_bspline->set_visible(false);
+ }
+ if (_tool_is_pencil) {
+ use_pencil_pressure();
+ }
+}
+
+GtkWidget *
+PencilToolbar::create_pencil(SPDesktop *desktop)
+{
+ auto toolbar = new PencilToolbar(desktop, true);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+PencilToolbar::~PencilToolbar()
+{
+ if(_repr) {
+ _repr->removeListenerByData(this);
+ GC::release(_repr);
+ _repr = nullptr;
+ }
+}
+
+void
+PencilToolbar::mode_changed(int mode)
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setInt(freehand_tool_name() + "/freehand-mode", mode);
+
+ if (mode == 1 || mode == 2) {
+ _flatten_spiro_bspline->set_visible(true);
+ } else {
+ _flatten_spiro_bspline->set_visible(false);
+ }
+
+ bool visible = (mode != 2);
+
+ if (_simplify) {
+ _simplify->set_visible(visible);
+ if (_flatten_simplify) {
+ _flatten_simplify->set_visible(visible && _simplify->get_active());
+ }
+ }
+
+ // Recall, the PencilToolbar is also used as the PenToolbar with minor changes.
+ auto *pt = dynamic_cast<Inkscape::UI::Tools::PenTool *>(_desktop->event_context);
+ if (pt) {
+ pt->setPolylineMode();
+ }
+}
+
+/* This is used in generic functions below to share large portions of code between pen and pencil tool */
+Glib::ustring const
+PencilToolbar::freehand_tool_name()
+{
+ return _tool_is_pencil ? "/tools/freehand/pencil" : "/tools/freehand/pen";
+}
+
+void
+PencilToolbar::add_freehand_mode_toggle()
+{
+ auto label = Gtk::manage(new UI::Widget::LabelToolItem(_("Mode:")));
+ label->set_tooltip_text(_("Mode of new lines drawn by this tool"));
+ add(*label);
+ /* Freehand mode toggle buttons */
+ Gtk::RadioToolButton::Group mode_group;
+ auto bezier_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Bezier")));
+ bezier_mode_btn->set_tooltip_text(_("Create regular Bezier path"));
+ bezier_mode_btn->set_icon_name(INKSCAPE_ICON("path-mode-bezier"));
+ _mode_buttons.push_back(bezier_mode_btn);
+
+ auto spiro_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Spiro")));
+ spiro_mode_btn->set_tooltip_text(_("Create Spiro path"));
+ spiro_mode_btn->set_icon_name(INKSCAPE_ICON("path-mode-spiro"));
+ _mode_buttons.push_back(spiro_mode_btn);
+
+ auto bspline_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("BSpline")));
+ bspline_mode_btn->set_tooltip_text(_("Create BSpline path"));
+ bspline_mode_btn->set_icon_name(INKSCAPE_ICON("path-mode-bspline"));
+ _mode_buttons.push_back(bspline_mode_btn);
+
+ if (!_tool_is_pencil) {
+ auto zigzag_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Zigzag")));
+ zigzag_mode_btn->set_tooltip_text(_("Create a sequence of straight line segments"));
+ zigzag_mode_btn->set_icon_name(INKSCAPE_ICON("path-mode-polyline"));
+ _mode_buttons.push_back(zigzag_mode_btn);
+
+ auto paraxial_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Paraxial")));
+ paraxial_mode_btn->set_tooltip_text(_("Create a sequence of paraxial line segments"));
+ paraxial_mode_btn->set_icon_name(INKSCAPE_ICON("path-mode-polyline-paraxial"));
+ _mode_buttons.push_back(paraxial_mode_btn);
+ }
+
+ int btn_idx = 0;
+ for (auto btn : _mode_buttons) {
+ btn->set_sensitive(true);
+ add(*btn);
+ btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &PencilToolbar::mode_changed), btn_idx++));
+ }
+
+ auto prefs = Inkscape::Preferences::get();
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* LPE bspline spiro flatten */
+ _flatten_spiro_bspline = Gtk::manage(new Gtk::ToolButton(_("Flatten Spiro or BSpline LPE")));
+ _flatten_spiro_bspline->set_tooltip_text(_("Flatten Spiro or BSpline LPE"));
+ _flatten_spiro_bspline->set_icon_name(INKSCAPE_ICON("flatten"));
+ _flatten_spiro_bspline->signal_clicked().connect(sigc::mem_fun(*this, &PencilToolbar::flatten_spiro_bspline));
+ add(*_flatten_spiro_bspline);
+
+ guint freehandMode = prefs->getInt(( _tool_is_pencil ?
+ "/tools/freehand/pencil/freehand-mode" :
+ "/tools/freehand/pen/freehand-mode" ), 0);
+ // freehandMode range is (0,5] for the pen tool, (0,3] for the pencil tool
+ // freehandMode = 3 is an old way of signifying pressure, set it to 0.
+ _mode_buttons[(freehandMode < _mode_buttons.size()) ? freehandMode : 0]->set_active();
+}
+
+void
+PencilToolbar::minpressure_value_changed()
+{
+ assert(_tool_is_pencil);
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/freehand/pencil/minpressure", _minpressure_adj->get_value());
+}
+
+void
+PencilToolbar::maxpressure_value_changed()
+{
+ assert(_tool_is_pencil);
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/freehand/pencil/maxpressure", _maxpressure_adj->get_value());
+}
+
+void
+PencilToolbar::shapewidth_value_changed()
+{
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ Inkscape::Selection *selection = _desktop->getSelection();
+ SPItem *item = selection->singleItem();
+ SPLPEItem *lpeitem = nullptr;
+ if (item) {
+ lpeitem = dynamic_cast<SPLPEItem *>(item);
+ }
+ using namespace Inkscape::LivePathEffect;
+ double width = _shapescale_adj->get_value();
+ switch (_shape_item->get_active()) {
+ case Inkscape::UI::Tools::TRIANGLE_IN:
+ case Inkscape::UI::Tools::TRIANGLE_OUT:
+ prefs->setDouble("/live_effects/powerstroke/width", width);
+ if (lpeitem) {
+ LPEPowerStroke *effect = dynamic_cast<LPEPowerStroke *>(lpeitem->getFirstPathEffectOfType(POWERSTROKE));
+ if (effect) {
+ std::vector<Geom::Point> points = effect->offset_points.data();
+ if (points.size() == 1) {
+ points[0][Geom::Y] = width;
+ effect->offset_points.param_set_and_write_new_value(points);
+ }
+ }
+ }
+ break;
+ case Inkscape::UI::Tools::ELLIPSE:
+ case Inkscape::UI::Tools::CLIPBOARD:
+ // The scale of the clipboard isn't known, so getting it to the right size isn't possible.
+ prefs->setDouble("/live_effects/skeletal/width", width);
+ if (lpeitem) {
+ LPEPatternAlongPath *effect =
+ dynamic_cast<LPEPatternAlongPath *>(lpeitem->getFirstPathEffectOfType(PATTERN_ALONG_PATH));
+ if (effect) {
+ effect->prop_scale.param_set_value(width);
+ sp_lpe_item_update_patheffect(lpeitem, false, true);
+ }
+ }
+ break;
+ case Inkscape::UI::Tools::BEND_CLIPBOARD:
+ prefs->setDouble("/live_effects/bend_path/width", width);
+ if (lpeitem) {
+ LPEBendPath *effect = dynamic_cast<LPEBendPath *>(lpeitem->getFirstPathEffectOfType(BEND_PATH));
+ if (effect) {
+ effect->prop_scale.param_set_value(width);
+ sp_lpe_item_update_patheffect(lpeitem, false, true);
+ }
+ }
+ break;
+ case Inkscape::UI::Tools::NONE:
+ case Inkscape::UI::Tools::LAST_APPLIED:
+ default:
+ break;
+ }
+}
+
+void
+PencilToolbar::use_pencil_pressure() {
+ assert(_tool_is_pencil);
+ bool pressure = _pressure_item->get_active();
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/freehand/pencil/pressure", pressure);
+ if (pressure) {
+ _minpressure->set_visible(true);
+ _maxpressure->set_visible(true);
+ _cap_item->set_visible(true);
+ _shape_item->set_visible(false);
+ _shapescale->set_visible(false);
+ _simplify->set_visible(false);
+ _flatten_spiro_bspline->set_visible(false);
+ _flatten_simplify->set_visible(false);
+ for (auto button : _mode_buttons) {
+ button->set_sensitive(false);
+ }
+ } else {
+ guint freehandMode = prefs->getInt("/tools/freehand/pencil/freehand-mode", 0);
+
+ _minpressure->set_visible(false);
+ _maxpressure->set_visible(false);
+ _cap_item->set_visible(false);
+ _shape_item->set_visible(true);
+ _shapescale->set_visible(true);
+ bool simplify_visible = freehandMode != 2;
+ _simplify->set_visible(simplify_visible);
+ _flatten_simplify->set_visible(simplify_visible && _simplify->get_active());
+ if (freehandMode == 1 || freehandMode == 2) {
+ _flatten_spiro_bspline->set_visible(true);
+ }
+ for (auto button : _mode_buttons) {
+ button->set_sensitive(true);
+ }
+ }
+}
+
+void
+PencilToolbar::add_advanced_shape_options()
+{
+ /*advanced shape options */
+ UI::Widget::ComboToolItemColumns columns;
+
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+
+ std::vector<gchar*> freehand_shape_dropdown_items_list = {
+ const_cast<gchar *>(C_("Freehand shape", "None")),
+ _("Triangle in"),
+ _("Triangle out"),
+ _("Ellipse"),
+ _("From clipboard"),
+ _("Bend from clipboard"),
+ _("Last applied")
+ };
+
+ for (auto item:freehand_shape_dropdown_items_list) {
+ Gtk::TreeModel::Row row = *(store->append());
+ row[columns.col_label ] = item;
+ row[columns.col_sensitive] = true;
+ }
+
+ _shape_item = Gtk::manage(UI::Widget::ComboToolItem::create(_("Shape"), _("Shape of new paths drawn by this tool"), "Not Used", store));
+ _shape_item->use_group_label(true);
+
+ auto prefs = Inkscape::Preferences::get();
+ int shape = prefs->getInt((_tool_is_pencil ?
+ "/tools/freehand/pencil/shape" :
+ "/tools/freehand/pen/shape" ), 0);
+ _shape_item->set_active(shape);
+
+ _shape_item->signal_changed().connect(sigc::mem_fun(*this, &PencilToolbar::change_shape));
+ add(*_shape_item);
+
+ /* power width setting */
+ {
+ _shapescale_adj = Gtk::Adjustment::create(2.0, 0.0, 1000.0, 0.5, 1.0);
+ _shapescale =
+ Gtk::manage(new UI::Widget::SpinButtonToolItem("pencil-maxpressure", _("Scale:"), _shapescale_adj, 1, 2));
+ _shapescale->set_tooltip_text(_("Scale of the width of the power stroke shape."));
+ _shapescale->set_focus_widget(_desktop->canvas);
+ _shapescale_adj->signal_value_changed().connect(sigc::mem_fun(*this, &PencilToolbar::shapewidth_value_changed));
+ update_width_value(shape);
+ add(*_shapescale);
+ }
+}
+
+void
+PencilToolbar::change_shape(int shape) {
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setInt(freehand_tool_name() + "/shape", shape);
+ update_width_value(shape);
+}
+
+void
+PencilToolbar::update_width_value(int shape) {
+ /* Update shape width with correct width */
+ auto prefs = Inkscape::Preferences::get();
+ double width = 1.0;
+ _shapescale->set_sensitive(true);
+ double powerstrokedefsize = 10 / (0.265 * _desktop->getDocument()->getDocumentScale()[0] * 2.0);
+ switch (shape) {
+ case Inkscape::UI::Tools::TRIANGLE_IN:
+ case Inkscape::UI::Tools::TRIANGLE_OUT:
+ width = prefs->getDouble("/live_effects/powerstroke/width", powerstrokedefsize);
+ break;
+ case Inkscape::UI::Tools::ELLIPSE:
+ case Inkscape::UI::Tools::CLIPBOARD:
+ width = prefs->getDouble("/live_effects/skeletal/width", 1.0);
+ break;
+ case Inkscape::UI::Tools::BEND_CLIPBOARD:
+ width = prefs->getDouble("/live_effects/bend_path/width", 1.0);
+ break;
+ case Inkscape::UI::Tools::NONE: // Apply width from style?
+ case Inkscape::UI::Tools::LAST_APPLIED:
+ default:
+ _shapescale->set_sensitive(false);
+ break;
+ }
+ _shapescale_adj->set_value(width);
+}
+
+void PencilToolbar::add_powerstroke_cap()
+{
+ /* Powerstroke cap */
+ UI::Widget::ComboToolItemColumns columns;
+
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+
+ std::vector<gchar *> powerstroke_cap_items_list = { const_cast<gchar *>(C_("Cap", "Butt")), _("Square"), _("Round"),
+ _("Peak"), _("Zero width") };
+ for (auto item : powerstroke_cap_items_list) {
+ Gtk::TreeModel::Row row = *(store->append());
+ row[columns.col_label] = item;
+ row[columns.col_sensitive] = true;
+ }
+
+ _cap_item = Gtk::manage(UI::Widget::ComboToolItem::create(_("Caps"), _("Line endings when drawing with pressure-sensitive PowerPencil"), "Not Used", store));
+
+ auto prefs = Inkscape::Preferences::get();
+
+ int cap = prefs->getInt("/live_effects/powerstroke/powerpencilcap", 2);
+ _cap_item->set_active(cap);
+ _cap_item->use_group_label(true);
+
+ _cap_item->signal_changed().connect(sigc::mem_fun(*this, &PencilToolbar::change_cap));
+
+ add(*_cap_item);
+}
+
+void PencilToolbar::change_cap(int cap)
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setInt("/live_effects/powerstroke/powerpencilcap", cap);
+}
+
+void
+PencilToolbar::simplify_lpe()
+{
+ bool simplify = _simplify->get_active();
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setBool(freehand_tool_name() + "/simplify", simplify);
+ _flatten_simplify->set_visible(simplify);
+}
+
+void
+PencilToolbar::simplify_flatten()
+{
+ auto selected = _desktop->getSelection()->items();
+ SPLPEItem* lpeitem = nullptr;
+ for (auto it(selected.begin()); it != selected.end(); ++it){
+ lpeitem = dynamic_cast<SPLPEItem*>(*it);
+ if (lpeitem && lpeitem->hasPathEffect()){
+ PathEffectList lpelist = lpeitem->getEffectList();
+ PathEffectList::iterator i;
+ for (i = lpelist.begin(); i != lpelist.end(); ++i) {
+ LivePathEffectObject *lpeobj = (*i)->lpeobject;
+ if (lpeobj) {
+ Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe();
+ if (dynamic_cast<Inkscape::LivePathEffect::LPESimplify *>(lpe)) {
+ SPShape * shape = dynamic_cast<SPShape *>(lpeitem);
+ if(shape){
+ auto c = SPCurve::copy(shape->curveForEdit());
+ lpe->doEffect(c.get());
+ lpeitem->setCurrentPathEffect(*i);
+ if (lpelist.size() > 1){
+ lpeitem->removeCurrentPathEffect(true);
+ shape->setCurveBeforeLPE(std::move(c));
+ } else {
+ lpeitem->removeCurrentPathEffect(false);
+ shape->setCurve(std::move(c));
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (lpeitem) {
+ _desktop->getSelection()->remove(lpeitem->getRepr());
+ _desktop->getSelection()->add(lpeitem->getRepr());
+ sp_lpe_item_update_patheffect(lpeitem, false, false);
+ }
+}
+
+void
+PencilToolbar::flatten_spiro_bspline()
+{
+ auto selected = _desktop->getSelection()->items();
+ SPLPEItem* lpeitem = nullptr;
+
+ for (auto it(selected.begin()); it != selected.end(); ++it){
+ lpeitem = dynamic_cast<SPLPEItem*>(*it);
+ if (lpeitem && lpeitem->hasPathEffect()){
+ PathEffectList lpelist = lpeitem->getEffectList();
+ PathEffectList::iterator i;
+ for (i = lpelist.begin(); i != lpelist.end(); ++i) {
+ LivePathEffectObject *lpeobj = (*i)->lpeobject;
+ if (lpeobj) {
+ Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe();
+ if (dynamic_cast<Inkscape::LivePathEffect::LPEBSpline *>(lpe) ||
+ dynamic_cast<Inkscape::LivePathEffect::LPESpiro *>(lpe))
+ {
+ SPShape * shape = dynamic_cast<SPShape *>(lpeitem);
+ if(shape){
+ auto c = SPCurve::copy(shape->curveForEdit());
+ lpe->doEffect(c.get());
+ lpeitem->setCurrentPathEffect(*i);
+ if (lpelist.size() > 1){
+ lpeitem->removeCurrentPathEffect(true);
+ shape->setCurveBeforeLPE(std::move(c));
+ } else {
+ lpeitem->removeCurrentPathEffect(false);
+ shape->setCurve(std::move(c));
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (lpeitem) {
+ _desktop->getSelection()->remove(lpeitem->getRepr());
+ _desktop->getSelection()->add(lpeitem->getRepr());
+ sp_lpe_item_update_patheffect(lpeitem, false, false);
+ }
+}
+
+GtkWidget *
+PencilToolbar::create_pen(SPDesktop *desktop)
+{
+ auto toolbar = new PencilToolbar(desktop, false);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+void
+PencilToolbar::tolerance_value_changed()
+{
+ assert(_tool_is_pencil);
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _freeze = true;
+ prefs->setDouble("/tools/freehand/pencil/tolerance",
+ _tolerance_adj->get_value());
+ _freeze = false;
+ auto selected = _desktop->getSelection()->items();
+ for (auto it(selected.begin()); it != selected.end(); ++it){
+ SPLPEItem* lpeitem = dynamic_cast<SPLPEItem*>(*it);
+ if (lpeitem && lpeitem->hasPathEffect()){
+ Inkscape::LivePathEffect::Effect *simplify =
+ lpeitem->getFirstPathEffectOfType(Inkscape::LivePathEffect::SIMPLIFY);
+ if(simplify){
+ Inkscape::LivePathEffect::LPESimplify *lpe_simplify = dynamic_cast<Inkscape::LivePathEffect::LPESimplify*>(simplify->getLPEObj()->get_lpe());
+ if (lpe_simplify) {
+ double tol = prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 1.0, 100.0);
+ tol = tol/(100.0*(102.0-tol));
+ std::ostringstream ss;
+ ss << tol;
+ Inkscape::LivePathEffect::Effect *powerstroke =
+ lpeitem->getFirstPathEffectOfType(Inkscape::LivePathEffect::POWERSTROKE);
+ bool simplified = false;
+ if(powerstroke){
+ Inkscape::LivePathEffect::LPEPowerStroke *lpe_powerstroke = dynamic_cast<Inkscape::LivePathEffect::LPEPowerStroke*>(powerstroke->getLPEObj()->get_lpe());
+ if(lpe_powerstroke){
+ lpe_powerstroke->getRepr()->setAttribute("is_visible", "false");
+ sp_lpe_item_update_patheffect(lpeitem, false, false);
+ SPShape *sp_shape = dynamic_cast<SPShape *>(lpeitem);
+ if (sp_shape) {
+ guint previous_curve_length = sp_shape->curve()->get_segment_count();
+ lpe_simplify->getRepr()->setAttribute("threshold", ss.str());
+ sp_lpe_item_update_patheffect(lpeitem, false, false);
+ simplified = true;
+ guint curve_length = sp_shape->curve()->get_segment_count();
+ std::vector<Geom::Point> ts = lpe_powerstroke->offset_points.data();
+ double factor = (double)curve_length/ (double)previous_curve_length;
+ for (auto & t : ts) {
+ t[Geom::X] = t[Geom::X] * factor;
+ }
+ lpe_powerstroke->offset_points.param_setValue(ts);
+ }
+ lpe_powerstroke->getRepr()->setAttribute("is_visible", "true");
+ sp_lpe_item_update_patheffect(lpeitem, false, false);
+ }
+ }
+ if(!simplified){
+ lpe_simplify->getRepr()->setAttribute("threshold", ss.str());
+ }
+ }
+ }
+ }
+ }
+}
+
+}
+}
+}
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/pencil-toolbar.h b/src/ui/toolbar/pencil-toolbar.h
new file mode 100644
index 0000000..74f0f63
--- /dev/null
+++ b/src/ui/toolbar/pencil-toolbar.h
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_PENCIL_TOOLBAR_H
+#define SEEN_PENCIL_TOOLBAR_H
+
+/**
+ * @file
+ * Pencil and pen toolbars
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+#include <gtkmm/adjustment.h>
+#include <vector>
+
+class SPDesktop;
+
+namespace Gtk {
+class RadioToolButton;
+class ToggleToolButton;
+class ToolButton;
+}
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+
+namespace UI {
+namespace Widget {
+class SpinButtonToolItem;
+class ComboToolItem;
+}
+
+namespace Toolbar {
+class PencilToolbar : public Toolbar {
+private:
+ bool const _tool_is_pencil;
+ std::vector<Gtk::RadioToolButton *> _mode_buttons;
+
+ Gtk::ToggleToolButton *_pressure_item = nullptr;
+ UI::Widget::SpinButtonToolItem *_minpressure = nullptr;
+ UI::Widget::SpinButtonToolItem *_maxpressure = nullptr;
+ UI::Widget::SpinButtonToolItem *_shapescale = nullptr;
+
+ XML::Node *_repr = nullptr;
+ Gtk::ToolButton *_flatten_spiro_bspline = nullptr;
+ Gtk::ToolButton *_flatten_simplify = nullptr;
+
+ UI::Widget::ComboToolItem *_shape_item = nullptr;
+ UI::Widget::ComboToolItem *_cap_item = nullptr;
+
+ Gtk::ToggleToolButton *_simplify = nullptr;
+
+ bool _freeze = false;
+
+ Glib::RefPtr<Gtk::Adjustment> _minpressure_adj;
+ Glib::RefPtr<Gtk::Adjustment> _maxpressure_adj;
+ Glib::RefPtr<Gtk::Adjustment> _tolerance_adj;
+ Glib::RefPtr<Gtk::Adjustment> _shapescale_adj;
+
+ void add_freehand_mode_toggle();
+ void mode_changed(int mode);
+ Glib::ustring const freehand_tool_name();
+ void minpressure_value_changed();
+ void maxpressure_value_changed();
+ void shapewidth_value_changed();
+ void use_pencil_pressure();
+ void tolerance_value_changed();
+ void add_advanced_shape_options();
+ void add_powerstroke_cap();
+ void change_shape(int shape);
+ void update_width_value(int shape);
+ void change_cap(int cap);
+ void simplify_lpe();
+ void simplify_flatten();
+ void flatten_spiro_bspline();
+
+protected:
+ PencilToolbar(SPDesktop *desktop, bool pencil_mode);
+ ~PencilToolbar() override;
+
+public:
+ static GtkWidget * create_pencil(SPDesktop *desktop);
+ static GtkWidget * create_pen(SPDesktop *desktop);
+};
+}
+}
+}
+
+#endif /* !SEEN_PENCIL_TOOLBAR_H */
diff --git a/src/ui/toolbar/rect-toolbar.cpp b/src/ui/toolbar/rect-toolbar.cpp
new file mode 100644
index 0000000..0e59f1e
--- /dev/null
+++ b/src/ui/toolbar/rect-toolbar.cpp
@@ -0,0 +1,416 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Rect aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "rect-toolbar.h"
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/separatortoolitem.h>
+#include <gtkmm/toolbutton.h>
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "selection.h"
+
+#include "object/sp-namedview.h"
+#include "object/sp-rect.h"
+
+#include "ui/icon-names.h"
+#include "ui/tools/rect-tool.h"
+#include "ui/widget/canvas.h"
+#include "ui/widget/combo-tool-item.h"
+#include "ui/widget/label-tool-item.h"
+#include "ui/widget/spinbutton.h"
+#include "ui/widget/spin-button-tool-item.h"
+#include "ui/widget/unit-tracker.h"
+
+#include "widgets/widget-sizes.h"
+
+#include "xml/node-event-vector.h"
+
+using Inkscape::UI::Widget::UnitTracker;
+using Inkscape::DocumentUndo;
+using Inkscape::Util::Unit;
+using Inkscape::Util::Quantity;
+using Inkscape::Util::unit_table;
+
+static Inkscape::XML::NodeEventVector rect_tb_repr_events = {
+ nullptr, /* child_added */
+ nullptr, /* child_removed */
+ Inkscape::UI::Toolbar::RectToolbar::event_attr_changed,
+ nullptr, /* content_changed */
+ nullptr /* order_changed */
+};
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+
+RectToolbar::RectToolbar(SPDesktop *desktop)
+ : Toolbar(desktop),
+ _tracker(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR)),
+ _freeze(false),
+ _single(true),
+ _repr(nullptr),
+ _mode_item(Gtk::manage(new UI::Widget::LabelToolItem(_("<b>New:</b>"))))
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ // rx/ry units menu: create
+ //tracker->addUnit( SP_UNIT_PERCENT, 0 );
+ // fixme: add % meaning per cent of the width/height
+ auto init_units = desktop->getNamedView()->display_units;
+ _tracker->setActiveUnit(init_units);
+ _mode_item->set_use_markup(true);
+
+ /* W */
+ {
+ auto width_val = prefs->getDouble("/tools/shapes/rect/width", 0);
+ width_val = Quantity::convert(width_val, "px", init_units);
+
+ _width_adj = Gtk::Adjustment::create(width_val, 0, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
+ _width_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("rect-width", _("W:"), _width_adj));
+ _width_item->get_spin_button()->addUnitTracker(_tracker);
+ _width_item->set_focus_widget(_desktop->canvas);
+ _width_item->set_all_tooltip_text(_("Width of rectangle"));
+
+ _width_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &RectToolbar::value_changed),
+ _width_adj,
+ "width",
+ &SPRect::setVisibleWidth));
+ _tracker->addAdjustment(_width_adj->gobj());
+ _width_item->set_sensitive(false);
+
+ std::vector<double> values = {1, 2, 3, 5, 10, 20, 50, 100, 200, 500};
+ _width_item->set_custom_numeric_menu_data(values);
+ }
+
+ /* H */
+ {
+ auto height_val = prefs->getDouble("/tools/shapes/rect/height", 0);
+ height_val = Quantity::convert(height_val, "px", init_units);
+
+ _height_adj = Gtk::Adjustment::create(height_val, 0, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
+ _height_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &RectToolbar::value_changed),
+ _height_adj,
+ "height",
+ &SPRect::setVisibleHeight));
+ _tracker->addAdjustment(_height_adj->gobj());
+
+ std::vector<double> values = { 1, 2, 3, 5, 10, 20, 50, 100, 200, 500};
+ _height_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("rect-height", _("H:"), _height_adj));
+ _height_item->get_spin_button()->addUnitTracker(_tracker);
+ _height_item->set_custom_numeric_menu_data(values);
+ _height_item->set_all_tooltip_text(_("Height of rectangle"));
+ _height_item->set_focus_widget(_desktop->canvas);
+ _height_item->set_sensitive(false);
+ }
+
+ /* rx */
+ {
+ std::vector<Glib::ustring> labels = {_("not rounded"), "", "", "", "", "", "", "", ""};
+ std::vector<double> values = { 0.5, 1, 2, 3, 5, 10, 20, 50, 100};
+ auto rx_val = prefs->getDouble("/tools/shapes/rect/rx", 0);
+ rx_val = Quantity::convert(rx_val, "px", init_units);
+
+ _rx_adj = Gtk::Adjustment::create(rx_val, 0, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
+ _rx_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &RectToolbar::value_changed),
+ _rx_adj,
+ "rx",
+ &SPRect::setVisibleRx));
+ _tracker->addAdjustment(_rx_adj->gobj());
+ _rx_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("rect-rx", _("Rx:"), _rx_adj));
+ _rx_item->get_spin_button()->addUnitTracker(_tracker);
+ _rx_item->set_all_tooltip_text(_("Horizontal radius of rounded corners"));
+ _rx_item->set_focus_widget(_desktop->canvas);
+ _rx_item->set_custom_numeric_menu_data(values, labels);
+ }
+
+ /* ry */
+ {
+ std::vector<Glib::ustring> labels = {_("not rounded"), "", "", "", "", "", "", "", ""};
+ std::vector<double> values = { 0.5, 1, 2, 3, 5, 10, 20, 50, 100};
+ auto ry_val = prefs->getDouble("/tools/shapes/rect/ry", 0);
+ ry_val = Quantity::convert(ry_val, "px", init_units);
+
+ _ry_adj = Gtk::Adjustment::create(ry_val, 0, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
+ _ry_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &RectToolbar::value_changed),
+ _ry_adj,
+ "ry",
+ &SPRect::setVisibleRy));
+ _tracker->addAdjustment(_ry_adj->gobj());
+ _ry_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("rect-ry", _("Ry:"), _ry_adj));
+ _ry_item->get_spin_button()->addUnitTracker(_tracker);
+ _ry_item->set_all_tooltip_text(_("Vertical radius of rounded corners"));
+ _ry_item->set_focus_widget(_desktop->canvas);
+ _ry_item->set_custom_numeric_menu_data(values, labels);
+ }
+
+ // add the units menu
+ auto unit_menu_ti = _tracker->create_tool_item(_("Units"), (""));
+
+ /* Reset */
+ {
+ _not_rounded = Gtk::manage(new Gtk::ToolButton(_("Not rounded")));
+ _not_rounded->set_tooltip_text(_("Make corners sharp"));
+ _not_rounded->set_icon_name(INKSCAPE_ICON("rectangle-make-corners-sharp"));
+ _not_rounded->signal_clicked().connect(sigc::mem_fun(*this, &RectToolbar::defaults));
+ _not_rounded->set_sensitive(true);
+ }
+
+ add(*_mode_item);
+ add(*_width_item);
+ add(*_height_item);
+ add(*_rx_item);
+ add(*_ry_item);
+ add(*unit_menu_ti);
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+ add(*_not_rounded);
+ show_all();
+
+ sensitivize();
+
+ _desktop->connectEventContextChanged(sigc::mem_fun(*this, &RectToolbar::watch_ec));
+}
+
+RectToolbar::~RectToolbar()
+{
+ if (_repr) { // remove old listener
+ _repr->removeListenerByData(this);
+ Inkscape::GC::release(_repr);
+ _repr = nullptr;
+ }
+ _changed.disconnect();
+}
+
+GtkWidget *
+RectToolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = new RectToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+void
+RectToolbar::value_changed(Glib::RefPtr<Gtk::Adjustment>& adj,
+ gchar const *value_name,
+ void (SPRect::*setter)(gdouble))
+{
+ Unit const *unit = _tracker->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble(Glib::ustring("/tools/shapes/rect/") + value_name,
+ Quantity::convert(adj->get_value(), unit, "px"));
+ }
+
+ // quit if run by the attr_changed listener
+ if (_freeze || _tracker->isUpdating()) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ bool modmade = false;
+ Inkscape::Selection *selection = _desktop->getSelection();
+ auto itemlist= selection->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ if (SP_IS_RECT(*i)) {
+ if (adj->get_value() != 0) {
+ (SP_RECT(*i)->*setter)(Quantity::convert(adj->get_value(), unit, "px"));
+ } else {
+ (*i)->removeAttribute(value_name);
+ }
+ modmade = true;
+ }
+ }
+
+ sensitivize();
+
+ if (modmade) {
+ DocumentUndo::done(_desktop->getDocument(), _("Change rectangle"), INKSCAPE_ICON("draw-rectangle"));
+ }
+
+ _freeze = false;
+}
+
+void
+RectToolbar::sensitivize()
+{
+ if (_rx_adj->get_value() == 0 && _ry_adj->get_value() == 0 && _single) { // only for a single selected rect (for now)
+ _not_rounded->set_sensitive(false);
+ } else {
+ _not_rounded->set_sensitive(true);
+ }
+}
+
+void
+RectToolbar::defaults()
+{
+ _rx_adj->set_value(0.0);
+ _ry_adj->set_value(0.0);
+
+ sensitivize();
+}
+
+void
+RectToolbar::watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec)
+{
+ // use of dynamic_cast<> seems wrong here -- we just need to check the current tool
+
+ if (dynamic_cast<Inkscape::UI::Tools::RectTool *>(ec)) {
+ Inkscape::Selection *sel = desktop->getSelection();
+
+ _changed = sel->connectChanged(sigc::mem_fun(*this, &RectToolbar::selection_changed));
+
+ // Synthesize an emission to trigger the update
+ selection_changed(sel);
+ } else {
+ if (_changed) {
+ _changed.disconnect();
+
+ if (_repr) { // remove old listener
+ _repr->removeListenerByData(this);
+ Inkscape::GC::release(_repr);
+ _repr = nullptr;
+ }
+ }
+ }
+}
+
+/**
+ * \param selection should not be NULL.
+ */
+void
+RectToolbar::selection_changed(Inkscape::Selection *selection)
+{
+ int n_selected = 0;
+ Inkscape::XML::Node *repr = nullptr;
+ SPItem *item = nullptr;
+
+ if (_repr) { // remove old listener
+ _item = nullptr;
+ _repr->removeListenerByData(this);
+ Inkscape::GC::release(_repr);
+ _repr = nullptr;
+ }
+
+ auto itemlist= selection->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ if (SP_IS_RECT(*i)) {
+ n_selected++;
+ item = *i;
+ repr = item->getRepr();
+ }
+ }
+
+ _single = false;
+
+ if (n_selected == 0) {
+ _mode_item->set_markup(_("<b>New:</b>"));
+ _width_item->set_sensitive(false);
+ _height_item->set_sensitive(false);
+ } else if (n_selected == 1) {
+ _mode_item->set_markup(_("<b>Change:</b>"));
+ _single = true;
+ _width_item->set_sensitive(true);
+ _height_item->set_sensitive(true);
+
+ if (repr) {
+ _repr = repr;
+ _item = item;
+ Inkscape::GC::anchor(_repr);
+ _repr->addListener(&rect_tb_repr_events, this);
+ _repr->synthesizeEvents(&rect_tb_repr_events, this);
+ }
+ } else {
+ // FIXME: implement averaging of all parameters for multiple selected
+ //gtk_label_set_markup(GTK_LABEL(l), _("<b>Average:</b>"));
+ _mode_item->set_markup(_("<b>Change:</b>"));
+ sensitivize();
+ }
+}
+
+void RectToolbar::event_attr_changed(Inkscape::XML::Node * /*repr*/, gchar const * /*name*/,
+ gchar const * /*old_value*/, gchar const * /*new_value*/,
+ bool /*is_interactive*/, gpointer data)
+{
+ auto toolbar = reinterpret_cast<RectToolbar*>(data);
+
+ // quit if run by the _changed callbacks
+ if (toolbar->_freeze) {
+ return;
+ }
+
+ // in turn, prevent callbacks from responding
+ toolbar->_freeze = true;
+
+ Unit const *unit = toolbar->_tracker->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+
+ if (toolbar->_item && SP_IS_RECT(toolbar->_item)) {
+ {
+ gdouble rx = SP_RECT(toolbar->_item)->getVisibleRx();
+ toolbar->_rx_adj->set_value(Quantity::convert(rx, "px", unit));
+ }
+
+ {
+ gdouble ry = SP_RECT(toolbar->_item)->getVisibleRy();
+ toolbar->_ry_adj->set_value(Quantity::convert(ry, "px", unit));
+ }
+
+ {
+ gdouble width = SP_RECT(toolbar->_item)->getVisibleWidth();
+ toolbar->_width_adj->set_value(Quantity::convert(width, "px", unit));
+ }
+
+ {
+ gdouble height = SP_RECT(toolbar->_item)->getVisibleHeight();
+ toolbar->_height_adj->set_value(Quantity::convert(height, "px", unit));
+ }
+ }
+
+ toolbar->sensitivize();
+ toolbar->_freeze = false;
+}
+
+}
+}
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
diff --git a/src/ui/toolbar/rect-toolbar.h b/src/ui/toolbar/rect-toolbar.h
new file mode 100644
index 0000000..4937673
--- /dev/null
+++ b/src/ui/toolbar/rect-toolbar.h
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_RECT_TOOLBAR_H
+#define SEEN_RECT_TOOLBAR_H
+
+/**
+ * @file
+ * Rect aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+#include <gtkmm/adjustment.h>
+
+class SPDesktop;
+class SPItem;
+class SPRect;
+
+namespace Gtk {
+class Toolbutton;
+}
+
+namespace Inkscape {
+class Selection;
+
+namespace XML {
+class Node;
+}
+
+namespace UI {
+namespace Tools {
+class ToolBase;
+}
+
+namespace Widget {
+class LabelToolItem;
+class SpinButtonToolItem;
+class UnitTracker;
+}
+
+namespace Toolbar {
+class RectToolbar : public Toolbar {
+private:
+ UI::Widget::UnitTracker *_tracker;
+
+ XML::Node *_repr;
+ SPItem *_item;
+
+ UI::Widget::LabelToolItem *_mode_item;
+ UI::Widget::SpinButtonToolItem *_width_item;
+ UI::Widget::SpinButtonToolItem *_height_item;
+ UI::Widget::SpinButtonToolItem *_rx_item;
+ UI::Widget::SpinButtonToolItem *_ry_item;
+ Gtk::ToolButton *_not_rounded;
+
+ Glib::RefPtr<Gtk::Adjustment> _width_adj;
+ Glib::RefPtr<Gtk::Adjustment> _height_adj;
+ Glib::RefPtr<Gtk::Adjustment> _rx_adj;
+ Glib::RefPtr<Gtk::Adjustment> _ry_adj;
+
+ bool _freeze;
+ bool _single;
+
+ void value_changed(Glib::RefPtr<Gtk::Adjustment>& adj,
+ gchar const *value_name,
+ void (SPRect::*setter)(gdouble));
+
+ void sensitivize();
+ void defaults();
+ void watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec);
+ void selection_changed(Inkscape::Selection *selection);
+
+ sigc::connection _changed;
+
+protected:
+ RectToolbar(SPDesktop *desktop);
+ ~RectToolbar() override;
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+
+ static void event_attr_changed(Inkscape::XML::Node *repr,
+ gchar const *name,
+ gchar const *old_value,
+ gchar const *new_value,
+ bool is_interactive,
+ gpointer data);
+
+};
+
+}
+}
+}
+
+#endif /* !SEEN_RECT_TOOLBAR_H */
diff --git a/src/ui/toolbar/select-toolbar.cpp b/src/ui/toolbar/select-toolbar.cpp
new file mode 100644
index 0000000..528b885
--- /dev/null
+++ b/src/ui/toolbar/select-toolbar.cpp
@@ -0,0 +1,631 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Selector aux toolbar
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2003-2005 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "select-toolbar.h"
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/adjustment.h>
+#include <gtkmm/separatortoolitem.h>
+
+#include <2geom/rect.h>
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "document.h"
+#include "selection.h"
+#include "message-stack.h"
+#include "selection-chemistry.h"
+
+#include "object/sp-item-transform.h"
+#include "object/sp-namedview.h"
+
+#include "ui/icon-names.h"
+#include "ui/widget/canvas.h" // Focus widget
+#include "ui/widget/combo-tool-item.h"
+#include "ui/widget/spin-button-tool-item.h"
+#include "ui/widget/spinbutton.h"
+#include "ui/widget/unit-tracker.h"
+
+#include "widgets/widget-sizes.h"
+
+using Inkscape::UI::Widget::UnitTracker;
+using Inkscape::Util::Unit;
+using Inkscape::Util::Quantity;
+using Inkscape::DocumentUndo;
+using Inkscape::Util::unit_table;
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+
+SelectToolbar::SelectToolbar(SPDesktop *desktop) :
+ Toolbar(desktop),
+ _tracker(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR)),
+ _lock_btn(Gtk::manage(new Gtk::ToggleToolButton())),
+ _select_touch_btn(Gtk::manage(new Gtk::ToggleToolButton())),
+ _transform_stroke_btn(Gtk::manage(new Gtk::ToggleToolButton())),
+ _transform_corners_btn(Gtk::manage(new Gtk::ToggleToolButton())),
+ _transform_gradient_btn(Gtk::manage(new Gtk::ToggleToolButton())),
+ _transform_pattern_btn(Gtk::manage(new Gtk::ToggleToolButton())),
+ _update(false),
+ _action_prefix("selector:toolbar:")
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ {
+ auto button = Gtk::manage(new Gtk::ToolButton(N_("Select Al_l")));
+ button->set_tooltip_text(N_("Select all objects"));
+ button->set_icon_name(INKSCAPE_ICON("edit-select-all"));
+ // Must use C API until GTK4
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(button->gobj()), "win.select-all");
+ add(*button);
+ }
+
+ {
+ auto button = Gtk::manage(new Gtk::ToolButton(N_("Select All in All La_yers")));
+ button->set_tooltip_text(N_("Select all objects in all visible and unlocked layers"));
+ button->set_icon_name(INKSCAPE_ICON("edit-select-all-layers"));
+ // Must use C API until GTK4
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(button->gobj()), "win.select-all-layers");
+ add(*button);
+ }
+
+ {
+ auto button = Gtk::manage(new Gtk::ToolButton(N_("D_eselect")));
+ button->set_tooltip_text(N_("Deselect any selected objects"));
+ button->set_icon_name(INKSCAPE_ICON("edit-select-none"));
+ // Must use C API until GTK4
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(button->gobj()), "win.select-none");
+ add(*button);
+ _context_items.push_back(button);
+ }
+
+ _select_touch_btn->set_label(_("Select by touch"));
+ _select_touch_btn->set_tooltip_text(_("Toggle selection box to select all touched objects."));
+ _select_touch_btn->set_icon_name(INKSCAPE_ICON("selection-touch"));
+ _select_touch_btn->set_active(prefs->getBool("/tools/select/touch_box", false));
+ _select_touch_btn->signal_toggled().connect(sigc::mem_fun(*this, &SelectToolbar::toggle_touch));
+
+ add(*_select_touch_btn);
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ auto button = Gtk::manage(new Gtk::ToolButton(N_("Rotate _90\xc2\xb0 CCW")));
+ button->set_tooltip_text(N_("Rotate selection 90\xc2\xb0 counter-clockwise"));
+ button->set_icon_name(INKSCAPE_ICON("object-rotate-left"));
+ // Must use C API until GTK4
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(button->gobj()), "app.object-rotate-90-ccw");
+ add(*button);
+ _context_items.push_back(button);
+ }
+
+ {
+ auto button = Gtk::manage(new Gtk::ToolButton(N_("Rotate _90\xc2\xb0 CW")));
+ button->set_tooltip_text(N_("Rotate selection 90\xc2\xb0 clockwise"));
+ button->set_icon_name(INKSCAPE_ICON("object-rotate-right"));
+ // Must use C API until GTK4
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(button->gobj()), "app.object-rotate-90-cw");
+ add(*button);
+ _context_items.push_back(button);
+ }
+
+ {
+ auto button = Gtk::manage(new Gtk::ToolButton(N_("Flip _Horizontal")));
+ button->set_tooltip_text(N_("Flip selected objects horizontally"));
+ button->set_icon_name(INKSCAPE_ICON("object-flip-horizontal"));
+ // Must use C API until GTK4
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(button->gobj()), "app.object-flip-horizontal");
+ add(*button);
+ _context_items.push_back(button);
+ }
+
+ {
+ auto button = Gtk::manage(new Gtk::ToolButton(N_("Flip _Vertical")));
+ button->set_tooltip_text(N_("Flip selected objects vertically"));
+ button->set_icon_name(INKSCAPE_ICON("object-flip-vertical"));
+ // Must use C API until GTK4
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(button->gobj()), "app.object-flip-vertical");
+ add(*button);
+ _context_items.push_back(button);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ auto button = Gtk::manage(new Gtk::ToolButton(N_("Raise to _Top")));
+ button->set_tooltip_text(N_("Raise selection to top"));
+ button->set_icon_name(INKSCAPE_ICON("selection-top"));
+ // Must use C API until GTK4
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(button->gobj()), "app.selection-top");
+ add(*button);
+ _context_items.push_back(button);
+ }
+
+ {
+ auto button = Gtk::manage(new Gtk::ToolButton(N_("_Raise")));
+ button->set_tooltip_text(N_("Raise selection one step"));
+ button->set_icon_name(INKSCAPE_ICON("selection-raise"));
+ // Must use C API until GTK4
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(button->gobj()), "app.selection-raise");
+ add(*button);
+ _context_items.push_back(button);
+ }
+
+ {
+ auto button = Gtk::manage(new Gtk::ToolButton(N_("_Lower")));
+ button->set_tooltip_text(N_("Lower selection one step"));
+ button->set_icon_name(INKSCAPE_ICON("selection-lower"));
+ // Must use C API until GTK4
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(button->gobj()), "app.selection-lower");
+ add(*button);
+ _context_items.push_back(button);
+ }
+
+ {
+ auto button = Gtk::manage(new Gtk::ToolButton(N_("Lower to _Bottom")));
+ button->set_tooltip_text(N_("Lower selection to bottom"));
+ button->set_icon_name(INKSCAPE_ICON("selection-bottom"));
+ // Must use C API until GTK4
+ gtk_actionable_set_action_name(GTK_ACTIONABLE(button->gobj()), "app.selection-bottom");
+ add(*button);
+ _context_items.push_back(button);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ _tracker->addUnit(unit_table.getUnit("%"));
+ _tracker->setActiveUnit( desktop->getNamedView()->display_units );
+
+ // x-value control
+ auto x_val = prefs->getDouble("/tools/select/X", 0.0);
+ _adj_x = Gtk::Adjustment::create(x_val, -1e6, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
+ _adj_x->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &SelectToolbar::any_value_changed), _adj_x));
+ _tracker->addAdjustment(_adj_x->gobj());
+
+ auto x_btn = Gtk::manage(new UI::Widget::SpinButtonToolItem("select-x",
+ C_("Select toolbar", "X:"),
+ _adj_x,
+ SPIN_STEP, 3));
+ x_btn->get_spin_button()->addUnitTracker(_tracker.get());
+ x_btn->set_focus_widget(_desktop->getCanvas());
+ x_btn->set_all_tooltip_text(C_("Select toolbar", "Horizontal coordinate of selection"));
+ _context_items.push_back(x_btn);
+ add(*x_btn);
+
+ // y-value control
+ auto y_val = prefs->getDouble("/tools/select/Y", 0.0);
+ _adj_y = Gtk::Adjustment::create(y_val, -1e6, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
+ _adj_y->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &SelectToolbar::any_value_changed), _adj_y));
+ _tracker->addAdjustment(_adj_y->gobj());
+
+ auto y_btn = Gtk::manage(new UI::Widget::SpinButtonToolItem("select-y",
+ C_("Select toolbar", "Y:"),
+ _adj_y,
+ SPIN_STEP, 3));
+ y_btn->get_spin_button()->addUnitTracker(_tracker.get());
+ y_btn->set_focus_widget(_desktop->getCanvas());
+ y_btn->set_all_tooltip_text(C_("Select toolbar", "Vertical coordinate of selection"));
+ _context_items.push_back(y_btn);
+ add(*y_btn);
+
+ // width-value control
+ auto w_val = prefs->getDouble("/tools/select/width", 0.0);
+ _adj_w = Gtk::Adjustment::create(w_val, 0.0, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
+ _adj_w->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &SelectToolbar::any_value_changed), _adj_w));
+ _tracker->addAdjustment(_adj_w->gobj());
+
+ auto w_btn = Gtk::manage(new UI::Widget::SpinButtonToolItem("select-width",
+ C_("Select toolbar", "W:"),
+ _adj_w,
+ SPIN_STEP, 3));
+ w_btn->get_spin_button()->addUnitTracker(_tracker.get());
+ w_btn->set_focus_widget(_desktop->getCanvas());
+ w_btn->set_all_tooltip_text(C_("Select toolbar", "Width of selection"));
+ _context_items.push_back(w_btn);
+ add(*w_btn);
+
+ // lock toggle
+ _lock_btn->set_label(_("Lock width and height"));
+ _lock_btn->set_tooltip_text(_("When locked, change both width and height by the same proportion"));
+ _lock_btn->set_icon_name(INKSCAPE_ICON("object-unlocked"));
+ _lock_btn->signal_toggled().connect(sigc::mem_fun(*this, &SelectToolbar::toggle_lock));
+ _lock_btn->set_name("lock");
+ add(*_lock_btn);
+
+ // height-value control
+ auto h_val = prefs->getDouble("/tools/select/height", 0.0);
+ _adj_h = Gtk::Adjustment::create(h_val, 0.0, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
+ _adj_h->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &SelectToolbar::any_value_changed), _adj_h));
+ _tracker->addAdjustment(_adj_h->gobj());
+
+ auto h_btn = Gtk::manage(new UI::Widget::SpinButtonToolItem("select-height",
+ C_("Select toolbar", "H:"),
+ _adj_h,
+ SPIN_STEP, 3));
+ h_btn->get_spin_button()->addUnitTracker(_tracker.get());
+ h_btn->set_focus_widget(_desktop->getCanvas());
+ h_btn->set_all_tooltip_text(C_("Select toolbar", "Height of selection"));
+ _context_items.push_back(h_btn);
+ add(*h_btn);
+
+ // units menu
+ auto unit_menu = _tracker->create_tool_item(_("Units"), ("") );
+ add(*unit_menu);
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ _transform_stroke_btn->set_label(_("Scale stroke width"));
+ _transform_stroke_btn->set_tooltip_text(_("When scaling objects, scale the stroke width by the same proportion"));
+ _transform_stroke_btn->set_icon_name(INKSCAPE_ICON("transform-affect-stroke"));
+ _transform_stroke_btn->set_active(prefs->getBool("/options/transform/stroke", true));
+ _transform_stroke_btn->signal_toggled().connect(sigc::mem_fun(*this, &SelectToolbar::toggle_stroke));
+ add(*_transform_stroke_btn);
+
+ _transform_corners_btn->set_label(_("Scale rounded corners"));
+ _transform_corners_btn->set_tooltip_text(_("When scaling rectangles, scale the radii of rounded corners"));
+ _transform_corners_btn->set_icon_name(INKSCAPE_ICON("transform-affect-rounded-corners"));
+ _transform_corners_btn->set_active(prefs->getBool("/options/transform/rectcorners", true));
+ _transform_corners_btn->signal_toggled().connect(sigc::mem_fun(*this, &SelectToolbar::toggle_corners));
+ add(*_transform_corners_btn);
+
+ _transform_gradient_btn->set_label(_("Move gradients"));
+ _transform_gradient_btn->set_tooltip_text(_("Move gradients (in fill or stroke) along with the objects"));
+ _transform_gradient_btn->set_icon_name(INKSCAPE_ICON("transform-affect-gradient"));
+ _transform_gradient_btn->set_active(prefs->getBool("/options/transform/gradient", true));
+ _transform_gradient_btn->signal_toggled().connect(sigc::mem_fun(*this, &SelectToolbar::toggle_gradient));
+ add(*_transform_gradient_btn);
+
+ _transform_pattern_btn->set_label(_("Move patterns"));
+ _transform_pattern_btn->set_tooltip_text(_("Move patterns (in fill or stroke) along with the objects"));
+ _transform_pattern_btn->set_icon_name(INKSCAPE_ICON("transform-affect-pattern"));
+ _transform_pattern_btn->set_active(prefs->getBool("/options/transform/pattern", true));
+ _transform_pattern_btn->signal_toggled().connect(sigc::mem_fun(*this, &SelectToolbar::toggle_pattern));
+ add(*_transform_pattern_btn);
+
+ assert(desktop);
+ auto *selection = desktop->getSelection();
+
+ // Force update when selection changes.
+ _connections.emplace_back( //
+ selection->connectModified(sigc::mem_fun(*this, &SelectToolbar::on_inkscape_selection_modified)));
+ _connections.emplace_back(
+ selection->connectChanged(sigc::mem_fun(*this, &SelectToolbar::on_inkscape_selection_changed)));
+
+ // Update now.
+ layout_widget_update(selection);
+
+ for (auto item : _context_items) {
+ if ( item->is_sensitive() ) {
+ item->set_sensitive(false);
+ }
+ }
+
+ show_all();
+}
+
+void SelectToolbar::on_unrealize()
+{
+ for (auto &conn : _connections) {
+ conn.disconnect();
+ }
+
+ parent_type::on_unrealize();
+}
+
+GtkWidget *
+SelectToolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = new SelectToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+void
+SelectToolbar::any_value_changed(Glib::RefPtr<Gtk::Adjustment>& adj)
+{
+ if (_update) {
+ return;
+ }
+
+ if ( !_tracker || _tracker->isUpdating() ) {
+ /*
+ * When only units are being changed, don't treat changes
+ * to adjuster values as object changes.
+ */
+ return;
+ }
+ _update = true;
+
+ SPDesktop *desktop = _desktop;
+ Inkscape::Selection *selection = desktop->getSelection();
+ SPDocument *document = desktop->getDocument();
+
+ document->ensureUpToDate ();
+
+ Geom::OptRect bbox_vis = selection->visualBounds();
+ Geom::OptRect bbox_geom = selection->geometricBounds();
+ Geom::OptRect bbox_user = selection->preferredBounds();
+
+ if ( !bbox_user ) {
+ _update = false;
+ return;
+ }
+
+ Unit const *unit = _tracker->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+
+ gdouble old_w = bbox_user->dimensions()[Geom::X];
+ gdouble old_h = bbox_user->dimensions()[Geom::Y];
+ gdouble new_w, new_h, new_x, new_y = 0;
+
+ if (unit->type == Inkscape::Util::UNIT_TYPE_LINEAR) {
+ new_w = Quantity::convert(_adj_w->get_value(), unit, "px");
+ new_h = Quantity::convert(_adj_h->get_value(), unit, "px");
+ new_x = Quantity::convert(_adj_x->get_value(), unit, "px");
+ new_y = Quantity::convert(_adj_y->get_value(), unit, "px");
+
+ } else {
+ gdouble old_x = bbox_user->min()[Geom::X] + (old_w * selection->anchor_x);
+ gdouble old_y = bbox_user->min()[Geom::Y] + (old_h * selection->anchor_y);
+
+ new_x = old_x * (_adj_x->get_value() / 100 / unit->factor);
+ new_y = old_y * (_adj_y->get_value() / 100 / unit->factor);
+ new_w = old_w * (_adj_w->get_value() / 100 / unit->factor);
+ new_h = old_h * (_adj_h->get_value() / 100 / unit->factor);
+ }
+
+ // Adjust depending on the selected anchor.
+ gdouble x0 = (new_x - (old_w * selection->anchor_x)) - ((new_w - old_w) * selection->anchor_x);
+ gdouble y0 = (new_y - (old_h * selection->anchor_y)) - ((new_h - old_h) * selection->anchor_y);
+
+ gdouble x1 = x0 + new_w;
+ gdouble xrel = new_w / old_w;
+ gdouble y1 = y0 + new_h;
+ gdouble yrel = new_h / old_h;
+
+ // Keep proportions if lock is on
+ if ( _lock_btn->get_active() ) {
+ if (adj == _adj_h) {
+ x1 = x0 + yrel * bbox_user->dimensions()[Geom::X];
+ } else if (adj == _adj_w) {
+ y1 = y0 + xrel * bbox_user->dimensions()[Geom::Y];
+ }
+ }
+
+ // scales and moves, in px
+ double mh = fabs(x0 - bbox_user->min()[Geom::X]);
+ double sh = fabs(x1 - bbox_user->max()[Geom::X]);
+ double mv = fabs(y0 - bbox_user->min()[Geom::Y]);
+ double sv = fabs(y1 - bbox_user->max()[Geom::Y]);
+
+ // unless the unit is %, convert the scales and moves to the unit
+ if (unit->type == Inkscape::Util::UNIT_TYPE_LINEAR) {
+ mh = Quantity::convert(mh, "px", unit);
+ sh = Quantity::convert(sh, "px", unit);
+ mv = Quantity::convert(mv, "px", unit);
+ sv = Quantity::convert(sv, "px", unit);
+ }
+
+ char const *const actionkey = get_action_key(mh, sh, mv, sv);
+
+ if (actionkey != nullptr) {
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool transform_stroke = prefs->getBool("/options/transform/stroke", true);
+ bool preserve = prefs->getBool("/options/preservetransform/value", false);
+
+ Geom::Affine scaler;
+ if (prefs->getInt("/tools/bounding_box") == 0) { // SPItem::VISUAL_BBOX
+ scaler = get_scale_transform_for_variable_stroke (*bbox_vis, *bbox_geom, transform_stroke, preserve, x0, y0, x1, y1);
+ } else {
+ // 1) We could have use the newer get_scale_transform_for_variable_stroke() here, but to avoid regressions
+ // we'll just use the old get_scale_transform_for_uniform_stroke() for now.
+ // 2) get_scale_transform_for_uniform_stroke() is intended for visual bounding boxes, not geometrical ones!
+ // we'll trick it into using a geometric bounding box though, by setting the stroke width to zero
+ scaler = get_scale_transform_for_uniform_stroke (*bbox_geom, 0, 0, false, false, x0, y0, x1, y1);
+ }
+
+ selection->applyAffine(scaler);
+ DocumentUndo::maybeDone(document, actionkey, _("Transform by toolbar"), INKSCAPE_ICON("tool-pointer"));
+ }
+
+ _update = false;
+}
+
+void
+SelectToolbar::layout_widget_update(Inkscape::Selection *sel)
+{
+ if (_update) {
+ return;
+ }
+
+ _update = true;
+ using Geom::X;
+ using Geom::Y;
+ if ( sel && !sel->isEmpty() ) {
+ Geom::OptRect const bbox(sel->preferredBounds());
+ if ( bbox ) {
+ Unit const *unit = _tracker->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+
+ auto width = bbox->dimensions()[X];
+ auto height = bbox->dimensions()[Y];
+ auto x = bbox->min()[X] + (width * sel->anchor_x);
+ auto y = bbox->min()[Y] + (height * sel->anchor_y);
+
+ if (unit->type == Inkscape::Util::UNIT_TYPE_DIMENSIONLESS) {
+ double const val = unit->factor * 100;
+ _adj_x->set_value(val);
+ _adj_y->set_value(val);
+ _adj_w->set_value(val);
+ _adj_h->set_value(val);
+ _tracker->setFullVal( _adj_x->gobj(), x );
+ _tracker->setFullVal( _adj_y->gobj(), y );
+ _tracker->setFullVal( _adj_w->gobj(), width );
+ _tracker->setFullVal( _adj_h->gobj(), height );
+ } else {
+ _adj_x->set_value(Quantity::convert(x, "px", unit));
+ _adj_y->set_value(Quantity::convert(y, "px", unit));
+ _adj_w->set_value(Quantity::convert(width, "px", unit));
+ _adj_h->set_value(Quantity::convert(height, "px", unit));
+ }
+ }
+ }
+
+ _update = false;
+}
+
+void
+SelectToolbar::on_inkscape_selection_modified(Inkscape::Selection *selection, guint flags)
+{
+ assert(_desktop->getSelection() == selection);
+ if ((flags & (SP_OBJECT_MODIFIED_FLAG |
+ SP_OBJECT_PARENT_MODIFIED_FLAG |
+ SP_OBJECT_CHILD_MODIFIED_FLAG )))
+ {
+ layout_widget_update(selection);
+ }
+}
+
+void
+SelectToolbar::on_inkscape_selection_changed(Inkscape::Selection *selection)
+{
+ assert(_desktop->getSelection() == selection);
+ {
+ bool setActive = (selection && !selection->isEmpty());
+
+ for (auto item : _context_items) {
+ if ( setActive != item->get_sensitive() ) {
+ item->set_sensitive(setActive);
+ }
+ }
+
+ layout_widget_update(selection);
+ _selection_seq++;
+ }
+}
+
+char const *SelectToolbar::get_action_key(double mh, double sh, double mv, double sv)
+{
+ // do the action only if one of the scales/moves is greater than half the last significant
+ // digit in the spinbox (currently spinboxes have 3 fractional digits, so that makes 0.0005). If
+ // the value was changed by the user, the difference will be at least that much; otherwise it's
+ // just rounding difference between the spinbox value and actual value, so no action is
+ // performed
+ double const threshold = 5e-4;
+ char const *const action = ( mh > threshold ? "move:horizontal:" :
+ sh > threshold ? "scale:horizontal:" :
+ mv > threshold ? "move:vertical:" :
+ sv > threshold ? "scale:vertical:" : nullptr );
+ if (!action) {
+ return nullptr;
+ }
+ _action_key = _action_prefix + action + std::to_string(_selection_seq);
+ return _action_key.c_str();
+}
+
+void
+SelectToolbar::toggle_lock() {
+ // use this roundabout way of changing image to make sure its size is preserved
+ auto btn = static_cast<Gtk::ToggleButton*>(_lock_btn->get_child());
+ auto image = static_cast<Gtk::Image*>(btn->get_child());
+ if (!image) {
+ g_warning("No GTK image in toolbar button 'lock'");
+ return;
+ }
+ auto size = image->get_pixel_size();
+
+ if ( _lock_btn->get_active() ) {
+ image->set_from_icon_name("object-locked", Gtk::ICON_SIZE_BUTTON);
+ } else {
+ image->set_from_icon_name("object-unlocked", Gtk::ICON_SIZE_BUTTON);
+ }
+ image->set_pixel_size(size);
+}
+
+void
+SelectToolbar::toggle_touch()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/select/touch_box", _select_touch_btn->get_active());
+}
+
+void
+SelectToolbar::toggle_stroke()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool active = _transform_stroke_btn->get_active();
+ prefs->setBool("/options/transform/stroke", active);
+ if ( active ) {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Now <b>stroke width</b> is <b>scaled</b> when objects are scaled."));
+ } else {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Now <b>stroke width</b> is <b>not scaled</b> when objects are scaled."));
+ }
+}
+
+void
+SelectToolbar::toggle_corners()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool active = _transform_corners_btn->get_active();
+ prefs->setBool("/options/transform/rectcorners", active);
+ if ( active ) {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Now <b>rounded rectangle corners</b> are <b>scaled</b> when rectangles are scaled."));
+ } else {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Now <b>rounded rectangle corners</b> are <b>not scaled</b> when rectangles are scaled."));
+ }
+}
+
+void
+SelectToolbar::toggle_gradient()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool active = _transform_gradient_btn->get_active();
+ prefs->setBool("/options/transform/gradient", active);
+ if ( active ) {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Now <b>gradients</b> are <b>transformed</b> along with their objects when those are transformed (moved, scaled, rotated, or skewed)."));
+ } else {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Now <b>gradients</b> remain <b>fixed</b> when objects are transformed (moved, scaled, rotated, or skewed)."));
+ }
+}
+
+void
+SelectToolbar::toggle_pattern()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool active = _transform_pattern_btn->get_active();
+ prefs->setInt("/options/transform/pattern", active);
+ if ( active ) {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Now <b>patterns</b> are <b>transformed</b> along with their objects when those are transformed (moved, scaled, rotated, or skewed)."));
+ } else {
+ _desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Now <b>patterns</b> remain <b>fixed</b> when objects are transformed (moved, scaled, rotated, or skewed)."));
+ }
+}
+
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/ui/toolbar/select-toolbar.h b/src/ui/toolbar/select-toolbar.h
new file mode 100644
index 0000000..3d8a2d6
--- /dev/null
+++ b/src/ui/toolbar/select-toolbar.h
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SELECT_TOOLBAR_H
+#define SEEN_SELECT_TOOLBAR_H
+
+/** \file
+ * Selector aux toolbar
+ */
+/*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <bulia@dr.com>
+ *
+ * Copyright (C) 2003 authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+class SPDesktop;
+
+namespace Inkscape {
+class Selection;
+
+namespace UI {
+
+namespace Widget {
+class UnitTracker;
+}
+
+namespace Toolbar {
+
+class SelectToolbar : public Toolbar {
+ using parent_type = Toolbar;
+
+private:
+ std::unique_ptr<UI::Widget::UnitTracker> _tracker;
+
+ Glib::RefPtr<Gtk::Adjustment> _adj_x;
+ Glib::RefPtr<Gtk::Adjustment> _adj_y;
+ Glib::RefPtr<Gtk::Adjustment> _adj_w;
+ Glib::RefPtr<Gtk::Adjustment> _adj_h;
+ Gtk::ToggleToolButton *_lock_btn;
+ Gtk::ToggleToolButton *_select_touch_btn;
+ Gtk::ToggleToolButton *_transform_stroke_btn;
+ Gtk::ToggleToolButton *_transform_corners_btn;
+ Gtk::ToggleToolButton *_transform_gradient_btn;
+ Gtk::ToggleToolButton *_transform_pattern_btn;
+
+ std::vector<Gtk::ToolItem *> _context_items;
+
+ std::vector<sigc::connection> _connections;
+
+ bool _update;
+ std::uint64_t _selection_seq = 0; ///< Increment to prevent coalescing of consecutive undo events
+ std::string _action_key;
+ std::string const _action_prefix;
+
+ char const *get_action_key(double mh, double sh, double mv, double sv);
+ void any_value_changed(Glib::RefPtr<Gtk::Adjustment>& adj);
+ void layout_widget_update(Inkscape::Selection *sel);
+ void on_inkscape_selection_modified(Inkscape::Selection *selection, guint flags);
+ void on_inkscape_selection_changed(Inkscape::Selection *selection);
+ void toggle_lock();
+ void toggle_touch();
+ void toggle_stroke();
+ void toggle_corners();
+ void toggle_gradient();
+ void toggle_pattern();
+
+protected:
+ SelectToolbar(SPDesktop *desktop);
+
+ void on_unrealize() override;
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+};
+
+}
+}
+}
+#endif /* !SEEN_SELECT_TOOLBAR_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/spiral-toolbar.cpp b/src/ui/toolbar/spiral-toolbar.cpp
new file mode 100644
index 0000000..73ea79e
--- /dev/null
+++ b/src/ui/toolbar/spiral-toolbar.cpp
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Spiral aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "spiral-toolbar.h"
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/separatortoolitem.h>
+#include <gtkmm/toolbutton.h>
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "selection.h"
+
+#include "object/sp-spiral.h"
+
+#include "ui/icon-names.h"
+#include "ui/widget/canvas.h"
+#include "ui/widget/label-tool-item.h"
+#include "ui/widget/spin-button-tool-item.h"
+
+#include "xml/node-event-vector.h"
+
+using Inkscape::DocumentUndo;
+
+static Inkscape::XML::NodeEventVector spiral_tb_repr_events = {
+ nullptr, /* child_added */
+ nullptr, /* child_removed */
+ Inkscape::UI::Toolbar::SpiralToolbar::event_attr_changed,
+ nullptr, /* content_changed */
+ nullptr /* order_changed */
+};
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+SpiralToolbar::SpiralToolbar(SPDesktop *desktop) :
+ Toolbar(desktop),
+ _freeze(false),
+ _repr(nullptr)
+{
+ auto prefs = Inkscape::Preferences::get();
+
+ {
+ _mode_item = Gtk::manage(new UI::Widget::LabelToolItem(_("<b>New:</b>")));
+ _mode_item->set_use_markup(true);
+ add(*_mode_item);
+ }
+
+ /* Revolution */
+ {
+ std::vector<Glib::ustring> labels = {_("just a curve"), "", _("one full revolution"), "", "", "", "", "", "", ""};
+ std::vector<double> values = { 0.01, 0.5, 1, 2, 3, 5, 10, 20, 50, 100};
+ auto revolution_val = prefs->getDouble("/tools/shapes/spiral/revolution", 3.0);
+ _revolution_adj = Gtk::Adjustment::create(revolution_val, 0.01, 1024.0, 0.1, 1.0);
+ _revolution_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("spiral-revolutions", _("Turns:"), _revolution_adj, 1, 2));
+ _revolution_item->set_tooltip_text(_("Number of revolutions"));
+ _revolution_item->set_custom_numeric_menu_data(values, labels);
+ _revolution_item->set_focus_widget(desktop->getCanvas());
+ _revolution_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &SpiralToolbar::value_changed),
+ _revolution_adj, "revolution"));
+ add(*_revolution_item);
+ }
+
+ /* Expansion */
+ {
+ std::vector<Glib::ustring> labels = {_("circle"), _("edge is much denser"), _("edge is denser"), _("even"), _("center is denser"), _("center is much denser"), ""};
+ std::vector<double> values = { 0, 0.1, 0.5, 1, 1.5, 5, 20};
+ auto expansion_val = prefs->getDouble("/tools/shapes/spiral/expansion", 1.0);
+ _expansion_adj = Gtk::Adjustment::create(expansion_val, 0.0, 1000.0, 0.01, 1.0);
+
+ _expansion_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("spiral-expansion", _("Divergence:"), _expansion_adj));
+ _expansion_item->set_tooltip_text(_("How much denser/sparser are outer revolutions; 1 = uniform"));
+ _expansion_item->set_custom_numeric_menu_data(values, labels);
+ _expansion_item->set_focus_widget(desktop->getCanvas());
+ _expansion_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &SpiralToolbar::value_changed),
+ _expansion_adj, "expansion"));
+ add(*_expansion_item);
+ }
+
+ /* T0 */
+ {
+ std::vector<Glib::ustring> labels = {_("starts from center"), _("starts mid-way"), _("starts near edge")};
+ std::vector<double> values = { 0, 0.5, 0.9};
+ auto t0_val = prefs->getDouble("/tools/shapes/spiral/t0", 0.0);
+ _t0_adj = Gtk::Adjustment::create(t0_val, 0.0, 0.999, 0.01, 1.0);
+ _t0_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("spiral-t0", _("Inner radius:"), _t0_adj));
+ _t0_item->set_tooltip_text(_("Radius of the innermost revolution (relative to the spiral size)"));
+ _t0_item->set_custom_numeric_menu_data(values, labels);
+ _t0_item->set_focus_widget(desktop->getCanvas());
+ _t0_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &SpiralToolbar::value_changed),
+ _t0_adj, "t0"));
+ add(*_t0_item);
+ }
+
+ add(*Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Reset */
+ {
+ _reset_item = Gtk::manage(new Gtk::ToolButton(_("Defaults")));
+ _reset_item->set_icon_name(INKSCAPE_ICON("edit-clear"));
+ _reset_item->set_tooltip_text(_("Reset shape parameters to defaults (use Inkscape Preferences > Tools to change defaults)"));
+ _reset_item->signal_clicked().connect(sigc::mem_fun(*this, &SpiralToolbar::defaults));
+ add(*_reset_item);
+ }
+
+ _connection.reset(new sigc::connection(
+ desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &SpiralToolbar::selection_changed))));
+
+ show_all();
+}
+
+SpiralToolbar::~SpiralToolbar()
+{
+ if(_repr) {
+ _repr->removeListenerByData(this);
+ GC::release(_repr);
+ _repr = nullptr;
+ }
+
+ if(_connection) {
+ _connection->disconnect();
+ }
+}
+
+GtkWidget *
+SpiralToolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = new SpiralToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+void
+SpiralToolbar::value_changed(Glib::RefPtr<Gtk::Adjustment> &adj,
+ Glib::ustring const &value_name)
+{
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble("/tools/shapes/spiral/" + value_name,
+ adj->get_value());
+ }
+
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ gchar* namespaced_name = g_strconcat("sodipodi:", value_name.data(), nullptr);
+
+ bool modmade = false;
+ auto itemlist= _desktop->getSelection()->items();
+ for(auto i=itemlist.begin();i!=itemlist.end(); ++i){
+ SPItem *item = *i;
+ if (SP_IS_SPIRAL(item)) {
+ Inkscape::XML::Node *repr = item->getRepr();
+ repr->setAttributeSvgDouble(namespaced_name, adj->get_value() );
+ item->updateRepr();
+ modmade = true;
+ }
+ }
+
+ g_free(namespaced_name);
+
+ if (modmade) {
+ DocumentUndo::done(_desktop->getDocument(), _("Change spiral"), INKSCAPE_ICON("draw-spiral"));
+ }
+
+ _freeze = false;
+}
+
+void
+SpiralToolbar::defaults()
+{
+ // fixme: make settable
+ gdouble rev = 3;
+ gdouble exp = 1.0;
+ gdouble t0 = 0.0;
+
+ _revolution_adj->set_value(rev);
+ _expansion_adj->set_value(exp);
+ _t0_adj->set_value(t0);
+
+ if(_desktop->getCanvas()) _desktop->getCanvas()->grab_focus();
+}
+
+void
+SpiralToolbar::selection_changed(Inkscape::Selection *selection)
+{
+ int n_selected = 0;
+ Inkscape::XML::Node *repr = nullptr;
+
+ if ( _repr ) {
+ _repr->removeListenerByData(this);
+ GC::release(_repr);
+ _repr = nullptr;
+ }
+
+ auto itemlist= selection->items();
+ for(auto i=itemlist.begin();i!=itemlist.end(); ++i){
+ SPItem *item = *i;
+ if (SP_IS_SPIRAL(item)) {
+ n_selected++;
+ repr = item->getRepr();
+ }
+ }
+
+ if (n_selected == 0) {
+ _mode_item->set_markup(_("<b>New:</b>"));
+ } else if (n_selected == 1) {
+ _mode_item->set_markup(_("<b>Change:</b>"));
+
+ if (repr) {
+ _repr = repr;
+ Inkscape::GC::anchor(_repr);
+ _repr->addListener(&spiral_tb_repr_events, this);
+ _repr->synthesizeEvents(&spiral_tb_repr_events, this);
+ }
+ } else {
+ // FIXME: implement averaging of all parameters for multiple selected
+ //gtk_label_set_markup(GTK_LABEL(l), _("<b>Average:</b>"));
+ _mode_item->set_markup(_("<b>Change:</b>"));
+ }
+}
+
+void
+SpiralToolbar::event_attr_changed(Inkscape::XML::Node *repr,
+ gchar const * /*name*/,
+ gchar const * /*old_value*/,
+ gchar const * /*new_value*/,
+ bool /*is_interactive*/,
+ gpointer data)
+{
+ auto toolbar = reinterpret_cast<SpiralToolbar *>(data);
+
+ // quit if run by the _changed callbacks
+ if (toolbar->_freeze) {
+ return;
+ }
+
+ // in turn, prevent callbacks from responding
+ toolbar->_freeze = true;
+
+ double revolution = repr->getAttributeDouble("sodipodi:revolution", 3.0);
+ toolbar->_revolution_adj->set_value(revolution);
+
+ double expansion = repr->getAttributeDouble("sodipodi:expansion", 1.0);
+ toolbar->_expansion_adj->set_value(expansion);
+
+ double t0 = repr->getAttributeDouble("sodipodi:t0", 0.0);
+ toolbar->_t0_adj->set_value(t0);
+
+ toolbar->_freeze = false;
+}
+
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/spiral-toolbar.h b/src/ui/toolbar/spiral-toolbar.h
new file mode 100644
index 0000000..9c27eb5
--- /dev/null
+++ b/src/ui/toolbar/spiral-toolbar.h
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SPIRAL_TOOLBAR_H
+#define SEEN_SPIRAL_TOOLBAR_H
+
+/**
+ * @file
+ * Spiral aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+#include <gtkmm/adjustment.h>
+
+class SPDesktop;
+
+namespace Gtk {
+class ToolButton;
+}
+
+namespace Inkscape {
+class Selection;
+
+namespace XML {
+class Node;
+}
+
+namespace UI {
+namespace Widget {
+class LabelToolItem;
+class SpinButtonToolItem;
+}
+
+namespace Toolbar {
+class SpiralToolbar : public Toolbar {
+private:
+ UI::Widget::LabelToolItem *_mode_item;
+
+ UI::Widget::SpinButtonToolItem *_revolution_item;
+ UI::Widget::SpinButtonToolItem *_expansion_item;
+ UI::Widget::SpinButtonToolItem *_t0_item;
+
+ Gtk::ToolButton *_reset_item;
+
+ Glib::RefPtr<Gtk::Adjustment> _revolution_adj;
+ Glib::RefPtr<Gtk::Adjustment> _expansion_adj;
+ Glib::RefPtr<Gtk::Adjustment> _t0_adj;
+
+ bool _freeze;
+
+ XML::Node *_repr;
+
+ void value_changed(Glib::RefPtr<Gtk::Adjustment> &adj,
+ Glib::ustring const &value_name);
+ void defaults();
+ void selection_changed(Inkscape::Selection *selection);
+
+ std::unique_ptr<sigc::connection> _connection;
+
+protected:
+ SpiralToolbar(SPDesktop *desktop);
+ ~SpiralToolbar() override;
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+
+ static void event_attr_changed(Inkscape::XML::Node *repr,
+ gchar const *name,
+ gchar const *old_value,
+ gchar const *new_value,
+ bool is_interactive,
+ gpointer data);
+};
+}
+}
+}
+
+#endif /* !SEEN_SPIRAL_TOOLBAR_H */
diff --git a/src/ui/toolbar/spray-toolbar.cpp b/src/ui/toolbar/spray-toolbar.cpp
new file mode 100644
index 0000000..de6939a
--- /dev/null
+++ b/src/ui/toolbar/spray-toolbar.cpp
@@ -0,0 +1,541 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Spray aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ * Jabiertxo Arraiza <jabier.arraiza@marker.es>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2015 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "spray-toolbar.h"
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/radiotoolbutton.h>
+#include <gtkmm/separatortoolitem.h>
+
+#include "desktop.h"
+
+#include "ui/icon-names.h"
+#include "ui/simple-pref-pusher.h"
+
+#include "ui/dialog/clonetiler.h"
+#include "ui/dialog/dialog-container.h"
+#include "ui/dialog/dialog-base.h"
+
+#include "ui/widget/canvas.h"
+#include "ui/widget/spin-button-tool-item.h"
+
+// Disabled in 0.91 because of Bug #1274831 (crash, spraying an object
+// with the mode: spray object in single path)
+// Please enable again when working on 1.0
+#define ENABLE_SPRAY_MODE_SINGLE_PATH
+
+Inkscape::UI::Dialog::CloneTiler *get_clone_tiler_panel(SPDesktop *desktop)
+{
+ Inkscape::UI::Dialog::DialogBase *dialog = desktop->getContainer()->get_dialog("CloneTiler");
+ if (!dialog) {
+ desktop->getContainer()->new_dialog("CloneTiler");
+ return dynamic_cast<Inkscape::UI::Dialog::CloneTiler *>(
+ desktop->getContainer()->get_dialog("CloneTiler"));
+ }
+ return dynamic_cast<Inkscape::UI::Dialog::CloneTiler *>(dialog);
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+SprayToolbar::SprayToolbar(SPDesktop *desktop) :
+ Toolbar(desktop)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ /* Mode */
+ {
+ add_label(_("Mode:"));
+
+ Gtk::RadioToolButton::Group mode_group;
+
+ auto copy_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Spray with copies")));
+ copy_mode_btn->set_tooltip_text(_("Spray copies of the initial selection"));
+ copy_mode_btn->set_icon_name(INKSCAPE_ICON("spray-mode-copy"));
+ _mode_buttons.push_back(copy_mode_btn);
+
+ auto clone_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Spray with clones")));
+ clone_mode_btn->set_tooltip_text(_("Spray clones of the initial selection"));
+ clone_mode_btn->set_icon_name(INKSCAPE_ICON("spray-mode-clone"));
+ _mode_buttons.push_back(clone_mode_btn);
+
+#ifdef ENABLE_SPRAY_MODE_SINGLE_PATH
+ auto union_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Spray single path")));
+ union_mode_btn->set_tooltip_text(_("Spray objects in a single path"));
+ union_mode_btn->set_icon_name(INKSCAPE_ICON("spray-mode-union"));
+ _mode_buttons.push_back(union_mode_btn);
+#endif
+
+ auto eraser_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Delete sprayed items")));
+ eraser_mode_btn->set_tooltip_text(_("Delete sprayed items from selection"));
+ eraser_mode_btn->set_icon_name(INKSCAPE_ICON("draw-eraser"));
+ _mode_buttons.push_back(eraser_mode_btn);
+
+ int btn_idx = 0;
+ for (auto btn : _mode_buttons) {
+ btn->set_sensitive(true);
+ add(*btn);
+ btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::mode_changed), btn_idx++));
+ }
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ /* Width */
+ std::vector<Glib::ustring> labels = {_("(narrow spray)"), "", "", "", _("(default)"), "", "", "", "", _("(broad spray)")};
+ std::vector<double> values = { 1, 3, 5, 10, 15, 20, 30, 50, 75, 100};
+ auto width_val = prefs->getDouble("/tools/spray/width", 15);
+ _width_adj = Gtk::Adjustment::create(width_val, 1, 100, 1.0, 10.0);
+ auto width_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("spray-width", _("Width:"), _width_adj, 1, 0));
+ width_item->set_tooltip_text(_("The width of the spray area (relative to the visible canvas area)"));
+ width_item->set_custom_numeric_menu_data(values, labels);
+ width_item->set_focus_widget(desktop->canvas);
+ _width_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::width_value_changed));
+ add(*width_item);
+ width_item->set_sensitive(true);
+ }
+
+ /* Use Pressure Width button */
+ {
+ auto pressure_item = add_toggle_button(_("Pressure"),
+ _("Use the pressure of the input device to alter the width of spray area"));
+ pressure_item->set_icon_name(INKSCAPE_ICON("draw-use-pressure"));
+ _usepressurewidth_pusher.reset(new UI::SimplePrefPusher(pressure_item, "/tools/spray/usepressurewidth"));
+ pressure_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled),
+ pressure_item,
+ "/tools/spray/usepressurewidth"));
+ }
+
+ { /* Population */
+ std::vector<Glib::ustring> labels = {_("(low population)"), "", "", "", _("(default)"), "", _("(high population)")};
+ std::vector<double> values = { 5, 20, 35, 50, 70, 85, 100};
+ auto population_val = prefs->getDouble("/tools/spray/population", 70);
+ _population_adj = Gtk::Adjustment::create(population_val, 1, 100, 1.0, 10.0);
+ _spray_population = Gtk::manage(new UI::Widget::SpinButtonToolItem("spray-population", _("Amount:"), _population_adj, 1, 0));
+ _spray_population->set_tooltip_text(_("Adjusts the number of items sprayed per click"));
+ _spray_population->set_custom_numeric_menu_data(values, labels);
+ _spray_population->set_focus_widget(desktop->canvas);
+ _population_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::population_value_changed));
+ add(*_spray_population);
+ _spray_population->set_sensitive(true);
+ }
+
+ /* Use Pressure Population button */
+ {
+ auto pressure_population_item = add_toggle_button(_("Pressure"),
+ _("Use the pressure of the input device to alter the amount of sprayed objects"));
+ pressure_population_item->set_icon_name(INKSCAPE_ICON("draw-use-pressure"));
+ _usepressurepopulation_pusher.reset(new UI::SimplePrefPusher(pressure_population_item, "/tools/spray/usepressurepopulation"));
+ pressure_population_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled),
+ pressure_population_item,
+ "/tools/spray/usepressurepopulation"));
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ { /* Rotation */
+ std::vector<Glib::ustring> labels = {_("(default)"), "", "", "", "", "", "", _("(high rotation variation)")};
+ std::vector<double> values = { 0, 10, 25, 35, 50, 60, 80, 100};
+ auto rotation_val = prefs->getDouble("/tools/spray/rotation_variation", 0);
+ _rotation_adj = Gtk::Adjustment::create(rotation_val, 0, 100, 1.0, 10.0);
+ _spray_rotation = Gtk::manage(new UI::Widget::SpinButtonToolItem("spray-rotation", _("Rotation:"), _rotation_adj, 1, 0));
+ // xgettext:no-c-format
+ _spray_rotation->set_tooltip_text(_("Variation of the rotation of the sprayed objects; 0% for the same rotation than the original object"));
+ _spray_rotation->set_custom_numeric_menu_data(values, labels);
+ _spray_rotation->set_focus_widget(desktop->canvas);
+ _rotation_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::rotation_value_changed));
+ add(*_spray_rotation);
+ _spray_rotation->set_sensitive();
+ }
+
+ { /* Scale */
+ std::vector<Glib::ustring> labels = {_("(default)"), "", "", "", "", "", "", _("(high scale variation)")};
+ std::vector<double> values = { 0, 10, 25, 35, 50, 60, 80, 100};
+ auto scale_val = prefs->getDouble("/tools/spray/scale_variation", 0);
+ _scale_adj = Gtk::Adjustment::create(scale_val, 0, 100, 1.0, 10.0);
+ _spray_scale = Gtk::manage(new UI::Widget::SpinButtonToolItem("spray-scale", C_("Spray tool", "Scale:"), _scale_adj, 1, 0));
+ // xgettext:no-c-format
+ _spray_scale->set_tooltip_text(_("Variation in the scale of the sprayed objects; 0% for the same scale than the original object"));
+ _spray_scale->set_custom_numeric_menu_data(values, labels);
+ _spray_scale->set_focus_widget(desktop->canvas);
+ _scale_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::scale_value_changed));
+ add(*_spray_scale);
+ _spray_scale->set_sensitive(true);
+ }
+
+ /* Use Pressure Scale button */
+ {
+ _usepressurescale = add_toggle_button(_("Pressure"),
+ _("Use the pressure of the input device to alter the scale of new items"));
+ _usepressurescale->set_icon_name(INKSCAPE_ICON("draw-use-pressure"));
+ _usepressurescale->set_active(prefs->getBool("/tools/spray/usepressurescale", false));
+ _usepressurescale->signal_toggled().connect(sigc::mem_fun(*this, &SprayToolbar::toggle_pressure_scale));
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ /* Standard_deviation */
+ std::vector<Glib::ustring> labels = {_("(minimum scatter)"), "", "", "", "", "", _("(default)"), _("(maximum scatter)")};
+ std::vector<double> values = { 1, 5, 10, 20, 30, 50, 70, 100};
+ auto sd_val = prefs->getDouble("/tools/spray/standard_deviation", 70);
+ _sd_adj = Gtk::Adjustment::create(sd_val, 1, 100, 1.0, 10.0);
+ auto sd_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("spray-standard-deviation", C_("Spray tool", "Scatter:"), _sd_adj, 1, 0));
+ sd_item->set_tooltip_text(_("Increase to scatter sprayed objects"));
+ sd_item->set_custom_numeric_menu_data(values, labels);
+ sd_item->set_focus_widget(desktop->canvas);
+ _sd_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::standard_deviation_value_changed));
+ add(*sd_item);
+ sd_item->set_sensitive(true);
+ }
+
+ {
+ /* Mean */
+ std::vector<Glib::ustring> labels = {_("(default)"), "", "", "", "", "", "", _("(maximum mean)")};
+ std::vector<double> values = { 0, 5, 10, 20, 30, 50, 70, 100};
+ auto mean_val = prefs->getDouble("/tools/spray/mean", 0);
+ _mean_adj = Gtk::Adjustment::create(mean_val, 0, 100, 1.0, 10.0);
+ auto mean_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("spray-mean", _("Focus:"), _mean_adj, 1, 0));
+ mean_item->set_tooltip_text(_("0 to spray a spot; increase to enlarge the ring radius"));
+ mean_item->set_custom_numeric_menu_data(values, labels);
+ mean_item->set_focus_widget(desktop->canvas);
+ _mean_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::mean_value_changed));
+ add(*mean_item);
+ mean_item->set_sensitive(true);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Over No Transparent */
+ {
+ _over_no_transparent = add_toggle_button(_("Apply over no transparent areas"),
+ _("Apply over no transparent areas"));
+ _over_no_transparent->set_icon_name(INKSCAPE_ICON("object-visible"));
+ _over_no_transparent->set_active(prefs->getBool("/tools/spray/over_no_transparent", true));
+ _over_no_transparent->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled),
+ _over_no_transparent,
+ "/tools/spray/over_no_transparent"));
+ }
+
+ /* Over Transparent */
+ {
+ _over_transparent = add_toggle_button(_("Apply over transparent areas"),
+ _("Apply over transparent areas"));
+ _over_transparent->set_icon_name(INKSCAPE_ICON("object-hidden"));
+ _over_transparent->set_active(prefs->getBool("/tools/spray/over_transparent", true));
+ _over_transparent->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled),
+ _over_transparent,
+ "/tools/spray/over_transparent"));
+ }
+
+ /* Pick No Overlap */
+ {
+ _pick_no_overlap = add_toggle_button(_("No overlap between colors"),
+ _("No overlap between colors"));
+ _pick_no_overlap->set_icon_name(INKSCAPE_ICON("symbol-bigger"));
+ _pick_no_overlap->set_active(prefs->getBool("/tools/spray/pick_no_overlap", false));
+ _pick_no_overlap->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled),
+ _pick_no_overlap,
+ "/tools/spray/pick_no_overlap"));
+ }
+
+ /* Overlap */
+ {
+ _no_overlap = add_toggle_button(_("Prevent overlapping objects"),
+ _("Prevent overlapping objects"));
+ _no_overlap->set_icon_name(INKSCAPE_ICON("distribute-randomize"));
+ _no_overlap->set_active(prefs->getBool("/tools/spray/no_overlap", false));
+ _no_overlap->signal_toggled().connect(sigc::mem_fun(*this, &SprayToolbar::toggle_no_overlap));
+ }
+
+ /* Offset */
+ {
+ std::vector<Glib::ustring> labels = {_("(minimum offset)"), "", "", "", _("(default)"), "", "", _("(maximum offset)")};
+ std::vector<double> values = { 0, 25, 50, 75, 100, 150, 200, 1000};
+ auto offset_val = prefs->getDouble("/tools/spray/offset", 100);
+ _offset_adj = Gtk::Adjustment::create(offset_val, 0, 1000, 1, 4);
+ _offset = Gtk::manage(new UI::Widget::SpinButtonToolItem("spray-offset", _("Offset %:"), _offset_adj, 0, 0));
+ _offset->set_tooltip_text(_("Increase to segregate objects more (value in percent)"));
+ _offset->set_custom_numeric_menu_data(values, labels);
+ _offset->set_focus_widget(desktop->canvas);
+ _offset_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::offset_value_changed));
+ add(*_offset);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Picker */
+ {
+ _picker = add_toggle_button(_("Pick color from the drawing. You can use clonetiler trace dialog for advanced effects. In clone mode original fill or stroke colors must be unset."),
+ _("Pick color from the drawing. You can use clonetiler trace dialog for advanced effects. In clone mode original fill or stroke colors must be unset."));
+ _picker->set_icon_name(INKSCAPE_ICON("color-picker"));
+ _picker->set_active(prefs->getBool("/tools/spray/picker", false));
+ _picker->signal_toggled().connect(sigc::mem_fun(*this, &SprayToolbar::toggle_picker));
+ }
+
+ /* Pick Fill */
+ {
+ _pick_fill = add_toggle_button(_("Apply picked color to fill"),
+ _("Apply picked color to fill"));
+ _pick_fill->set_icon_name(INKSCAPE_ICON("paint-solid"));
+ _pick_fill->set_active(prefs->getBool("/tools/spray/pick_fill", false));
+ _pick_fill->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled),
+ _pick_fill,
+ "/tools/spray/pick_fill"));
+ }
+
+ /* Pick Stroke */
+ {
+ _pick_stroke = add_toggle_button(_("Apply picked color to stroke"),
+ _("Apply picked color to stroke"));
+ _pick_stroke->set_icon_name(INKSCAPE_ICON("no-marker"));
+ _pick_stroke->set_active(prefs->getBool("/tools/spray/pick_stroke", false));
+ _pick_stroke->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled),
+ _pick_stroke,
+ "/tools/spray/pick_stroke"));
+ }
+
+ /* Inverse Value Size */
+ {
+ _pick_inverse_value = add_toggle_button(_("Inverted pick value, retaining color in advanced trace mode"),
+ _("Inverted pick value, retaining color in advanced trace mode"));
+ _pick_inverse_value->set_icon_name(INKSCAPE_ICON("object-tweak-shrink"));
+ _pick_inverse_value->set_active(prefs->getBool("/tools/spray/pick_inverse_value", false));
+ _pick_inverse_value->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled),
+ _pick_inverse_value,
+ "/tools/spray/pick_inverse_value"));
+ }
+
+ /* Pick from center */
+ {
+ _pick_center = add_toggle_button(_("Pick from center instead of average area."),
+ _("Pick from center instead of average area."));
+ _pick_center->set_icon_name(INKSCAPE_ICON("snap-bounding-box-center"));
+ _pick_center->set_active(prefs->getBool("/tools/spray/pick_center", true));
+ _pick_center->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SprayToolbar::on_pref_toggled),
+ _pick_center,
+ "/tools/spray/pick_center"));
+ }
+
+ gint mode = prefs->getInt("/tools/spray/mode", 1);
+ _mode_buttons[mode]->set_active();
+ show_all();
+ init();
+}
+
+GtkWidget *
+SprayToolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = new SprayToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+void
+SprayToolbar::width_value_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/spray/width",
+ _width_adj->get_value());
+}
+
+void
+SprayToolbar::mean_value_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/spray/mean",
+ _mean_adj->get_value());
+}
+
+void
+SprayToolbar::standard_deviation_value_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/spray/standard_deviation",
+ _sd_adj->get_value());
+}
+
+void
+SprayToolbar::mode_changed(int mode)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/tools/spray/mode", mode);
+ init();
+}
+
+void
+SprayToolbar::init(){
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int mode = prefs->getInt("/tools/spray/mode", 0);
+
+ bool show = true;
+ if(mode == 3 || mode == 2){
+ show = false;
+ }
+ _no_overlap->set_visible(show);
+ _over_no_transparent->set_visible(show);
+ _over_transparent->set_visible(show);
+ _pick_no_overlap->set_visible(show);
+ _pick_stroke->set_visible(show);
+ _pick_fill->set_visible(show);
+ _pick_inverse_value->set_visible(show);
+ _pick_center->set_visible(show);
+ _picker->set_visible(show);
+ _offset->set_visible(show);
+ _pick_fill->set_visible(show);
+ _pick_stroke->set_visible(show);
+ _pick_inverse_value->set_visible(show);
+ _pick_center->set_visible(show);
+ if(mode == 2){
+ show = true;
+ }
+ _spray_rotation->set_visible(show);
+ update_widgets();
+}
+
+void
+SprayToolbar::population_value_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/spray/population",
+ _population_adj->get_value());
+}
+
+void
+SprayToolbar::rotation_value_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/spray/rotation_variation",
+ _rotation_adj->get_value());
+}
+
+void
+SprayToolbar::update_widgets()
+{
+ _offset_adj->set_value(100.0);
+
+ bool no_overlap_is_active = _no_overlap->get_active() && _no_overlap->get_visible();
+ _offset->set_visible(no_overlap_is_active);
+ if (_usepressurescale->get_active()) {
+ _scale_adj->set_value(0.0);
+ _spray_scale->set_sensitive(false);
+ } else {
+ _spray_scale->set_sensitive(true);
+ }
+
+ bool picker_is_active = _picker->get_active() && _picker->get_visible();
+ _pick_fill->set_visible(picker_is_active);
+ _pick_stroke->set_visible(picker_is_active);
+ _pick_inverse_value->set_visible(picker_is_active);
+ _pick_center->set_visible(picker_is_active);
+}
+
+void
+SprayToolbar::toggle_no_overlap()
+{
+ auto prefs = Inkscape::Preferences::get();
+ bool active = _no_overlap->get_active();
+ prefs->setBool("/tools/spray/no_overlap", active);
+ update_widgets();
+}
+
+void
+SprayToolbar::scale_value_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/spray/scale_variation",
+ _scale_adj->get_value());
+}
+
+void
+SprayToolbar::offset_value_changed()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/spray/offset",
+ _offset_adj->get_value());
+}
+
+void
+SprayToolbar::toggle_pressure_scale()
+{
+ auto prefs = Inkscape::Preferences::get();
+ bool active = _usepressurescale->get_active();
+ prefs->setBool("/tools/spray/usepressurescale", active);
+ if(active){
+ prefs->setDouble("/tools/spray/scale_variation", 0);
+ }
+ update_widgets();
+}
+
+void
+SprayToolbar::toggle_picker()
+{
+ auto prefs = Inkscape::Preferences::get();
+ bool active = _picker->get_active();
+ prefs->setBool("/tools/spray/picker", active);
+ if(active){
+ prefs->setBool("/dialogs/clonetiler/dotrace", false);
+ SPDesktop *dt = _desktop;
+ if (Inkscape::UI::Dialog::CloneTiler *ct = get_clone_tiler_panel(dt)){
+ dt->getContainer()->new_dialog("CloneTiler");
+ ct->show_page_trace();
+ }
+ }
+ update_widgets();
+}
+
+void
+SprayToolbar::on_pref_toggled(Gtk::ToggleToolButton *btn,
+ const Glib::ustring& path)
+{
+ auto prefs = Inkscape::Preferences::get();
+ bool active = btn->get_active();
+ prefs->setBool(path, active);
+}
+
+void
+SprayToolbar::set_mode(int mode)
+{
+ _mode_buttons[mode]->set_active();
+}
+
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/spray-toolbar.h b/src/ui/toolbar/spray-toolbar.h
new file mode 100644
index 0000000..4587cf0
--- /dev/null
+++ b/src/ui/toolbar/spray-toolbar.h
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SPRAY_TOOLBAR_H
+#define SEEN_SPRAY_TOOLBAR_H
+
+/**
+ * @file
+ * Spray aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2015 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+#include <gtkmm/adjustment.h>
+
+class SPDesktop;
+
+namespace Gtk {
+class RadioToolButton;
+}
+
+namespace Inkscape {
+namespace UI {
+class SimplePrefPusher;
+
+namespace Widget {
+class SpinButtonToolItem;
+}
+
+namespace Toolbar {
+class SprayToolbar : public Toolbar {
+private:
+ Glib::RefPtr<Gtk::Adjustment> _width_adj;
+ Glib::RefPtr<Gtk::Adjustment> _mean_adj;
+ Glib::RefPtr<Gtk::Adjustment> _sd_adj;
+ Glib::RefPtr<Gtk::Adjustment> _population_adj;
+ Glib::RefPtr<Gtk::Adjustment> _rotation_adj;
+ Glib::RefPtr<Gtk::Adjustment> _offset_adj;
+ Glib::RefPtr<Gtk::Adjustment> _scale_adj;
+
+ std::unique_ptr<SimplePrefPusher> _usepressurewidth_pusher;
+ std::unique_ptr<SimplePrefPusher> _usepressurepopulation_pusher;
+
+ std::vector<Gtk::RadioToolButton *> _mode_buttons;
+ UI::Widget::SpinButtonToolItem *_spray_population;
+ UI::Widget::SpinButtonToolItem *_spray_rotation;
+ UI::Widget::SpinButtonToolItem *_spray_scale;
+ Gtk::ToggleToolButton *_usepressurescale;
+ Gtk::ToggleToolButton *_picker;
+ Gtk::ToggleToolButton *_pick_center;
+ Gtk::ToggleToolButton *_pick_inverse_value;
+ Gtk::ToggleToolButton *_pick_fill;
+ Gtk::ToggleToolButton *_pick_stroke;
+ Gtk::ToggleToolButton *_pick_no_overlap;
+ Gtk::ToggleToolButton *_over_transparent;
+ Gtk::ToggleToolButton *_over_no_transparent;
+ Gtk::ToggleToolButton *_no_overlap;
+ UI::Widget::SpinButtonToolItem *_offset;
+
+ void width_value_changed();
+ void mean_value_changed();
+ void standard_deviation_value_changed();
+ void mode_changed(int mode);
+ void init();
+ void population_value_changed();
+ void rotation_value_changed();
+ void update_widgets();
+ void scale_value_changed();
+ void offset_value_changed();
+ void on_pref_toggled(Gtk::ToggleToolButton *btn,
+ const Glib::ustring& path);
+ void toggle_no_overlap();
+ void toggle_pressure_scale();
+ void toggle_picker();
+
+protected:
+ SprayToolbar(SPDesktop *desktop);
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+
+ void set_mode(int mode);
+};
+}
+}
+}
+
+#endif /* !SEEN_SELECT_TOOLBAR_H */
diff --git a/src/ui/toolbar/star-toolbar.cpp b/src/ui/toolbar/star-toolbar.cpp
new file mode 100644
index 0000000..6fc15b6
--- /dev/null
+++ b/src/ui/toolbar/star-toolbar.cpp
@@ -0,0 +1,569 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Star aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "star-toolbar.h"
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/radiotoolbutton.h>
+#include <gtkmm/separatortoolitem.h>
+
+#include "desktop.h"
+#include "document-undo.h"
+#include "selection.h"
+
+#include "object/sp-star.h"
+
+#include "ui/icon-names.h"
+#include "ui/tools/star-tool.h"
+#include "ui/widget/canvas.h"
+#include "ui/widget/label-tool-item.h"
+#include "ui/widget/spin-button-tool-item.h"
+
+#include "xml/node-event-vector.h"
+
+using Inkscape::DocumentUndo;
+
+static Inkscape::XML::NodeEventVector star_tb_repr_events =
+{
+ nullptr, /* child_added */
+ nullptr, /* child_removed */
+ Inkscape::UI::Toolbar::StarToolbar::event_attr_changed,
+ nullptr, /* content_changed */
+ nullptr /* order_changed */
+};
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+StarToolbar::StarToolbar(SPDesktop *desktop) :
+ Toolbar(desktop),
+ _mode_item(Gtk::manage(new UI::Widget::LabelToolItem(_("<b>New:</b>")))),
+ _repr(nullptr),
+ _freeze(false)
+{
+ _mode_item->set_use_markup(true);
+ add(*_mode_item);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool isFlatSided = prefs->getBool("/tools/shapes/star/isflatsided", false);
+
+ /* Flatsided checkbox */
+ {
+ Gtk::RadioToolButton::Group flat_item_group;
+
+ auto flat_polygon_button = Gtk::manage(new Gtk::RadioToolButton(flat_item_group, _("Polygon")));
+ flat_polygon_button->set_tooltip_text(_("Regular polygon (with one handle) instead of a star"));
+ flat_polygon_button->set_icon_name(INKSCAPE_ICON("draw-polygon"));
+ _flat_item_buttons.push_back(flat_polygon_button);
+
+ auto flat_star_button = Gtk::manage(new Gtk::RadioToolButton(flat_item_group, _("Star")));
+ flat_star_button->set_tooltip_text(_("Star instead of a regular polygon (with one handle)"));
+ flat_star_button->set_icon_name(INKSCAPE_ICON("draw-star"));
+ _flat_item_buttons.push_back(flat_star_button);
+
+ _flat_item_buttons[ isFlatSided ? 0 : 1 ]->set_active();
+
+ int btn_index = 0;
+
+ for (auto btn : _flat_item_buttons)
+ {
+ add(*btn);
+ btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &StarToolbar::side_mode_changed), btn_index++));
+ }
+ }
+
+ add(*Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Magnitude */
+ {
+ std::vector<Glib::ustring> labels = {"",
+ _("triangle/tri-star"),
+ _("square/quad-star"),
+ _("pentagon/five-pointed star"),
+ _("hexagon/six-pointed star"),
+ "",
+ "",
+ "",
+ "",
+ ""};
+ std::vector<double> values = {2, 3, 4, 5, 6, 7, 8, 10, 12, 20};
+ auto magnitude_val = prefs->getDouble("/tools/shapes/star/magnitude", 3);
+ _magnitude_adj = Gtk::Adjustment::create(magnitude_val, isFlatSided ? 3 : 2, 1024, 1, 5);
+ _magnitude_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("star-magnitude", _("Corners:"), _magnitude_adj, 1.0, 0));
+ _magnitude_item->set_tooltip_text(_("Number of corners of a polygon or star"));
+ _magnitude_item->set_custom_numeric_menu_data(values, labels);
+ _magnitude_item->set_focus_widget(desktop->canvas);
+ _magnitude_adj->signal_value_changed().connect(sigc::mem_fun(*this, &StarToolbar::magnitude_value_changed));
+ _magnitude_item->set_sensitive(true);
+ add(*_magnitude_item);
+ }
+
+ /* Spoke ratio */
+ {
+ std::vector<Glib::ustring> labels = {_("thin-ray star"), "", _("pentagram"), _("hexagram"), _("heptagram"), _("octagram"), _("regular polygon")};
+ std::vector<double> values = { 0.01, 0.2, 0.382, 0.577, 0.692, 0.765, 1};
+ auto prop_val = prefs->getDouble("/tools/shapes/star/proportion", 0.5);
+ _spoke_adj = Gtk::Adjustment::create(prop_val, 0.01, 1.0, 0.01, 0.1);
+ _spoke_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("star-spoke", _("Spoke ratio:"), _spoke_adj));
+ // TRANSLATORS: Tip radius of a star is the distance from the center to the farthest handle.
+ // Base radius is the same for the closest handle.
+ _spoke_item->set_tooltip_text(_("Base radius to tip radius ratio"));
+ _spoke_item->set_custom_numeric_menu_data(values, labels);
+ _spoke_item->set_focus_widget(desktop->canvas);
+ _spoke_adj->signal_value_changed().connect(sigc::mem_fun(*this, &StarToolbar::proportion_value_changed));
+
+ add(*_spoke_item);
+ }
+
+ /* Roundedness */
+ {
+ std::vector<Glib::ustring> labels = {_("stretched"), _("twisted"), _("slightly pinched"), _("NOT rounded"), _("slightly rounded"),
+ _("visibly rounded"), _("well rounded"), _("amply rounded"), "", _("stretched"), _("blown up")};
+ std::vector<double> values = {-1, -0.2, -0.03, 0, 0.05, 0.1, 0.2, 0.3, 0.5, 1, 10};
+ auto roundedness_val = prefs->getDouble("/tools/shapes/star/rounded", 0.0);
+ _roundedness_adj = Gtk::Adjustment::create(roundedness_val, -10.0, 10.0, 0.01, 0.1);
+ _roundedness_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("star-roundedness", _("Rounded:"), _roundedness_adj));
+ _roundedness_item->set_tooltip_text(_("How rounded are the corners (0 for sharp)"));
+ _roundedness_item->set_custom_numeric_menu_data(values, labels);
+ _roundedness_item->set_focus_widget(desktop->canvas);
+ _roundedness_adj->signal_value_changed().connect(sigc::mem_fun(*this, &StarToolbar::rounded_value_changed));
+ _roundedness_item->set_sensitive(true);
+ add(*_roundedness_item);
+ }
+
+ /* Randomization */
+ {
+ std::vector<Glib::ustring> labels = {_("NOT randomized"), _("slightly irregular"), _("visibly randomized"), _("strongly randomized"), _("blown up")};
+ std::vector<double> values = { 0, 0.01, 0.1, 0.5, 10};
+ auto randomized_val = prefs->getDouble("/tools/shapes/star/randomized", 0.0);
+ _randomization_adj = Gtk::Adjustment::create(randomized_val, -10.0, 10.0, 0.001, 0.01);
+ _randomization_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("star-randomized", _("Randomized:"), _randomization_adj, 0.1, 3));
+ _randomization_item->set_tooltip_text(_("Scatter randomly the corners and angles"));
+ _randomization_item->set_custom_numeric_menu_data(values, labels);
+ _randomization_item->set_focus_widget(desktop->canvas);
+ _randomization_adj->signal_value_changed().connect(sigc::mem_fun(*this, &StarToolbar::randomized_value_changed));
+ _randomization_item->set_sensitive(true);
+ add(*_randomization_item);
+ }
+
+ add(*Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Reset */
+ {
+ _reset_item = Gtk::manage(new Gtk::ToolButton(_("Defaults")));
+ _reset_item->set_icon_name(INKSCAPE_ICON("edit-clear"));
+ _reset_item->set_tooltip_text(_("Reset shape parameters to defaults (use Inkscape Preferences > Tools to change defaults)"));
+ _reset_item->signal_clicked().connect(sigc::mem_fun(*this, &StarToolbar::defaults));
+ _reset_item->set_sensitive(true);
+ add(*_reset_item);
+ }
+
+ desktop->connectEventContextChanged(sigc::mem_fun(*this, &StarToolbar::watch_ec));
+
+ show_all();
+ _spoke_item->set_visible(!isFlatSided);
+}
+
+StarToolbar::~StarToolbar()
+{
+ if (_repr) { // remove old listener
+ _repr->removeListenerByData(this);
+ Inkscape::GC::release(_repr);
+ _repr = nullptr;
+ }
+}
+
+GtkWidget *
+StarToolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = new StarToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+void
+StarToolbar::side_mode_changed(int mode)
+{
+ bool flat = (mode == 0);
+
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool( "/tools/shapes/star/isflatsided", flat );
+ }
+
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ Inkscape::Selection *selection = _desktop->getSelection();
+ bool modmade = false;
+
+ if (_spoke_item) {
+ _spoke_item->set_visible(!flat);
+ }
+
+ auto itemlist= selection->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ SPItem *item = *i;
+ if (SP_IS_STAR(item)) {
+ Inkscape::XML::Node *repr = item->getRepr();
+ if (flat) {
+ gint sides = (gint)_magnitude_adj->get_value();
+ if (sides < 3) {
+ repr->setAttributeInt("sodipodi:sides", 3);
+ }
+ }
+ repr->setAttribute("inkscape:flatsided", flat ? "true" : "false" );
+
+ item->updateRepr();
+ modmade = true;
+ }
+ }
+
+ _magnitude_adj->set_lower(flat ? 3 : 2);
+ if (flat && _magnitude_adj->get_value() < 3) {
+ _magnitude_adj->set_value(3);
+ }
+
+ if (modmade) {
+ DocumentUndo::done(_desktop->getDocument(), flat ? _("Make polygon") : _("Make star"), INKSCAPE_ICON("draw-polygon-star"));
+ }
+
+ _freeze = false;
+}
+
+void
+StarToolbar::magnitude_value_changed()
+{
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ // do not remember prefs if this call is initiated by an undo change, because undoing object
+ // creation sets bogus values to its attributes before it is deleted
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/tools/shapes/star/magnitude",
+ (gint)_magnitude_adj->get_value());
+ }
+
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ bool modmade = false;
+
+ Inkscape::Selection *selection = _desktop->getSelection();
+ auto itemlist= selection->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ SPItem *item = *i;
+ if (SP_IS_STAR(item)) {
+ Inkscape::XML::Node *repr = item->getRepr();
+ repr->setAttributeInt("sodipodi:sides", (gint)_magnitude_adj->get_value());
+ double arg1 = repr->getAttributeDouble("sodipodi:arg1", 0.5);
+ repr->setAttributeSvgDouble("sodipodi:arg2", (arg1 + M_PI / (gint)_magnitude_adj->get_value()));
+ item->updateRepr();
+ modmade = true;
+ }
+ }
+ if (modmade) {
+ DocumentUndo::done(_desktop->getDocument(), _("Star: Change number of corners"), INKSCAPE_ICON("draw-polygon-star"));
+ }
+
+ _freeze = false;
+}
+
+void
+StarToolbar::proportion_value_changed()
+{
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ if (!std::isnan(_spoke_adj->get_value())) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble("/tools/shapes/star/proportion",
+ _spoke_adj->get_value());
+ }
+ }
+
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ bool modmade = false;
+ Inkscape::Selection *selection = _desktop->getSelection();
+ auto itemlist= selection->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ SPItem *item = *i;
+ if (SP_IS_STAR(item)) {
+ Inkscape::XML::Node *repr = item->getRepr();
+
+ gdouble r1 = repr->getAttributeDouble("sodipodi:r1", 1.0);;
+ gdouble r2 = repr->getAttributeDouble("sodipodi:r2", 1.0);
+
+ if (r2 < r1) {
+ repr->setAttributeSvgDouble("sodipodi:r2", r1*_spoke_adj->get_value());
+ } else {
+ repr->setAttributeSvgDouble("sodipodi:r1", r2*_spoke_adj->get_value());
+ }
+
+ item->updateRepr();
+ modmade = true;
+ }
+ }
+
+ if (modmade) {
+ DocumentUndo::done(_desktop->getDocument(), _("Star: Change spoke ratio"), INKSCAPE_ICON("draw-polygon-star"));
+ }
+
+ _freeze = false;
+}
+
+void
+StarToolbar::rounded_value_changed()
+{
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble("/tools/shapes/star/rounded", (gdouble) _roundedness_adj->get_value());
+ }
+
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ bool modmade = false;
+
+ Inkscape::Selection *selection = _desktop->getSelection();
+ auto itemlist= selection->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ SPItem *item = *i;
+ if (SP_IS_STAR(item)) {
+ Inkscape::XML::Node *repr = item->getRepr();
+ repr->setAttributeSvgDouble("inkscape:rounded", (gdouble) _roundedness_adj->get_value());
+ item->updateRepr();
+ modmade = true;
+ }
+ }
+ if (modmade) {
+ DocumentUndo::done(_desktop->getDocument(), _("Star: Change rounding"), INKSCAPE_ICON("draw-polygon-star"));
+ }
+
+ _freeze = false;
+}
+
+void
+StarToolbar::randomized_value_changed()
+{
+ if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble("/tools/shapes/star/randomized",
+ (gdouble) _randomization_adj->get_value());
+ }
+
+ // quit if run by the attr_changed listener
+ if (_freeze) {
+ return;
+ }
+
+ // in turn, prevent listener from responding
+ _freeze = true;
+
+ bool modmade = false;
+
+ Inkscape::Selection *selection = _desktop->getSelection();
+ auto itemlist= selection->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ SPItem *item = *i;
+ if (SP_IS_STAR(item)) {
+ Inkscape::XML::Node *repr = item->getRepr();
+ repr->setAttributeSvgDouble("inkscape:randomized", (gdouble) _randomization_adj->get_value());
+ item->updateRepr();
+ modmade = true;
+ }
+ }
+ if (modmade) {
+ DocumentUndo::done(_desktop->getDocument(), _("Star: Change randomization"), INKSCAPE_ICON("draw-polygon-star"));
+ }
+
+ _freeze = false;
+}
+
+void
+StarToolbar::defaults()
+{
+
+ // FIXME: in this and all other _default functions, set some flag telling the value_changed
+ // callbacks to lump all the changes for all selected objects in one undo step
+
+ // fixme: make settable in prefs!
+ gint mag = 5;
+ gdouble prop = 0.5;
+ gboolean flat = FALSE;
+ gdouble randomized = 0;
+ gdouble rounded = 0;
+
+ _flat_item_buttons[ flat ? 0 : 1 ]->set_active();
+
+ _spoke_item->set_visible(!flat);
+
+ _magnitude_adj->set_value(mag);
+ _spoke_adj->set_value(prop);
+ _roundedness_adj->set_value(rounded);
+ _randomization_adj->set_value(randomized);
+}
+
+void
+StarToolbar::watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec)
+{
+ if (dynamic_cast<Inkscape::UI::Tools::StarTool const*>(ec) != nullptr) {
+ _changed = desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &StarToolbar::selection_changed));
+ selection_changed(desktop->getSelection());
+ } else {
+ if (_changed)
+ _changed.disconnect();
+ }
+}
+
+/**
+ * \param selection Should not be NULL.
+ */
+void
+StarToolbar::selection_changed(Inkscape::Selection *selection)
+{
+ int n_selected = 0;
+ Inkscape::XML::Node *repr = nullptr;
+
+ if (_repr) { // remove old listener
+ _repr->removeListenerByData(this);
+ Inkscape::GC::release(_repr);
+ _repr = nullptr;
+ }
+
+ auto itemlist= selection->items();
+ for(auto i=itemlist.begin();i!=itemlist.end();++i){
+ SPItem *item = *i;
+ if (SP_IS_STAR(item)) {
+ n_selected++;
+ repr = item->getRepr();
+ }
+ }
+
+ if (n_selected == 0) {
+ _mode_item->set_markup(_("<b>New:</b>"));
+ } else if (n_selected == 1) {
+ _mode_item->set_markup(_("<b>Change:</b>"));
+
+ if (repr) {
+ _repr = repr;
+ Inkscape::GC::anchor(_repr);
+ _repr->addListener(&star_tb_repr_events, this);
+ _repr->synthesizeEvents(&star_tb_repr_events, this);
+ }
+ } else {
+ // FIXME: implement averaging of all parameters for multiple selected stars
+ //gtk_label_set_markup(GTK_LABEL(l), _("<b>Average:</b>"));
+ //gtk_label_set_markup(GTK_LABEL(l), _("<b>Change:</b>"));
+ }
+}
+
+void
+StarToolbar::event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
+ gchar const * /*old_value*/, gchar const * /*new_value*/,
+ bool /*is_interactive*/, gpointer dataPointer)
+{
+ auto toolbar = reinterpret_cast<StarToolbar *>(dataPointer);
+
+ // quit if run by the _changed callbacks
+ if (toolbar->_freeze) {
+ return;
+ }
+
+ // in turn, prevent callbacks from responding
+ toolbar->_freeze = true;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool isFlatSided = prefs->getBool("/tools/shapes/star/isflatsided", false);
+
+ if (!strcmp(name, "inkscape:randomized")) {
+ double randomized = repr->getAttributeDouble("inkscape:randomized", 0.0);
+ toolbar->_randomization_adj->set_value(randomized);
+ } else if (!strcmp(name, "inkscape:rounded")) {
+ double rounded = repr->getAttributeDouble("inkscape:rounded", 0.0);
+ toolbar->_roundedness_adj->set_value(rounded);
+ } else if (!strcmp(name, "inkscape:flatsided")) {
+ char const *flatsides = repr->attribute("inkscape:flatsided");
+ if ( flatsides && !strcmp(flatsides,"false") ) {
+ toolbar->_flat_item_buttons[1]->set_active();
+ toolbar->_spoke_item->set_visible(true);
+ toolbar->_magnitude_adj->set_lower(2);
+ } else {
+ toolbar->_flat_item_buttons[0]->set_active();
+ toolbar->_spoke_item->set_visible(false);
+ toolbar->_magnitude_adj->set_lower(3);
+ }
+ } else if ((!strcmp(name, "sodipodi:r1") || !strcmp(name, "sodipodi:r2")) && (!isFlatSided) ) {
+ gdouble r1 = repr->getAttributeDouble("sodipodi:r1", 1.0);
+ gdouble r2 = repr->getAttributeDouble("sodipodi:r2", 1.0);
+
+
+ if (r2 < r1) {
+ toolbar->_spoke_adj->set_value(r2/r1);
+ } else {
+ toolbar->_spoke_adj->set_value(r1/r2);
+ }
+ } else if (!strcmp(name, "sodipodi:sides")) {
+ int sides = repr->getAttributeInt("sodipodi:sides", 0);
+ toolbar->_magnitude_adj->set_value(sides);
+ }
+
+ toolbar->_freeze = false;
+}
+
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
diff --git a/src/ui/toolbar/star-toolbar.h b/src/ui/toolbar/star-toolbar.h
new file mode 100644
index 0000000..c44caab
--- /dev/null
+++ b/src/ui/toolbar/star-toolbar.h
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_STAR_TOOLBAR_H
+#define SEEN_STAR_TOOLBAR_H
+
+/**
+ * @file
+ * Star aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+#include <gtkmm/adjustment.h>
+
+class SPDesktop;
+
+namespace Gtk {
+class RadioToolButton;
+class ToolButton;
+}
+
+namespace Inkscape {
+class Selection;
+
+namespace XML {
+class Node;
+}
+
+namespace UI {
+namespace Tools {
+class ToolBase;
+}
+
+namespace Widget {
+class LabelToolItem;
+class SpinButtonToolItem;
+}
+
+namespace Toolbar {
+class StarToolbar : public Toolbar {
+private:
+ UI::Widget::LabelToolItem *_mode_item;
+ std::vector<Gtk::RadioToolButton *> _flat_item_buttons;
+ UI::Widget::SpinButtonToolItem *_magnitude_item;
+ UI::Widget::SpinButtonToolItem *_spoke_item;
+ UI::Widget::SpinButtonToolItem *_roundedness_item;
+ UI::Widget::SpinButtonToolItem *_randomization_item;
+ Gtk::ToolButton *_reset_item;
+
+ XML::Node *_repr;
+
+ Glib::RefPtr<Gtk::Adjustment> _magnitude_adj;
+ Glib::RefPtr<Gtk::Adjustment> _spoke_adj;
+ Glib::RefPtr<Gtk::Adjustment> _roundedness_adj;
+ Glib::RefPtr<Gtk::Adjustment> _randomization_adj;
+
+ bool _freeze;
+ sigc::connection _changed;
+
+ void side_mode_changed(int mode);
+ void magnitude_value_changed();
+ void proportion_value_changed();
+ void rounded_value_changed();
+ void randomized_value_changed();
+ void defaults();
+ void watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec);
+ void selection_changed(Inkscape::Selection *selection);
+
+protected:
+ StarToolbar(SPDesktop *desktop);
+ ~StarToolbar() override;
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+
+ static void event_attr_changed(Inkscape::XML::Node *repr,
+ gchar const *name,
+ gchar const *old_value,
+ gchar const *new_value,
+ bool is_interactive,
+ gpointer dataPointer);
+};
+
+}
+}
+}
+
+#endif /* !SEEN_SELECT_TOOLBAR_H */
diff --git a/src/ui/toolbar/text-toolbar.cpp b/src/ui/toolbar/text-toolbar.cpp
new file mode 100644
index 0000000..7a6144e
--- /dev/null
+++ b/src/ui/toolbar/text-toolbar.cpp
@@ -0,0 +1,2576 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Text aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ * Copyright (C) 1999-2013 authors
+ * Copyright (C) 2017 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/i18n.h>
+
+#include "text-toolbar.h"
+
+#include "desktop-style.h"
+#include "desktop.h"
+#include "document-undo.h"
+#include "document.h"
+#include "inkscape.h"
+#include "selection-chemistry.h"
+
+#include "libnrtype/font-lister.h"
+
+#include "object/sp-flowdiv.h"
+#include "object/sp-flowtext.h"
+#include "object/sp-root.h"
+#include "object/sp-text.h"
+#include "object/sp-tspan.h"
+#include "object/sp-string.h"
+
+#include "svg/css-ostringstream.h"
+#include "ui/icon-names.h"
+#include "ui/tools/select-tool.h"
+#include "ui/tools/text-tool.h"
+#include "ui/widget/canvas.h" // Focus
+#include "ui/widget/combo-box-entry-tool-item.h"
+#include "ui/widget/combo-tool-item.h"
+#include "ui/widget/spin-button-tool-item.h"
+#include "ui/widget/unit-tracker.h"
+#include "util/units.h"
+
+#include "widgets/style-utils.h"
+
+using Inkscape::DocumentUndo;
+using Inkscape::Util::Unit;
+using Inkscape::Util::Quantity;
+using Inkscape::Util::unit_table;
+using Inkscape::UI::Widget::UnitTracker;
+
+//#define DEBUG_TEXT
+
+//########################
+//## Text Toolbox ##
+//########################
+
+// Functions for debugging:
+#ifdef DEBUG_TEXT
+static void sp_print_font(SPStyle *query)
+{
+
+
+ bool family_set = query->font_family.set;
+ bool style_set = query->font_style.set;
+ bool fontspec_set = query->font_specification.set;
+
+ std::cout << " Family set? " << family_set
+ << " Style set? " << style_set
+ << " FontSpec set? " << fontspec_set
+ << std::endl;
+}
+
+static void sp_print_fontweight( SPStyle *query ) {
+ const gchar* names[] = {"100", "200", "300", "400", "500", "600", "700", "800", "900",
+ "NORMAL", "BOLD", "LIGHTER", "BOLDER", "Out of range"};
+ // Missing book = 380
+ int index = query->font_weight.computed;
+ if (index < 0 || index > 13)
+ index = 13;
+ std::cout << " Weight: " << names[ index ]
+ << " (" << query->font_weight.computed << ")" << std::endl;
+}
+
+static void sp_print_fontstyle( SPStyle *query ) {
+
+ const gchar* names[] = {"NORMAL", "ITALIC", "OBLIQUE", "Out of range"};
+ int index = query->font_style.computed;
+ if( index < 0 || index > 3 ) index = 3;
+ std::cout << " Style: " << names[ index ] << std::endl;
+
+}
+#endif
+
+static bool is_relative( Unit const *unit ) {
+ return (unit->abbr == "" || unit->abbr == "em" || unit->abbr == "ex" || unit->abbr == "%");
+}
+
+static bool is_relative(SPCSSUnit const unit)
+{
+ return (unit == SP_CSS_UNIT_NONE || unit == SP_CSS_UNIT_EM || unit == SP_CSS_UNIT_EX ||
+ unit == SP_CSS_UNIT_PERCENT);
+}
+
+// Set property for object, but unset all descendents
+// Should probably be moved to desktop_style.cpp
+static void recursively_set_properties(SPObject *object, SPCSSAttr *css, bool unset_descendents = true)
+{
+ object->changeCSS (css, "style");
+
+ SPCSSAttr *css_unset = sp_repr_css_attr_unset_all( css );
+ std::vector<SPObject *> children = object->childList(false);
+ for (auto i: children) {
+ recursively_set_properties(i, unset_descendents ? css_unset : css);
+ }
+ sp_repr_css_attr_unref (css_unset);
+}
+
+/*
+ * Set the default list of font sizes, scaled to the users preferred unit
+ */
+static void sp_text_set_sizes(GtkListStore* model_size, int unit)
+{
+ gtk_list_store_clear(model_size);
+
+ // List of font sizes for dropchange-down menu
+ int sizes[] = {
+ 4, 6, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 28,
+ 32, 36, 40, 48, 56, 64, 72, 144
+ };
+
+ // Array must be same length as SPCSSUnit in style.h
+ float ratios[] = {1, 1, 1, 10, 4, 40, 100, 16, 8, 0.16};
+
+ for(int i : sizes) {
+ GtkTreeIter iter;
+ Glib::ustring size = Glib::ustring::format(i / (float)ratios[unit]);
+ gtk_list_store_append( model_size, &iter );
+ gtk_list_store_set( model_size, &iter, 0, size.c_str(), -1 );
+ }
+}
+
+
+// TODO: possibly share with font-selector by moving most code to font-lister (passing family name)
+static void sp_text_toolbox_select_cb( GtkEntry* entry, GtkEntryIconPosition /*position*/, GdkEvent /*event*/, gpointer /*data*/ ) {
+
+ Glib::ustring family = gtk_entry_get_text ( entry );
+ //std::cout << "text_toolbox_missing_font_cb: selecting: " << family << std::endl;
+
+ // Get all items with matching font-family set (not inherited!).
+ std::vector<SPItem*> selectList;
+
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ SPDocument *document = desktop->getDocument();
+ std::vector<SPItem*> x,y;
+ std::vector<SPItem*> allList = get_all_items(x, document->getRoot(), desktop, false, false, true, y);
+ for(std::vector<SPItem*>::const_reverse_iterator i=allList.rbegin();i!=allList.rend(); ++i){
+ SPItem *item = *i;
+ SPStyle *style = item->style;
+
+ if (style) {
+
+ Glib::ustring family_style;
+ if (style->font_family.set) {
+ family_style = style->font_family.value();
+ //std::cout << " family style from font_family: " << family_style << std::endl;
+ }
+ else if (style->font_specification.set) {
+ family_style = style->font_specification.value();
+ //std::cout << " family style from font_spec: " << family_style << std::endl;
+ }
+
+ if (family_style.compare( family ) == 0 ) {
+ //std::cout << " found: " << item->getId() << std::endl;
+ selectList.push_back(item);
+ }
+ }
+ }
+
+ // Update selection
+ Inkscape::Selection *selection = desktop->getSelection();
+ selection->clear();
+ //std::cout << " list length: " << g_slist_length ( selectList ) << std::endl;
+ selection->setList(selectList);
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+
+TextToolbar::TextToolbar(SPDesktop *desktop)
+ : Toolbar(desktop)
+ , _freeze(false)
+ , _text_style_from_prefs(false)
+ , _outer(true)
+ , _updating(false)
+ , _tracker(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR))
+ , _tracker_fs(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR))
+ , _cusor_numbers(0)
+{
+ /* Line height unit tracker */
+ _tracker->prependUnit(unit_table.getUnit("")); // Ratio
+ _tracker->addUnit(unit_table.getUnit("%"));
+ _tracker->addUnit(unit_table.getUnit("em"));
+ _tracker->addUnit(unit_table.getUnit("ex"));
+ _tracker->setActiveUnit(unit_table.getUnit(""));
+ // We change only the display value
+ _tracker->changeLabel("lines", 0, true);
+ _tracker_fs->setActiveUnit(unit_table.getUnit("mm"));
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ /* Font family */
+ {
+ // Font list
+ Inkscape::FontLister* fontlister = Inkscape::FontLister::get_instance();
+ fontlister->update_font_list(desktop->getDocument());
+ Glib::RefPtr<Gtk::ListStore> store = fontlister->get_font_list();
+ GtkListStore* model = store->gobj();
+
+ _font_family_item =
+ Gtk::manage(new UI::Widget::ComboBoxEntryToolItem( "TextFontFamilyAction",
+ _("Font Family"),
+ _("Select Font Family (Alt-X to access)"),
+ GTK_TREE_MODEL(model),
+ -1, // Entry width
+ 50, // Extra list width
+ (gpointer)font_lister_cell_data_func2, // Cell layout
+ (gpointer)font_lister_separator_func2,
+ GTK_WIDGET(desktop->getCanvas()->gobj()))); // Focus widget
+ _font_family_item->popup_enable(); // Enable entry completion
+ gchar *const info = _("Select all text with this font-family");
+ _font_family_item->set_info( info ); // Show selection icon
+ _font_family_item->set_info_cb( (gpointer)sp_text_toolbox_select_cb );
+
+ gchar *const warning = _("Font not found on system");
+ _font_family_item->set_warning( warning ); // Show icon w/ tooltip if font missing
+ _font_family_item->set_warning_cb( (gpointer)sp_text_toolbox_select_cb );
+
+ //ink_comboboxentry_action_set_warning_callback( act, sp_text_fontfamily_select_all );
+ _font_family_item->signal_changed().connect( sigc::mem_fun(*this, &TextToolbar::fontfamily_value_changed) );
+ add(*_font_family_item);
+
+ // Change style of drop-down from menu to list
+ auto css_provider = gtk_css_provider_new();
+ gtk_css_provider_load_from_data(css_provider,
+ "#TextFontFamilyAction_combobox {\n"
+ " -GtkComboBox-appears-as-list: true;\n"
+ "}\n",
+ -1, nullptr);
+
+ auto screen = gdk_screen_get_default();
+ _font_family_item->focus_on_click(false);
+ gtk_style_context_add_provider_for_screen(screen,
+ GTK_STYLE_PROVIDER(css_provider),
+ GTK_STYLE_PROVIDER_PRIORITY_USER);
+ }
+
+ /* Font styles */
+ {
+ Inkscape::FontLister* fontlister = Inkscape::FontLister::get_instance();
+ Glib::RefPtr<Gtk::ListStore> store = fontlister->get_style_list();
+ GtkListStore* model_style = store->gobj();
+
+ _font_style_item =
+ Gtk::manage(new UI::Widget::ComboBoxEntryToolItem( "TextFontStyleAction",
+ _("Font Style"),
+ _("Font style"),
+ GTK_TREE_MODEL(model_style),
+ 12, // Width in characters
+ 0, // Extra list width
+ nullptr, // Cell layout
+ nullptr, // Separator
+ GTK_WIDGET(desktop->getCanvas()->gobj()))); // Focus widget
+
+ _font_style_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::fontstyle_value_changed));
+ _font_style_item->focus_on_click(false);
+ add(*_font_style_item);
+ }
+
+ add_separator();
+
+ /* Font size */
+ {
+ // List of font sizes for drop-down menu
+ GtkListStore* model_size = gtk_list_store_new( 1, G_TYPE_STRING );
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
+
+ sp_text_set_sizes(model_size, unit);
+
+ auto unit_str = sp_style_get_css_unit_string(unit);
+ Glib::ustring tooltip = Glib::ustring::format(_("Font size"), " (", unit_str, ")");
+
+ _font_size_item =
+ Gtk::manage(new UI::Widget::ComboBoxEntryToolItem( "TextFontSizeAction",
+ _("Font Size"),
+ tooltip,
+ GTK_TREE_MODEL(model_size),
+ 8, // Width in characters
+ 0, // Extra list width
+ nullptr, // Cell layout
+ nullptr, // Separator
+ GTK_WIDGET(desktop->getCanvas()->gobj()))); // Focus widget
+
+ _font_size_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::fontsize_value_changed));
+ _font_size_item->focus_on_click(false);
+ add(*_font_size_item);
+ }
+ /* Font_ size units */
+ {
+ _font_size_units_item = _tracker_fs->create_tool_item(_("Units"), (""));
+ _font_size_units_item->signal_changed_after().connect(
+ sigc::mem_fun(*this, &TextToolbar::fontsize_unit_changed));
+ _font_size_units_item->focus_on_click(false);
+ add(*_font_size_units_item);
+ }
+ {
+ // Drop down menu
+ std::vector<Glib::ustring> labels = {_("Smaller spacing"), "", "", "", "", C_("Text tool", "Normal"), "", "", "", "", "", _("Larger spacing")};
+ std::vector<double> values = { 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 2.0};
+
+ auto line_height_val = 1.25;
+ _line_height_adj = Gtk::Adjustment::create(line_height_val, 0.0, 1000.0, 0.1, 1.0);
+ _line_height_item =
+ Gtk::manage(new UI::Widget::SpinButtonToolItem("text-line-height", "", _line_height_adj, 0.1, 2));
+ _line_height_item->set_tooltip_text(_("Spacing between baselines"));
+ _line_height_item->set_custom_numeric_menu_data(values, labels);
+ _line_height_item->set_focus_widget(desktop->getCanvas());
+ _line_height_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::lineheight_value_changed));
+ //_tracker->addAdjustment(_line_height_adj->gobj()); // (Alex V) Why is this commented out?
+ _line_height_item->set_sensitive(true);
+ _line_height_item->set_icon(INKSCAPE_ICON("text_line_spacing"));
+ add(*_line_height_item);
+ }
+ /* Line height units */
+ {
+ _line_height_units_item = _tracker->create_tool_item( _("Units"), (""));
+ _line_height_units_item->signal_changed_after().connect(sigc::mem_fun(*this, &TextToolbar::lineheight_unit_changed));
+ _line_height_units_item->focus_on_click(false);
+ add(*_line_height_units_item);
+ }
+
+ /* Alignment */
+ {
+ UI::Widget::ComboToolItemColumns columns;
+
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+
+ Gtk::TreeModel::Row row;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Align left");
+ row[columns.col_tooltip ] = _("Align left");
+ row[columns.col_icon ] = INKSCAPE_ICON("format-justify-left");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Align center");
+ row[columns.col_tooltip ] = _("Align center");
+ row[columns.col_icon ] = INKSCAPE_ICON("format-justify-center");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Align right");
+ row[columns.col_tooltip ] = _("Align right");
+ row[columns.col_icon ] = INKSCAPE_ICON("format-justify-right");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Justify");
+ row[columns.col_tooltip ] = _("Justify (only flowed text)");
+ row[columns.col_icon ] = INKSCAPE_ICON("format-justify-fill");
+ row[columns.col_sensitive] = false;
+
+ _align_item =
+ UI::Widget::ComboToolItem::create(_("Alignment"), // Label
+ _("Text alignment"), // Tooltip
+ "Not Used", // Icon
+ store ); // Tree store
+ _align_item->use_icon( true );
+ _align_item->use_label( false );
+ gint mode = prefs->getInt("/tools/text/align_mode", 0);
+ _align_item->set_active( mode );
+
+ add(*_align_item);
+ _align_item->focus_on_click(false);
+ _align_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::align_mode_changed));
+ }
+
+ /* Style - Superscript */
+ {
+ _superscript_item = Gtk::manage(new Gtk::ToggleToolButton());
+ _superscript_item->set_label(_("Toggle superscript"));
+ _superscript_item->set_tooltip_text(_("Toggle superscript"));
+ _superscript_item->set_icon_name(INKSCAPE_ICON("text_superscript"));
+ _superscript_item->set_name("text-superscript");
+ add(*_superscript_item);
+ _superscript_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &TextToolbar::script_changed), _superscript_item));
+ _superscript_item->set_active(prefs->getBool("/tools/text/super", false));
+ }
+
+ /* Style - Subscript */
+ {
+ _subscript_item = Gtk::manage(new Gtk::ToggleToolButton());
+ _subscript_item->set_label(_("Toggle subscript"));
+ _subscript_item->set_tooltip_text(_("Toggle subscript"));
+ _subscript_item->set_icon_name(INKSCAPE_ICON("text_subscript"));
+ _subscript_item->set_name("text-subscript");
+ add(*_subscript_item);
+ _subscript_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &TextToolbar::script_changed), _subscript_item));
+ _subscript_item->set_active(prefs->getBool("/tools/text/sub", false));
+ }
+
+ /* Character positioning popover */
+
+ auto positioning_item = Gtk::manage(new Gtk::ToolItem);
+ add(*positioning_item);
+
+ auto positioning_button = Gtk::manage(new Gtk::MenuButton);
+ positioning_button->set_image_from_icon_name(INKSCAPE_ICON("text_horz_kern"));
+ positioning_button->set_always_show_image(true);
+ positioning_button->set_tooltip_text(_("Kerning, word spacing, character positioning"));
+ positioning_button->set_label(_("Spacing"));
+ positioning_item->add(*positioning_button);
+
+ auto positioning_popover = Gtk::manage(new Gtk::Popover(*positioning_button));
+ positioning_popover->set_modal(false); // Stay open until button clicked again.
+ positioning_button->set_popover(*positioning_popover);
+
+ auto positioning_grid = Gtk::manage(new Gtk::Grid);
+ positioning_popover->add(*positioning_grid);
+
+
+ /* Letter spacing */
+ {
+ // Drop down menu
+ std::vector<Glib::ustring> labels = {_("Negative spacing"), "", "", "", C_("Text tool", "Normal"), "", "", "", "", "", "", "", _("Positive spacing")};
+ std::vector<double> values = { -2.0, -1.5, -1.0, -0.5, 0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0};
+ auto letter_spacing_val = prefs->getDouble("/tools/text/letterspacing", 0.0);
+ _letter_spacing_adj = Gtk::Adjustment::create(letter_spacing_val, -1000.0, 1000.0, 0.01, 0.10);
+ _letter_spacing_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-letter-spacing", _("Letter:"), _letter_spacing_adj, 0.1, 2));
+ _letter_spacing_item->set_tooltip_text(_("Spacing between letters (px)"));
+ _letter_spacing_item->set_custom_numeric_menu_data(values, labels);
+ _letter_spacing_item->set_focus_widget(desktop->getCanvas());
+ _letter_spacing_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::letterspacing_value_changed));
+ _letter_spacing_item->set_sensitive(true);
+ _letter_spacing_item->set_icon(INKSCAPE_ICON("text_letter_spacing"));
+
+ positioning_grid->attach(*_letter_spacing_item, 0, 0);
+ }
+
+ /* Word spacing */
+ {
+ // Drop down menu
+ std::vector<Glib::ustring> labels = {_("Negative spacing"), "", "", "", C_("Text tool", "Normal"), "", "", "", "", "", "", "", _("Positive spacing")};
+ std::vector<double> values = { -2.0, -1.5, -1.0, -0.5, 0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0};
+ auto word_spacing_val = prefs->getDouble("/tools/text/wordspacing", 0.0);
+ _word_spacing_adj = Gtk::Adjustment::create(word_spacing_val, -1000.0, 1000.0, 0.01, 0.10);
+ _word_spacing_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-word-spacing", _("Word:"), _word_spacing_adj, 0.1, 2));
+ _word_spacing_item->set_tooltip_text(_("Spacing between words (px)"));
+ _word_spacing_item->set_custom_numeric_menu_data(values, labels);
+ _word_spacing_item->set_focus_widget(desktop->getCanvas());
+ _word_spacing_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::wordspacing_value_changed));
+ _word_spacing_item->set_sensitive(true);
+ _word_spacing_item->set_icon(INKSCAPE_ICON("text_word_spacing"));
+
+ positioning_grid->attach(*_word_spacing_item, 1, 0);
+ }
+
+ /* Character kerning (horizontal shift) */
+ {
+ // Drop down menu
+ std::vector<double> values = { -2.0, -1.5, -1.0, -0.5, 0, 0.5, 1.0, 1.5, 2.0, 2.5 };
+ auto dx_val = prefs->getDouble("/tools/text/dx", 0.0);
+ _dx_adj = Gtk::Adjustment::create(dx_val, -1000.0, 1000.0, 0.01, 0.1);
+ _dx_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-dx", _("Kern:"), _dx_adj, 0.1, 2));
+ _dx_item->set_custom_numeric_menu_data(values);
+ _dx_item->set_tooltip_text(_("Horizontal kerning (px)"));
+ _dx_item->set_focus_widget(desktop->getCanvas());
+ _dx_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::dx_value_changed));
+ _dx_item->set_sensitive(true);
+ _dx_item->set_icon(INKSCAPE_ICON("text_horz_kern"));
+
+ positioning_grid->attach(*_dx_item, 0, 1);
+ }
+
+ /* Character vertical shift */
+ {
+ // Drop down menu
+ std::vector<double> values = { -2.0, -1.5, -1.0, -0.5, 0, 0.5, 1.0, 1.5, 2.0, 2.5 };
+ auto dy_val = prefs->getDouble("/tools/text/dy", 0.0);
+ _dy_adj = Gtk::Adjustment::create(dy_val, -1000.0, 1000.0, 0.01, 0.1);
+ _dy_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-dy", _("Vert:"), _dy_adj, 0.1, 2));
+ _dy_item->set_tooltip_text(_("Vertical kerning (px)"));
+ _dy_item->set_custom_numeric_menu_data(values);
+ _dy_item->set_focus_widget(desktop->getCanvas());
+ _dy_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::dy_value_changed));
+ _dy_item->set_sensitive(true);
+ _dy_item->set_icon(INKSCAPE_ICON("text_vert_kern"));
+
+ positioning_grid->attach(*_dy_item, 1, 1);
+ }
+
+ /* Character rotation */
+ {
+ std::vector<double> values = { -90, -45, -30, -15, 0, 15, 30, 45, 90, 180 };
+ auto rotation_val = prefs->getDouble("/tools/text/rotation", 0.0);
+ _rotation_adj = Gtk::Adjustment::create(rotation_val, -180.0, 180.0, 0.1, 1.0);
+ _rotation_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("text-rotation", _("Rot:"), _rotation_adj, 0.1, 2));
+ _rotation_item->set_tooltip_text(_("Character rotation (degrees)"));
+ _rotation_item->set_custom_numeric_menu_data(values);
+ _rotation_item->set_focus_widget(desktop->getCanvas());
+ _rotation_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::rotation_value_changed));
+ _rotation_item->set_sensitive();
+ _rotation_item->set_icon(INKSCAPE_ICON("text_rotation"));
+
+ positioning_grid->attach(*_rotation_item, 2, 1);
+ }
+
+ positioning_grid->show_all();
+
+ /* Writing mode (Horizontal, Vertical-LR, Vertical-RL) */
+ {
+ UI::Widget::ComboToolItemColumns columns;
+
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+
+ Gtk::TreeModel::Row row;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Horizontal");
+ row[columns.col_tooltip ] = _("Horizontal text");
+ row[columns.col_icon ] = INKSCAPE_ICON("frmt-text-direction-horizontal");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Vertical — RL");
+ row[columns.col_tooltip ] = _("Vertical text — lines: right to left");
+ row[columns.col_icon ] = INKSCAPE_ICON("frmt-text-direction-vertical");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Vertical — LR");
+ row[columns.col_tooltip ] = _("Vertical text — lines: left to right");
+ row[columns.col_icon ] = INKSCAPE_ICON("frmt-text-direction-vertical-lr");
+ row[columns.col_sensitive] = true;
+
+ _writing_mode_item =
+ UI::Widget::ComboToolItem::create( _("Writing mode"), // Label
+ _("Block progression"), // Tooltip
+ "Not Used", // Icon
+ store ); // Tree store
+ _writing_mode_item->use_icon(true);
+ _writing_mode_item->use_label( false );
+ gint mode = prefs->getInt("/tools/text/writing_mode", 0);
+ _writing_mode_item->set_active( mode );
+ add(*_writing_mode_item);
+ _writing_mode_item->focus_on_click(false);
+ _writing_mode_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::writing_mode_changed));
+ }
+
+
+ /* Text (glyph) orientation (Auto (mixed), Upright, Sideways) */
+ {
+ UI::Widget::ComboToolItemColumns columns;
+
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+
+ Gtk::TreeModel::Row row;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Auto");
+ row[columns.col_tooltip ] = _("Auto glyph orientation");
+ row[columns.col_icon ] = INKSCAPE_ICON("text-orientation-auto");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Upright");
+ row[columns.col_tooltip ] = _("Upright glyph orientation");
+ row[columns.col_icon ] = INKSCAPE_ICON("text-orientation-upright");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("Sideways");
+ row[columns.col_tooltip ] = _("Sideways glyph orientation");
+ row[columns.col_icon ] = INKSCAPE_ICON("text-orientation-sideways");
+ row[columns.col_sensitive] = true;
+
+ _orientation_item =
+ UI::Widget::ComboToolItem::create(_("Text orientation"), // Label
+ _("Text (glyph) orientation in vertical text."), // Tooltip
+ "Not Used", // Icon
+ store ); // List store
+ _orientation_item->use_icon(true);
+ _orientation_item->use_label(false);
+ gint mode = prefs->getInt("/tools/text/text_orientation", 0);
+ _orientation_item->set_active( mode );
+ _orientation_item->focus_on_click(false);
+ add(*_orientation_item);
+
+ _orientation_item->signal_changed().connect(sigc::mem_fun(*this, &TextToolbar::orientation_changed));
+ }
+
+ // Text direction (predominant direction of horizontal text).
+ {
+ UI::Widget::ComboToolItemColumns columns;
+
+ Glib::RefPtr<Gtk::ListStore> store = Gtk::ListStore::create(columns);
+
+ Gtk::TreeModel::Row row;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("LTR");
+ row[columns.col_tooltip ] = _("Left to right text");
+ row[columns.col_icon ] = INKSCAPE_ICON("frmt-text-direction-horizontal");
+ row[columns.col_sensitive] = true;
+
+ row = *(store->append());
+ row[columns.col_label ] = _("RTL");
+ row[columns.col_tooltip ] = _("Right to left text");
+ row[columns.col_icon ] = INKSCAPE_ICON("frmt-text-direction-r2l");
+ row[columns.col_sensitive] = true;
+
+ _direction_item =
+ UI::Widget::ComboToolItem::create( _("Text direction"), // Label
+ _("Text direction for normally horizontal text."), // Tooltip
+ "Not Used", // Icon
+ store ); // List store
+ _direction_item->use_icon(true);
+ _direction_item->use_label(false);
+ gint mode = prefs->getInt("/tools/text/text_direction", 0);
+ _direction_item->set_active( mode );
+ _direction_item->focus_on_click(false);
+ add(*_direction_item);
+
+ _direction_item->signal_changed_after().connect(sigc::mem_fun(*this, &TextToolbar::direction_changed));
+ }
+
+ show_all();
+
+ // we emit a selection change on tool switch to text
+ desktop->connectEventContextChanged(sigc::mem_fun(*this, &TextToolbar::watch_ec));
+}
+
+/*
+ * Set the style, depending on the inner or outer text being selected
+ */
+void TextToolbar::text_outer_set_style(SPCSSAttr *css)
+{
+ // Calling sp_desktop_set_style will result in a call to TextTool::_styleSet() which
+ // will set the style on selected text inside the <text> element. If we want to set
+ // the style on the outer <text> objects we need to bypass this call.
+ SPDesktop *desktop = _desktop;
+ if(_outer) {
+ // Apply css to parent text objects directly.
+ for (auto i : desktop->getSelection()->items()) {
+ SPItem *item = dynamic_cast<SPItem *>(i);
+ if (dynamic_cast<SPText *>(item) || dynamic_cast<SPFlowtext *>(item)) {
+ // Scale by inverse of accumulated parent transform
+ SPCSSAttr *css_set = sp_repr_css_attr_new();
+ sp_repr_css_merge(css_set, css);
+ Geom::Affine const local(item->i2doc_affine());
+ double const ex(local.descrim());
+ if ((ex != 0.0) && (ex != 1.0)) {
+ sp_css_attr_scale(css_set, 1 / ex);
+ }
+ recursively_set_properties(item, css_set);
+ sp_repr_css_attr_unref(css_set);
+ }
+ }
+ } else {
+ // Apply css to selected inner objects.
+ sp_desktop_set_style (desktop, css, true, false);
+ }
+}
+
+void
+TextToolbar::fontfamily_value_changed()
+{
+#ifdef DEBUG_TEXT
+ std::cout << std::endl;
+ std::cout << "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" << std::endl;
+ std::cout << "sp_text_fontfamily_value_changed: " << std::endl;
+#endif
+
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+#ifdef DEBUG_TEXT
+ std::cout << "sp_text_fontfamily_value_changed: frozen... return" << std::endl;
+ std::cout << "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n" << std::endl;
+#endif
+ return;
+ }
+ _freeze = true;
+
+ Glib::ustring new_family = _font_family_item->get_active_text();
+ css_font_family_unquote( new_family ); // Remove quotes around font family names.
+
+ // TODO: Think about how to handle handle multiple selections. While
+ // the font-family may be the same for all, the styles might be different.
+ // See: TextEdit::onApply() for example of looping over selected items.
+ Inkscape::FontLister* fontlister = Inkscape::FontLister::get_instance();
+#ifdef DEBUG_TEXT
+ std::cout << " Old family: " << fontlister->get_font_family() << std::endl;
+ std::cout << " New family: " << new_family << std::endl;
+ std::cout << " Old active: " << fontlister->get_font_family_row() << std::endl;
+ // std::cout << " New active: " << act->active << std::endl;
+#endif
+ if( new_family.compare( fontlister->get_font_family() ) != 0 ) {
+ // Changed font-family
+
+ if( _font_family_item->get_active() == -1 ) {
+ // New font-family, not in document, not on system (could be fallback list)
+ fontlister->insert_font_family( new_family );
+
+ // This just sets a variable in the ComboBoxEntryAction object...
+ // shouldn't we also set the actual active row in the combobox?
+ _font_family_item->set_active(0); // New family is always at top of list.
+ }
+
+ fontlister->set_font_family( _font_family_item->get_active() );
+ // active text set in sp_text_toolbox_selection_changed()
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ fontlister->fill_css( css );
+
+ SPDesktop *desktop = _desktop;
+ if( desktop->getSelection()->isEmpty() ) {
+ // Update default
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ } else {
+ // If there is a selection, update
+ sp_desktop_set_style (desktop, css, true, true); // Results in selection change called twice.
+ DocumentUndo::done(desktop->getDocument(), _("Text: Change font family"), INKSCAPE_ICON("draw-text"));
+ }
+ sp_repr_css_attr_unref (css);
+ }
+
+ // unfreeze
+ _freeze = false;
+
+#ifdef DEBUG_TEXT
+ std::cout << "sp_text_toolbox_fontfamily_changes: exit" << std::endl;
+ std::cout << "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" << std::endl;
+ std::cout << std::endl;
+#endif
+}
+
+GtkWidget *
+TextToolbar::create(SPDesktop *desktop)
+{
+ auto tb = Gtk::manage(new TextToolbar(desktop));
+ return GTK_WIDGET(tb->gobj());
+}
+
+void
+TextToolbar::fontsize_value_changed()
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ auto active_text = _font_size_item->get_active_text();
+ char const *text = active_text.c_str();
+ gchar *endptr;
+ gdouble size = g_strtod( text, &endptr );
+ if (endptr == text) { // Conversion failed, non-numeric input.
+ g_warning( "Conversion of size text to double failed, input: %s\n", text );
+ _freeze = false;
+ return;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int max_size = prefs->getInt("/dialogs/textandfont/maxFontSize", 10000); // somewhat arbitrary, but text&font preview freezes with too huge fontsizes
+
+ if (size > max_size)
+ size = max_size;
+
+ // Set css font size.
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ Inkscape::CSSOStringStream osfs;
+ int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
+ if (prefs->getBool("/options/font/textOutputPx", true)) {
+ osfs << sp_style_css_size_units_to_px(size, unit) << sp_style_get_css_unit_string(SP_CSS_UNIT_PX);
+ } else {
+ osfs << size << sp_style_get_css_unit_string(unit);
+ }
+ sp_repr_css_set_property (css, "font-size", osfs.str().c_str());
+ double factor = size / selection_fontsize;
+
+ // Apply font size to selected objects.
+ text_outer_set_style(css);
+
+ Unit const *unit_lh = _tracker->getActiveUnit();
+ g_return_if_fail(unit_lh != nullptr);
+ if (!is_relative(unit_lh) && _outer) {
+ double lineheight = _line_height_adj->get_value();
+ _freeze = false;
+ _line_height_adj->set_value(lineheight * factor);
+ _freeze = true;
+ }
+ // If no selected objects, set default.
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ } else {
+ // Save for undo
+ sp_desktop_set_style (_desktop, css, true, true);
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:size", _("Text: Change font size"), INKSCAPE_ICON("draw-text"));
+ }
+
+ sp_repr_css_attr_unref(css);
+
+ _freeze = false;
+}
+
+void
+TextToolbar::fontstyle_value_changed()
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ Glib::ustring new_style = _font_style_item->get_active_text();
+
+ Inkscape::FontLister* fontlister = Inkscape::FontLister::get_instance();
+
+ if( new_style.compare( fontlister->get_font_style() ) != 0 ) {
+
+ fontlister->set_font_style( new_style );
+ // active text set in sp_text_toolbox_seletion_changed()
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ fontlister->fill_css( css );
+
+ SPDesktop *desktop = _desktop;
+ sp_desktop_set_style (desktop, css, true, true);
+
+
+ // If no selected objects, set default.
+ SPStyle query(_desktop->getDocument());
+ int result_style =
+ sp_desktop_query_style (desktop, &query, QUERY_STYLE_PROPERTY_FONTSTYLE);
+ if (result_style == QUERY_STYLE_NOTHING) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ } else {
+ // Save for undo
+ DocumentUndo::done(desktop->getDocument(), _("Text: Change font style"), INKSCAPE_ICON("draw-text"));
+ }
+
+ sp_repr_css_attr_unref (css);
+
+ }
+
+ _freeze = false;
+}
+
+// Handles both Superscripts and Subscripts
+void
+TextToolbar::script_changed(Gtk::ToggleToolButton *btn)
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+
+ _freeze = true;
+
+ // Called by Superscript or Subscript button?
+ auto name = btn->get_name();
+ gint prop = (btn == _superscript_item) ? 0 : 1;
+
+#ifdef DEBUG_TEXT
+ std::cout << "TextToolbar::script_changed: " << prop << std::endl;
+#endif
+
+ // Query baseline
+ SPStyle query(_desktop->getDocument());
+ int result_baseline = sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_BASELINES);
+
+ bool setSuper = false;
+ bool setSub = false;
+
+ if (Inkscape::is_query_style_updateable(result_baseline)) {
+ // If not set or mixed, turn on superscript or subscript
+ if( prop == 0 ) {
+ setSuper = true;
+ } else {
+ setSub = true;
+ }
+ } else {
+ // Superscript
+ gboolean superscriptSet = (query.baseline_shift.set &&
+ query.baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
+ query.baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUPER );
+
+ // Subscript
+ gboolean subscriptSet = (query.baseline_shift.set &&
+ query.baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
+ query.baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUB );
+
+ setSuper = !superscriptSet && prop == 0;
+ setSub = !subscriptSet && prop == 1;
+ }
+
+ // Set css properties
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ if( setSuper || setSub ) {
+ // Openoffice 2.3 and Adobe use 58%, Microsoft Word 2002 uses 65%, LaTex about 70%.
+ // 58% looks too small to me, especially if a superscript is placed on a superscript.
+ // If you make a change here, consider making a change to baseline-shift amount
+ // in style.cpp.
+ sp_repr_css_set_property (css, "font-size", "65%");
+ } else {
+ sp_repr_css_set_property (css, "font-size", "");
+ }
+ if( setSuper ) {
+ sp_repr_css_set_property (css, "baseline-shift", "super");
+ } else if( setSub ) {
+ sp_repr_css_set_property (css, "baseline-shift", "sub");
+ } else {
+ sp_repr_css_set_property (css, "baseline-shift", "baseline");
+ }
+
+ // Apply css to selected objects.
+ SPDesktop *desktop = _desktop;
+ sp_desktop_set_style (desktop, css, true, false);
+
+ // Save for undo
+ if(result_baseline != QUERY_STYLE_NOTHING) {
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:script", _("Text: Change superscript or subscript"), INKSCAPE_ICON("draw-text"));
+ }
+ _freeze = false;
+}
+
+void
+TextToolbar::align_mode_changed(int mode)
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/tools/text/align_mode", mode);
+
+ SPDesktop *desktop = _desktop;
+
+ // move the x of all texts to preserve the same bbox
+ Inkscape::Selection *selection = desktop->getSelection();
+ auto itemlist= selection->items();
+ for (auto i : itemlist) {
+ SPText *text = dynamic_cast<SPText *>(i);
+ // SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
+ if (text) {
+ SPItem *item = i;
+
+ unsigned writing_mode = item->style->writing_mode.value;
+ // below, variable names suggest horizontal move, but we check the writing direction
+ // and move in the corresponding axis
+ Geom::Dim2 axis;
+ if (writing_mode == SP_CSS_WRITING_MODE_LR_TB || writing_mode == SP_CSS_WRITING_MODE_RL_TB) {
+ axis = Geom::X;
+ } else {
+ axis = Geom::Y;
+ }
+
+ Geom::OptRect bbox = item->geometricBounds();
+ if (!bbox)
+ continue;
+ double width = bbox->dimensions()[axis];
+ // If you want to align within some frame, other than the text's own bbox, calculate
+ // the left and right (or top and bottom for tb text) slacks of the text inside that
+ // frame (currently unused)
+ double left_slack = 0;
+ double right_slack = 0;
+ unsigned old_align = item->style->text_align.value;
+ double move = 0;
+ if (old_align == SP_CSS_TEXT_ALIGN_START || old_align == SP_CSS_TEXT_ALIGN_LEFT) {
+ switch (mode) {
+ case 0:
+ move = -left_slack;
+ break;
+ case 1:
+ move = width/2 + (right_slack - left_slack)/2;
+ break;
+ case 2:
+ move = width + right_slack;
+ break;
+ }
+ } else if (old_align == SP_CSS_TEXT_ALIGN_CENTER) {
+ switch (mode) {
+ case 0:
+ move = -width/2 - left_slack;
+ break;
+ case 1:
+ move = (right_slack - left_slack)/2;
+ break;
+ case 2:
+ move = width/2 + right_slack;
+ break;
+ }
+ } else if (old_align == SP_CSS_TEXT_ALIGN_END || old_align == SP_CSS_TEXT_ALIGN_RIGHT) {
+ switch (mode) {
+ case 0:
+ move = -width - left_slack;
+ break;
+ case 1:
+ move = -width/2 + (right_slack - left_slack)/2;
+ break;
+ case 2:
+ move = right_slack;
+ break;
+ }
+ }
+ Geom::Point XY = SP_TEXT(item)->attributes.firstXY();
+ if (axis == Geom::X) {
+ XY = XY + Geom::Point (move, 0);
+ } else {
+ XY = XY + Geom::Point (0, move);
+ }
+ SP_TEXT(item)->attributes.setFirstXY(XY);
+ item->updateRepr();
+ item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ }
+ }
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ switch (mode)
+ {
+ case 0:
+ {
+ sp_repr_css_set_property (css, "text-anchor", "start");
+ sp_repr_css_set_property (css, "text-align", "start");
+ break;
+ }
+ case 1:
+ {
+ sp_repr_css_set_property (css, "text-anchor", "middle");
+ sp_repr_css_set_property (css, "text-align", "center");
+ break;
+ }
+
+ case 2:
+ {
+ sp_repr_css_set_property (css, "text-anchor", "end");
+ sp_repr_css_set_property (css, "text-align", "end");
+ break;
+ }
+
+ case 3:
+ {
+ sp_repr_css_set_property (css, "text-anchor", "start");
+ sp_repr_css_set_property (css, "text-align", "justify");
+ break;
+ }
+ }
+
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+
+ // If querying returned nothing, update default style.
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ }
+
+ sp_desktop_set_style (desktop, css, true, true);
+ if (result_numbers != QUERY_STYLE_NOTHING)
+ {
+ DocumentUndo::done(_desktop->getDocument(), _("Text: Change alignment"), INKSCAPE_ICON("draw-text"));
+ }
+ sp_repr_css_attr_unref (css);
+
+ desktop->getCanvas()->grab_focus();
+
+ _freeze = false;
+}
+
+void
+TextToolbar::writing_mode_changed(int mode)
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ switch (mode)
+ {
+ case 0:
+ {
+ sp_repr_css_set_property (css, "writing-mode", "lr-tb");
+ break;
+ }
+
+ case 1:
+ {
+ sp_repr_css_set_property (css, "writing-mode", "tb-rl");
+ break;
+ }
+
+ case 2:
+ {
+ sp_repr_css_set_property (css, "writing-mode", "vertical-lr");
+ break;
+ }
+ }
+
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_WRITINGMODES);
+
+ // If querying returned nothing, update default style.
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ }
+
+ sp_desktop_set_style (_desktop, css, true, true);
+ if(result_numbers != QUERY_STYLE_NOTHING)
+ {
+ DocumentUndo::done(_desktop->getDocument(), _("Text: Change writing mode"), INKSCAPE_ICON("draw-text"));
+ }
+ sp_repr_css_attr_unref (css);
+
+ _desktop->getCanvas()->grab_focus();
+
+ _freeze = false;
+}
+
+void
+TextToolbar::orientation_changed(int mode)
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ switch (mode)
+ {
+ case 0:
+ {
+ sp_repr_css_set_property (css, "text-orientation", "auto");
+ break;
+ }
+
+ case 1:
+ {
+ sp_repr_css_set_property (css, "text-orientation", "upright");
+ break;
+ }
+
+ case 2:
+ {
+ sp_repr_css_set_property (css, "text-orientation", "sideways");
+ break;
+ }
+ }
+
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_WRITINGMODES);
+
+ // If querying returned nothing, update default style.
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ }
+
+ sp_desktop_set_style (_desktop, css, true, true);
+ if(result_numbers != QUERY_STYLE_NOTHING)
+ {
+ DocumentUndo::done(_desktop->getDocument(), _("Text: Change orientation"), INKSCAPE_ICON("draw-text"));
+ }
+ sp_repr_css_attr_unref (css);
+ _desktop->canvas->grab_focus();
+
+ _freeze = false;
+}
+
+void
+TextToolbar::direction_changed(int mode)
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ switch (mode)
+ {
+ case 0:
+ {
+ sp_repr_css_set_property (css, "direction", "ltr");
+ break;
+ }
+
+ case 1:
+ {
+ sp_repr_css_set_property (css, "direction", "rtl");
+ break;
+ }
+ }
+
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_WRITINGMODES);
+
+ // If querying returned nothing, update default style.
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ }
+
+ sp_desktop_set_style (_desktop, css, true, true);
+ if(result_numbers != QUERY_STYLE_NOTHING)
+ {
+ DocumentUndo::done(_desktop->getDocument(), _("Text: Change direction"), INKSCAPE_ICON("draw-text"));
+ }
+ sp_repr_css_attr_unref (css);
+
+ _desktop->getCanvas()->grab_focus();
+
+ _freeze = false;
+}
+
+void
+TextToolbar::lineheight_value_changed()
+{
+ // quit if run by the _changed callbacks or is not text tool
+ if (_freeze || !SP_IS_TEXT_CONTEXT(_desktop->event_context)) {
+ return;
+ }
+
+ _freeze = true;
+ SPDesktop *desktop = _desktop;
+ // Get user selected unit and save as preference
+ Unit const *unit = _tracker->getActiveUnit();
+ // @Tav same disabled unit
+ g_return_if_fail(unit != nullptr);
+
+ // This nonsense is to get SP_CSS_UNIT_xx value corresponding to unit so
+ // we can save it (allows us to adjust line height value when unit changes).
+
+ // Set css line height.
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ Inkscape::CSSOStringStream osfs;
+ if ( is_relative(unit) ) {
+ osfs << _line_height_adj->get_value() << unit->abbr;
+ } else {
+ // Inside SVG file, always use "px" for absolute units.
+ osfs << Quantity::convert(_line_height_adj->get_value(), unit, "px") << "px";
+ }
+
+ sp_repr_css_set_property (css, "line-height", osfs.str().c_str());
+
+ Inkscape::Selection *selection = desktop->getSelection();
+ auto itemlist = selection->items();
+ if (_outer) {
+ // Special else makes this different from other uses of text_outer_set_style
+ text_outer_set_style(css);
+ } else {
+ SPItem *parent = dynamic_cast<SPItem *>(*itemlist.begin());
+ SPStyle *parent_style = parent->style;
+ SPCSSAttr *parent_cssatr = sp_css_attr_from_style(parent_style, SP_STYLE_FLAG_IFSET);
+ Glib::ustring parent_lineheight = sp_repr_css_property(parent_cssatr, "line-height", "1.25");
+ SPCSSAttr *cssfit = sp_repr_css_attr_new();
+ sp_repr_css_set_property(cssfit, "line-height", parent_lineheight.c_str());
+ double minheight = 0;
+ if (parent_style) {
+ minheight = parent_style->line_height.computed;
+ }
+ if (minheight) {
+ for (auto i : parent->childList(false)) {
+ SPItem *child = dynamic_cast<SPItem *>(i);
+ if (!child) {
+ continue;
+ }
+ recursively_set_properties(child, cssfit);
+ }
+ }
+ sp_repr_css_set_property(cssfit, "line-height", "0");
+ parent->changeCSS(cssfit, "style");
+ subselection_wrap_toggle(true);
+ sp_desktop_set_style(desktop, css, true, true);
+ subselection_wrap_toggle(false);
+ sp_repr_css_attr_unref(cssfit);
+ }
+ // Only need to save for undo if a text item has been changed.
+ itemlist = selection->items();
+ bool modmade = false;
+ for (auto i : itemlist) {
+ SPText *text = dynamic_cast<SPText *>(i);
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
+ if (text || flowtext) {
+ modmade = true;
+ break;
+ }
+ }
+
+ // Save for undo
+ if (modmade) {
+ // Call ensureUpToDate() causes rebuild of text layout (with all proper style
+ // cascading, etc.). For multi-line text with sodipodi::role="line", we must explicitly
+ // save new <tspan> 'x' and 'y' attribute values by calling updateRepr().
+ // Partial fix for bug #1590141.
+
+ desktop->getDocument()->ensureUpToDate();
+ for (auto i : itemlist) {
+ SPText *text = dynamic_cast<SPText *>(i);
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
+ if (text || flowtext) {
+ (i)->updateRepr();
+ }
+ }
+ if (!_outer) {
+ prepare_inner();
+ }
+ DocumentUndo::maybeDone(desktop->getDocument(), "ttb:line-height", _("Text: Change line-height"), INKSCAPE_ICON("draw-text"));
+ }
+
+ // If no selected objects, set default.
+ SPStyle query(_desktop->getDocument());
+ int result_numbers = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ }
+
+ sp_repr_css_attr_unref (css);
+
+ _freeze = false;
+}
+
+void
+TextToolbar::lineheight_unit_changed(int /* Not Used */)
+{
+ // quit if run by the _changed callbacks or is not text tool
+ if (_freeze || !SP_IS_TEXT_CONTEXT(_desktop->event_context)) {
+ return;
+ }
+ _freeze = true;
+
+ // Get old saved unit
+ int old_unit = _lineheight_unit;
+
+ // Get user selected unit and save as preference
+ Unit const *unit = _tracker->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ // This nonsense is to get SP_CSS_UNIT_xx value corresponding to unit.
+ SPILength temp_length;
+ Inkscape::CSSOStringStream temp_stream;
+ temp_stream << 1 << unit->abbr;
+ temp_length.read(temp_stream.str().c_str());
+ prefs->setInt("/tools/text/lineheight/display_unit", temp_length.unit);
+ if (old_unit == temp_length.unit) {
+ _freeze = false;
+ return;
+ } else {
+ _lineheight_unit = temp_length.unit;
+ }
+
+ // Read current line height value
+ double line_height = _line_height_adj->get_value();
+ SPDesktop *desktop = _desktop;
+ Inkscape::Selection *selection = desktop->getSelection();
+ auto itemlist = selection->items();
+
+ // Convert between units
+ double font_size = 0;
+ double doc_scale = 1;
+ int count = 0;
+ bool has_flow = false;
+
+ for (auto i : itemlist) {
+ SPText *text = dynamic_cast<SPText *>(i);
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
+ if (text || flowtext) {
+ doc_scale = Geom::Affine(i->i2dt_affine()).descrim();
+ font_size += i->style->font_size.computed * doc_scale;
+ ++count;
+ }
+ if (flowtext) {
+ has_flow = true;
+ }
+ }
+ if (count > 0) {
+ font_size /= count;
+ } else {
+ // ideally use default font-size.
+ font_size = 20;
+ }
+ if ((unit->abbr == "" || unit->abbr == "em") && (old_unit == SP_CSS_UNIT_NONE || old_unit == SP_CSS_UNIT_EM)) {
+ // Do nothing
+ } else if ((unit->abbr == "" || unit->abbr == "em") && old_unit == SP_CSS_UNIT_EX) {
+ line_height *= 0.5;
+ } else if ((unit->abbr) == "ex" && (old_unit == SP_CSS_UNIT_EM || old_unit == SP_CSS_UNIT_NONE)) {
+ line_height *= 2.0;
+ } else if ((unit->abbr == "" || unit->abbr == "em") && old_unit == SP_CSS_UNIT_PERCENT) {
+ line_height /= 100.0;
+ } else if ((unit->abbr) == "%" && (old_unit == SP_CSS_UNIT_EM || old_unit == SP_CSS_UNIT_NONE)) {
+ line_height *= 100;
+ } else if ((unit->abbr) == "ex" && old_unit == SP_CSS_UNIT_PERCENT) {
+ line_height /= 50.0;
+ } else if ((unit->abbr) == "%" && old_unit == SP_CSS_UNIT_EX) {
+ line_height *= 50;
+ } else if (is_relative(unit)) {
+ // Convert absolute to relative... for the moment use average font-size
+ if (old_unit == SP_CSS_UNIT_NONE) old_unit = SP_CSS_UNIT_EM;
+ line_height = Quantity::convert(line_height, sp_style_get_css_unit_string(old_unit), "px");
+
+ if (font_size > 0) {
+ line_height /= font_size;
+ }
+ if ((unit->abbr) == "%") {
+ line_height *= 100;
+ } else if ((unit->abbr) == "ex") {
+ line_height *= 2;
+ }
+ } else if (old_unit == SP_CSS_UNIT_NONE || old_unit == SP_CSS_UNIT_PERCENT || old_unit == SP_CSS_UNIT_EM ||
+ old_unit == SP_CSS_UNIT_EX) {
+ // Convert relative to absolute... for the moment use average font-size
+ if (old_unit == SP_CSS_UNIT_PERCENT) {
+ line_height /= 100.0;
+ } else if (old_unit == SP_CSS_UNIT_EX) {
+ line_height /= 2.0;
+ }
+ line_height *= font_size;
+ line_height = Quantity::convert(line_height, "px", unit);
+ } else {
+ // Convert between different absolute units (only used in GUI)
+ line_height = Quantity::convert(line_height, sp_style_get_css_unit_string(old_unit), unit);
+ }
+ // Set css line height.
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ Inkscape::CSSOStringStream osfs;
+ // Set css line height.
+ if ( is_relative(unit) ) {
+ osfs << line_height << unit->abbr;
+ } else {
+ osfs << Quantity::convert(line_height, unit, "px") << "px";
+ }
+ sp_repr_css_set_property (css, "line-height", osfs.str().c_str());
+
+ // Update GUI with line_height value.
+ _line_height_adj->set_value(line_height);
+ // Update "climb rate" The custom action has a step property but no way to set it.
+ if (unit->abbr == "%") {
+ _line_height_adj->set_step_increment(1.0);
+ _line_height_adj->set_page_increment(10.0);
+ } else {
+ _line_height_adj->set_step_increment(0.1);
+ _line_height_adj->set_page_increment(1.0);
+ }
+ // Internal function to set line-height which is spacing mode dependent.
+ SPItem *parent = itemlist.empty() ? nullptr : dynamic_cast<SPItem *>(*itemlist.begin());
+ SPStyle *parent_style = nullptr;
+ if (parent) {
+ parent_style = parent->style;
+ }
+ bool inside = false;
+ if (_outer) {
+ if (!selection->singleItem() || !parent_style || parent_style->line_height.computed != 0) {
+ for (auto i = itemlist.begin(); i != itemlist.end(); ++i) {
+ if (dynamic_cast<SPText *>(*i) || dynamic_cast<SPFlowtext *>(*i)) {
+ SPItem *item = *i;
+ // Scale by inverse of accumulated parent transform
+ SPCSSAttr *css_set = sp_repr_css_attr_new();
+ sp_repr_css_merge(css_set, css);
+ Geom::Affine const local(item->i2doc_affine());
+ double const ex(local.descrim());
+ if ((ex != 0.0) && (ex != 1.0)) {
+ sp_css_attr_scale(css_set, 1 / ex);
+ }
+ recursively_set_properties(item, css_set);
+ sp_repr_css_attr_unref(css_set);
+ }
+ }
+ } else {
+ inside = true;
+ }
+ }
+ if (!_outer || inside) {
+ SPCSSAttr *parent_cssatr = sp_css_attr_from_style(parent_style, SP_STYLE_FLAG_IFSET);
+ Glib::ustring parent_lineheight = sp_repr_css_property(parent_cssatr, "line-height", "1.25");
+ SPCSSAttr *cssfit = sp_repr_css_attr_new();
+ sp_repr_css_set_property(cssfit, "line-height", parent_lineheight.c_str());
+ double minheight = 0;
+ if (parent_style) {
+ minheight = parent_style->line_height.computed;
+ }
+ if (minheight) {
+ for (auto i : parent->childList(false)) {
+ SPItem *child = dynamic_cast<SPItem *>(i);
+ if (!child) {
+ continue;
+ }
+ recursively_set_properties(child, cssfit);
+ }
+ }
+ sp_repr_css_set_property(cssfit, "line-height", "0");
+ parent->changeCSS(cssfit, "style");
+ subselection_wrap_toggle(true);
+ sp_desktop_set_style(desktop, css, true, true);
+ subselection_wrap_toggle(false);
+ sp_repr_css_attr_unref(cssfit);
+ }
+ itemlist= selection->items();
+ // Only need to save for undo if a text item has been changed.
+ bool modmade = false;
+ for (auto i : itemlist) {
+ SPText *text = dynamic_cast<SPText *>(i);
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
+ if (text || flowtext) {
+ modmade = true;
+ break;
+ }
+ }
+ // Save for undo
+ if(modmade) {
+ // Call ensureUpToDate() causes rebuild of text layout (with all proper style
+ // cascading, etc.). For multi-line text with sodipodi::role="line", we must explicitly
+ // save new <tspan> 'x' and 'y' attribute values by calling updateRepr().
+ // Partial fix for bug #1590141.
+
+ desktop->getDocument()->ensureUpToDate();
+ for (auto i : itemlist) {
+ SPText *text = dynamic_cast<SPText *>(i);
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
+ if (text || flowtext) {
+ (i)->updateRepr();
+ }
+ }
+ if (_outer) {
+ prepare_inner();
+ }
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:line-height", _("Text: Change line-height unit"), INKSCAPE_ICON("draw-text"));
+ }
+
+ // If no selected objects, set default.
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ }
+
+ sp_repr_css_attr_unref (css);
+
+ _freeze = false;
+}
+
+void TextToolbar::fontsize_unit_changed(int /* Not Used */)
+{
+ // quit if run by the _changed callbacks
+ Unit const *unit = _tracker_fs->getActiveUnit();
+ g_return_if_fail(unit != nullptr);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ // This nonsense is to get SP_CSS_UNIT_xx value corresponding to unit.
+ SPILength temp_size;
+ Inkscape::CSSOStringStream temp_size_stream;
+ temp_size_stream << 1 << unit->abbr;
+ temp_size.read(temp_size_stream.str().c_str());
+ prefs->setInt("/options/font/unitType", temp_size.unit);
+ selection_changed(_desktop->selection);
+}
+
+void
+TextToolbar::wordspacing_value_changed()
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ // At the moment this handles only numerical values (i.e. no em unit).
+ // Set css word-spacing
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ Inkscape::CSSOStringStream osfs;
+ osfs << _word_spacing_adj->get_value() << "px"; // For now always use px
+ sp_repr_css_set_property (css, "word-spacing", osfs.str().c_str());
+ text_outer_set_style(css);
+
+ // If no selected objects, set default.
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ } else {
+ // Save for undo
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:word-spacing", _("Text: Change word-spacing"), INKSCAPE_ICON("draw-text"));
+ }
+
+ sp_repr_css_attr_unref (css);
+
+ _freeze = false;
+}
+
+void
+TextToolbar::letterspacing_value_changed()
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ // At the moment this handles only numerical values (i.e. no em unit).
+ // Set css letter-spacing
+ SPCSSAttr *css = sp_repr_css_attr_new ();
+ Inkscape::CSSOStringStream osfs;
+ osfs << _letter_spacing_adj->get_value() << "px"; // For now always use px
+ sp_repr_css_set_property (css, "letter-spacing", osfs.str().c_str());
+ text_outer_set_style(css);
+
+ // If no selected objects, set default.
+ SPStyle query(_desktop->getDocument());
+ int result_numbers =
+ sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ if (result_numbers == QUERY_STYLE_NOTHING)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->mergeStyle("/tools/text/style", css);
+ }
+ else
+ {
+ // Save for undo
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:letter-spacing", _("Text: Change letter-spacing"), INKSCAPE_ICON("draw-text"));
+ }
+
+ sp_repr_css_attr_unref (css);
+
+ _freeze = false;
+}
+
+void
+TextToolbar::dx_value_changed()
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ gdouble new_dx = _dx_adj->get_value();
+ bool modmade = false;
+
+ if( SP_IS_TEXT_CONTEXT(_desktop->event_context) ) {
+ Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
+ if( tc ) {
+ unsigned char_index = -1;
+ TextTagAttributes *attributes =
+ text_tag_attributes_at_position( tc->text, std::min(tc->text_sel_start, tc->text_sel_end), &char_index );
+ if( attributes ) {
+ double old_dx = attributes->getDx( char_index );
+ double delta_dx = new_dx - old_dx;
+ sp_te_adjust_dx( tc->text, tc->text_sel_start, tc->text_sel_end, _desktop, delta_dx );
+ modmade = true;
+ }
+ }
+ }
+
+ if(modmade) {
+ // Save for undo
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:dx", _("Text: Change dx (kern)"), INKSCAPE_ICON("draw-text"));
+ }
+ _freeze = false;
+}
+
+void
+TextToolbar::dy_value_changed()
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ gdouble new_dy = _dy_adj->get_value();
+ bool modmade = false;
+
+ if( SP_IS_TEXT_CONTEXT(_desktop->event_context) ) {
+ Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
+ if( tc ) {
+ unsigned char_index = -1;
+ TextTagAttributes *attributes =
+ text_tag_attributes_at_position( tc->text, std::min(tc->text_sel_start, tc->text_sel_end), &char_index );
+ if( attributes ) {
+ double old_dy = attributes->getDy( char_index );
+ double delta_dy = new_dy - old_dy;
+ sp_te_adjust_dy( tc->text, tc->text_sel_start, tc->text_sel_end, _desktop, delta_dy );
+ modmade = true;
+ }
+ }
+ }
+
+ if(modmade) {
+ // Save for undo
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:dy", _("Text: Change dy"), INKSCAPE_ICON("draw-text"));
+ }
+
+ _freeze = false;
+}
+
+void
+TextToolbar::rotation_value_changed()
+{
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+ return;
+ }
+ _freeze = true;
+
+ gdouble new_degrees = _rotation_adj->get_value();
+
+ bool modmade = false;
+ if( SP_IS_TEXT_CONTEXT(_desktop->event_context) ) {
+ Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
+ if( tc ) {
+ unsigned char_index = -1;
+ TextTagAttributes *attributes =
+ text_tag_attributes_at_position( tc->text, std::min(tc->text_sel_start, tc->text_sel_end), &char_index );
+ if( attributes ) {
+ double old_degrees = attributes->getRotate( char_index );
+ double delta_deg = new_degrees - old_degrees;
+ sp_te_adjust_rotation( tc->text, tc->text_sel_start, tc->text_sel_end, _desktop, delta_deg );
+ modmade = true;
+ }
+ }
+ }
+
+ // Save for undo
+ if(modmade) {
+ DocumentUndo::maybeDone(_desktop->getDocument(), "ttb:rotate", _("Text: Change rotate"), INKSCAPE_ICON("draw-text"));
+ }
+
+ _freeze = false;
+}
+
+void TextToolbar::selection_modified_select_tool(Inkscape::Selection *selection, guint flags)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double factor = prefs->getDouble("/options/font/scaleLineHeightFromFontSIze", 1.0);
+ if (factor != 1.0) {
+ Unit const *unit_lh = _tracker->getActiveUnit();
+ g_return_if_fail(unit_lh != nullptr);
+ if (!is_relative(unit_lh) && _outer) {
+ double lineheight = _line_height_adj->get_value();
+ bool is_freeze = _freeze;
+ _freeze = false;
+ _line_height_adj->set_value(lineheight * factor);
+ _freeze = is_freeze;
+ }
+ prefs->setDouble("/options/font/scaleLineHeightFromFontSIze", 1.0);
+ }
+}
+
+void TextToolbar::selection_changed(Inkscape::Selection *selection) // don't bother to update font list if subsel
+ // changed
+{
+#ifdef DEBUG_TEXT
+ static int count = 0;
+ ++count;
+ std::cout << std::endl;
+ std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
+ std::cout << "sp_text_toolbox_selection_changed: start " << count << std::endl;
+#endif
+
+ // quit if run by the _changed callbacks
+ if (_freeze) {
+
+#ifdef DEBUG_TEXT
+ std::cout << " Frozen, returning" << std::endl;
+ std::cout << "sp_text_toolbox_selection_changed: exit " << count << std::endl;
+ std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
+ std::cout << std::endl;
+#endif
+ return;
+ }
+ _freeze = true;
+
+ // selection defined as argument but not used, argh!!!
+ SPDesktop *desktop = _desktop;
+ SPDocument *document = _desktop->getDocument();
+ selection = desktop->getSelection();
+ auto itemlist = selection->items();
+
+#ifdef DEBUG_TEXT
+ for(auto i : itemlist) {
+ const gchar* id = i->getId();
+ std::cout << " " << id << std::endl;
+ }
+ Glib::ustring selected_text = sp_text_get_selected_text(_desktop->event_context);
+ std::cout << " Selected text: |" << selected_text << "|" << std::endl;
+#endif
+
+ // Only flowed text can be justified, only normal text can be kerned...
+ // Find out if we have flowed text now so we can use it several places
+ gboolean isFlow = false;
+ std::vector<SPItem *> to_work;
+ for (auto i : itemlist) {
+ SPText *text = dynamic_cast<SPText *>(i);
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(i);
+ if (text || flowtext) {
+ to_work.push_back(i);
+ }
+ if (flowtext ||
+ (text && text->style && text->style->shape_inside.set)) {
+ isFlow = true;
+ }
+ }
+ bool outside = false;
+ if (selection && to_work.size() == 0) {
+ outside = true;
+ }
+
+ Inkscape::FontLister *fontlister = Inkscape::FontLister::get_instance();
+ fontlister->selection_update();
+ // Update font list, but only if widget already created.
+ if (_font_family_item->get_combobox() != nullptr) {
+ _font_family_item->set_active_text(fontlister->get_font_family().c_str(), fontlister->get_font_family_row());
+ _font_style_item->set_active_text(fontlister->get_font_style().c_str());
+ }
+
+ /*
+ * Query from current selection:
+ * Font family (font-family)
+ * Style (font-weight, font-style, font-stretch, font-variant, font-align)
+ * Numbers (font-size, letter-spacing, word-spacing, line-height, text-anchor, writing-mode)
+ * Font specification (Inkscape private attribute)
+ */
+ SPStyle query(document);
+ SPStyle query_fallback(document);
+ int result_family = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTFAMILY);
+ int result_style = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTSTYLE);
+ int result_baseline = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_BASELINES);
+ int result_wmode = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_WRITINGMODES);
+
+ // Calling sp_desktop_query_style will result in a call to TextTool::_styleQueried().
+ // This returns the style of the selected text inside the <text> element... which
+ // is often the style of one or more <tspan>s. If we want the style of the outer
+ // <text> objects then we need to bypass the call to TextTool::_styleQueried().
+ // The desktop selection never includes the elements inside the <text> element.
+ int result_numbers = 0;
+ int result_numbers_fallback = 0;
+ if (!outside) {
+ if (_outer && this->_sub_active_item) {
+ std::vector<SPItem *> qactive{ this->_sub_active_item };
+ SPItem *parent = dynamic_cast<SPItem *>(this->_sub_active_item->parent);
+ std::vector<SPItem *> qparent{ parent };
+ result_numbers =
+ sp_desktop_query_style_from_list(qactive, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ result_numbers_fallback =
+ sp_desktop_query_style_from_list(qparent, &query_fallback, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ } else if (_outer) {
+ result_numbers = sp_desktop_query_style_from_list(to_work, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ } else {
+ result_numbers = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ }
+ } else {
+ result_numbers =
+ sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ }
+
+ /*
+ * If no text in selection (querying returned nothing), read the style from
+ * the /tools/text preferences (default style for new texts). Return if
+ * tool bar already set to these preferences.
+ */
+ if (result_family == QUERY_STYLE_NOTHING ||
+ result_style == QUERY_STYLE_NOTHING ||
+ result_numbers == QUERY_STYLE_NOTHING ||
+ result_wmode == QUERY_STYLE_NOTHING ) {
+ // There are no texts in selection, read from preferences.
+ query.readFromPrefs("/tools/text");
+#ifdef DEBUG_TEXT
+ std::cout << " read style from prefs:" << std::endl;
+ sp_print_font( &query );
+#endif
+ if (_text_style_from_prefs) {
+ // Do not reset the toolbar style from prefs if we already did it last time
+ _freeze = false;
+#ifdef DEBUG_TEXT
+ std::cout << " text_style_from_prefs: toolbar already set" << std:: endl;
+ std::cout << "sp_text_toolbox_selection_changed: exit " << count << std::endl;
+ std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
+ std::cout << std::endl;
+#endif
+ return;
+ }
+
+ // To ensure the value of the combobox is properly set on start-up, only mark
+ // the prefs set if the combobox has already been constructed.
+ if( _font_family_item->get_combobox() != nullptr ) {
+ _text_style_from_prefs = true;
+ }
+ } else {
+ _text_style_from_prefs = false;
+ }
+
+ // If we have valid query data for text (font-family, font-specification) set toolbar accordingly.
+ {
+ // Size (average of text selected)
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT);
+ double size = 0;
+ if (!size && _cusor_numbers != QUERY_STYLE_NOTHING) {
+ size = sp_style_css_size_px_to_units(_query_cursor.font_size.computed, unit);
+ }
+ if (!size && result_numbers != QUERY_STYLE_NOTHING) {
+ size = sp_style_css_size_px_to_units(query.font_size.computed, unit);
+ }
+ if (!size && result_numbers_fallback != QUERY_STYLE_NOTHING) {
+ size = sp_style_css_size_px_to_units(query_fallback.font_size.computed, unit);
+ }
+ if (!size && _text_style_from_prefs) {
+ size = sp_style_css_size_px_to_units(query.font_size.computed, unit);
+ }
+
+ auto unit_str = sp_style_get_css_unit_string(unit);
+ Glib::ustring tooltip = Glib::ustring::format(_("Font size"), " (", unit_str, ")");
+
+ _font_size_item->set_tooltip(tooltip.c_str());
+
+ Inkscape::CSSOStringStream os;
+ // We dot want to parse values just show
+
+ _tracker_fs->setActiveUnitByAbbr(sp_style_get_css_unit_string(unit));
+ int rounded_size = std::round(size);
+ if (std::abs((size - rounded_size)/size) < 0.0001) {
+ // We use rounded_size to avoid rounding errors when, say, converting stored 'px' values to displayed 'pt' values.
+ os << rounded_size;
+ selection_fontsize = rounded_size;
+ } else {
+ os << size;
+ selection_fontsize = size;
+ }
+
+ // Freeze to ignore callbacks.
+ //g_object_freeze_notify( G_OBJECT( fontSizeAction->combobox ) );
+ sp_text_set_sizes(GTK_LIST_STORE(_font_size_item->get_model()), unit);
+ //g_object_thaw_notify( G_OBJECT( fontSizeAction->combobox ) );
+
+ _font_size_item->set_active_text( os.str().c_str() );
+
+ // Superscript
+ gboolean superscriptSet =
+ ((result_baseline == QUERY_STYLE_SINGLE || result_baseline == QUERY_STYLE_MULTIPLE_SAME ) &&
+ query.baseline_shift.set &&
+ query.baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
+ query.baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUPER );
+
+ _superscript_item->set_active(superscriptSet);
+
+ // Subscript
+ gboolean subscriptSet =
+ ((result_baseline == QUERY_STYLE_SINGLE || result_baseline == QUERY_STYLE_MULTIPLE_SAME ) &&
+ query.baseline_shift.set &&
+ query.baseline_shift.type == SP_BASELINE_SHIFT_LITERAL &&
+ query.baseline_shift.literal == SP_CSS_BASELINE_SHIFT_SUB );
+
+ _subscript_item->set_active(subscriptSet);
+
+ // Alignment
+
+ // Note: SVG 1.1 doesn't include text-align, SVG 1.2 Tiny doesn't include text-align="justify"
+ // text-align="justify" was a draft SVG 1.2 item (along with flowed text).
+ // Only flowed text can be left and right justified at the same time.
+ // Disable button if we don't have flowed text.
+
+ Glib::RefPtr<Gtk::ListStore> store = _align_item->get_store();
+ Gtk::TreeModel::Row row = *(store->get_iter("3")); // Justify entry
+ UI::Widget::ComboToolItemColumns columns;
+ row[columns.col_sensitive] = isFlow;
+
+ int activeButton = 0;
+ if (query.text_align.computed == SP_CSS_TEXT_ALIGN_JUSTIFY)
+ {
+ activeButton = 3;
+ } else {
+ // This should take 'direction' into account
+ if (query.text_anchor.computed == SP_CSS_TEXT_ANCHOR_START) activeButton = 0;
+ if (query.text_anchor.computed == SP_CSS_TEXT_ANCHOR_MIDDLE) activeButton = 1;
+ if (query.text_anchor.computed == SP_CSS_TEXT_ANCHOR_END) activeButton = 2;
+ }
+ _align_item->set_active( activeButton );
+
+ double height = 0;
+ gint line_height_unit = 0;
+
+ if (!height && _cusor_numbers != QUERY_STYLE_NOTHING) {
+ height = _query_cursor.line_height.value;
+ line_height_unit = _query_cursor.line_height.unit;
+ }
+
+ if (!height && result_numbers != QUERY_STYLE_NOTHING) {
+ height = query.line_height.value;
+ line_height_unit = query.line_height.unit;
+ }
+
+ if (!height && result_numbers_fallback != QUERY_STYLE_NOTHING) {
+ height = query_fallback.line_height.value;
+ line_height_unit = query_fallback.line_height.unit;
+ }
+
+ if (!height && _text_style_from_prefs) {
+ height = query.line_height.value;
+ line_height_unit = query.line_height.unit;
+ }
+
+ if (line_height_unit == SP_CSS_UNIT_PERCENT) {
+ height *= 100.0; // Inkscape store % as fraction in .value
+ }
+
+ // We dot want to parse values just show
+ if (!is_relative(SPCSSUnit(line_height_unit))) {
+ gint curunit = prefs->getInt("/tools/text/lineheight/display_unit", 1);
+ // For backwards comaptibility
+ if (is_relative(SPCSSUnit(curunit))) {
+ prefs->setInt("/tools/text/lineheight/display_unit", 1);
+ curunit = 1;
+ }
+ height = Quantity::convert(height, "px", sp_style_get_css_unit_string(curunit));
+ line_height_unit = curunit;
+ }
+ _line_height_adj->set_value(height);
+
+
+ // Update "climb rate"
+ if (line_height_unit == SP_CSS_UNIT_PERCENT) {
+ _line_height_adj->set_step_increment(1.0);
+ _line_height_adj->set_page_increment(10.0);
+ } else {
+ _line_height_adj->set_step_increment(0.1);
+ _line_height_adj->set_page_increment(1.0);
+ }
+
+ if( line_height_unit == SP_CSS_UNIT_NONE ) {
+ // Function 'sp_style_get_css_unit_string' returns 'px' for unit none.
+ // We need to avoid this.
+ _tracker->setActiveUnitByAbbr("");
+ } else {
+ _tracker->setActiveUnitByAbbr(sp_style_get_css_unit_string(line_height_unit));
+ }
+
+ // Save unit so we can do conversions between new/old units.
+ _lineheight_unit = line_height_unit;
+ // Word spacing
+ double wordSpacing;
+ if (query.word_spacing.normal) wordSpacing = 0.0;
+ else wordSpacing = query.word_spacing.computed; // Assume no units (change in desktop-style.cpp)
+
+ _word_spacing_adj->set_value(wordSpacing);
+
+ // Letter spacing
+ double letterSpacing;
+ if (query.letter_spacing.normal) letterSpacing = 0.0;
+ else letterSpacing = query.letter_spacing.computed; // Assume no units (change in desktop-style.cpp)
+
+ _letter_spacing_adj->set_value(letterSpacing);
+
+ // Writing mode
+ int activeButton2 = 0;
+ if (query.writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB) activeButton2 = 0;
+ if (query.writing_mode.computed == SP_CSS_WRITING_MODE_TB_RL) activeButton2 = 1;
+ if (query.writing_mode.computed == SP_CSS_WRITING_MODE_TB_LR) activeButton2 = 2;
+
+ _writing_mode_item->set_active( activeButton2 );
+
+ // Orientation
+ int activeButton3 = 0;
+ if (query.text_orientation.computed == SP_CSS_TEXT_ORIENTATION_MIXED ) activeButton3 = 0;
+ if (query.text_orientation.computed == SP_CSS_TEXT_ORIENTATION_UPRIGHT ) activeButton3 = 1;
+ if (query.text_orientation.computed == SP_CSS_TEXT_ORIENTATION_SIDEWAYS) activeButton3 = 2;
+
+ _orientation_item->set_active( activeButton3 );
+
+ // Disable text orientation for horizontal text...
+ _orientation_item->set_sensitive( activeButton2 != 0 );
+
+ // Direction
+ int activeButton4 = 0;
+ if (query.direction.computed == SP_CSS_DIRECTION_LTR ) activeButton4 = 0;
+ if (query.direction.computed == SP_CSS_DIRECTION_RTL ) activeButton4 = 1;
+ _direction_item->set_active( activeButton4 );
+ }
+
+#ifdef DEBUG_TEXT
+ std::cout << " GUI: fontfamily.value: " << query.font_family.value() << std::endl;
+ std::cout << " GUI: font_size.computed: " << query.font_size.computed << std::endl;
+ std::cout << " GUI: font_weight.computed: " << query.font_weight.computed << std::endl;
+ std::cout << " GUI: font_style.computed: " << query.font_style.computed << std::endl;
+ std::cout << " GUI: text_anchor.computed: " << query.text_anchor.computed << std::endl;
+ std::cout << " GUI: text_align.computed: " << query.text_align.computed << std::endl;
+ std::cout << " GUI: line_height.computed: " << query.line_height.computed
+ << " line_height.value: " << query.line_height.value
+ << " line_height.unit: " << query.line_height.unit << std::endl;
+ std::cout << " GUI: word_spacing.computed: " << query.word_spacing.computed
+ << " word_spacing.value: " << query.word_spacing.value
+ << " word_spacing.unit: " << query.word_spacing.unit << std::endl;
+ std::cout << " GUI: letter_spacing.computed: " << query.letter_spacing.computed
+ << " letter_spacing.value: " << query.letter_spacing.value
+ << " letter_spacing.unit: " << query.letter_spacing.unit << std::endl;
+ std::cout << " GUI: writing_mode.computed: " << query.writing_mode.computed << std::endl;
+#endif
+
+ // Kerning (xshift), yshift, rotation. NB: These are not CSS attributes.
+ if( SP_IS_TEXT_CONTEXT(_desktop->event_context) ) {
+ Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
+ if( tc ) {
+ unsigned char_index = -1;
+ TextTagAttributes *attributes =
+ text_tag_attributes_at_position( tc->text, std::min(tc->text_sel_start, tc->text_sel_end), &char_index );
+ if( attributes ) {
+
+ // Dx
+ double dx = attributes->getDx( char_index );
+ _dx_adj->set_value(dx);
+
+ // Dy
+ double dy = attributes->getDy( char_index );
+ _dy_adj->set_value(dy);
+
+ // Rotation
+ double rotation = attributes->getRotate( char_index );
+ /* SVG value is between 0 and 360 but we're using -180 to 180 in widget */
+ if( rotation > 180.0 ) rotation -= 360.0;
+ _rotation_adj->set_value(rotation);
+
+#ifdef DEBUG_TEXT
+ std::cout << " GUI: Dx: " << dx << std::endl;
+ std::cout << " GUI: Dy: " << dy << std::endl;
+ std::cout << " GUI: Rotation: " << rotation << std::endl;
+#endif
+ }
+ }
+ }
+
+ {
+ // Set these here as we don't always have kerning/rotating attributes
+ _dx_item->set_sensitive(!isFlow);
+ _dy_item->set_sensitive(!isFlow);
+ _rotation_item->set_sensitive(!isFlow);
+ }
+
+#ifdef DEBUG_TEXT
+ std::cout << "sp_text_toolbox_selection_changed: exit " << count << std::endl;
+ std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
+ std::cout << std::endl;
+#endif
+
+ _freeze = false;
+}
+
+void
+TextToolbar::watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec) {
+ bool is_text_toolbar = SP_IS_TEXT_CONTEXT(ec);
+ bool is_select_toolbar = !is_text_toolbar && SP_IS_SELECT_CONTEXT(ec);
+ if (is_text_toolbar) {
+ // Watch selection
+ // Ensure FontLister is updated here first..................
+ c_selection_changed =
+ desktop->getSelection()->connectChangedFirst(sigc::mem_fun(*this, &TextToolbar::selection_changed));
+ c_selection_modified = desktop->getSelection()->connectModifiedFirst(sigc::mem_fun(*this, &TextToolbar::selection_modified));
+ c_subselection_changed = desktop->connect_text_cursor_moved([=](void* sender, Inkscape::UI::Tools::TextTool* tool){
+ subselection_changed(tool);
+ });
+ this->_sub_active_item = nullptr;
+ this->_cusor_numbers = 0;
+ selection_changed(desktop->getSelection());
+ } else if (is_select_toolbar) {
+ c_selection_modified_select_tool = desktop->getSelection()->connectModifiedFirst(
+ sigc::mem_fun(*this, &TextToolbar::selection_modified_select_tool));
+ }
+
+
+ if (!is_text_toolbar) {
+ c_selection_changed.disconnect();
+ c_selection_modified.disconnect();
+ c_subselection_changed.disconnect();
+ }
+
+ if (!is_select_toolbar) {
+ c_selection_modified_select_tool.disconnect();
+ }
+}
+
+void
+TextToolbar::selection_modified(Inkscape::Selection *selection, guint /*flags*/)
+{
+ this->_sub_active_item = nullptr;
+ selection_changed(selection);
+
+}
+
+void TextToolbar::subselection_wrap_toggle(bool start)
+{
+ if (SP_IS_TEXT_CONTEXT(_desktop->event_context)) {
+ Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
+ if (tc) {
+ _updating = true;
+ Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
+ if (layout) {
+ Inkscape::Text::Layout::iterator start_selection = tc->text_sel_start;
+ Inkscape::Text::Layout::iterator end_selection = tc->text_sel_end;
+ tc->text_sel_start = wrap_start;
+ tc->text_sel_end = wrap_end;
+ wrap_start = start_selection;
+ wrap_end = end_selection;
+ }
+ _updating = start;
+ }
+ }
+}
+
+/*
+* This function parses the just created line height in one or more lines of a text subselection.
+* It can describe 2 kinds of input because when we store a text element we apply a fallback that change
+* structure. This visually is not reflected but user maybe want to change a part of this subselection
+* once the fallback is created, so we need more complex logic here to fill the gap.
+* Basically, we have a line height changed in the new wrapper element/s between wrap_start and wrap_end.
+* These variables store starting iterator of first char in line and last char in line in a subselection.
+* These elements are styled well but we can have orphaned text nodes before and after the subselection.
+* So, normally 3 elements are inside a container as direct child of a text element.
+* We need to apply the container style to the optional first and last text nodes,
+* wrapping into a new element that gets the container style (this is not part to the sub-selection).
+* After wrapping, we unindent all children of the container and remove the container.
+*
+*/
+void TextToolbar::prepare_inner()
+{
+ Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(_desktop->event_context);
+ if (!tc) {
+ return;
+ }
+ Inkscape::Text::Layout *layout = const_cast<Inkscape::Text::Layout *>(te_get_layout(tc->text));
+ if (!layout) {
+ return;
+ }
+ SPDocument *doc = _desktop->getDocument();
+ SPObject *spobject = dynamic_cast<SPObject *>(tc->text);
+ SPItem *spitem = dynamic_cast<SPItem *>(tc->text);
+ SPText *text = dynamic_cast<SPText *>(tc->text);
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(tc->text);
+ Inkscape::XML::Document *xml_doc = doc->getReprDoc();
+ if (!spobject) {
+ return;
+ }
+
+ // We check for external files with text nodes direct children of text element
+ // and wrap it into a tspan elements as inkscape do.
+ if (text) {
+ bool changed = false;
+ std::vector<SPObject *> childs = spitem->childList(false);
+ for (auto child : childs) {
+ SPString *spstring = dynamic_cast<SPString *>(child);
+ if (spstring) {
+ Glib::ustring content = spstring->string;
+ if (content != "\n") {
+ Inkscape::XML::Node *rstring = xml_doc->createTextNode(content.c_str());
+ Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
+ //Inkscape::XML::Node *rnl = xml_doc->createTextNode("\n");
+ rtspan->setAttribute("sodipodi:role", "line");
+ rtspan->addChild(rstring, nullptr);
+ text->getRepr()->addChild(rtspan, child->getRepr());
+ Inkscape::GC::release(rstring);
+ Inkscape::GC::release(rtspan);
+ text->getRepr()->removeChild(spstring->getRepr());
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ // proper rebuild happens later,
+ // this just updates layout to use now, avoids use after free
+ text->rebuildLayout();
+ }
+ }
+
+ std::vector<SPObject *> containers;
+ {
+ // populate `containers` with objects that will be modified.
+
+ // Temporarily remove the shape so Layout calculates
+ // the position of wrap_end and wrap_start, even if
+ // one of these are hidden because the previous line height was changed
+ if (text) {
+ text->hide_shape_inside();
+ } else if (flowtext) {
+ flowtext->fix_overflow_flowregion(false);
+ }
+ SPObject *rawptr_start = nullptr;
+ SPObject *rawptr_end = nullptr;
+ layout->validateIterator(&wrap_start);
+ layout->validateIterator(&wrap_end);
+ layout->getSourceOfCharacter(wrap_start, &rawptr_start);
+ layout->getSourceOfCharacter(wrap_end, &rawptr_end);
+ if (text) {
+ text->show_shape_inside();
+ } else if (flowtext) {
+ flowtext->fix_overflow_flowregion(true);
+ }
+ if (!rawptr_start || !rawptr_end) {
+ return;
+ }
+
+ // Loop through parents of start and end till we reach
+ // first children of the text element.
+ // Get all objects between start and end (inclusive)
+ SPObject *start = rawptr_start;
+ SPObject *end = rawptr_end;
+ while (start->parent != spobject) {
+ start = start->parent;
+ }
+ while (end->parent != spobject) {
+ end = end->parent;
+ }
+
+ while (start && start != end) {
+ containers.push_back(start);
+ start = start->getNext();
+ }
+ if (start) {
+ containers.push_back(start);
+ }
+ }
+
+ for (auto container : containers) {
+ Inkscape::XML::Node *prevchild = container->getRepr();
+ std::vector<SPObject*> childs = container->childList(false);
+ for (auto child : childs) {
+ SPString *spstring = dynamic_cast<SPString *>(child);
+ SPFlowtspan *flowtspan = dynamic_cast<SPFlowtspan *>(child);
+ SPTSpan *tspan = dynamic_cast<SPTSpan *>(child);
+ // we need to upper all flowtspans to container level
+ // to do this we need to change the element from flowspan to flowpara
+ if (flowtspan) {
+ Inkscape::XML::Node *flowpara = xml_doc->createElement("svg:flowPara");
+ std::vector<SPObject*> fts_childs = flowtspan->childList(false);
+ bool hascontent = false;
+ // we need to move the contents to the new created element
+ // maybe we can move directly but it is safer for me to duplicate,
+ // inject into the new element and delete original
+ for (auto fts_child : fts_childs) {
+ // is this check necessary?
+ if (fts_child) {
+ Inkscape::XML::Node *fts_child_node = fts_child->getRepr()->duplicate(xml_doc);
+ flowtspan->getRepr()->removeChild(fts_child->getRepr());
+ flowpara->addChild(fts_child_node, nullptr);
+ Inkscape::GC::release(fts_child_node);
+ hascontent = true;
+ }
+ }
+ // if no contents we dont want to add
+ if (hascontent) {
+ flowpara->setAttribute("style", flowtspan->getRepr()->attribute("style"));
+ spobject->getRepr()->addChild(flowpara, prevchild);
+ Inkscape::GC::release(flowpara);
+ prevchild = flowpara;
+ }
+ container->getRepr()->removeChild(flowtspan->getRepr());
+ } else if (tspan) {
+ if (child->childList(false).size()) {
+ child->getRepr()->setAttribute("sodipodi:role", "line");
+ // maybe we need to move unindent function here
+ // to be the same as other here
+ prevchild = unindent_node(child->getRepr(), prevchild);
+ } else {
+ // if no contents we dont want to add
+ container->getRepr()->removeChild(child->getRepr());
+ }
+ } else if (spstring) {
+ // we are on a text node, we act different if in a text or flowtext.
+ // wrap a duplicate of the element and unindent after the prevchild
+ // and finally delete original
+ Inkscape::XML::Node *string_node = xml_doc->createTextNode(spstring->string.c_str());
+ if (text) {
+ Inkscape::XML::Node *tspan_node = xml_doc->createElement("svg:tspan");
+ tspan_node->setAttribute("style", container->getRepr()->attribute("style"));
+ tspan_node->addChild(string_node, nullptr);
+ tspan_node->setAttribute("sodipodi:role", "line");
+ text->getRepr()->addChild(tspan_node, prevchild);
+ Inkscape::GC::release(string_node);
+ Inkscape::GC::release(tspan_node);
+ prevchild = tspan_node;
+ } else if (flowtext) {
+ Inkscape::XML::Node *flowpara_node = xml_doc->createElement("svg:flowPara");
+ flowpara_node->setAttribute("style", container->getRepr()->attribute("style"));
+ flowpara_node->addChild(string_node, nullptr);
+ flowtext->getRepr()->addChild(flowpara_node, prevchild);
+ Inkscape::GC::release(string_node);
+ Inkscape::GC::release(flowpara_node);
+ prevchild = flowpara_node;
+ }
+ container->getRepr()->removeChild(spstring->getRepr());
+ }
+ }
+ tc->text->getRepr()->removeChild(container->getRepr());
+ }
+}
+
+Inkscape::XML::Node *TextToolbar::unindent_node(Inkscape::XML::Node *repr, Inkscape::XML::Node *prevchild)
+{
+ g_assert(repr != nullptr);
+
+ Inkscape::XML::Node *parent = repr->parent();
+ if (parent) {
+ Inkscape::XML::Node *grandparent = parent->parent();
+ if (grandparent) {
+ SPDocument *doc = _desktop->getDocument();
+ Inkscape::XML::Document *xml_doc = doc->getReprDoc();
+ Inkscape::XML::Node *newrepr = repr->duplicate(xml_doc);
+ parent->removeChild(repr);
+ grandparent->addChild(newrepr, prevchild);
+ Inkscape::GC::release(newrepr);
+ newrepr->setAttribute("sodipodi:role", "line");
+ return newrepr;
+ }
+ }
+ std::cout << "error on TextToolbar.cpp::2433" << std::endl;
+ return repr;
+}
+
+void TextToolbar::subselection_changed(Inkscape::UI::Tools::TextTool* tc)
+{
+#ifdef DEBUG_TEXT
+ std::cout << std::endl;
+ std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
+ std::cout << "subselection_changed: start " << std::endl;
+#endif
+ // quit if run by the _changed callbacks
+ this->_sub_active_item = nullptr;
+ if (_updating) {
+ return;
+ }
+ if (tc) {
+ Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
+ if (layout) {
+ Inkscape::Text::Layout::iterator start = layout->begin();
+ Inkscape::Text::Layout::iterator end = layout->end();
+ Inkscape::Text::Layout::iterator start_selection = tc->text_sel_start;
+ Inkscape::Text::Layout::iterator end_selection = tc->text_sel_end;
+#ifdef DEBUG_TEXT
+ std::cout << " GUI: Start of text: " << layout->iteratorToCharIndex(start) << std::endl;
+ std::cout << " GUI: End of text: " << layout->iteratorToCharIndex(end) << std::endl;
+ std::cout << " GUI: Start of selection: " << layout->iteratorToCharIndex(start_selection) << std::endl;
+ std::cout << " GUI: End of selection: " << layout->iteratorToCharIndex(end_selection) << std::endl;
+ std::cout << " GUI: Loop Subelements: " << std::endl;
+ std::cout << " ::::::::::::::::::::::::::::::::::::::::::::: " << std::endl;
+#endif
+ gint startline = layout->paragraphIndex(start_selection);
+ if (start_selection == end_selection) {
+ this->_outer = true;
+ gint counter = 0;
+ for (auto child : tc->text->childList(false)) {
+ SPItem *item = dynamic_cast<SPItem *>(child);
+ if (item && counter == startline) {
+ this->_sub_active_item = item;
+ int origin_selection = layout->iteratorToCharIndex(start_selection);
+ Inkscape::Text::Layout::iterator next = layout->charIndexToIterator(origin_selection + 1);
+ Inkscape::Text::Layout::iterator prev = layout->charIndexToIterator(origin_selection - 1);
+ //TODO: find a better way to init
+ _updating = true;
+ SPStyle query(_desktop->getDocument());
+ _query_cursor = query;
+ Inkscape::Text::Layout::iterator start_line = tc->text_sel_start;
+ start_line.thisStartOfLine();
+ if (tc->text_sel_start == start_line) {
+ tc->text_sel_start = next;
+ } else {
+ tc->text_sel_start = prev;
+ }
+ _cusor_numbers = sp_desktop_query_style(_desktop, &_query_cursor, QUERY_STYLE_PROPERTY_FONTNUMBERS);
+ tc->text_sel_start = start_selection;
+ wrap_start = tc->text_sel_start;
+ wrap_end = tc->text_sel_end;
+ wrap_start.thisStartOfLine();
+ wrap_end.thisEndOfLine();
+ _updating = false;
+ break;
+ }
+ ++counter;
+ }
+ selection_changed(nullptr);
+ } else if ((start_selection == start && end_selection == end) ||
+ (start_selection == end && end_selection == start)) {
+ // full subselection
+ _cusor_numbers = 0;
+ this->_outer = true;
+ selection_changed(nullptr);
+ } else {
+ _cusor_numbers = 0;
+ this->_outer = false;
+ wrap_start = tc->text_sel_start;
+ wrap_end = tc->text_sel_end;
+ if (tc->text_sel_start > tc->text_sel_end) {
+ wrap_start.thisEndOfLine();
+ wrap_end.thisStartOfLine();
+ } else {
+ wrap_start.thisStartOfLine();
+ wrap_end.thisEndOfLine();
+ }
+ selection_changed(nullptr);
+ }
+ }
+ }
+#ifdef DEBUG_TEXT
+ std::cout << "subselection_changed: exit " << std::endl;
+ std::cout << "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&" << std::endl;
+ std::cout << std::endl;
+#endif
+}
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
diff --git a/src/ui/toolbar/text-toolbar.h b/src/ui/toolbar/text-toolbar.h
new file mode 100644
index 0000000..b91166e
--- /dev/null
+++ b/src/ui/toolbar/text-toolbar.h
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_TEXT_TOOLBAR_H
+#define SEEN_TEXT_TOOLBAR_H
+
+/**
+ * @file
+ * Text aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "object/sp-item.h"
+#include "object/sp-object.h"
+#include "toolbar.h"
+#include "text-editing.h"
+#include "style.h"
+#include <gtkmm/adjustment.h>
+#include <gtkmm/box.h>
+#include <gtkmm/popover.h>
+#include <gtkmm/separatortoolitem.h>
+#include <sigc++/connection.h>
+
+class SPDesktop;
+
+namespace Gtk {
+class ComboBoxText;
+class ToggleToolButton;
+}
+
+namespace Inkscape {
+class Selection;
+
+namespace UI {
+namespace Tools {
+class ToolBase;
+class TextTool;
+}
+
+namespace Widget {
+class ComboBoxEntryToolItem;
+class ComboToolItem;
+class SpinButtonToolItem;
+class UnitTracker;
+}
+
+namespace Toolbar {
+class TextToolbar : public Toolbar {
+private:
+ bool _freeze;
+ bool _text_style_from_prefs;
+ UI::Widget::UnitTracker *_tracker;
+ UI::Widget::UnitTracker *_tracker_fs;
+
+ UI::Widget::ComboBoxEntryToolItem *_font_family_item;
+ UI::Widget::ComboBoxEntryToolItem *_font_size_item;
+ UI::Widget::ComboToolItem *_font_size_units_item;
+ UI::Widget::ComboBoxEntryToolItem *_font_style_item;
+ UI::Widget::ComboToolItem *_line_height_units_item;
+ UI::Widget::SpinButtonToolItem *_line_height_item;
+ Gtk::ToggleToolButton *_superscript_item;
+ Gtk::ToggleToolButton *_subscript_item;
+
+ UI::Widget::ComboToolItem *_align_item;
+ UI::Widget::ComboToolItem *_writing_mode_item;
+ UI::Widget::ComboToolItem *_orientation_item;
+ UI::Widget::ComboToolItem *_direction_item;
+
+ UI::Widget::SpinButtonToolItem *_word_spacing_item;
+ UI::Widget::SpinButtonToolItem *_letter_spacing_item;
+ UI::Widget::SpinButtonToolItem *_dx_item;
+ UI::Widget::SpinButtonToolItem *_dy_item;
+ UI::Widget::SpinButtonToolItem *_rotation_item;
+
+ Glib::RefPtr<Gtk::Adjustment> _line_height_adj;
+ Glib::RefPtr<Gtk::Adjustment> _word_spacing_adj;
+ Glib::RefPtr<Gtk::Adjustment> _letter_spacing_adj;
+ Glib::RefPtr<Gtk::Adjustment> _dx_adj;
+ Glib::RefPtr<Gtk::Adjustment> _dy_adj;
+ Glib::RefPtr<Gtk::Adjustment> _rotation_adj;
+ bool _outer;
+ SPItem *_sub_active_item;
+ int _lineheight_unit;
+ Inkscape::Text::Layout::iterator wrap_start;
+ Inkscape::Text::Layout::iterator wrap_end;
+ bool _updating;
+ int _cusor_numbers;
+ SPStyle _query_cursor;
+ double selection_fontsize;
+ sigc::connection c_selection_changed;
+ sigc::connection c_selection_modified;
+ sigc::connection c_selection_modified_select_tool;
+ sigc::connection c_subselection_changed;
+ void text_outer_set_style(SPCSSAttr *css);
+ void fontfamily_value_changed();
+ void fontsize_value_changed();
+ void subselection_wrap_toggle(bool start);
+ void fontstyle_value_changed();
+ void script_changed(Gtk::ToggleToolButton *btn);
+ void align_mode_changed(int mode);
+ void writing_mode_changed(int mode);
+ void orientation_changed(int mode);
+ void direction_changed(int mode);
+ void lineheight_value_changed();
+ void lineheight_unit_changed(int not_used);
+ void wordspacing_value_changed();
+ void letterspacing_value_changed();
+ void dx_value_changed();
+ void dy_value_changed();
+ void prepare_inner();
+ void focus_text();
+ void rotation_value_changed();
+ void fontsize_unit_changed(int not_used);
+ void selection_changed(Inkscape::Selection *selection);
+ void selection_modified(Inkscape::Selection *selection, guint flags);
+ void selection_modified_select_tool(Inkscape::Selection *selection, guint flags);
+ void subselection_changed(Inkscape::UI::Tools::TextTool* texttool);
+ void watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec);
+ void set_sizes(int unit);
+ Inkscape::XML::Node *unindent_node(Inkscape::XML::Node *repr, Inkscape::XML::Node *before);
+
+ protected:
+ TextToolbar(SPDesktop *desktop);
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+};
+}
+}
+}
+
+#endif /* !SEEN_TEXT_TOOLBAR_H */
diff --git a/src/ui/toolbar/toolbar.cpp b/src/ui/toolbar/toolbar.cpp
new file mode 100644
index 0000000..c15a4ca
--- /dev/null
+++ b/src/ui/toolbar/toolbar.cpp
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "toolbar.h"
+
+#include <gtkmm/label.h>
+#include <gtkmm/separatortoolitem.h>
+#include <gtkmm/toggletoolbutton.h>
+
+#include "desktop.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+
+Gtk::ToolItem *
+Toolbar::add_label(const Glib::ustring &label_text)
+{
+ auto ti = Gtk::manage(new Gtk::ToolItem());
+
+ // For now, we always enable mnemonic
+ auto label = Gtk::manage(new Gtk::Label(label_text, true));
+
+ ti->add(*label);
+ add(*ti);
+
+ return ti;
+}
+
+/**
+ * \brief Add a toggle toolbutton to the toolbar
+ *
+ * \param[in] label_text The text to display in the toolbar
+ * \param[in] tooltip_text The tooltip text for the toolitem
+ *
+ * \returns The toggle button
+ */
+Gtk::ToggleToolButton *
+Toolbar::add_toggle_button(const Glib::ustring &label_text,
+ const Glib::ustring &tooltip_text)
+{
+ auto btn = Gtk::manage(new Gtk::ToggleToolButton(label_text));
+ btn->set_tooltip_text(tooltip_text);
+ add(*btn);
+ return btn;
+}
+
+/**
+ * \brief Add a separator line to the toolbar
+ *
+ * \details This is just a convenience wrapper for the
+ * standard GtkMM functionality
+ */
+void
+Toolbar::add_separator()
+{
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+}
+
+GtkWidget *
+Toolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = Gtk::manage(new Toolbar(desktop));
+ return GTK_WIDGET(toolbar->gobj());
+}
+}
+}
+}
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/toolbar.h b/src/ui/toolbar/toolbar.h
new file mode 100644
index 0000000..bbbd7f0
--- /dev/null
+++ b/src/ui/toolbar/toolbar.h
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_TOOLBAR_H
+#define SEEN_TOOLBAR_H
+
+#include <gtkmm/toolbar.h>
+
+class SPDesktop;
+
+namespace Gtk {
+ class Label;
+ class ToggleToolButton;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+/**
+ * \brief An abstract definition for a toolbar within Inkscape
+ *
+ * \detail This is basically the same as a Gtk::Toolbar but contains a
+ * few convenience functions. All toolbars must define a "create"
+ * function that adds all the required tool-items and returns the
+ * toolbar as a GtkWidget
+ */
+class Toolbar : public Gtk::Toolbar {
+protected:
+ SPDesktop *_desktop;
+
+ /**
+ * \brief A default constructor that just assigns the desktop
+ */
+ Toolbar(SPDesktop *desktop)
+ : _desktop(desktop)
+ {}
+
+ Gtk::ToolItem * add_label(const Glib::ustring &label_text);
+ Gtk::ToggleToolButton * add_toggle_button(const Glib::ustring &label_text,
+ const Glib::ustring &tooltip_text);
+ void add_separator();
+
+protected:
+ static GtkWidget * create(SPDesktop *desktop);
+};
+}
+}
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/tweak-toolbar.cpp b/src/ui/toolbar/tweak-toolbar.cpp
new file mode 100644
index 0000000..ed840cd
--- /dev/null
+++ b/src/ui/toolbar/tweak-toolbar.cpp
@@ -0,0 +1,346 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Tweak aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "tweak-toolbar.h"
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/radiotoolbutton.h>
+#include <gtkmm/separatortoolitem.h>
+
+#include "desktop.h"
+#include "document-undo.h"
+
+#include "ui/icon-names.h"
+#include "ui/tools/tweak-tool.h"
+#include "ui/widget/canvas.h"
+#include "ui/widget/label-tool-item.h"
+#include "ui/widget/spinbutton.h"
+#include "ui/widget/spin-button-tool-item.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+TweakToolbar::TweakToolbar(SPDesktop *desktop)
+ : Toolbar(desktop)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ /* Width */
+ {
+ std::vector<Glib::ustring> labels = {_("(pinch tweak)"), "", "", "", _("(default)"), "", "", "", "", _("(broad tweak)")};
+ std::vector<double> values = { 1, 3, 5, 10, 15, 20, 30, 50, 75, 100};
+
+ auto width_val = prefs->getDouble("/tools/tweak/width", 15);
+ _width_adj = Gtk::Adjustment::create(width_val * 100, 1, 100, 1.0, 10.0);
+ _width_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("tweak-width", _("Width:"), _width_adj, 0.01, 0));
+ _width_item->set_tooltip_text(_("The width of the tweak area (relative to the visible canvas area)"));
+ _width_item->set_custom_numeric_menu_data(values, labels);
+ _width_item->set_focus_widget(desktop->canvas);
+ _width_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TweakToolbar::width_value_changed));
+ add(*_width_item);
+ _width_item->set_sensitive(true);
+ }
+
+ // Force
+ {
+ std::vector<Glib::ustring> labels = {_("(minimum force)"), "", "", _("(default)"), "", "", "", _("(maximum force)")};
+ std::vector<double> values = { 1, 5, 10, 20, 30, 50, 70, 100};
+ auto force_val = prefs->getDouble("/tools/tweak/force", 20);
+ _force_adj = Gtk::Adjustment::create(force_val * 100, 1, 100, 1.0, 10.0);
+ _force_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("tweak-force", _("Force:"), _force_adj, 0.01, 0));
+ _force_item->set_tooltip_text(_("The force of the tweak action"));
+ _force_item->set_custom_numeric_menu_data(values, labels);
+ _force_item->set_focus_widget(desktop->canvas);
+ _force_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TweakToolbar::force_value_changed));
+ add(*_force_item);
+ _force_item->set_sensitive(true);
+ }
+
+ /* Use Pressure button */
+ {
+ _pressure_item = add_toggle_button(_("Pressure"),
+ _("Use the pressure of the input device to alter the force of tweak action"));
+ _pressure_item->set_icon_name(INKSCAPE_ICON("draw-use-pressure"));
+ _pressure_item->signal_toggled().connect(sigc::mem_fun(*this, &TweakToolbar::pressure_state_changed));
+ _pressure_item->set_active(prefs->getBool("/tools/tweak/usepressure", true));
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ /* Mode */
+ {
+ add_label(_("Mode:"));
+ Gtk::RadioToolButton::Group mode_group;
+
+ auto mode_move_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Move mode")));
+ mode_move_btn->set_tooltip_text(_("Move objects in any direction"));
+ mode_move_btn->set_icon_name(INKSCAPE_ICON("object-tweak-push"));
+ _mode_buttons.push_back(mode_move_btn);
+
+ auto mode_inout_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Move in/out mode")));
+ mode_inout_btn->set_tooltip_text(_("Move objects towards cursor; with Shift from cursor"));
+ mode_inout_btn->set_icon_name(INKSCAPE_ICON("object-tweak-attract"));
+ _mode_buttons.push_back(mode_inout_btn);
+
+ auto mode_jitter_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Move jitter mode")));
+ mode_jitter_btn->set_tooltip_text(_("Move objects in random directions"));
+ mode_jitter_btn->set_icon_name(INKSCAPE_ICON("object-tweak-randomize"));
+ _mode_buttons.push_back(mode_jitter_btn);
+
+ auto mode_scale_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Scale mode")));
+ mode_scale_btn->set_tooltip_text(_("Shrink objects, with Shift enlarge"));
+ mode_scale_btn->set_icon_name(INKSCAPE_ICON("object-tweak-shrink"));
+ _mode_buttons.push_back(mode_scale_btn);
+
+ auto mode_rotate_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Rotate mode")));
+ mode_rotate_btn->set_tooltip_text(_("Rotate objects, with Shift counterclockwise"));
+ mode_rotate_btn->set_icon_name(INKSCAPE_ICON("object-tweak-rotate"));
+ _mode_buttons.push_back(mode_rotate_btn);
+
+ auto mode_dupdel_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Duplicate/delete mode")));
+ mode_dupdel_btn->set_tooltip_text(_("Duplicate objects, with Shift delete"));
+ mode_dupdel_btn->set_icon_name(INKSCAPE_ICON("object-tweak-duplicate"));
+ _mode_buttons.push_back(mode_dupdel_btn);
+
+ auto mode_push_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Push mode")));
+ mode_push_btn->set_tooltip_text(_("Push parts of paths in any direction"));
+ mode_push_btn->set_icon_name(INKSCAPE_ICON("path-tweak-push"));
+ _mode_buttons.push_back(mode_push_btn);
+
+ auto mode_shrinkgrow_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Shrink/grow mode")));
+ mode_shrinkgrow_btn->set_tooltip_text(_("Shrink (inset) parts of paths; with Shift grow (outset)"));
+ mode_shrinkgrow_btn->set_icon_name(INKSCAPE_ICON("path-tweak-shrink"));
+ _mode_buttons.push_back(mode_shrinkgrow_btn);
+
+ auto mode_attrep_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Attract/repel mode")));
+ mode_attrep_btn->set_tooltip_text(_("Attract parts of paths towards cursor; with Shift from cursor"));
+ mode_attrep_btn->set_icon_name(INKSCAPE_ICON("path-tweak-attract"));
+ _mode_buttons.push_back(mode_attrep_btn);
+
+ auto mode_roughen_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Roughen mode")));
+ mode_roughen_btn->set_tooltip_text(_("Roughen parts of paths"));
+ mode_roughen_btn->set_icon_name(INKSCAPE_ICON("path-tweak-roughen"));
+ _mode_buttons.push_back(mode_roughen_btn);
+
+ auto mode_colpaint_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Color paint mode")));
+ mode_colpaint_btn->set_tooltip_text(_("Paint the tool's color upon selected objects"));
+ mode_colpaint_btn->set_icon_name(INKSCAPE_ICON("object-tweak-paint"));
+ _mode_buttons.push_back(mode_colpaint_btn);
+
+ auto mode_coljitter_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Color jitter mode")));
+ mode_coljitter_btn->set_tooltip_text(_("Jitter the colors of selected objects"));
+ mode_coljitter_btn->set_icon_name(INKSCAPE_ICON("object-tweak-jitter-color"));
+ _mode_buttons.push_back(mode_coljitter_btn);
+
+ auto mode_blur_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Blur mode")));
+ mode_blur_btn->set_tooltip_text(_("Blur selected objects more; with Shift, blur less"));
+ mode_blur_btn->set_icon_name(INKSCAPE_ICON("object-tweak-blur"));
+ _mode_buttons.push_back(mode_blur_btn);
+
+ int btn_idx = 0;
+
+ for (auto btn : _mode_buttons) {
+ btn->set_sensitive();
+ add(*btn);
+ btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &TweakToolbar::mode_changed), btn_idx++));
+ }
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ guint mode = prefs->getInt("/tools/tweak/mode", 0);
+
+ /* Fidelity */
+ {
+ std::vector<Glib::ustring> labels = {_("(rough, simplified)"), "", "", _("(default)"), "", "", _("(fine, but many nodes)")};
+ std::vector<double> values = { 10, 25, 35, 50, 60, 80, 100};
+
+ auto fidelity_val = prefs->getDouble("/tools/tweak/fidelity", 50);
+ _fidelity_adj = Gtk::Adjustment::create(fidelity_val * 100, 1, 100, 1.0, 10.0);
+ _fidelity_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("tweak-fidelity", _("Fidelity:"), _fidelity_adj, 0.01, 0));
+ _fidelity_item->set_tooltip_text(_("Low fidelity simplifies paths; high fidelity preserves path features but may generate a lot of new nodes"));
+ _fidelity_item->set_custom_numeric_menu_data(values, labels);
+ _fidelity_item->set_focus_widget(desktop->canvas);
+ _fidelity_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TweakToolbar::fidelity_value_changed));
+ add(*_fidelity_item);
+ }
+
+ add(* Gtk::manage(new Gtk::SeparatorToolItem()));
+
+ {
+ _channels_label = Gtk::manage(new UI::Widget::LabelToolItem(_("Channels:")));
+ _channels_label->set_use_markup(true);
+ add(*_channels_label);
+ }
+
+ {
+ //TRANSLATORS: "H" here stands for hue
+ _doh_item = add_toggle_button(C_("Hue", "H"),
+ _("In color mode, act on object's hue"));
+ _doh_item->signal_toggled().connect(sigc::mem_fun(*this, &TweakToolbar::toggle_doh));
+ _doh_item->set_active(prefs->getBool("/tools/tweak/doh", true));
+ }
+ {
+ //TRANSLATORS: "S" here stands for saturation
+ _dos_item = add_toggle_button(C_("Saturation", "S"),
+ _("In color mode, act on object's saturation"));
+ _dos_item->signal_toggled().connect(sigc::mem_fun(*this, &TweakToolbar::toggle_dos));
+ _dos_item->set_active(prefs->getBool("/tools/tweak/dos", true));
+ }
+ {
+ //TRANSLATORS: "S" here stands for saturation
+ _dol_item = add_toggle_button(C_("Lightness", "L"),
+ _("In color mode, act on object's lightness"));
+ _dol_item->signal_toggled().connect(sigc::mem_fun(*this, &TweakToolbar::toggle_dol));
+ _dol_item->set_active(prefs->getBool("/tools/tweak/dol", true));
+ }
+ {
+ //TRANSLATORS: "O" here stands for opacity
+ _doo_item = add_toggle_button(C_("Opacity", "O"),
+ _("In color mode, act on object's opacity"));
+ _doo_item->signal_toggled().connect(sigc::mem_fun(*this, &TweakToolbar::toggle_doo));
+ _doo_item->set_active(prefs->getBool("/tools/tweak/doo", true));
+ }
+
+ _mode_buttons[mode]->set_active();
+ show_all();
+
+ // Elements must be hidden after show_all() is called
+ if (mode == Inkscape::UI::Tools::TWEAK_MODE_COLORPAINT || mode == Inkscape::UI::Tools::TWEAK_MODE_COLORJITTER) {
+ _fidelity_item->set_visible(false);
+ } else {
+ _channels_label->set_visible(false);
+ _doh_item->set_visible(false);
+ _dos_item->set_visible(false);
+ _dol_item->set_visible(false);
+ _doo_item->set_visible(false);
+ }
+}
+
+void
+TweakToolbar::set_mode(int mode)
+{
+ _mode_buttons[mode]->set_active();
+}
+
+GtkWidget *
+TweakToolbar::create(SPDesktop *desktop)
+{
+ auto toolbar = new TweakToolbar(desktop);
+ return GTK_WIDGET(toolbar->gobj());
+}
+
+void
+TweakToolbar::width_value_changed()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/tweak/width",
+ _width_adj->get_value() * 0.01 );
+}
+
+void
+TweakToolbar::force_value_changed()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/tweak/force",
+ _force_adj->get_value() * 0.01 );
+}
+
+void
+TweakToolbar::mode_changed(int mode)
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setInt("/tools/tweak/mode", mode);
+
+ bool flag = ((mode == Inkscape::UI::Tools::TWEAK_MODE_COLORPAINT) ||
+ (mode == Inkscape::UI::Tools::TWEAK_MODE_COLORJITTER));
+
+ _doh_item->set_visible(flag);
+ _dos_item->set_visible(flag);
+ _dol_item->set_visible(flag);
+ _doo_item->set_visible(flag);
+ _channels_label->set_visible(flag);
+
+ if (_fidelity_item) {
+ _fidelity_item->set_visible(!flag);
+ }
+}
+
+void
+TweakToolbar::fidelity_value_changed()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setDouble( "/tools/tweak/fidelity",
+ _fidelity_adj->get_value() * 0.01 );
+}
+
+void
+TweakToolbar::pressure_state_changed()
+{
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/tweak/usepressure", _pressure_item->get_active());
+}
+
+void
+TweakToolbar::toggle_doh() {
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/tweak/doh", _doh_item->get_active());
+}
+
+void
+TweakToolbar::toggle_dos() {
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/tweak/dos", _dos_item->get_active());
+}
+
+void
+TweakToolbar::toggle_dol() {
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/tweak/dol", _dol_item->get_active());
+}
+
+void
+TweakToolbar::toggle_doo() {
+ auto prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/tweak/doo", _doo_item->get_active());
+}
+
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/tweak-toolbar.h b/src/ui/toolbar/tweak-toolbar.h
new file mode 100644
index 0000000..cd1c7d0
--- /dev/null
+++ b/src/ui/toolbar/tweak-toolbar.h
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_TWEAK_TOOLBAR_H
+#define SEEN_TWEAK_TOOLBAR_H
+
+/**
+ * @file
+ * Tweak aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+class SPDesktop;
+
+namespace Gtk {
+class RadioToolButton;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+class LabelToolItem;
+class SpinButtonToolItem;
+}
+
+namespace Toolbar {
+class TweakToolbar : public Toolbar {
+private:
+ UI::Widget::SpinButtonToolItem *_width_item;
+ UI::Widget::SpinButtonToolItem *_force_item;
+ UI::Widget::SpinButtonToolItem *_fidelity_item;
+
+ Gtk::ToggleToolButton *_pressure_item;
+
+ Glib::RefPtr<Gtk::Adjustment> _width_adj;
+ Glib::RefPtr<Gtk::Adjustment> _force_adj;
+ Glib::RefPtr<Gtk::Adjustment> _fidelity_adj;
+
+ std::vector<Gtk::RadioToolButton *> _mode_buttons;
+
+ UI::Widget::LabelToolItem *_channels_label;
+ Gtk::ToggleToolButton *_doh_item;
+ Gtk::ToggleToolButton *_dos_item;
+ Gtk::ToggleToolButton *_dol_item;
+ Gtk::ToggleToolButton *_doo_item;
+
+ void width_value_changed();
+ void force_value_changed();
+ void mode_changed(int mode);
+ void fidelity_value_changed();
+ void pressure_state_changed();
+ void toggle_doh();
+ void toggle_dos();
+ void toggle_dol();
+ void toggle_doo();
+
+protected:
+ TweakToolbar(SPDesktop *desktop);
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+
+ void set_mode(int mode);
+};
+}
+}
+}
+
+#endif /* !SEEN_SELECT_TOOLBAR_H */
diff --git a/src/ui/toolbar/zoom-toolbar.cpp b/src/ui/toolbar/zoom-toolbar.cpp
new file mode 100644
index 0000000..56422cc
--- /dev/null
+++ b/src/ui/toolbar/zoom-toolbar.cpp
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Zoom aux toolbar: Temp until we convert all toolbars to ui files with Gio::Actions.
+ */
+/* Authors:
+ * Tavmjong Bah <tavmjong@free.fr>
+
+ * Copyright (C) 2019 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm.h>
+
+#include "zoom-toolbar.h"
+
+#include "desktop.h"
+#include "io/resource.h"
+
+using Inkscape::IO::Resource::UIS;
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+
+GtkWidget *
+ZoomToolbar::create(SPDesktop *desktop)
+{
+ Glib::ustring zoom_toolbar_builder_file = get_filename(UIS, "toolbar-zoom.ui");
+ auto builder = Gtk::Builder::create();
+ try
+ {
+ builder->add_from_file(zoom_toolbar_builder_file);
+ }
+ catch (const Glib::Error& ex)
+ {
+ std::cerr << "ZoomToolbar: " << zoom_toolbar_builder_file << " file not read! " << ex.what().raw() << std::endl;
+ }
+
+ Gtk::Toolbar* toolbar = nullptr;
+ builder->get_widget("zoom-toolbar", toolbar);
+ if (!toolbar) {
+ std::cerr << "InkscapeWindow: Failed to load zoom toolbar!" << std::endl;
+ return nullptr;
+ }
+
+ toolbar->reference(); // Or it will be deleted when builder is destroyed since we haven't added
+ // it to a container yet. This probably causes a memory leak but we'll
+ // fix it when all toolbars are converted to use Gio::Actions.
+
+ return GTK_WIDGET(toolbar->gobj());
+}
+}
+}
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/toolbar/zoom-toolbar.h b/src/ui/toolbar/zoom-toolbar.h
new file mode 100644
index 0000000..e3cfd29
--- /dev/null
+++ b/src/ui/toolbar/zoom-toolbar.h
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_ZOOM_TOOLBAR_H
+#define SEEN_ZOOM_TOOLBAR_H
+
+/**
+ * @file
+ * Zoom aux toolbar
+ */
+/* Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Frank Felfe <innerspace@iname.com>
+ * John Cliff <simarilius@yahoo.com>
+ * David Turner <novalis@gnu.org>
+ * Josh Andler <scislac@scislac.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2003 MenTaLguY
+ * Copyright (C) 1999-2011 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "toolbar.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Toolbar {
+
+/**
+ * \brief A toolbar for controlling the zoom
+ */
+class ZoomToolbar {
+protected:
+ ZoomToolbar(SPDesktop *desktop) {};
+
+public:
+ static GtkWidget * create(SPDesktop *desktop);
+};
+}
+}
+}
+
+#endif /* !SEEN_ZOOM_TOOLBAR_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :