diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:29:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:29:01 +0000 |
commit | 35a96bde514a8897f6f0fcc41c5833bf63df2e2a (patch) | |
tree | 657d15a03cc46bd099fc2c6546a7a4ad43815d9f /src/ui/toolbar | |
parent | Initial commit. (diff) | |
download | inkscape-upstream.tar.xz inkscape-upstream.zip |
Adding upstream version 1.0.2.upstream/1.0.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
46 files changed, 14643 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..6c17ab1 --- /dev/null +++ b/src/ui/toolbar/arc-toolbar.cpp @@ -0,0 +1,561 @@ +// 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 "verbs.h" + +#include "object/sp-ellipse.h" +#include "object/sp-namedview.h" + +#include "ui/icon-names.h" +#include "ui/pref-pusher.h" +#include "ui/tools/arc-tool.h" +#include "ui/uxmanager.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" + +#include "widgets/spinbutton-events.h" +#include "widgets/widget-sizes.h" + +#include "xml/node-event-vector.h" + +using Inkscape::UI::Widget::UnitTracker; +using Inkscape::UI::UXManager; +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) +{ + _tracker->setActiveUnit(desktop->getNamedView()->display_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_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->set_focus_widget(Glib::wrap(GTK_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_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->set_focus_widget(Glib::wrap(GTK_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(Glib::wrap(GTK_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(Glib::wrap(GTK_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(); + Geom::Scale scale = document->getDocumentScale(); + + 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(); + (SP_OBJECT(ge))->updateRepr(); + (SP_OBJECT(ge))->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + + modmade = true; + } + } + + if (modmade) { + DocumentUndo::done(_desktop->getDocument(), SP_VERB_CONTEXT_ARC, + _("Ellipse: Change radius")); + } + + _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, NULL); + + 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(); + (SP_OBJECT(ge))->updateRepr(); + (SP_OBJECT(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, SP_VERB_CONTEXT_ARC, + _("Arc: Change start/end")); + } + + _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(), SP_VERB_CONTEXT_ARC, + _("Arc: Changed arc type")); + } + + _freeze = false; +} + +void +ArcToolbar::defaults() +{ + _start_adj->set_value(0.0); + _end_adj->set_value(0.0); + + if(_desktop->canvas) gtk_widget_grab_focus(GTK_WIDGET(_desktop->canvas)); +} + +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 = 0.; + gdouble end = 0.; + sp_repr_get_double(repr, "sodipodi:start", &start); + sp_repr_get_double(repr, "sodipodi:end", &end); + + 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..8f14277 --- /dev/null +++ b/src/ui/toolbar/box3d-toolbar.cpp @@ -0,0 +1,418 @@ +// 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 "desktop.h" +#include "document-undo.h" +#include "document.h" +#include "inkscape.h" +#include "verbs.h" + +#include "object/box3d.h" +#include "object/persp3d.h" + +#include "ui/icon-names.h" +#include "ui/pref-pusher.h" +#include "ui/tools/box3d-tool.h" +#include "ui/uxmanager.h" +#include "ui/widget/spin-button-tool-item.h" + +#include "xml/node-event-vector.h" + +using Inkscape::UI::UXManager; +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(Glib::wrap(GTK_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(Glib::wrap(GTK_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(Glib::wrap(GTK_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", SP_VERB_CONTEXT_3DBOX, _("3D Box: Change perspective (angle of infinite axis)")); + + _freeze = false; +} + +void +Box3DToolbar::vp_state_changed(Proj::Axis axis) +{ + // TODO: Take all selected perspectives into account + auto sel_persps = SP_ACTIVE_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(); + persp3d_set_VP_state (persp, 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(); + } +} + +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 = box3d_get_perspective(box); + 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); + + SP_ACTIVE_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 = persp3d_get_infinite_angle(persp, 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); + persp3d_update_box_reprs(persp); + + 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..dc74664 --- /dev/null +++ b/src/ui/toolbar/box3d-toolbar.h @@ -0,0 +1,106 @@ +// 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 <gtkmm/adjustment.h> + +#include "axis-manip.h" + +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..34e7a89 --- /dev/null +++ b/src/ui/toolbar/calligraphy-toolbar.cpp @@ -0,0 +1,599 @@ +// 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 "document-undo.h" +#include "ui/icon-names.h" +#include "ui/simple-pref-pusher.h" +#include "ui/uxmanager.h" +#include "ui/dialog/calligraphic-profile-rename.h" +#include "ui/widget/spin-button-tool-item.h" + +using Inkscape::DocumentUndo; + +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), + _presets_blocked(false) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + /*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); + _width_adj = Gtk::Adjustment::create(width_val, 1, 100, 1.0, 10.0); + auto width_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("calligraphy-width", _("Width:"), _width_adj, 1, 0)); + 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(Glib::wrap(GTK_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()); + // ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + add(*width_item); + width_item->set_sensitive(true); + } + + /* 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(Glib::wrap(GTK_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); + } + + 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(Glib::wrap(GTK_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, 0.0, 100, 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)")); + flatness_item->set_custom_numeric_menu_data(values, labels); + flatness_item->set_focus_widget(Glib::wrap(GTK_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(Glib::wrap(GTK_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(Glib::wrap(GTK_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()); + // ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + 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(Glib::wrap(GTK_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()); + // ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + add(*wiggle_item); + wiggle_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(Glib::wrap(GTK_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()); + // ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + add(*mass_item); + mass_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() +{ + auto prefs = Inkscape::Preferences::get(); + prefs->setDouble( "/tools/calligraphic/width", _width_adj->get_value() ); + 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) { + GtkTreeIter iter; + 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::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..d216888 --- /dev/null +++ b/src/ui/toolbar/calligraphy-toolbar.h @@ -0,0 +1,103 @@ +// 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; +} + +namespace Toolbar { + +class CalligraphyToolbar : public Toolbar { +private: + 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 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..0220a0a --- /dev/null +++ b/src/ui/toolbar/connector-toolbar.cpp @@ -0,0 +1,437 @@ +// 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 "graphlayout.h" +#include "inkscape.h" +#include "verbs.h" + +#include "object/sp-namedview.h" +#include "object/sp-path.h" + +#include "ui/icon-names.h" +#include "ui/tools/connector-tool.h" +#include "ui/uxmanager.h" +#include "ui/widget/spin-button-tool-item.h" + +#include "widgets/spinbutton-events.h" + +#include "xml/node-event-vector.h" + +using Inkscape::UI::UXManager; +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(Glib::wrap(GTK_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(Glib::wrap(GTK_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(Glib::wrap(GTK_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(true); +} + +void +ConnectorToolbar::path_set_ignore() +{ + Inkscape::UI::Tools::cc_selection_set_avoid(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, nullptr); + item->getAvoidRef().handleSettingChange(); + modmade = true; + } + } + + if (!modmade) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/tools/connector/orthogonal", is_orthog); + } else { + + DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR, + is_orthog ? _("Set connector type: orthogonal"): _("Set connector type: polyline")); + } + + _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, nullptr); + 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, SP_VERB_CONTEXT_CONNECTOR, + _("Change connector curvature")); + } + + _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; + + sp_repr_set_css_double(repr, "inkscape:connector-spacing", _spacing_adj->get_value()); + _desktop->namedview->updateRepr(); + bool modmade = false; + + std::vector<SPItem *> items; + items = get_avoided_items(items, _desktop->currentRoot(), _desktop); + for (auto item : items) { + Geom::Affine m = Geom::identity(); + avoid_item_move(&m, item); + modmade = true; + } + + if(modmade) { + DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR, + _("Change connector spacing")); + } + _freeze = false; +} + +void +ConnectorToolbar::graph_layout() +{ + if (!SP_ACTIVE_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 = SP_ACTIVE_DESKTOP->getSelection()->items(); + std::vector<SPItem *> vec(tmp.begin(), tmp.end()); + graphlayout(vec); + + prefs->setInt("/options/clonecompensation/value", saved_compensation); + + DocumentUndo::done(SP_ACTIVE_DESKTOP->getDocument(), SP_VERB_DIALOG_ALIGN_DISTRIBUTE, _("Arrange connector network")); +} + +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 = defaultConnSpacing; + sp_repr_get_double(repr, "inkscape:connector-spacing", &spacing); + + toolbar->_spacing_adj->set_value(spacing); + + if(toolbar->_desktop->canvas) gtk_widget_grab_focus(GTK_WIDGET(toolbar->_desktop->canvas)); + } +} + +} +} +} + +/* + 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..3d6c4f9 --- /dev/null +++ b/src/ui/toolbar/dropper-toolbar.cpp @@ -0,0 +1,116 @@ +// 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 "widgets/spinbutton-events.h" + +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); + + spinbutton_defocus(GTK_WIDGET(gobj())); +} + +void DropperToolbar::on_set_alpha_button_toggled() +{ + auto prefs = Inkscape::Preferences::get(); + prefs->setBool( "/tools/dropper/setalpha", _set_alpha_button->get_active( ) ); + spinbutton_defocus(GTK_WIDGET(gobj())); +} + +/* + * 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..279b82d --- /dev/null +++ b/src/ui/toolbar/eraser-toolbar.cpp @@ -0,0 +1,335 @@ +// 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/spin-button-tool-item.h" + +using Inkscape::DocumentUndo; + +namespace Inkscape { +namespace UI { +namespace Toolbar { +EraserToolbar::EraserToolbar(SPDesktop *desktop) + : Toolbar(desktop), + _freeze(false) +{ + gint eraser_mode = ERASER_MODE_DELETE; + auto prefs = Inkscape::Preferences::get(); + + // 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); + + eraser_mode = prefs->getInt("/tools/eraser/mode", ERASER_MODE_CLIP); // Used at end + + 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(Glib::wrap(GTK_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 + // ege_adjustment_action_set_appearance( toolbar->_width, TOOLBAR_SLIDER_HINT ); + 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(Glib::wrap(GTK_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(Glib::wrap(GTK_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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _tremor_adj->signal_value_changed().connect(sigc::mem_fun(*this, &EraserToolbar::tremor_value_changed)); + + // TODO: Allow slider appearance + //ege_adjustment_action_set_appearance( toolbar->_tremor, TOOLBAR_SLIDER_HINT ); + 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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _mass_adj->signal_value_changed().connect(sigc::mem_fun(*this, &EraserToolbar::mass_value_changed)); + // TODO: Allow slider appearance + //ege_adjustment_action_set_appearance( toolbar->_mass, TOOLBAR_SLIDER_HINT ); + 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()); +} + +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) +{ + _split->set_visible((eraser_mode == ERASER_MODE_CUT)); + + const gboolean visibility = (eraser_mode != ERASER_MODE_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..4ab94b7 --- /dev/null +++ b/src/ui/toolbar/eraser-toolbar.h @@ -0,0 +1,95 @@ +// 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 Widget { +class SpinButtonToolItem; +} + +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; + + 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..e49dab4 --- /dev/null +++ b/src/ui/toolbar/gradient-toolbar.cpp @@ -0,0 +1,1178 @@ +// 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 "verbs.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/color-preview.h" +#include "ui/widget/combo-tool-item.h" +#include "ui/widget/spin-button-tool-item.h" + +#include "widgets/gradient-image.h" +#include "widgets/gradient-vector.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(Glib::wrap(GTK_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(), SP_VERB_CONTEXT_GRADIENT, + _("Assign gradient to object")); + } + + 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(), SP_VERB_CONTEXT_GRADIENT, + _("Set gradient repeat")); + } + + 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(); + sp_repr_set_css_double(stop->getRepr(), "offset", stop->offset); + + DocumentUndo::maybeDone(stop->document, "gradient:stop:offset", SP_VERB_CONTEXT_GRADIENT, + _("Change gradient stop offset")); + + } + + 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(); + auto rc = SP_GRADIENT_CONTEXT(ev); + + if (rc) { + sp_gradient_context_add_stops_between_selected_stops(rc); + } +} + +/** + * \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->connectToolSubselectionChanged(sigc::mem_fun(*this, &GradientToolbar::drag_selection_changed)); + + // 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..d5ea1b0 --- /dev/null +++ b/src/ui/toolbar/gradient-toolbar.h @@ -0,0 +1,102 @@ +// 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; + +namespace Gtk { +class ComboBoxText; +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..ad08f6a --- /dev/null +++ b/src/ui/toolbar/lpe-toolbar.cpp @@ -0,0 +1,418 @@ +// 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 "helper/action-context.h" +#include "helper/action.h" + +#include "ui/icon-names.h" +#include "ui/tools-switch.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); + + if (tools_isactive(_desktop, TOOLS_LPETOOL)) { + LpeTool *lc = SP_LPETOOL_CONTEXT(_desktop->event_context); + 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() +{ + if (!tools_isactive(_desktop, TOOLS_LPETOOL)) { + return; + } + + bool show = _measuring_item->get_active(); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/tools/lpetool/show_measuring_info", show); + + LpeTool *lc = SP_LPETOOL_CONTEXT(_desktop->event_context); + 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 (tools_isactive(_desktop, TOOLS_LPETOOL)) { + sp_action_perform(Inkscape::Verb::get(SP_VERB_DIALOG_LIVE_PATH_EFFECT)->get_action(Inkscape::ActionContext(_desktop)), nullptr); + } + _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/measure-toolbar.cpp b/src/ui/toolbar/measure-toolbar.cpp new file mode 100644 index 0000000..811a47c --- /dev/null +++ b/src/ui/toolbar/measure-toolbar.cpp @@ -0,0 +1,452 @@ +// 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 "inkscape.h" +#include "message-stack.h" + +#include "ui/icon-names.h" +#include "ui/tools/measure-tool.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; + +/** Temporary hack: Returns the node tool in the active desktop. + * Will go away during tool refactoring. */ +static MeasureTool *get_measure_tool() +{ + MeasureTool *tool = nullptr; + if (SP_ACTIVE_DESKTOP ) { + Inkscape::UI::Tools::ToolBase *ec = SP_ACTIVE_DESKTOP->event_context; + if (SP_IS_MEASURE_CONTEXT(ec)) { + tool = static_cast<MeasureTool*>(ec); + } + } + return tool; +} + + + +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(); + _tracker->setActiveUnitByAbbr(prefs->getString("/tools/measure/unit").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(Glib::wrap(GTK_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(Glib::wrap(GTK_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(Glib::wrap(GTK_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(Glib::wrap(GTK_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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + if (mt) { + mt->showCanvasItems(); + } +} + +void +MeasureToolbar::reverse_knots() +{ + MeasureTool *mt = get_measure_tool(); + if (mt) { + mt->reverseKnots(); + } +} + +void +MeasureToolbar::to_phantom() +{ + MeasureTool *mt = get_measure_tool(); + if (mt) { + mt->toPhantom(); + } +} + +void +MeasureToolbar::to_guides() +{ + MeasureTool *mt = get_measure_tool(); + if (mt) { + mt->toGuides(); + } +} + +void +MeasureToolbar::to_item() +{ + MeasureTool *mt = get_measure_tool(); + if (mt) { + mt->toItem(); + } +} + +void +MeasureToolbar::to_mark_dimension() +{ + MeasureTool *mt = get_measure_tool(); + 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..c1ff08a --- /dev/null +++ b/src/ui/toolbar/mesh-toolbar.cpp @@ -0,0 +1,621 @@ +// 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 "verbs.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/color-preview.h" +#include "ui/widget/combo-tool-item.h" +#include "ui/widget/spin-button-tool-item.h" + +#include "widgets/gradient-image.h" +#include "widgets/spinbutton-events.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; +} + + +static void mesh_toolbox_watch_ec(SPDesktop* dt, Inkscape::UI::Tools::ToolBase* ec, GObject* holder); + +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(Glib::wrap(GTK_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(Glib::wrap(GTK_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->_grdrag; + 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->_grdrag; + 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(), SP_VERB_CONTEXT_MESH,_("Set mesh type")); + } +} + +void +MeshToolbar::toggle_sides() +{ + MeshTool *mt = get_mesh_tool(); + if (mt) { + sp_mesh_context_corner_operation( mt, MG_CORNER_SIDE_TOGGLE ); + } +} + +void +MeshToolbar::make_elliptical() +{ + MeshTool *mt = get_mesh_tool(); + if (mt) { + sp_mesh_context_corner_operation( mt, MG_CORNER_SIDE_ARC ); + } +} + +void +MeshToolbar::pick_colors() +{ + MeshTool *mt = get_mesh_tool(); + if (mt) { + sp_mesh_context_corner_operation( mt, MG_CORNER_COLOR_PICK ); + } +} + +void +MeshToolbar::fit_mesh() +{ + MeshTool *mt = get_mesh_tool(); + if (mt) { + sp_mesh_context_fit_mesh_in_bbox( mt ); + } +} + + +} +} +} + +/* + 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..9aa8328 --- /dev/null +++ b/src/ui/toolbar/node-toolbar.cpp @@ -0,0 +1,651 @@ +// 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 "verbs.h" + +#include "helper/action.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/combo-tool-item.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 context = Inkscape::ActionContext(_desktop); + + { + auto object_to_path_item = SPAction::create_toolbutton_for_verb(SP_VERB_OBJECT_TO_CURVE, context); + add(*object_to_path_item); + } + + { + auto stroke_to_path_item = SPAction::create_toolbutton_for_verb(SP_VERB_SELECTION_OUTLINE, context); + 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->set_focus_widget(Glib::wrap(GTK_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->set_focus_widget(Glib::wrap(GTK_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 = SPAction::create_toolbutton_for_verb(SP_VERB_EDIT_NEXT_PATHEFFECT_PARAMETER, context); + 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->connectToolSubselectionChanged(sigc::mem_fun(*this, &NodeToolbar::coord_changed)); + + 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(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); + + NodeTool *nt = get_node_tool(); + if (!nt || !(nt->_selected_nodes) ||nt->_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 = nt->_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..fc603cb --- /dev/null +++ b/src/ui/toolbar/node-toolbar.h @@ -0,0 +1,115 @@ +// 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; + +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(gpointer shape_editor); + 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/paintbucket-toolbar.cpp b/src/ui/toolbar/paintbucket-toolbar.cpp new file mode 100644 index 0000000..fc0b394 --- /dev/null +++ b/src/ui/toolbar/paintbucket-toolbar.cpp @@ -0,0 +1,221 @@ +// 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 "document-undo.h" + +#include "ui/icon-names.h" +#include "ui/tools/flood-tool.h" +#include "ui/uxmanager.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::DocumentUndo; +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; + 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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _threshold_adj->signal_value_changed().connect(sigc::mem_fun(*this, &PaintbucketToolbar::threshold_changed)); + // ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + 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->set_focus_widget(Glib::wrap(GTK_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..2b7fdb2 --- /dev/null +++ b/src/ui/toolbar/pencil-toolbar.cpp @@ -0,0 +1,622 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Pencil 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 <gtkmm.h> +#include <glibmm/i18n.h> + +#include "pencil-toolbar.h" + +#include "desktop.h" +#include "selection.h" + +#include "live_effects/lpe-bspline.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 "display/curve.h" + +#include "object/sp-shape.h" + +#include "ui/icon-names.h" +#include "ui/tools-switch.h" +#include "ui/tools/pen-tool.h" + +#include "ui/widget/label-tool-item.h" +#include "ui/widget/combo-tool-item.h" +#include "ui/widget/spin-button-tool-item.h" + +#include "ui/uxmanager.h" + +#include "widgets/spinbutton-events.h" + +using Inkscape::UI::UXManager; + +/* +class PencilToleranceObserver : public Inkscape::Preferences::Observer { +public: + PencilToleranceObserver(Glib::ustring const &path, GObject *x) : Observer(path), _obj(x) + { + g_object_set_data(_obj, "prefobserver", this); + } + virtual ~PencilToleranceObserver() { + if (g_object_get_data(_obj, "prefobserver") == this) { + g_object_set_data(_obj, "prefobserver", NULL); + } + } + virtual void notify(Inkscape::Preferences::Entry const &val) { + GObject* tbl = _obj; + if (g_object_get_data( tbl, "freeze" )) { + return; + } + g_object_set_data( tbl, "freeze", GINT_TO_POINTER(TRUE) ); + + GtkAdjustment * adj = GTK_ADJUSTMENT(g_object_get_data(tbl, "tolerance")); + + double v = val.getDouble(adj->value); + gtk_adjustment_set_value(adj, v); + g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) ); + } +private: + GObject *_obj; +}; +*/ + +namespace Inkscape { +namespace UI { +namespace Toolbar { +PencilToolbar::PencilToolbar(SPDesktop *desktop, + bool pencil_mode) + : Toolbar(desktop), + _repr(nullptr), + _freeze(false), + _flatten_simplify(nullptr), + _simplify(nullptr) +{ + auto prefs = Inkscape::Preferences::get(); + + add_freehand_mode_toggle(pencil_mode); + + add(* Gtk::manage(new Gtk::SeparatorToolItem())); + + if (pencil_mode) { + /* 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(freehand_tool_name() + "/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(Glib::wrap(GTK_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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _maxpressure_adj->signal_value_changed().connect( + sigc::mem_fun(*this, &PencilToolbar::maxpressure_value_changed)); + add(*_maxpressure); + } + + /* powerstoke */ + add_powerstroke_cap(pencil_mode); + + 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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _tolerance_adj->signal_value_changed().connect(sigc::mem_fun(*this, &PencilToolbar::tolerance_value_changed)); + // ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + 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(pencil_mode); + + show_all(); + + // Elements must be hidden after show_all() is called + guint freehandMode = prefs->getInt(( pencil_mode ? + "/tools/freehand/pencil/freehand-mode" : + "/tools/freehand/pen/freehand-mode" ), 0); + if (freehandMode != 1 && freehandMode != 2) { + _flatten_spiro_bspline->set_visible(false); + } + if (pencil_mode) { + 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()); + } + } + if (tools_isactive(_desktop, TOOLS_FREEHAND_PEN)) { + SP_PEN_CONTEXT(_desktop->event_context)->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 ( tools_isactive(_desktop, TOOLS_FREEHAND_PEN) + ? "/tools/freehand/pen" + : "/tools/freehand/pencil" ); +} + +void +PencilToolbar::add_freehand_mode_toggle(bool tool_is_pencil) +{ + 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(_("LPE spiro or bspline flatten"))); + _flatten_spiro_bspline->set_tooltip_text(_("LPE spiro or bspline flatten")); + _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() +{ + // 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() +{ + // 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::use_pencil_pressure() { + // assumes called by pencil toolbar (and all these widgets exist) + bool pressure = _pressure_item->get_active(); + auto prefs = Inkscape::Preferences::get(); + prefs->setBool(freehand_tool_name() + "/pressure", pressure); + if (pressure) { + _minpressure->set_visible(true); + _maxpressure->set_visible(true); + _cap_item->set_visible(true); + _shape_item->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); + 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(bool tool_is_pencil) +{ + /*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); +} + +void +PencilToolbar::change_shape(int shape) { + auto prefs = Inkscape::Preferences::get(); + prefs->setInt(freehand_tool_name() + "/shape", shape); +} + +void PencilToolbar::add_powerstroke_cap(bool tool_is_pencil) +{ + /* 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:"), _("Cap for powerstroke pressure"), "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){ + SPCurve * c = shape->getCurveForEdit(); + lpe->doEffect(c); + lpeitem->setCurrentPathEffect(*i); + if (lpelist.size() > 1){ + lpeitem->removeCurrentPathEffect(true); + shape->setCurveBeforeLPE(c); + } else { + lpeitem->removeCurrentPathEffect(false); + shape->setCurve(c, false); + } + 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){ + SPCurve * c = shape->getCurveForEdit(); + lpe->doEffect(c); + lpeitem->setCurrentPathEffect(*i); + if (lpelist.size() > 1){ + lpeitem->removeCurrentPathEffect(true); + shape->setCurveBeforeLPE(c); + } else { + lpeitem->removeCurrentPathEffect(false); + shape->setCurve(c, false); + } + 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() +{ + // 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->getPathEffectOfType(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->getPathEffectOfType(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->getCurve(true)->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->getCurve(true)->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..2b2f676 --- /dev/null +++ b/src/ui/toolbar/pencil-toolbar.h @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_PENCIL_TOOLBAR_H +#define SEEN_PENCIL_TOOLBAR_H + +/** + * @file + * Pencil 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 XML { +class Node; +} + +namespace UI { +namespace Widget { +class SpinButtonToolItem; +class ComboToolItem; +} + +namespace Toolbar { +class PencilToolbar : public Toolbar { +private: + std::vector<Gtk::RadioToolButton *> _mode_buttons; + + Gtk::ToggleToolButton *_pressure_item; + UI::Widget::SpinButtonToolItem *_minpressure; + UI::Widget::SpinButtonToolItem *_maxpressure; + + XML::Node *_repr; + Gtk::ToolButton *_flatten_spiro_bspline; + Gtk::ToolButton *_flatten_simplify; + + UI::Widget::ComboToolItem *_shape_item; + UI::Widget::ComboToolItem *_cap_item; + + Gtk::ToggleToolButton *_simplify; + + bool _freeze; + + Glib::RefPtr<Gtk::Adjustment> _minpressure_adj; + Glib::RefPtr<Gtk::Adjustment> _maxpressure_adj; + Glib::RefPtr<Gtk::Adjustment> _tolerance_adj; + + void add_freehand_mode_toggle(bool tool_is_pencil); + void mode_changed(int mode); + Glib::ustring const freehand_tool_name(); + void minpressure_value_changed(); + void maxpressure_value_changed(); + void use_pencil_pressure(); + void tolerance_value_changed(); + void add_advanced_shape_options(bool tool_is_pencil); + void add_powerstroke_cap(bool tool_is_pencil); + void change_shape(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..c938bb9 --- /dev/null +++ b/src/ui/toolbar/rect-toolbar.cpp @@ -0,0 +1,407 @@ +// 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 "inkscape.h" +#include "verbs.h" + +#include "object/sp-namedview.h" +#include "object/sp-rect.h" + +#include "ui/icon-names.h" +#include "ui/tools/rect-tool.h" +#include "ui/uxmanager.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" + +#include "widgets/widget-sizes.h" + +#include "xml/node-event-vector.h" + +using Inkscape::UI::Widget::UnitTracker; +using Inkscape::UI::UXManager; +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 + _tracker->setActiveUnit(desktop->getNamedView()->display_units); + _mode_item->set_use_markup(true); + + /* W */ + { + auto width_val = prefs->getDouble("/tools/shapes/rect/width", 0); + _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->set_focus_widget(Glib::wrap(GTK_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_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->set_custom_numeric_menu_data(values); + _height_item->set_all_tooltip_text(_("Height of rectangle")); + _height_item->set_focus_widget(Glib::wrap(GTK_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_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->set_all_tooltip_text(_("Horizontal radius of rounded corners")); + _rx_item->set_focus_widget(Glib::wrap(GTK_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_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->set_all_tooltip_text(_("Vertical radius of rounded corners")); + _ry_item->set_focus_widget(Glib::wrap(GTK_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; + } +} + +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(), SP_VERB_CONTEXT_RECT, + _("Change 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) +{ + static sigc::connection changed; + + // 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..58d4b2c --- /dev/null +++ b/src/ui/toolbar/rect-toolbar.h @@ -0,0 +1,113 @@ +// 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); + +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..87682b1 --- /dev/null +++ b/src/ui/toolbar/select-toolbar.cpp @@ -0,0 +1,508 @@ +// 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 "inkscape.h" +#include "message-stack.h" +#include "selection-chemistry.h" +#include "verbs.h" + +#include "display/sp-canvas.h" + +#include "object/sp-item-transform.h" +#include "object/sp-namedview.h" + +#include "ui/icon-names.h" +#include "ui/widget/combo-tool-item.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; + +namespace Inkscape { +namespace UI { +namespace Toolbar { + +SelectToolbar::SelectToolbar(SPDesktop *desktop) : + Toolbar(desktop), + _tracker(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR)), + _update(false), + _lock_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())) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + add_toolbutton_for_verb(SP_VERB_EDIT_SELECT_ALL); + add_toolbutton_for_verb(SP_VERB_EDIT_SELECT_ALL_IN_ALL_LAYERS); + auto deselect_button = add_toolbutton_for_verb(SP_VERB_EDIT_DESELECT); + _context_items.push_back(deselect_button); + + add(* Gtk::manage(new Gtk::SeparatorToolItem())); + + auto object_rotate_90_ccw_button = add_toolbutton_for_verb(SP_VERB_OBJECT_ROTATE_90_CCW); + _context_items.push_back(object_rotate_90_ccw_button); + + auto object_rotate_90_cw_button = add_toolbutton_for_verb(SP_VERB_OBJECT_ROTATE_90_CW); + _context_items.push_back(object_rotate_90_cw_button); + + auto object_flip_horizontal_button = add_toolbutton_for_verb(SP_VERB_OBJECT_FLIP_HORIZONTAL); + _context_items.push_back(object_flip_horizontal_button); + + auto object_flip_vertical_button = add_toolbutton_for_verb(SP_VERB_OBJECT_FLIP_VERTICAL); + _context_items.push_back(object_flip_vertical_button); + + add(* Gtk::manage(new Gtk::SeparatorToolItem())); + + auto selection_to_back_button = add_toolbutton_for_verb(SP_VERB_SELECTION_TO_BACK); + _context_items.push_back(selection_to_back_button); + + auto selection_lower_button = add_toolbutton_for_verb(SP_VERB_SELECTION_LOWER); + _context_items.push_back(selection_lower_button); + + auto selection_raise_button = add_toolbutton_for_verb(SP_VERB_SELECTION_RAISE); + _context_items.push_back(selection_raise_button); + + auto selection_to_front_button = add_toolbutton_for_verb(SP_VERB_SELECTION_TO_FRONT); + _context_items.push_back(selection_to_front_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->set_focus_widget(Glib::wrap(GTK_WIDGET(_desktop->canvas))); + 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->set_focus_widget(Glib::wrap(GTK_WIDGET(_desktop->canvas))); + 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->set_focus_widget(Glib::wrap(GTK_WIDGET(_desktop->canvas))); + 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)); + set_data("lock", _lock_btn->gobj()); + 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->set_focus_widget(Glib::wrap(GTK_WIDGET(_desktop->canvas))); + 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); + + // Force update when selection changes. + INKSCAPE.signal_selection_modified.connect(sigc::mem_fun(*this, &SelectToolbar::on_inkscape_selection_modified)); + INKSCAPE.signal_selection_changed.connect (sigc::mem_fun(*this, &SelectToolbar::on_inkscape_selection_changed)); + + // Update now. + layout_widget_update(SP_ACTIVE_DESKTOP ? SP_ACTIVE_DESKTOP->getSelection() : nullptr); + + for (auto item : _context_items) { + if ( item->is_sensitive() ) { + item->set_sensitive(false); + } + } + + show_all(); +} + +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 = SP_ACTIVE_DESKTOP; + Inkscape::Selection *selection = desktop->getSelection(); + SPDocument *document = desktop->getDocument(); + + document->ensureUpToDate (); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + Geom::OptRect bbox_vis = selection->visualBounds(); + Geom::OptRect bbox_geom = selection->geometricBounds(); + + int prefs_bbox = prefs->getInt("/tools/bounding_box"); + SPItem::BBoxType bbox_type = (prefs_bbox == 0)? + SPItem::VISUAL_BBOX : SPItem::GEOMETRIC_BBOX; + Geom::OptRect bbox_user = selection->bounds(bbox_type); + + if ( !bbox_user ) { + _update = false; + return; + } + + gdouble x0 = 0; + gdouble y0 = 0; + gdouble x1 = 0; + gdouble y1 = 0; + gdouble xrel = 0; + gdouble yrel = 0; + Unit const *unit = _tracker->getActiveUnit(); + g_return_if_fail(unit != nullptr); + + if (unit->type == Inkscape::Util::UNIT_TYPE_LINEAR) { + x0 = Quantity::convert(_adj_x->get_value(), unit, "px"); + y0 = Quantity::convert(_adj_y->get_value(), unit, "px"); + x1 = x0 + Quantity::convert(_adj_w->get_value(), unit, "px"); + xrel = Quantity::convert(_adj_w->get_value(), unit, "px") / bbox_user->dimensions()[Geom::X]; + y1 = y0 + Quantity::convert(_adj_h->get_value(), unit, "px");; + yrel = Quantity::convert(_adj_h->get_value(), unit, "px") / bbox_user->dimensions()[Geom::Y]; + } else { + double const x0_propn = _adj_x->get_value() / 100 / unit->factor; + x0 = bbox_user->min()[Geom::X] * x0_propn; + double const y0_propn = _adj_y->get_value() / 100 / unit->factor; + y0 = y0_propn * bbox_user->min()[Geom::Y]; + xrel = _adj_w->get_value() / (100 / unit->factor); + x1 = x0 + xrel * bbox_user->dimensions()[Geom::X]; + yrel = _adj_h->get_value() / (100 / unit->factor); + y1 = y0 + yrel * bbox_user->dimensions()[Geom::Y]; + } + + // 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); + } + + // 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 + char const * const actionkey = ( mh > 5e-4 ? "selector:toolbar:move:horizontal" : + sh > 5e-4 ? "selector:toolbar:scale:horizontal" : + mv > 5e-4 ? "selector:toolbar:move:vertical" : + sv > 5e-4 ? "selector:toolbar:scale:vertical" : nullptr ); + + if (actionkey != nullptr) { + + // FIXME: fix for GTK breakage, see comment in SelectedStyle::on_opacity_changed + desktop->getCanvas()->forceFullRedrawAfterInterruptions(0); + + bool transform_stroke = prefs->getBool("/options/transform/stroke", true); + bool preserve = prefs->getBool("/options/preservetransform/value", false); + + Geom::Affine scaler; + if (bbox_type == 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, SP_VERB_CONTEXT_SELECT, + _("Transform by toolbar")); + + // resume interruptibility + desktop->getCanvas()->endForcedFullRedraws(); + } + + _update = false; +} + +void +SelectToolbar::layout_widget_update(Inkscape::Selection *sel) +{ + if (_update) { + return; + } + + _update = true; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + using Geom::X; + using Geom::Y; + if ( sel && !sel->isEmpty() ) { + int prefs_bbox = prefs->getInt("/tools/bounding_box", 0); + SPItem::BBoxType bbox_type = (prefs_bbox ==0)? + SPItem::VISUAL_BBOX : SPItem::GEOMETRIC_BBOX; + Geom::OptRect const bbox(sel->bounds(bbox_type)); + if ( bbox ) { + Unit const *unit = _tracker->getActiveUnit(); + g_return_if_fail(unit != nullptr); + + struct { char const *key; double val; } const keyval[] = { + { "X", bbox->min()[X] }, + { "Y", bbox->min()[Y] }, + { "width", bbox->dimensions()[X] }, + { "height", bbox->dimensions()[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(), keyval[0].val ); + _tracker->setFullVal( _adj_y->gobj(), keyval[1].val ); + _tracker->setFullVal( _adj_w->gobj(), keyval[2].val ); + _tracker->setFullVal( _adj_h->gobj(), keyval[3].val ); + } else { + _adj_x->set_value(Quantity::convert(keyval[0].val, "px", unit)); + _adj_y->set_value(Quantity::convert(keyval[1].val, "px", unit)); + _adj_w->set_value(Quantity::convert(keyval[2].val, "px", unit)); + _adj_h->set_value(Quantity::convert(keyval[3].val, "px", unit)); + } + } + } + + _update = false; +} + +void +SelectToolbar::on_inkscape_selection_modified(Inkscape::Selection *selection, guint flags) +{ + if ((_desktop->getSelection() == selection) // only respond to changes in our desktop + && (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) +{ + if (_desktop->getSelection() == selection) { // only respond to changes in our desktop + bool setActive = (selection && !selection->isEmpty()); + + for (auto item : _context_items) { + if ( setActive != item->get_sensitive() ) { + item->set_sensitive(setActive); + } + } + + layout_widget_update(selection); + } +} + +void +SelectToolbar::toggle_lock() { + if ( _lock_btn->get_active() ) { + _lock_btn->set_icon_name(INKSCAPE_ICON("object-locked")); + } else { + _lock_btn->set_icon_name(INKSCAPE_ICON("object-unlocked")); + } +} + +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..79c3310 --- /dev/null +++ b/src/ui/toolbar/select-toolbar.h @@ -0,0 +1,82 @@ +// 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 { +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 *_transform_stroke_btn; + Gtk::ToggleToolButton *_transform_corners_btn; + Gtk::ToggleToolButton *_transform_gradient_btn; + Gtk::ToggleToolButton *_transform_pattern_btn; + + std::vector<Gtk::ToolItem *> _context_items; + + bool _update; + + 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_stroke(); + void toggle_corners(); + void toggle_gradient(); + void toggle_pattern(); + +protected: + SelectToolbar(SPDesktop *desktop); + +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/snap-toolbar.cpp b/src/ui/toolbar/snap-toolbar.cpp new file mode 100644 index 0000000..483faf3 --- /dev/null +++ b/src/ui/toolbar/snap-toolbar.cpp @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/** + * @file + * Inkscape Snap toolbar + * + * @authors Inkscape Authors + * Copyright (C) 1999-2019 authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include "snap-toolbar.h" + +#include <glibmm/i18n.h> + +#include "attributes.h" +#include "desktop.h" +#include "verbs.h" + +#include "object/sp-namedview.h" + +#include "ui/icon-names.h" + +namespace Inkscape { +namespace UI { +namespace Toolbar { + +SnapToolbar::SnapToolbar(SPDesktop *desktop) + : Toolbar(desktop), + _freeze(false) +{ + // Global snapping control + { + auto snap_global_verb = Inkscape::Verb::get(SP_VERB_TOGGLE_SNAPPING); + _snap_global_item = add_toggle_button(snap_global_verb->get_name(), + snap_global_verb->get_tip()); + _snap_global_item->set_icon_name(INKSCAPE_ICON("snap")); + _snap_global_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_GLOBAL)); + } + + add_separator(); + + // Snapping to bounding boxes + { + _snap_from_bbox_corner_item = add_toggle_button(_("Bounding box"), + _("Snap bounding boxes")); + _snap_from_bbox_corner_item->set_icon_name(INKSCAPE_ICON("snap")); + _snap_from_bbox_corner_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_BBOX)); + } + + { + _snap_to_bbox_path_item = add_toggle_button(_("Bounding box edges"), + _("Snap to edges of a bounding box")); + _snap_to_bbox_path_item->set_icon_name(INKSCAPE_ICON("snap-bounding-box-edges")); + _snap_to_bbox_path_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_BBOX_EDGE)); + } + + { + _snap_to_bbox_node_item = add_toggle_button(_("Bounding box corners"), + _("Snap bounding box corners")); + _snap_to_bbox_node_item->set_icon_name(INKSCAPE_ICON("snap-bounding-box-corners")); + _snap_to_bbox_node_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_BBOX_CORNER)); + } + + { + _snap_to_from_bbox_edge_midpoints_item = add_toggle_button(_("BBox Edge Midpoints"), + _("Snap midpoints of bounding box edges")); + _snap_to_from_bbox_edge_midpoints_item->set_icon_name(INKSCAPE_ICON("snap-bounding-box-midpoints")); + _snap_to_from_bbox_edge_midpoints_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_BBOX_EDGE_MIDPOINT)); + } + + { + _snap_to_from_bbox_edge_centers_item = add_toggle_button(_("BBox Centers"), + _("Snapping centers of bounding boxes")); + _snap_to_from_bbox_edge_centers_item->set_icon_name(INKSCAPE_ICON("snap-bounding-box-center")); + _snap_to_from_bbox_edge_centers_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_BBOX_MIDPOINT)); + } + + add_separator(); + + // Snapping to nodes, paths & handles + { + _snap_from_node_item = add_toggle_button(_("Nodes"), + _("Snap nodes, paths, and handles")); + _snap_from_node_item->set_icon_name(INKSCAPE_ICON("snap")); + _snap_from_node_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_NODE)); + } + + { + _snap_to_item_path_item = add_toggle_button(_("Paths"), + _("Snap to paths")); + _snap_to_item_path_item->set_icon_name(INKSCAPE_ICON("snap-nodes-path")); + _snap_to_item_path_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_PATH)); + } + + { + _snap_to_path_intersections_item = add_toggle_button(_("Path intersections"), + _("Snap to path intersections")); + _snap_to_path_intersections_item->set_icon_name(INKSCAPE_ICON("snap-nodes-intersection")); + _snap_to_path_intersections_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_PATH_INTERSECTION)); + } + + { + _snap_to_item_node_item = add_toggle_button(_("To nodes"), + _("Snap to cusp nodes, incl. rectangle corners")); + _snap_to_item_node_item->set_icon_name(INKSCAPE_ICON("snap-nodes-cusp")); + _snap_to_item_node_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_NODE_CUSP)); + } + + { + _snap_to_smooth_nodes_item = add_toggle_button(_("Smooth nodes"), + _("Snap smooth nodes, incl. quadrant points of ellipses")); + _snap_to_smooth_nodes_item->set_icon_name(INKSCAPE_ICON("snap-nodes-smooth")); + _snap_to_smooth_nodes_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_NODE_SMOOTH)); + } + + { + _snap_to_from_line_midpoints_item = add_toggle_button(_("Line Midpoints"), + _("Snap midpoints of line segments")); + _snap_to_from_line_midpoints_item->set_icon_name(INKSCAPE_ICON("snap-nodes-midpoint")); + _snap_to_from_line_midpoints_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_LINE_MIDPOINT)); + } + + add_separator(); + + { + _snap_from_others_item = add_toggle_button(_("Others"), + _("Snap other points (centers, guide origins, gradient handles, etc.)")); + _snap_from_others_item->set_icon_name(INKSCAPE_ICON("snap")); + _snap_from_others_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_OTHERS)); + } + + { + _snap_to_from_object_centers_item = add_toggle_button(_("Object Centers"), + _("Snap centers of objects")); + _snap_to_from_object_centers_item->set_icon_name(INKSCAPE_ICON("snap-nodes-center")); + _snap_to_from_object_centers_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_OBJECT_MIDPOINT)); + } + + { + _snap_to_from_rotation_center_item = add_toggle_button(_("Rotation Centers"), + _("Snap an item's rotation center")); + _snap_to_from_rotation_center_item->set_icon_name(INKSCAPE_ICON("snap-nodes-rotation-center")); + _snap_to_from_rotation_center_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_ROTATION_CENTER)); + } + + { + _snap_to_from_text_baseline_item = add_toggle_button(_("Text baseline"), + _("Snap text anchors and baselines")); + _snap_to_from_text_baseline_item->set_icon_name(INKSCAPE_ICON("snap-text-baseline")); + _snap_to_from_text_baseline_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_TEXT_BASELINE)); + } + + add_separator(); + + { + _snap_to_page_border_item = add_toggle_button(_("Page border"), + _("Snap to the page border")); + _snap_to_page_border_item->set_icon_name(INKSCAPE_ICON("snap-page")); + _snap_to_page_border_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_PAGE_BORDER)); + } + + { + _snap_to_grids_item = add_toggle_button(_("Grids"), + _("Snap to grids")); + _snap_to_grids_item->set_icon_name(INKSCAPE_ICON("grid-rectangular")); + _snap_to_grids_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_GRID)); + } + + { + _snap_to_guides_item = add_toggle_button(_("Guides"), + _("Snap guides")); + _snap_to_guides_item->set_icon_name(INKSCAPE_ICON("guides")); + _snap_to_guides_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SnapToolbar::on_snap_toggled), + SP_ATTR_INKSCAPE_SNAP_GUIDE)); + } + + show_all(); +} + +GtkWidget * +SnapToolbar::create(SPDesktop *desktop) +{ + auto tb = Gtk::manage(new SnapToolbar(desktop)); + return GTK_WIDGET(tb->gobj()); +} + +void +SnapToolbar::update(SnapToolbar *tb) +{ + auto nv = tb->_desktop->getNamedView(); + + if (nv == nullptr) { + g_warning("Namedview cannot be retrieved (in updateSnapToolbox)!"); + return; + } + + // The ..._set_active calls below will toggle the buttons, but this shouldn't lead to + // changes in our document because we're only updating the UI; + // Setting the "freeze" parameter to true will block the code in toggle_snap_callback() + tb->_freeze = true; + + bool const c1 = nv->snap_manager.snapprefs.getSnapEnabledGlobally(); + tb->_snap_global_item->set_active(c1); + + bool const c2 = nv->snap_manager.snapprefs.isTargetSnappable(SNAPTARGET_BBOX_CATEGORY); + tb->_snap_from_bbox_corner_item->set_active(c2); + tb->_snap_from_bbox_corner_item->set_sensitive(c1); + + tb->_snap_to_bbox_path_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(SNAPTARGET_BBOX_EDGE)); + tb->_snap_to_bbox_path_item->set_sensitive(c1 && c2); + + tb->_snap_to_bbox_node_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(SNAPTARGET_BBOX_CORNER)); + tb->_snap_to_bbox_node_item->set_sensitive(c1 && c2); + + tb->_snap_to_from_bbox_edge_midpoints_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(SNAPTARGET_BBOX_EDGE_MIDPOINT)); + tb->_snap_to_from_bbox_edge_midpoints_item->set_sensitive(c1 && c2); + tb->_snap_to_from_bbox_edge_centers_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(SNAPTARGET_BBOX_MIDPOINT)); + tb->_snap_to_from_bbox_edge_centers_item->set_sensitive(c1 && c2); + + bool const c3 = nv->snap_manager.snapprefs.isTargetSnappable(SNAPTARGET_NODE_CATEGORY); + tb->_snap_from_node_item->set_active(c3); + tb->_snap_from_node_item->set_sensitive(c1); + + tb->_snap_to_item_path_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_PATH)); + tb->_snap_to_item_path_item->set_sensitive(c1 && c3); + tb->_snap_to_path_intersections_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_PATH_INTERSECTION)); + tb->_snap_to_path_intersections_item->set_sensitive(c1 && c3); + tb->_snap_to_item_node_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_NODE_CUSP)); + tb->_snap_to_item_node_item->set_sensitive(c1 && c3); + tb->_snap_to_smooth_nodes_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_NODE_SMOOTH)); + tb->_snap_to_smooth_nodes_item->set_sensitive(c1 && c3); + tb->_snap_to_from_line_midpoints_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_LINE_MIDPOINT)); + tb->_snap_to_from_line_midpoints_item->set_sensitive(c1 && c3); + + bool const c5 = nv->snap_manager.snapprefs.isTargetSnappable(SNAPTARGET_OTHERS_CATEGORY); + tb->_snap_from_others_item->set_active(c5); + tb->_snap_from_others_item->set_sensitive(c1); + tb->_snap_to_from_object_centers_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_OBJECT_MIDPOINT)); + tb->_snap_to_from_object_centers_item->set_sensitive(c1 && c5); + tb->_snap_to_from_rotation_center_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_ROTATION_CENTER)); + tb->_snap_to_from_rotation_center_item->set_sensitive(c1 && c5); + tb->_snap_to_from_text_baseline_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_TEXT_BASELINE)); + tb->_snap_to_from_text_baseline_item->set_sensitive(c1 && c5); + tb->_snap_to_page_border_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_PAGE_BORDER)); + tb->_snap_to_page_border_item->set_sensitive(c1); + tb->_snap_to_grids_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_GRID)); + tb->_snap_to_grids_item->set_sensitive(c1); + tb->_snap_to_guides_item->set_active(nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_GUIDE)); + tb->_snap_to_guides_item->set_sensitive(c1); + + tb->_freeze = false; +} + +void +SnapToolbar::on_snap_toggled(SPAttributeEnum attr) +{ + if(_freeze) return; + + auto dt = _desktop; + auto nv = dt->getNamedView(); + + if(!nv) { + g_warning("No namedview specified in toggle-snap callback"); + return; + } + + auto doc = nv->document; + auto repr = nv->getRepr(); + + if(!repr) { + g_warning("This namedview doesn't have an XML representation attached!"); + return; + } + + DocumentUndo::ScopedInsensitive _no_undo(doc); + + bool v = false; + + switch (attr) { + case SP_ATTR_INKSCAPE_SNAP_GLOBAL: + dt->toggleSnapGlobal(); + break; + case SP_ATTR_INKSCAPE_SNAP_BBOX: + v = nv->snap_manager.snapprefs.isTargetSnappable(Inkscape::SNAPTARGET_BBOX_CATEGORY); + sp_repr_set_boolean(repr, "inkscape:snap-bbox", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_BBOX_EDGE: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_BBOX_EDGE); + sp_repr_set_boolean(repr, "inkscape:bbox-paths", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_BBOX_CORNER: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_BBOX_CORNER); + sp_repr_set_boolean(repr, "inkscape:bbox-nodes", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_NODE: + v = nv->snap_manager.snapprefs.isTargetSnappable(Inkscape::SNAPTARGET_NODE_CATEGORY); + sp_repr_set_boolean(repr, "inkscape:snap-nodes", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_PATH: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_PATH); + sp_repr_set_boolean(repr, "inkscape:object-paths", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_PATH_CLIP: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_PATH_CLIP); + sp_repr_set_boolean(repr, "inkscape:snap-path-clip", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_PATH_MASK: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_PATH_MASK); + sp_repr_set_boolean(repr, "inkscape:snap-path-mask", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_NODE_CUSP: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_NODE_CUSP); + sp_repr_set_boolean(repr, "inkscape:object-nodes", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_NODE_SMOOTH: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_NODE_SMOOTH); + sp_repr_set_boolean(repr, "inkscape:snap-smooth-nodes", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_PATH_INTERSECTION: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_PATH_INTERSECTION); + sp_repr_set_boolean(repr, "inkscape:snap-intersection-paths", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_OTHERS: + v = nv->snap_manager.snapprefs.isTargetSnappable(Inkscape::SNAPTARGET_OTHERS_CATEGORY); + sp_repr_set_boolean(repr, "inkscape:snap-others", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_ROTATION_CENTER: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_ROTATION_CENTER); + sp_repr_set_boolean(repr, "inkscape:snap-center", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_GRID: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_GRID); + sp_repr_set_boolean(repr, "inkscape:snap-grids", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_GUIDE: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_GUIDE); + sp_repr_set_boolean(repr, "inkscape:snap-to-guides", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_PAGE_BORDER: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_PAGE_BORDER); + sp_repr_set_boolean(repr, "inkscape:snap-page", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_LINE_MIDPOINT: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_LINE_MIDPOINT); + sp_repr_set_boolean(repr, "inkscape:snap-midpoints", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_OBJECT_MIDPOINT: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_OBJECT_MIDPOINT); + sp_repr_set_boolean(repr, "inkscape:snap-object-midpoints", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_TEXT_BASELINE: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_TEXT_BASELINE); + sp_repr_set_boolean(repr, "inkscape:snap-text-baseline", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_BBOX_EDGE_MIDPOINT: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_BBOX_EDGE_MIDPOINT); + sp_repr_set_boolean(repr, "inkscape:snap-bbox-edge-midpoints", !v); + break; + case SP_ATTR_INKSCAPE_SNAP_BBOX_MIDPOINT: + v = nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_BBOX_MIDPOINT); + sp_repr_set_boolean(repr, "inkscape:snap-bbox-midpoints", !v); + break; + default: + g_warning("toggle_snap_callback has been called with an ID for which no action has been defined"); + break; + } + + doc->setModifiedSinceSave(); +} + +} +} +} +/* + 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/snap-toolbar.h b/src/ui/toolbar/snap-toolbar.h new file mode 100644 index 0000000..7f8528c --- /dev/null +++ b/src/ui/toolbar/snap-toolbar.h @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_SNAP_TOOLBAR_H +#define SEEN_SNAP_TOOLBAR_H + +/** + * @file + * Snapping toolbar + * + * @authors Inkscape authors, 2004-2019 + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "toolbar.h" + +enum SPAttributeEnum : unsigned; + +namespace Inkscape { +namespace UI { +namespace Toolbar { +class SnapToolbar : public Toolbar { +private: + bool _freeze; + + // Toolbar widgets + Gtk::ToggleToolButton *_snap_global_item; + Gtk::ToggleToolButton *_snap_from_bbox_corner_item; + Gtk::ToggleToolButton *_snap_to_bbox_path_item; + Gtk::ToggleToolButton *_snap_to_bbox_node_item; + Gtk::ToggleToolButton *_snap_to_from_bbox_edge_midpoints_item; + Gtk::ToggleToolButton *_snap_to_from_bbox_edge_centers_item; + Gtk::ToggleToolButton *_snap_from_node_item; + Gtk::ToggleToolButton *_snap_to_item_path_item; + Gtk::ToggleToolButton *_snap_to_path_intersections_item; + Gtk::ToggleToolButton *_snap_to_item_node_item; + Gtk::ToggleToolButton *_snap_to_smooth_nodes_item; + Gtk::ToggleToolButton *_snap_to_from_line_midpoints_item; + Gtk::ToggleToolButton *_snap_from_others_item; + Gtk::ToggleToolButton *_snap_to_from_object_centers_item; + Gtk::ToggleToolButton *_snap_to_from_rotation_center_item; + Gtk::ToggleToolButton *_snap_to_from_text_baseline_item; + Gtk::ToggleToolButton *_snap_to_page_border_item; + Gtk::ToggleToolButton *_snap_to_grids_item; + Gtk::ToggleToolButton *_snap_to_guides_item; + + void on_snap_toggled(SPAttributeEnum attr); + +protected: + SnapToolbar(SPDesktop *desktop); + +public: + static GtkWidget * create(SPDesktop *desktop); + static void update(SnapToolbar *tb); +}; + +} +} +} +#endif /* !SEEN_SNAP_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..35981c1 --- /dev/null +++ b/src/ui/toolbar/spiral-toolbar.cpp @@ -0,0 +1,304 @@ +// 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 "verbs.h" + +#include "object/sp-spiral.h" + +#include "ui/icon-names.h" +#include "ui/uxmanager.h" +#include "ui/widget/label-tool-item.h" +#include "ui/widget/spin-button-tool-item.h" + +#include "widgets/spinbutton-events.h" + +#include "xml/node-event-vector.h" + +using Inkscape::UI::UXManager; +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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _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(), NULL); + + 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(); + sp_repr_set_svg_double( repr, namespaced_name, + adj->get_value() ); + item->updateRepr(); + modmade = true; + } + } + + g_free(namespaced_name); + + if (modmade) { + DocumentUndo::done(_desktop->getDocument(), SP_VERB_CONTEXT_SPIRAL, + _("Change 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->canvas) gtk_widget_grab_focus(GTK_WIDGET(_desktop->canvas)); +} + +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 = 3.0; + sp_repr_get_double(repr, "sodipodi:revolution", &revolution); + toolbar->_revolution_adj->set_value(revolution); + + double expansion = 1.0; + sp_repr_get_double(repr, "sodipodi:expansion", &expansion); + toolbar->_expansion_adj->set_value(expansion); + + double t0 = 0.0; + sp_repr_get_double(repr, "sodipodi:t0", &t0); + 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..9e7c4e8 --- /dev/null +++ b/src/ui/toolbar/spray-toolbar.cpp @@ -0,0 +1,550 @@ +// 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 "inkscape.h" + +#include "ui/icon-names.h" +#include "ui/simple-pref-pusher.h" + +#include "ui/dialog/clonetiler.h" +#include "ui/dialog/dialog-manager.h" +#include "ui/dialog/panel-dialog.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) +{ + if (Inkscape::UI::Dialog::PanelDialogBase *panel_dialog = + dynamic_cast<Inkscape::UI::Dialog::PanelDialogBase *>(desktop->_dlg_mgr->getDialog("CloneTiler"))) { + try { + Inkscape::UI::Dialog::CloneTiler &clone_tiler = + dynamic_cast<Inkscape::UI::Dialog::CloneTiler &>(panel_dialog->getPanel()); + return &clone_tiler; + } catch (std::exception &e) { } + } + + return nullptr; +} + +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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _width_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::width_value_changed)); + // ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + 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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _population_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::population_value_changed)); + //ege_adjustment_action_set_appearance( holder->_spray_population, TOOLBAR_SLIDER_HINT ); + 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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _rotation_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::rotation_value_changed)); + // ege_adjustment_action_set_appearance(holder->_spray_rotation, TOOLBAR_SLIDER_HINT ); + 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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _scale_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::scale_value_changed)); + // ege_adjustment_action_set_appearance( holder->_spray_scale, TOOLBAR_SLIDER_HINT ); + 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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _sd_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::standard_deviation_value_changed)); + // ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + 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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _mean_adj->signal_value_changed().connect(sigc::mem_fun(*this, &SprayToolbar::mean_value_changed)); + // ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + 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(Glib::wrap(GTK_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 = SP_ACTIVE_DESKTOP; + if (Inkscape::UI::Dialog::CloneTiler *ct = get_clone_tiler_panel(dt)){ + dt->_dlg_mgr->showDialog("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..2c020cf --- /dev/null +++ b/src/ui/toolbar/star-toolbar.cpp @@ -0,0 +1,564 @@ +// 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 "verbs.h" + +#include "object/sp-star.h" + +#include "ui/icon-names.h" +#include "ui/tools/star-tool.h" +#include "ui/uxmanager.h" +#include "ui/widget/label-tool-item.h" +#include "ui/widget/spin-button-tool-item.h" + +#include "xml/node-event-vector.h" + +using Inkscape::UI::UXManager; +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 = { 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, 3, 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(Glib::wrap(GTK_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(Glib::wrap(GTK_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(Glib::wrap(GTK_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(Glib::wrap(GTK_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(); + repr->setAttribute("inkscape:flatsided", flat ? "true" : "false" ); + item->updateRepr(); + modmade = true; + } + } + + if (modmade) { + DocumentUndo::done(_desktop->getDocument(), SP_VERB_CONTEXT_STAR, + flat ? _("Make polygon") : _("Make 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(); + sp_repr_set_int(repr,"sodipodi:sides", + (gint)_magnitude_adj->get_value()); + double arg1 = 0.5; + sp_repr_get_double(repr, "sodipodi:arg1", &arg1); + sp_repr_set_svg_double(repr, "sodipodi:arg2", + (arg1 + M_PI / (gint)_magnitude_adj->get_value())); + item->updateRepr(); + modmade = true; + } + } + if (modmade) { + DocumentUndo::done(_desktop->getDocument(), SP_VERB_CONTEXT_STAR, + _("Star: Change number of corners")); + } + + _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 = 1.0; + gdouble r2 = 1.0; + sp_repr_get_double(repr, "sodipodi:r1", &r1); + sp_repr_get_double(repr, "sodipodi:r2", &r2); + if (r2 < r1) { + sp_repr_set_svg_double(repr, "sodipodi:r2", + r1*_spoke_adj->get_value()); + } else { + sp_repr_set_svg_double(repr, "sodipodi:r1", + r2*_spoke_adj->get_value()); + } + + item->updateRepr(); + modmade = true; + } + } + + if (modmade) { + DocumentUndo::done(_desktop->getDocument(), SP_VERB_CONTEXT_STAR, + _("Star: Change spoke ratio")); + } + + _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(); + sp_repr_set_svg_double(repr, "inkscape:rounded", + (gdouble) _roundedness_adj->get_value()); + item->updateRepr(); + modmade = true; + } + } + if (modmade) { + DocumentUndo::done(_desktop->getDocument(), SP_VERB_CONTEXT_STAR, + _("Star: Change rounding")); + } + + _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(); + sp_repr_set_svg_double(repr, "inkscape:randomized", + (gdouble) _randomization_adj->get_value()); + item->updateRepr(); + modmade = true; + } + } + if (modmade) { + DocumentUndo::done(_desktop->getDocument(), SP_VERB_CONTEXT_STAR, + _("Star: Change randomization")); + } + + _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 = 0.0; + sp_repr_get_double(repr, "inkscape:randomized", &randomized); + toolbar->_randomization_adj->set_value(randomized); + } else if (!strcmp(name, "inkscape:rounded")) { + double rounded = 0.0; + sp_repr_get_double(repr, "inkscape:rounded", &rounded); + 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); + } else { + toolbar->_flat_item_buttons[0]->set_active(); + toolbar->_spoke_item->set_visible(false); + } + } else if ((!strcmp(name, "sodipodi:r1") || !strcmp(name, "sodipodi:r2")) && (!isFlatSided) ) { + gdouble r1 = 1.0; + gdouble r2 = 1.0; + sp_repr_get_double(repr, "sodipodi:r1", &r1); + sp_repr_get_double(repr, "sodipodi:r2", &r2); + 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 = 0; + sp_repr_get_int(repr, "sodipodi:sides", &sides); + 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..3b9a152 --- /dev/null +++ b/src/ui/toolbar/text-toolbar.cpp @@ -0,0 +1,2540 @@ +// 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 "verbs.h" + +#include "libnrtype/font-lister.h" + +#include "display/sp-canvas.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/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); +} + +static void text_toolbox_watch_ec(SPDesktop* dt, Inkscape::UI::Tools::ToolBase* ec, GObject* holder); + +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( SP_ACTIVE_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->canvas))); // 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->set_altx_name( "altx-text" ); // Set Alt-X keyboard shortcut + _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->canvas))); // 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->canvas))); // 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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _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); + } + + Gtk::SeparatorToolItem *separator = Gtk::manage(new Gtk::SeparatorToolItem()); + /* 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)); + } + + /* 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, -100.0, 100.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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _letter_spacing_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::letterspacing_value_changed)); + add(*_letter_spacing_item); + + _letter_spacing_item->set_sensitive(true); + _letter_spacing_item->set_icon(INKSCAPE_ICON("text_letter_spacing")); + } + + /* 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, -100.0, 100.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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _word_spacing_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::wordspacing_value_changed)); + + add(*_word_spacing_item); + _word_spacing_item->set_sensitive(true); + _word_spacing_item->set_icon(INKSCAPE_ICON("text_word_spacing")); + } + + /* 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, -100.0, 100.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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _dx_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TextToolbar::dx_value_changed)); + add(*_dx_item); + _dx_item->set_sensitive(true); + _dx_item->set_icon(INKSCAPE_ICON("text_horz_kern")); + } + + /* 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, -100.0, 100.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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _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")); + add(*_dy_item); + } + + /* 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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _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")); + add(*_rotation_item); + } + + + /* 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 = SP_ACTIVE_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; + + gchar *temp_family = _font_family_item->get_active_text(); + Glib::ustring new_family(temp_family); + g_free(temp_family); + 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 = SP_ACTIVE_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(), SP_VERB_CONTEXT_TEXT, + _("Text: Change font family")); + } + 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; + + gchar *text = _font_size_item->get_active_text(); + 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 ); + g_free( text ); + _freeze = false; + return; + } + g_free( text ); + + 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(SP_ACTIVE_DOCUMENT); + int result_numbers = + sp_desktop_query_style (SP_ACTIVE_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 { + sp_desktop_set_style(_desktop, css, true, true); + // Save for undo + DocumentUndo::maybeDone(SP_ACTIVE_DESKTOP->getDocument(), "ttb:size", SP_VERB_NONE, + _("Text: Change font size")); + } + + 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 = SP_ACTIVE_DESKTOP; + sp_desktop_set_style (desktop, css, true, true); + + + // If no selected objects, set default. + SPStyle query(SP_ACTIVE_DOCUMENT); + int result_style = + sp_desktop_query_style (SP_ACTIVE_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(), SP_VERB_CONTEXT_TEXT, + _("Text: Change font style")); + } + + 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(SP_ACTIVE_DOCUMENT); + int result_baseline = sp_desktop_query_style (SP_ACTIVE_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 = SP_ACTIVE_DESKTOP; + sp_desktop_set_style (desktop, css, true, false); + + // Save for undo + if(result_baseline != QUERY_STYLE_NOTHING) { + DocumentUndo::maybeDone(SP_ACTIVE_DESKTOP->getDocument(), "ttb:script", SP_VERB_NONE, + _("Text: Change superscript or subscript")); + } + _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 = SP_ACTIVE_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(SP_ACTIVE_DOCUMENT); + int result_numbers = + sp_desktop_query_style (SP_ACTIVE_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(SP_ACTIVE_DESKTOP->getDocument(), SP_VERB_CONTEXT_TEXT, + _("Text: Change alignment")); + } + sp_repr_css_attr_unref (css); + + gtk_widget_grab_focus (GTK_WIDGET(SP_ACTIVE_DESKTOP->canvas)); + + _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(SP_ACTIVE_DOCUMENT); + int result_numbers = + sp_desktop_query_style (SP_ACTIVE_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 (SP_ACTIVE_DESKTOP, css, true, true); + if(result_numbers != QUERY_STYLE_NOTHING) + { + DocumentUndo::done(SP_ACTIVE_DESKTOP->getDocument(), SP_VERB_CONTEXT_TEXT, + _("Text: Change writing mode")); + } + sp_repr_css_attr_unref (css); + + gtk_widget_grab_focus (GTK_WIDGET(SP_ACTIVE_DESKTOP->canvas)); + + _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(SP_ACTIVE_DOCUMENT); + int result_numbers = + sp_desktop_query_style (SP_ACTIVE_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 (SP_ACTIVE_DESKTOP, css, true, true); + if(result_numbers != QUERY_STYLE_NOTHING) + { + DocumentUndo::done(SP_ACTIVE_DESKTOP->getDocument(), SP_VERB_CONTEXT_TEXT, + _("Text: Change orientation")); + } + sp_repr_css_attr_unref (css); + + gtk_widget_grab_focus (GTK_WIDGET(SP_ACTIVE_DESKTOP->canvas)); + + _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(SP_ACTIVE_DOCUMENT); + int result_numbers = + sp_desktop_query_style (SP_ACTIVE_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 (SP_ACTIVE_DESKTOP, css, true, true); + if(result_numbers != QUERY_STYLE_NOTHING) + { + DocumentUndo::done(SP_ACTIVE_DESKTOP->getDocument(), SP_VERB_CONTEXT_TEXT, + _("Text: Change direction")); + } + sp_repr_css_attr_unref (css); + + gtk_widget_grab_focus (GTK_WIDGET(SP_ACTIVE_DESKTOP->canvas)); + + _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 = SP_ACTIVE_DESKTOP; + // Get user selected unit and save as preference + Unit const *unit = _tracker->getActiveUnit(); + // @Tav same disabled unit + 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 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", SP_VERB_NONE, _("Text: Change line-height")); + } + + // If no selected objects, set default. + SPStyle query(SP_ACTIVE_DOCUMENT); + 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; + } + + // Read current line height value + double line_height = _line_height_adj->get_value(); + SPDesktop *desktop = SP_ACTIVE_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 { + 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. + if (_outer) { + 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 { + 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); + } + 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(SP_ACTIVE_DESKTOP->getDocument(), "ttb:line-height", SP_VERB_NONE, + _("Text: Change line-height unit")); + } + + // If no selected objects, set default. + SPStyle query(SP_ACTIVE_DOCUMENT); + int result_numbers = + sp_desktop_query_style (SP_ACTIVE_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(SP_ACTIVE_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(SP_ACTIVE_DOCUMENT); + int result_numbers = + sp_desktop_query_style (SP_ACTIVE_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(SP_ACTIVE_DESKTOP->getDocument(), "ttb:word-spacing", SP_VERB_NONE, + _("Text: Change word-spacing")); + } + + 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(SP_ACTIVE_DOCUMENT); + int result_numbers = + sp_desktop_query_style (SP_ACTIVE_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(SP_ACTIVE_DESKTOP->getDocument(), "ttb:letter-spacing", SP_VERB_NONE, + _("Text: Change letter-spacing")); + } + + 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((SP_ACTIVE_DESKTOP)->event_context) ) { + Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT((SP_ACTIVE_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, SP_ACTIVE_DESKTOP, delta_dx ); + modmade = true; + } + } + } + + if(modmade) { + // Save for undo + DocumentUndo::maybeDone(SP_ACTIVE_DESKTOP->getDocument(), "ttb:dx", SP_VERB_NONE, + _("Text: Change dx (kern)")); + } + _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((SP_ACTIVE_DESKTOP)->event_context) ) { + Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT((SP_ACTIVE_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, SP_ACTIVE_DESKTOP, delta_dy ); + modmade = true; + } + } + } + + if(modmade) { + // Save for undo + DocumentUndo::maybeDone(SP_ACTIVE_DESKTOP->getDocument(), "ttb:dy", SP_VERB_NONE, + _("Text: Change dy")); + } + + _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((SP_ACTIVE_DESKTOP)->event_context) ) { + Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT((SP_ACTIVE_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, SP_ACTIVE_DESKTOP, delta_deg ); + modmade = true; + } + } + } + + // Save for undo + if(modmade) { + DocumentUndo::maybeDone(SP_ACTIVE_DESKTOP->getDocument(), "ttb:rotate", SP_VERB_NONE, + _("Text: Change rotate")); + } + + _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 = SP_ACTIVE_DESKTOP; + SPDocument *document = SP_ACTIVE_DOCUMENT; + 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((SP_ACTIVE_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; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + 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); + } + + 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 (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((SP_ACTIVE_DESKTOP)->event_context) ) { + Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT((SP_ACTIVE_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->connectToolSubselectionChanged(sigc::mem_fun(*this, &TextToolbar::subselection_changed)); + this->_sub_active_item = nullptr; + 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((SP_ACTIVE_DESKTOP)->event_context)) { + Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT((SP_ACTIVE_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 parse the just created line height in one or more lines of a text subselection +* can recibe 2 kinds of input because when we store a text element we apply a fallback that change +* structure. This visualy is not reflected but user maybe want to change a part of this subselection +* once the falback is created, so we need more complex here to fill the gap. +* Basicaly we have a line height changed in the new wraper element/s between wrap_start and wrap_end +* this variables store starting iterator of first char in line and last char in line in a subselection +* this elements are styled well but we can have orphands text nodes before and after the subselection. +* this normaly 3 elements are inside a container as direct child of a text element, so we need to apply +* the container style to the optional previous and last text nodes warping into a new element that get the +* container style (this are not part to the subsselection). After wrap we unindent all child of the container and +* remove the container +* +*/ +void TextToolbar::prepare_inner() +{ + Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT((SP_ACTIVE_DESKTOP)->event_context); + if (tc) { + Inkscape::Text::Layout *layout = const_cast<Inkscape::Text::Layout *>(te_get_layout(tc->text)); + if (layout) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + SPDocument *doc = SP_ACTIVE_DOCUMENT; + 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 elemet + // and wrap it into a tspan elements as inkscape do. + if (text) { + 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()); + } + } + } + } + + // Here we remove temporary the shape to allow layout calculate where are the warp_end and warpo_start + // position if one of this are hiden because the previous line height changed + if (text) { + text->hide_shape_inside(); + } + if (flowtext) { + flowtext->fix_overflow_flowregion(false); + } + SPObject *rawptr_start = nullptr; + SPObject *rawptr_end = nullptr; + layout->getSourceOfCharacter(wrap_start, &rawptr_start); + layout->getSourceOfCharacter(wrap_end, &rawptr_end); + if (text) { + text->show_shape_inside(); + } + if (flowtext) { + flowtext->fix_overflow_flowregion(true); + } + if (!rawptr_start || !rawptr_end || !SP_IS_OBJECT(rawptr_start)|| !SP_IS_OBJECT(rawptr_end)) { + return; + } + SPObject *startobj = reinterpret_cast<SPObject *>(rawptr_start); + SPObject *endobj = reinterpret_cast<SPObject *>(rawptr_end); + // here we try to slect the elements where we need to work + // looping parent while text element in start and end + // and geting this and the inbetween elements + SPObject *start = startobj; + SPObject *end = endobj; + while (start->parent != spobject) + { + start = start->parent; + } + while (end->parent != spobject) + { + end = end->parent; + } + std::vector<SPObject *> containers; + while (start && start != end) { + containers.push_back(start); + start = start->getNext(); + } + if (start) { + containers.push_back(start); + } + for (auto container : containers) { + //we store the parent style to apply to the childs unselected + const gchar * style = container->getRepr()->attribute("style"); + 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 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 + // mayve we can move directly but the safer for me is duplicate + // inject into the new element and delete original + for (auto fts_child : fts_childs) { + // is this check necesary? + 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 diferent if in a text or flowtext. + // wrap a duplicate of the element and unindent after the prevchild + // and finaly 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 = SP_ACTIVE_DOCUMENT; + 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(gpointer texttool) +{ +#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; + } + Inkscape::UI::Tools::TextTool *const tc = SP_TEXT_CONTEXT(SP_EVENT_CONTEXT(texttool)); + 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); + gint endline = layout->paragraphIndex(end_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(SP_ACTIVE_DOCUMENT); + _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(SP_ACTIVE_DESKTOP, &_query_cursor, QUERY_STYLE_PROPERTY_FONTNUMBERS); + tc->text_sel_start = start_selection; + _updating = false; + break; + } + ++counter; + } + selection_changed(nullptr); + } else if ((start_selection == start && end_selection == end)) { + // 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..6108dd6 --- /dev/null +++ b/src/ui/toolbar/text-toolbar.h @@ -0,0 +1,149 @@ +// 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; +} + +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(gpointer 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..445f5b7 --- /dev/null +++ b/src/ui/toolbar/toolbar.cpp @@ -0,0 +1,102 @@ +// 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" + +#include "helper/action.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 toolbutton that performs a given verb + * + * \param[in] verb_code The code for the verb (e.g., SP_VERB_EDIT_SELECT_ALL) + * + * \returns a pointer to the toolbutton + */ +Gtk::ToolButton * +Toolbar::add_toolbutton_for_verb(unsigned int verb_code) +{ + auto context = Inkscape::ActionContext(_desktop); + auto button = SPAction::create_toolbutton_for_verb(verb_code, context); + add(*button); + return button; +} + +/** + * \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..18c0510 --- /dev/null +++ b/src/ui/toolbar/toolbar.h @@ -0,0 +1,67 @@ +// 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); + Gtk::ToolButton * add_toolbutton_for_verb(unsigned int verb_code); + 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..dc5352c --- /dev/null +++ b/src/ui/toolbar/tweak-toolbar.cpp @@ -0,0 +1,347 @@ +// 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/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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _width_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TweakToolbar::width_value_changed)); + // ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + 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(Glib::wrap(GTK_WIDGET(desktop->canvas))); + _force_adj->signal_value_changed().connect(sigc::mem_fun(*this, &TweakToolbar::force_value_changed)); + // ege_adjustment_action_set_appearance( eact, TOOLBAR_SLIDER_HINT ); + 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(Glib::wrap(GTK_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..3b4d3d6 --- /dev/null +++ b/src/ui/toolbar/zoom-toolbar.cpp @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @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 "zoom-toolbar.h" + +#include "desktop.h" +#include "verbs.h" + +#include "helper/action.h" + +namespace Inkscape { +namespace UI { +namespace Toolbar { +ZoomToolbar::ZoomToolbar(SPDesktop *desktop) + : Toolbar(desktop) +{ + add_toolbutton_for_verb(SP_VERB_ZOOM_IN); + add_toolbutton_for_verb(SP_VERB_ZOOM_OUT); + + add_separator(); + + add_toolbutton_for_verb(SP_VERB_ZOOM_1_1); + add_toolbutton_for_verb(SP_VERB_ZOOM_1_2); + add_toolbutton_for_verb(SP_VERB_ZOOM_2_1); + + add_separator(); + + add_toolbutton_for_verb(SP_VERB_ZOOM_SELECTION); + add_toolbutton_for_verb(SP_VERB_ZOOM_DRAWING); + add_toolbutton_for_verb(SP_VERB_ZOOM_PAGE); + add_toolbutton_for_verb(SP_VERB_ZOOM_PAGE_WIDTH); + add_toolbutton_for_verb(SP_VERB_ZOOM_CENTER_PAGE); + + add_separator(); + + add_toolbutton_for_verb(SP_VERB_ZOOM_PREV); + add_toolbutton_for_verb(SP_VERB_ZOOM_NEXT); + + show_all(); +} + +GtkWidget * +ZoomToolbar::create(SPDesktop *desktop) +{ + auto toolbar = Gtk::manage(new ZoomToolbar(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/zoom-toolbar.h b/src/ui/toolbar/zoom-toolbar.h new file mode 100644 index 0000000..b5d34de --- /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 : public Toolbar { +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 : |