summaryrefslogtreecommitdiffstats
path: root/src/ui/tools
diff options
context:
space:
mode:
authorJabier Arraiza Cenoz <jabier.arraiza@marker.es>2013-11-10 02:08:46 +0000
committerJabiertxof <jtx@jtx.marker.es>2013-11-10 02:08:46 +0000
commitca1286aa677b5ff8d80fe274dfa3e759478570b3 (patch)
tree36c7edfa575cea3d3bddfc4b034f7a35a8374ade /src/ui/tools
parentFixing bugs on update to trunk (diff)
parentRevert unintentional change. (diff)
downloadinkscape-ca1286aa677b5ff8d80fe274dfa3e759478570b3.tar.gz
inkscape-ca1286aa677b5ff8d80fe274dfa3e759478570b3.zip
Update to trunk
(bzr r11950.1.195)
Diffstat (limited to 'src/ui/tools')
-rw-r--r--src/ui/tools/Makefile_insert28
-rw-r--r--src/ui/tools/arc-tool.cpp503
-rw-r--r--src/ui/tools/arc-tool.h76
-rw-r--r--src/ui/tools/box3d-tool.cpp637
-rw-r--r--src/ui/tools/box3d-tool.h95
-rw-r--r--src/ui/tools/calligraphic-tool.cpp1216
-rw-r--r--src/ui/tools/calligraphic-tool.h94
-rw-r--r--src/ui/tools/connector-tool.cpp1496
-rw-r--r--src/ui/tools/connector-tool.h135
-rw-r--r--src/ui/tools/dropper-tool.cpp414
-rw-r--r--src/ui/tools/dropper-tool.h73
-rw-r--r--src/ui/tools/dynamic-base.cpp162
-rw-r--r--src/ui/tools/dynamic-base.h123
-rw-r--r--src/ui/tools/eraser-tool.cpp1019
-rw-r--r--src/ui/tools/eraser-tool.h76
-rw-r--r--src/ui/tools/flood-tool.cpp1263
-rw-r--r--src/ui/tools/flood-tool.h74
-rw-r--r--src/ui/tools/freehand-base.cpp818
-rw-r--r--src/ui/tools/freehand-base.h146
-rw-r--r--src/ui/tools/gradient-tool.cpp977
-rw-r--r--src/ui/tools/gradient-tool.h76
-rw-r--r--src/ui/tools/lpe-tool.cpp503
-rw-r--r--src/ui/tools/lpe-tool.h99
-rw-r--r--src/ui/tools/measure-tool.cpp777
-rw-r--r--src/ui/tools/measure-tool.h50
-rw-r--r--src/ui/tools/mesh-tool.cpp1017
-rw-r--r--src/ui/tools/mesh-tool.h77
-rw-r--r--src/ui/tools/node-tool.cpp705
-rw-r--r--src/ui/tools/node-tool.h105
-rw-r--r--src/ui/tools/pen-tool.cpp2371
-rw-r--r--src/ui/tools/pen-tool.h110
-rw-r--r--src/ui/tools/pencil-tool.cpp952
-rw-r--r--src/ui/tools/pencil-tool.h68
-rw-r--r--src/ui/tools/rect-tool.cpp527
-rw-r--r--src/ui/tools/rect-tool.h65
-rw-r--r--src/ui/tools/select-tool.cpp1252
-rw-r--r--src/ui/tools/select-tool.h73
-rw-r--r--src/ui/tools/spiral-tool.cpp467
-rw-r--r--src/ui/tools/spiral-tool.h66
-rw-r--r--src/ui/tools/spray-tool.cpp890
-rw-r--r--src/ui/tools/spray-tool.h121
-rw-r--r--src/ui/tools/star-tool.cpp494
-rw-r--r--src/ui/tools/star-tool.h74
-rw-r--r--src/ui/tools/text-tool.cpp1765
-rw-r--r--src/ui/tools/text-tool.h101
-rw-r--r--src/ui/tools/tool-base.cpp1541
-rw-r--r--src/ui/tools/tool-base.h244
-rw-r--r--src/ui/tools/tweak-tool.cpp1524
-rw-r--r--src/ui/tools/tweak-tool.h106
-rw-r--r--src/ui/tools/zoom-tool.cpp250
-rw-r--r--src/ui/tools/zoom-tool.h47
51 files changed, 25942 insertions, 0 deletions
diff --git a/src/ui/tools/Makefile_insert b/src/ui/tools/Makefile_insert
new file mode 100644
index 000000000..cd09a3230
--- /dev/null
+++ b/src/ui/tools/Makefile_insert
@@ -0,0 +1,28 @@
+## Makefile.am fragment sourced by src/Makefile.am.
+
+ink_common_sources += \
+ ui/tools/arc-tool.cpp ui/tools/arc-tool.h \
+ ui/tools/box3d-tool.cpp ui/tools/box3d-tool.h \
+ ui/tools/calligraphic-tool.cpp ui/tools/calligraphic-tool.h \
+ ui/tools/connector-tool.cpp ui/tools/connector-tool.h \
+ ui/tools/dropper-tool.cpp ui/tools/dropper-tool.h \
+ ui/tools/dynamic-base.cpp ui/tools/dynamic-base.h \
+ ui/tools/eraser-tool.cpp ui/tools/eraser-tool.h \
+ ui/tools/flood-tool.cpp ui/tools/flood-tool.h \
+ ui/tools/freehand-base.cpp ui/tools/freehand-base.h \
+ ui/tools/gradient-tool.cpp ui/tools/gradient-tool.h \
+ ui/tools/lpe-tool.cpp ui/tools/lpe-tool.h \
+ ui/tools/measure-tool.cpp ui/tools/measure-tool.h \
+ ui/tools/mesh-tool.cpp ui/tools/mesh-tool.h \
+ ui/tools/node-tool.cpp ui/tools/node-tool.h \
+ ui/tools/pen-tool.cpp ui/tools/pen-tool.h \
+ ui/tools/pencil-tool.cpp ui/tools/pencil-tool.h \
+ ui/tools/rect-tool.cpp ui/tools/rect-tool.h \
+ ui/tools/select-tool.cpp ui/tools/select-tool.h \
+ ui/tools/spiral-tool.cpp ui/tools/spiral-tool.h \
+ ui/tools/spray-tool.cpp ui/tools/spray-tool.h \
+ ui/tools/star-tool.cpp ui/tools/star-tool.h \
+ ui/tools/text-tool.cpp ui/tools/text-tool.h \
+ ui/tools/tool-base.cpp ui/tools/tool-base.h \
+ ui/tools/tweak-tool.cpp ui/tools/tweak-tool.h \
+ ui/tools/zoom-tool.cpp ui/tools/zoom-tool.h \ No newline at end of file
diff --git a/src/ui/tools/arc-tool.cpp b/src/ui/tools/arc-tool.cpp
new file mode 100644
index 000000000..bb7dfa21c
--- /dev/null
+++ b/src/ui/tools/arc-tool.cpp
@@ -0,0 +1,503 @@
+/**
+ * @file
+ * Ellipse drawing context.
+ */
+/* Authors:
+ * Mitsuru Oka
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Johan Engelen <johan@shouraizou.nl>
+ * Abhishek Sharma
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2000-2006 Authors
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <gdk/gdkkeysyms.h>
+
+#include "macros.h"
+#include <glibmm/i18n.h>
+#include "display/sp-canvas.h"
+#include "sp-ellipse.h"
+#include "document.h"
+#include "document-undo.h"
+#include "sp-namedview.h"
+#include "selection.h"
+#include "desktop-handles.h"
+#include "snap.h"
+#include "pixmaps/cursor-ellipse.xpm"
+#include "xml/repr.h"
+#include "xml/node-event-vector.h"
+#include "preferences.h"
+#include "message-context.h"
+#include "desktop.h"
+#include "desktop-style.h"
+#include "context-fns.h"
+#include "verbs.h"
+#include "shape-editor.h"
+#include "ui/tools/tool-base.h"
+
+#include "ui/tools/arc-tool.h"
+#include "display/sp-canvas-item.h"
+
+using Inkscape::DocumentUndo;
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+namespace {
+ ToolBase* createArcContext() {
+ return new ArcTool();
+ }
+
+ bool arcContextRegistered = ToolFactory::instance().registerObject("/tools/shapes/arc", createArcContext);
+}
+
+const std::string& ArcTool::getPrefsPath() {
+ return ArcTool::prefsPath;
+}
+
+const std::string ArcTool::prefsPath = "/tools/shapes/arc";
+
+
+ArcTool::ArcTool() : ToolBase() {
+ this->cursor_shape = cursor_ellipse_xpm;
+ this->hot_x = 4;
+ this->hot_y = 4;
+ this->xp = 0;
+ this->yp = 0;
+ this->tolerance = 0;
+ this->within_tolerance = false;
+ this->item_to_select = NULL;
+ //this->tool_url = "/tools/shapes/arc";
+
+ this->arc = NULL;
+}
+
+void ArcTool::finish() {
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME);
+ this->finishItem();
+ this->sel_changed_connection.disconnect();
+
+ ToolBase::finish();
+}
+
+ArcTool::~ArcTool() {
+ this->enableGrDrag(false);
+
+ this->sel_changed_connection.disconnect();
+
+ delete this->shape_editor;
+ this->shape_editor = NULL;
+
+ /* fixme: This is necessary because we do not grab */
+ if (this->arc) {
+ this->finishItem();
+ }
+}
+
+/**
+ * Callback that processes the "changed" signal on the selection;
+ * destroys old and creates new knotholder.
+ */
+void ArcTool::selection_changed(Inkscape::Selection* selection) {
+ this->shape_editor->unset_item(SH_KNOTHOLDER);
+ this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER);
+}
+
+void ArcTool::setup() {
+ ToolBase::setup();
+
+ Inkscape::Selection *selection = sp_desktop_selection(this->desktop);
+
+ this->shape_editor = new ShapeEditor(this->desktop);
+
+ SPItem *item = sp_desktop_selection(this->desktop)->singleItem();
+ if (item) {
+ this->shape_editor->set_item(item, SH_KNOTHOLDER);
+ }
+
+ this->sel_changed_connection.disconnect();
+ this->sel_changed_connection = selection->connectChanged(
+ sigc::mem_fun(this, &ArcTool::selection_changed)
+ );
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/shapes/selcue")) {
+ this->enableSelectionCue();
+ }
+
+ if (prefs->getBool("/tools/shapes/gradientdrag")) {
+ this->enableGrDrag();
+ }
+}
+
+bool ArcTool::item_handler(SPItem* item, GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+ Inkscape::setup_for_drag_start(desktop, this, event);
+ ret = TRUE;
+ }
+ break;
+ // motion and release are always on root (why?)
+ default:
+ break;
+ }
+
+// if ((SP_EVENT_CONTEXT_CLASS(sp_arc_context_parent_class))->item_handler) {
+// ret = (SP_EVENT_CONTEXT_CLASS(sp_arc_context_parent_class))->item_handler(event_context, item, event);
+// }
+ // CPPIFY: ret is overwritten...
+ ret = ToolBase::item_handler(item, event);
+
+ return ret;
+}
+
+bool ArcTool::root_handler(GdkEvent* event) {
+ static bool dragging;
+
+ Inkscape::Selection *selection = sp_desktop_selection(desktop);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+ dragging = true;
+
+ this->center = Inkscape::setup_for_drag_start(desktop, this, event);
+
+ /* Snap center */
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+ m.freeSnapReturnByRef(this->center, Inkscape::SNAPSOURCE_NODE_HANDLE);
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK,
+ NULL, event->button.time);
+ ret = TRUE;
+ m.unSetup();
+ }
+ break;
+ case GDK_MOTION_NOTIFY:
+ if (dragging && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) {
+ if ( this->within_tolerance
+ && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance )
+ && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) {
+ break; // do not drag if we're within tolerance from origin
+ }
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to draw, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ this->within_tolerance = false;
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point motion_dt(desktop->w2d(motion_w));
+
+ this->drag(motion_dt, event->motion.state);
+
+ gobble_motion_events(GDK_BUTTON1_MASK);
+
+ ret = TRUE;
+ } else if (!sp_event_context_knot_mouseover(this)){
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point motion_dt(desktop->w2d(motion_w));
+ m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE));
+ m.unSetup();
+ }
+ break;
+ case GDK_BUTTON_RELEASE:
+ this->xp = this->yp = 0;
+ if (event->button.button == 1 && !this->space_panning) {
+ dragging = false;
+ sp_event_context_discard_delayed_snap_event(this);
+
+ if (!this->within_tolerance) {
+ // we've been dragging, finish the arc
+ this->finishItem();
+ } else if (this->item_to_select) {
+ // no dragging, select clicked item if any
+ if (event->button.state & GDK_SHIFT_MASK) {
+ selection->toggle(this->item_to_select);
+ } else {
+ selection->set(this->item_to_select);
+ }
+ } else {
+ // click in an empty space
+ selection->clear();
+ }
+
+ this->xp = 0;
+ this->yp = 0;
+ this->item_to_select = NULL;
+ ret = TRUE;
+ }
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
+ break;
+
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Alt_L:
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine)
+ case GDK_KEY_Meta_R:
+ if (!dragging) {
+ sp_event_show_modifier_tip(this->defaultMessageContext(), event,
+ _("<b>Ctrl</b>: make circle or integer-ratio ellipse, snap arc/segment angle"),
+ _("<b>Shift</b>: draw around the starting point"),
+ NULL);
+ }
+ break;
+
+ case GDK_KEY_Up:
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_Down:
+ // prevent the zoom field from activation
+ if (!MOD__CTRL_ONLY(event))
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_x:
+ case GDK_KEY_X:
+ if (MOD__ALT_ONLY(event)) {
+ desktop->setToolboxFocusTo ("altx-arc");
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Escape:
+ if (dragging) {
+ dragging = false;
+ sp_event_context_discard_delayed_snap_event(this);
+ // if drawing, cancel, otherwise pass it up for deselecting
+ this->cancel();
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_space:
+ if (dragging) {
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
+ dragging = false;
+ sp_event_context_discard_delayed_snap_event(this);
+
+ if (!this->within_tolerance) {
+ // we've been dragging, finish the arc
+ this->finishItem();
+ }
+ // do not return true, so that space would work switching to selector
+ }
+ break;
+
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ case GDK_KEY_BackSpace:
+ ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event));
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case GDK_KEY_RELEASE:
+ switch (event->key.keyval) {
+ case GDK_KEY_Alt_L:
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt
+ case GDK_KEY_Meta_R:
+ this->defaultMessageContext()->clear();
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+void ArcTool::drag(Geom::Point pt, guint state) {
+ if (!this->arc) {
+ if (Inkscape::have_viable_layer(desktop, this->message_context) == false) {
+ return;
+ }
+
+ // Create object
+ Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
+ Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
+ repr->setAttribute("sodipodi:type", "arc");
+
+ // Set style
+ sp_desktop_apply_style_tool(desktop, repr, "/tools/shapes/arc", false);
+
+ this->arc = SP_GENERICELLIPSE(desktop->currentLayer()->appendChildRepr(repr));
+ Inkscape::GC::release(repr);
+ this->arc->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
+ this->arc->updateRepr();
+
+ desktop->canvas->forceFullRedrawAfterInterruptions(5);
+ }
+
+ bool ctrl_save = false;
+
+ if ((state & GDK_MOD1_MASK) && (state & GDK_CONTROL_MASK) && !(state & GDK_SHIFT_MASK)) {
+ // if Alt is pressed without Shift in addition to Control, temporarily drop the CONTROL mask
+ // so that the ellipse is not constrained to integer ratios
+ ctrl_save = true;
+ state = state ^ GDK_CONTROL_MASK;
+ }
+
+ Geom::Rect r = Inkscape::snap_rectangular_box(desktop, this->arc, pt, this->center, state);
+
+ if (ctrl_save) {
+ state = state ^ GDK_CONTROL_MASK;
+ }
+
+ Geom::Point dir = r.dimensions() / 2;
+
+ if (state & GDK_MOD1_MASK) {
+ /* With Alt let the ellipse pass through the mouse pointer */
+ Geom::Point c = r.midpoint();
+
+ if (!ctrl_save) {
+ if (fabs(dir[Geom::X]) > 1E-6 && fabs(dir[Geom::Y]) > 1E-6) {
+ Geom::Affine const i2d ( (this->arc)->i2dt_affine() );
+ Geom::Point new_dir = pt * i2d - c;
+ new_dir[Geom::X] *= dir[Geom::Y] / dir[Geom::X];
+ double lambda = new_dir.length() / dir[Geom::Y];
+ r = Geom::Rect (c - lambda*dir, c + lambda*dir);
+ }
+ } else {
+ /* with Alt+Ctrl (without Shift) we generate a perfect circle
+ with diameter click point <--> mouse pointer */
+ double l = dir.length();
+ Geom::Point d (l, l);
+ r = Geom::Rect (c - d, c + d);
+ }
+ }
+
+ this->arc->position_set(
+ r.midpoint()[Geom::X], r.midpoint()[Geom::Y],
+ r.dimensions()[Geom::X] / 2, r.dimensions()[Geom::Y] / 2);
+
+ double rdimx = r.dimensions()[Geom::X];
+ double rdimy = r.dimensions()[Geom::Y];
+
+ Inkscape::Util::Quantity rdimx_q = Inkscape::Util::Quantity(rdimx, "px");
+ Inkscape::Util::Quantity rdimy_q = Inkscape::Util::Quantity(rdimy, "px");
+ GString *xs = g_string_new(rdimx_q.string(desktop->namedview->doc_units).c_str());
+ GString *ys = g_string_new(rdimy_q.string(desktop->namedview->doc_units).c_str());
+
+ if (state & GDK_CONTROL_MASK) {
+ int ratio_x, ratio_y;
+
+ if (fabs (rdimx) > fabs (rdimy)) {
+ ratio_x = (int) rint (rdimx / rdimy);
+ ratio_y = 1;
+ } else {
+ ratio_x = 1;
+ ratio_y = (int) rint (rdimy / rdimx);
+ }
+
+ this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Ellipse</b>: %s &#215; %s (constrained to ratio %d:%d); with <b>Shift</b> to draw around the starting point"), xs->str, ys->str, ratio_x, ratio_y);
+ } else {
+ this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Ellipse</b>: %s &#215; %s; with <b>Ctrl</b> to make square or integer-ratio ellipse; with <b>Shift</b> to draw around the starting point"), xs->str, ys->str);
+ }
+
+ g_string_free(xs, FALSE);
+ g_string_free(ys, FALSE);
+}
+
+void ArcTool::finishItem() {
+ this->message_context->clear();
+
+ if (this->arc != NULL) {
+ if (this->arc->rx.computed == 0 || this->arc->ry.computed == 0) {
+ this->cancel(); // Don't allow the creating of zero sized arc, for example when the start and and point snap to the snap grid point
+ return;
+ }
+
+ this->arc->updateRepr();
+ this->arc->doWriteTransform(this->arc->getRepr(), this->arc->transform, NULL, true);
+
+ desktop->canvas->endForcedFullRedraws();
+
+ sp_desktop_selection(desktop)->set(this->arc);
+
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_ARC, _("Create ellipse"));
+
+ this->arc = NULL;
+ }
+}
+
+void ArcTool::cancel() {
+ sp_desktop_selection(desktop)->clear();
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), 0);
+
+ if (this->arc != NULL) {
+ this->arc->deleteObject();
+ this->arc = NULL;
+ }
+
+ this->within_tolerance = false;
+ this->xp = 0;
+ this->yp = 0;
+ this->item_to_select = NULL;
+
+ desktop->canvas->endForcedFullRedraws();
+
+ DocumentUndo::cancel(sp_desktop_document(desktop));
+}
+
+}
+}
+}
+
+
+/*
+ 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/tools/arc-tool.h b/src/ui/tools/arc-tool.h
new file mode 100644
index 000000000..eaa50f2b9
--- /dev/null
+++ b/src/ui/tools/arc-tool.h
@@ -0,0 +1,76 @@
+#ifndef SEEN_ARC_CONTEXT_H
+#define SEEN_ARC_CONTEXT_H
+
+/*
+ * Ellipse drawing context
+ *
+ * Authors:
+ * Mitsuru Oka
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2000-2002 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2002 Mitsuru Oka
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <stddef.h>
+#include <sigc++/connection.h>
+
+#include <2geom/point.h>
+#include "ui/tools/tool-base.h"
+
+#include "sp-ellipse.h"
+
+#define SP_ARC_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::ArcTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_ARC_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::ArcTool*>(const Inkscape::UI::Tools::ToolBase*(obj)) != NULL)
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class ArcTool : public ToolBase {
+public:
+ ArcTool();
+ virtual ~ArcTool();
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual void finish();
+ virtual bool root_handler(GdkEvent* event);
+ virtual bool item_handler(SPItem* item, GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+private:
+ SPGenericEllipse *arc;
+
+ Geom::Point center;
+
+ sigc::connection sel_changed_connection;
+
+ void selection_changed(Inkscape::Selection* selection);
+
+ void drag(Geom::Point pt, guint state);
+ void finishItem();
+ void cancel();
+};
+
+}
+}
+}
+
+#endif /* !SEEN_ARC_CONTEXT_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/ui/tools/box3d-tool.cpp b/src/ui/tools/box3d-tool.cpp
new file mode 100644
index 000000000..80cc75e79
--- /dev/null
+++ b/src/ui/tools/box3d-tool.cpp
@@ -0,0 +1,637 @@
+/*
+ * 3D box drawing context
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2007 Maximilian Albert <Anhalter42@gmx.de>
+ * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl>
+ * Copyright (C) 2000-2005 authors
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "config.h"
+
+#include <gdk/gdkkeysyms.h>
+
+#include "macros.h"
+#include "display/sp-canvas.h"
+#include "document.h"
+#include "document-undo.h"
+#include "sp-namedview.h"
+#include "selection.h"
+#include "selection-chemistry.h"
+#include "desktop-handles.h"
+#include "snap.h"
+#include "display/curve.h"
+#include "display/sp-canvas-item.h"
+#include "desktop.h"
+#include "message-context.h"
+#include "pixmaps/cursor-3dbox.xpm"
+#include "box3d.h"
+#include "ui/tools/box3d-tool.h"
+#include <glibmm/i18n.h>
+#include "xml/repr.h"
+#include "xml/node-event-vector.h"
+#include "preferences.h"
+#include "context-fns.h"
+#include "desktop-style.h"
+#include "transf_mat_3x4.h"
+#include "perspective-line.h"
+#include "persp3d.h"
+#include "box3d-side.h"
+#include "document-private.h"
+#include "line-geometry.h"
+#include "shape-editor.h"
+#include "verbs.h"
+
+using Inkscape::DocumentUndo;
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+namespace {
+ ToolBase* createBox3dTool() {
+ return new Box3dTool();
+ }
+
+ bool Box3dToolRegistered = ToolFactory::instance().registerObject("/tools/shapes/3dbox", createBox3dTool);
+}
+
+const std::string& Box3dTool::getPrefsPath() {
+ return Box3dTool::prefsPath;
+}
+
+const std::string Box3dTool::prefsPath = "/tools/shapes/3dbox";
+
+Box3dTool::Box3dTool() : ToolBase() {
+ this->cursor_shape = cursor_3dbox_xpm;
+ this->hot_x = 4;
+ this->hot_y = 4;
+ this->xp = 0;
+ this->yp = 0;
+ this->tolerance = 0;
+ this->within_tolerance = false;
+ this->item_to_select = NULL;
+
+ this->box3d = NULL;
+
+ this->ctrl_dragged = false;
+ this->extruded = false;
+
+ this->_vpdrag = NULL;
+}
+
+void Box3dTool::finish() {
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME);
+ this->finishItem();
+ this->sel_changed_connection.disconnect();
+
+ ToolBase::finish();
+}
+
+
+Box3dTool::~Box3dTool() {
+ this->enableGrDrag(false);
+
+ delete (this->_vpdrag);
+ this->_vpdrag = NULL;
+
+ this->sel_changed_connection.disconnect();
+
+ delete this->shape_editor;
+ this->shape_editor = NULL;
+
+ /* fixme: This is necessary because we do not grab */
+ if (this->box3d) {
+ this->finishItem();
+ }
+}
+
+/**
+ * Callback that processes the "changed" signal on the selection;
+ * destroys old and creates new knotholder.
+ */
+void Box3dTool::selection_changed(Inkscape::Selection* selection) {
+ this->shape_editor->unset_item(SH_KNOTHOLDER);
+ this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER);
+
+ if (selection->perspList().size() == 1) {
+ // selecting a single box changes the current perspective
+ this->desktop->doc()->setCurrentPersp3D(selection->perspList().front());
+ }
+}
+
+/* Create a default perspective in document defs if none is present (which can happen, among other
+ * circumstances, after 'vacuum defs' or when a pre-0.46 file is opened).
+ */
+static void sp_box3d_context_ensure_persp_in_defs(SPDocument *document) {
+ SPDefs *defs = document->getDefs();
+
+ bool has_persp = false;
+ for ( SPObject *child = defs->firstChild(); child; child = child->getNext() ) {
+ if (SP_IS_PERSP3D(child)) {
+ has_persp = true;
+ break;
+ }
+ }
+
+ if (!has_persp) {
+ document->setCurrentPersp3D(persp3d_create_xml_element (document));
+ }
+}
+
+void Box3dTool::setup() {
+ ToolBase::setup();
+
+ this->shape_editor = new ShapeEditor(this->desktop);
+
+ SPItem *item = sp_desktop_selection(this->desktop)->singleItem();
+ if (item) {
+ this->shape_editor->set_item(item, SH_KNOTHOLDER);
+ }
+
+ this->sel_changed_connection.disconnect();
+ this->sel_changed_connection = sp_desktop_selection(this->desktop)->connectChanged(
+ sigc::mem_fun(this, &Box3dTool::selection_changed)
+ );
+
+ this->_vpdrag = new Box3D::VPDrag(sp_desktop_document(this->desktop));
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (prefs->getBool("/tools/shapes/selcue")) {
+ this->enableSelectionCue();
+ }
+
+ if (prefs->getBool("/tools/shapes/gradientdrag")) {
+ this->enableGrDrag();
+ }
+}
+
+bool Box3dTool::item_handler(SPItem* item, GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if ( event->button.button == 1 && !this->space_panning) {
+ Inkscape::setup_for_drag_start(desktop, this, event);
+ ret = TRUE;
+ }
+ break;
+ // motion and release are always on root (why?)
+ default:
+ break;
+ }
+
+// if (((ToolBaseClass *) sp_box3d_context_parent_class)->item_handler) {
+// ret = ((ToolBaseClass *) sp_box3d_context_parent_class)->item_handler(event_context, item, event);
+// }
+ // CPPIFY: ret is always overwritten...
+ ret = ToolBase::item_handler(item, event);
+
+ return ret;
+}
+
+bool Box3dTool::root_handler(GdkEvent* event) {
+ static bool dragging;
+
+ SPDocument *document = sp_desktop_document (desktop);
+ Inkscape::Selection *selection = sp_desktop_selection (desktop);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
+
+ Persp3D *cur_persp = document->getCurrentPersp3D();
+
+ this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ gint ret = FALSE;
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if ( event->button.button == 1 && !this->space_panning) {
+ Geom::Point const button_w(event->button.x, event->button.y);
+ Geom::Point button_dt(desktop->w2d(button_w));
+
+ // save drag origin
+ this->xp = (gint) button_w[Geom::X];
+ this->yp = (gint) button_w[Geom::Y];
+ this->within_tolerance = true;
+
+ // remember clicked box3d, *not* disregarding groups (since a 3D box is a group), honoring Alt
+ this->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, event->button.state & GDK_CONTROL_MASK);
+
+ dragging = true;
+
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop, true, this->box3d);
+ m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ m.unSetup();
+ this->center = button_dt;
+
+ this->drag_origin = button_dt;
+ this->drag_ptB = button_dt;
+ this->drag_ptC = button_dt;
+
+ // This can happen after saving when the last remaining perspective was purged and must be recreated.
+ if (!cur_persp) {
+ sp_box3d_context_ensure_persp_in_defs(document);
+ cur_persp = document->getCurrentPersp3D();
+ }
+
+ /* Projective preimages of clicked point under current perspective */
+ this->drag_origin_proj = cur_persp->perspective_impl->tmat.preimage (button_dt, 0, Proj::Z);
+ this->drag_ptB_proj = this->drag_origin_proj;
+ this->drag_ptC_proj = this->drag_origin_proj;
+ this->drag_ptC_proj.normalize();
+ this->drag_ptC_proj[Proj::Z] = 0.25;
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ ( GDK_KEY_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
+ GDK_BUTTON_PRESS_MASK ),
+ NULL, event->button.time);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ if (dragging && ( event->motion.state & GDK_BUTTON1_MASK ) && !this->space_panning) {
+ if ( this->within_tolerance
+ && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance )
+ && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) {
+ break; // do not drag if we're within tolerance from origin
+ }
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to draw, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ this->within_tolerance = false;
+
+ Geom::Point const motion_w(event->motion.x,
+ event->motion.y);
+ Geom::Point motion_dt(desktop->w2d(motion_w));
+
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop, true, this->box3d);
+ m.freeSnapReturnByRef(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ this->ctrl_dragged = event->motion.state & GDK_CONTROL_MASK;
+
+ if ((event->motion.state & GDK_SHIFT_MASK) && !this->extruded && this->box3d) {
+ // once shift is pressed, set this->extruded
+ this->extruded = true;
+ }
+
+ if (!this->extruded) {
+ this->drag_ptB = motion_dt;
+ this->drag_ptC = motion_dt;
+
+ this->drag_ptB_proj = cur_persp->perspective_impl->tmat.preimage (motion_dt, 0, Proj::Z);
+ this->drag_ptC_proj = this->drag_ptB_proj;
+ this->drag_ptC_proj.normalize();
+ this->drag_ptC_proj[Proj::Z] = 0.25;
+ } else {
+ // Without Ctrl, motion of the extruded corner is constrained to the
+ // perspective line from drag_ptB to vanishing point Y.
+ if (!this->ctrl_dragged) {
+ /* snapping */
+ Box3D::PerspectiveLine pline (this->drag_ptB, Proj::Z, document->getCurrentPersp3D());
+ this->drag_ptC = pline.closest_to (motion_dt);
+
+ this->drag_ptB_proj.normalize();
+ this->drag_ptC_proj = cur_persp->perspective_impl->tmat.preimage (this->drag_ptC, this->drag_ptB_proj[Proj::X], Proj::X);
+ } else {
+ this->drag_ptC = motion_dt;
+
+ this->drag_ptB_proj.normalize();
+ this->drag_ptC_proj = cur_persp->perspective_impl->tmat.preimage (motion_dt, this->drag_ptB_proj[Proj::X], Proj::X);
+ }
+
+ m.freeSnapReturnByRef(this->drag_ptC, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ }
+
+ m.unSetup();
+
+ this->drag(event->motion.state);
+
+ ret = TRUE;
+ } else if (!sp_event_context_knot_mouseover(this)) {
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point motion_dt(desktop->w2d(motion_w));
+ m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE));
+ m.unSetup();
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ this->xp = this->yp = 0;
+
+ if (event->button.button == 1 && !this->space_panning) {
+ dragging = false;
+ sp_event_context_discard_delayed_snap_event(this);
+
+ if (!this->within_tolerance) {
+ // we've been dragging, finish the box
+ this->finishItem();
+ } else if (this->item_to_select) {
+ // no dragging, select clicked box3d if any
+ if (event->button.state & GDK_SHIFT_MASK) {
+ selection->toggle(this->item_to_select);
+ } else {
+ selection->set(this->item_to_select);
+ }
+ } else {
+ // click in an empty space
+ selection->clear();
+ }
+
+ this->item_to_select = NULL;
+ ret = TRUE;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
+ event->button.time);
+ }
+ break;
+
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Up:
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_Down:
+ // prevent the zoom field from activation
+ if (!MOD__CTRL_ONLY(event))
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_bracketright:
+ persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::X, -180/snaps, MOD__ALT(event));
+ DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX,
+ _("Change perspective (angle of PLs)"));
+ ret = true;
+ break;
+
+ case GDK_KEY_bracketleft:
+ persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::X, 180/snaps, MOD__ALT(event));
+ DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX,
+ _("Change perspective (angle of PLs)"));
+ ret = true;
+ break;
+
+ case GDK_KEY_parenright:
+ persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Y, -180/snaps, MOD__ALT(event));
+ DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX,
+ _("Change perspective (angle of PLs)"));
+ ret = true;
+ break;
+
+ case GDK_KEY_parenleft:
+ persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Y, 180/snaps, MOD__ALT(event));
+ DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX,
+ _("Change perspective (angle of PLs)"));
+ ret = true;
+ break;
+
+ case GDK_KEY_braceright:
+ persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Z, -180/snaps, MOD__ALT(event));
+ DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX,
+ _("Change perspective (angle of PLs)"));
+ ret = true;
+ break;
+
+ case GDK_KEY_braceleft:
+ persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Z, 180/snaps, MOD__ALT(event));
+ DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX,
+ _("Change perspective (angle of PLs)"));
+ ret = true;
+ break;
+
+ /* TODO: what is this???
+ case GDK_O:
+ if (MOD__CTRL(event) && MOD__SHIFT(event)) {
+ Box3D::create_canvas_point(persp3d_get_VP(document()->getCurrentPersp3D(), Proj::W).affine(),
+ 6, 0xff00ff00);
+ }
+ ret = true;
+ break;
+ */
+
+ case GDK_KEY_g:
+ case GDK_KEY_G:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_selection_to_guides(desktop);
+ ret = true;
+ }
+ break;
+
+ case GDK_KEY_p:
+ case GDK_KEY_P:
+ if (MOD__SHIFT_ONLY(event)) {
+ if (document->getCurrentPersp3D()) {
+ persp3d_print_debugging_info (document->getCurrentPersp3D());
+ }
+ ret = true;
+ }
+ break;
+
+ case GDK_KEY_x:
+ case GDK_KEY_X:
+ if (MOD__ALT_ONLY(event)) {
+ desktop->setToolboxFocusTo ("altx-box3d");
+ ret = TRUE;
+ }
+ if (MOD__SHIFT_ONLY(event)) {
+ persp3d_toggle_VPs(selection->perspList(), Proj::X);
+ this->_vpdrag->updateLines(); // FIXME: Shouldn't this be done automatically?
+ ret = true;
+ }
+ break;
+
+ case GDK_KEY_y:
+ case GDK_KEY_Y:
+ if (MOD__SHIFT_ONLY(event)) {
+ persp3d_toggle_VPs(selection->perspList(), Proj::Y);
+ this->_vpdrag->updateLines(); // FIXME: Shouldn't this be done automatically?
+ ret = true;
+ }
+ break;
+
+ case GDK_KEY_z:
+ case GDK_KEY_Z:
+ if (MOD__SHIFT_ONLY(event)) {
+ persp3d_toggle_VPs(selection->perspList(), Proj::Z);
+ this->_vpdrag->updateLines(); // FIXME: Shouldn't this be done automatically?
+ ret = true;
+ }
+ break;
+
+ case GDK_KEY_Escape:
+ sp_desktop_selection(desktop)->clear();
+ //TODO: make dragging escapable by Esc
+ break;
+
+ case GDK_KEY_space:
+ if (dragging) {
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
+ event->button.time);
+ dragging = false;
+ sp_event_context_discard_delayed_snap_event(this);
+ if (!this->within_tolerance) {
+ // we've been dragging, finish the box
+ this->finishItem();
+ }
+ // do not return true, so that space would work switching to selector
+ }
+ break;
+
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ case GDK_KEY_BackSpace:
+ ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event));
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+void Box3dTool::drag(guint state) {
+ if (!this->box3d) {
+ if (Inkscape::have_viable_layer(desktop, this->message_context) == false) {
+ return;
+ }
+
+ // Create object
+ SPBox3D *box3d = SPBox3D::createBox3D((SPItem*)desktop->currentLayer());
+
+ // Set style
+ desktop->applyCurrentOrToolStyle(box3d, "/tools/shapes/3dbox", false);
+
+ this->box3d = box3d;
+
+ // TODO: Incorporate this in box3d-side.cpp!
+ for (int i = 0; i < 6; ++i) {
+ Box3DSide *side = Box3DSide::createBox3DSide(box3d);
+
+ guint desc = Box3D::int_to_face(i);
+
+ Box3D::Axis plane = (Box3D::Axis) (desc & 0x7);
+ plane = (Box3D::is_plane(plane) ? plane : Box3D::orth_plane_or_axis(plane));
+ side->dir1 = Box3D::extract_first_axis_direction(plane);
+ side->dir2 = Box3D::extract_second_axis_direction(plane);
+ side->front_or_rear = (Box3D::FrontOrRear) (desc & 0x8);
+
+ // Set style
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ Glib::ustring descr = "/desktop/";
+ descr += box3d_side_axes_string(side);
+ descr += "/style";
+
+ Glib::ustring cur_style = prefs->getString(descr);
+
+ bool use_current = prefs->getBool("/tools/shapes/3dbox/usecurrent", false);
+
+ if (use_current && !cur_style.empty()) {
+ // use last used style
+ side->setAttribute("style", cur_style.data());
+ } else {
+ // use default style
+ GString *pstring = g_string_new("");
+ g_string_printf (pstring, "/tools/shapes/3dbox/%s", box3d_side_axes_string(side));
+ desktop->applyCurrentOrToolStyle (side, pstring->str, false);
+ }
+
+ side->updateRepr(); // calls box3d_side_write() and updates, e.g., the axes string description
+ }
+
+ box3d_set_z_orders(this->box3d);
+ this->box3d->updateRepr();
+
+ // TODO: It would be nice to show the VPs during dragging, but since there is no selection
+ // at this point (only after finishing the box), we must do this "manually"
+ /* this._vpdrag->updateDraggers(); */
+
+ desktop->canvas->forceFullRedrawAfterInterruptions(5);
+ }
+
+ g_assert(this->box3d);
+
+ this->box3d->orig_corner0 = this->drag_origin_proj;
+ this->box3d->orig_corner7 = this->drag_ptC_proj;
+
+ box3d_check_for_swapped_coords(this->box3d);
+
+ /* we need to call this from here (instead of from box3d_position_set(), for example)
+ because z-order setting must not interfere with display updates during undo/redo */
+ box3d_set_z_orders (this->box3d);
+
+ box3d_position_set(this->box3d);
+
+ // status text
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, "%s", _("<b>3D Box</b>; with <b>Shift</b> to extrude along the Z axis"));
+}
+
+void Box3dTool::finishItem() {
+ this->message_context->clear();
+ this->ctrl_dragged = false;
+ this->extruded = false;
+
+ if (this->box3d != NULL) {
+ SPDocument *doc = sp_desktop_document(this->desktop);
+
+ if (!doc || !doc->getCurrentPersp3D()) {
+ return;
+ }
+
+ this->box3d->orig_corner0 = this->drag_origin_proj;
+ this->box3d->orig_corner7 = this->drag_ptC_proj;
+
+ this->box3d->updateRepr();
+
+ box3d_relabel_corners(this->box3d);
+
+ desktop->canvas->endForcedFullRedraws();
+
+ sp_desktop_selection(desktop)->set(this->box3d);
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_3DBOX,
+ _("Create 3D box"));
+
+ this->box3d = NULL;
+ }
+}
+
+}
+}
+}
+
+/*
+ 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/tools/box3d-tool.h b/src/ui/tools/box3d-tool.h
new file mode 100644
index 000000000..99bf99a7a
--- /dev/null
+++ b/src/ui/tools/box3d-tool.h
@@ -0,0 +1,95 @@
+#ifndef __SP_BOX3D_CONTEXT_H__
+#define __SP_BOX3D_CONTEXT_H__
+
+/*
+ * 3D box drawing context
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2000 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2002 Lauris Kaplinski
+ * Copyright (C) 2007 Maximilian Albert <Anhalter42@gmx.de>
+ *
+ * Released under GNU GPL
+ */
+
+#include <stddef.h>
+#include <sigc++/sigc++.h>
+#include "ui/tools/tool-base.h"
+#include "proj_pt.h"
+#include "vanishing-point.h"
+
+#include "box3d.h"
+
+#define SP_BOX3D_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::Box3dTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_BOX3D_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::Box3dTool*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class Box3dTool : public ToolBase {
+public:
+ Box3dTool();
+ virtual ~Box3dTool();
+
+ Box3D::VPDrag * _vpdrag;
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual void finish();
+ virtual bool root_handler(GdkEvent* event);
+ virtual bool item_handler(SPItem* item, GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+private:
+ SPBox3D* box3d;
+ Geom::Point center;
+
+ /**
+ * save three corners while dragging:
+ * 1) the starting point (already done by the event_context)
+ * 2) drag_ptB --> the opposite corner of the front face (before pressing shift)
+ * 3) drag_ptC --> the "extruded corner" (which coincides with the mouse pointer location
+ * if we are ctrl-dragging but is constrained to the perspective line from drag_ptC
+ * to the vanishing point Y otherwise)
+ */
+ Geom::Point drag_origin;
+ Geom::Point drag_ptB;
+ Geom::Point drag_ptC;
+
+ Proj::Pt3 drag_origin_proj;
+ Proj::Pt3 drag_ptB_proj;
+ Proj::Pt3 drag_ptC_proj;
+
+ bool ctrl_dragged; /* whether we are ctrl-dragging */
+ bool extruded; /* whether shift-dragging already occured (i.e. the box is already extruded) */
+
+ sigc::connection sel_changed_connection;
+
+ void selection_changed(Inkscape::Selection* selection);
+
+ void drag(guint state);
+ void finishItem();
+};
+
+}
+}
+}
+
+#endif /* __SP_BOX3D_CONTEXT_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/tools/calligraphic-tool.cpp b/src/ui/tools/calligraphic-tool.cpp
new file mode 100644
index 000000000..2c5e6561c
--- /dev/null
+++ b/src/ui/tools/calligraphic-tool.cpp
@@ -0,0 +1,1216 @@
+/*
+ * Handwriting-like drawing mode
+ *
+ * Authors:
+ * Mitsuru Oka <oka326@parkcity.ne.jp>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * MenTaLguY <mental@rydia.net>
+ * Abhishek Sharma
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * The original dynadraw code:
+ * Paul Haeberli <paul@sgi.com>
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ * Copyright (C) 1999-2005 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ * Copyright (C) 2005-2007 bulia byak
+ * Copyright (C) 2006 MenTaLguY
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#define noDYNA_DRAW_VERBOSE
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glibmm/i18n.h>
+#include <string>
+#include <cstring>
+#include <numeric>
+
+#include "svg/svg.h"
+#include "display/canvas-bpath.h"
+#include "display/cairo-utils.h"
+#include <2geom/math-utils.h>
+#include <2geom/pathvector.h>
+#include <2geom/bezier-utils.h>
+#include <2geom/circle.h>
+#include "display/curve.h"
+#include <glib.h>
+#include "macros.h"
+#include "document.h"
+#include "document-undo.h"
+#include "selection.h"
+#include "desktop.h"
+#include "desktop-events.h"
+#include "desktop-handles.h"
+#include "desktop-style.h"
+#include "message-context.h"
+#include "preferences.h"
+#include "pixmaps/cursor-calligraphy.xpm"
+#include "xml/repr.h"
+#include "context-fns.h"
+#include "sp-item.h"
+#include "inkscape.h"
+#include "color.h"
+#include "splivarot.h"
+#include "sp-item-group.h"
+#include "sp-shape.h"
+#include "sp-path.h"
+#include "sp-text.h"
+#include "display/sp-canvas.h"
+#include "display/canvas-bpath.h"
+#include "display/canvas-arena.h"
+#include "livarot/Shape.h"
+#include "verbs.h"
+
+#include "ui/tools/calligraphic-tool.h"
+
+using Inkscape::DocumentUndo;
+
+#define DDC_RED_RGBA 0xff0000ff
+
+#define TOLERANCE_CALLIGRAPHIC 0.1
+
+#define DYNA_EPSILON 0.5e-6
+#define DYNA_EPSILON_START 0.5e-2
+#define DYNA_VEL_START 1e-5
+
+#define DYNA_MIN_WIDTH 1.0e-6
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+static void add_cap(SPCurve *curve, Geom::Point const &from, Geom::Point const &to, double rounding);
+
+namespace {
+ ToolBase* createCalligraphicContext() {
+ return new CalligraphicTool();
+ }
+
+ bool calligraphicContextRegistered = ToolFactory::instance().registerObject("/tools/calligraphic", createCalligraphicContext);
+}
+
+const std::string& CalligraphicTool::getPrefsPath() {
+ return CalligraphicTool::prefsPath;
+}
+
+
+const std::string CalligraphicTool::prefsPath = "/tools/calligraphic";
+
+CalligraphicTool::CalligraphicTool() : DynamicBase() {
+ this->cursor_shape = cursor_calligraphy_xpm;
+ this->hot_x = 4;
+ this->hot_y = 4;
+
+ this->vel_thin = 0.1;
+ this->flatness = 0.9;
+ this->cap_rounding = 0.0;
+
+ this->abs_width = false;
+ this->keep_selected = true;
+
+ this->hatch_spacing = 0;
+ this->hatch_spacing_step = 0;
+
+ this->hatch_last_nearest = Geom::Point(0,0);
+ this->hatch_last_pointer = Geom::Point(0,0);
+ this->hatch_escaped = false;
+ this->hatch_area = NULL;
+ this->hatch_item = NULL;
+ this->hatch_livarot_path = NULL;
+
+ this->trace_bg = false;
+ this->just_started_drawing = false;
+}
+
+CalligraphicTool::~CalligraphicTool() {
+ if (this->hatch_area) {
+ sp_canvas_item_destroy(this->hatch_area);
+ this->hatch_area = NULL;
+ }
+}
+
+void CalligraphicTool::setup() {
+ DynamicBase::setup();
+
+ this->accumulated = new SPCurve();
+ this->currentcurve = new SPCurve();
+
+ this->cal1 = new SPCurve();
+ this->cal2 = new SPCurve();
+
+ this->currentshape = sp_canvas_item_new(sp_desktop_sketch(this->desktop), SP_TYPE_CANVAS_BPATH, NULL);
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->currentshape), DDC_RED_RGBA, SP_WIND_RULE_EVENODD);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->currentshape), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+
+ /* fixme: Cannot we cascade it to root more clearly? */
+ g_signal_connect(G_OBJECT(this->currentshape), "event", G_CALLBACK(sp_desktop_root_handler), this->desktop);
+
+ {
+ /* TODO: have a look at DropperTool::setup where the same is done.. generalize? */
+ Geom::PathVector path;
+ Geom::Circle(0, 0, 1).getPath(path);
+
+ SPCurve *c = new SPCurve(path);
+
+ this->hatch_area = sp_canvas_bpath_new(sp_desktop_controls(this->desktop), c);
+
+ c->unref();
+
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->hatch_area), 0x00000000,(SPWindRule)0);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0x0000007f, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_item_hide(this->hatch_area);
+ }
+
+ sp_event_context_read(this, "mass");
+ sp_event_context_read(this, "wiggle");
+ sp_event_context_read(this, "angle");
+ sp_event_context_read(this, "width");
+ sp_event_context_read(this, "thinning");
+ sp_event_context_read(this, "tremor");
+ sp_event_context_read(this, "flatness");
+ sp_event_context_read(this, "tracebackground");
+ sp_event_context_read(this, "usepressure");
+ sp_event_context_read(this, "usetilt");
+ sp_event_context_read(this, "abs_width");
+ sp_event_context_read(this, "keep_selected");
+ sp_event_context_read(this, "cap_rounding");
+
+ this->is_drawing = false;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/calligraphic/selcue")) {
+ this->enableSelectionCue();
+ }
+}
+
+void CalligraphicTool::set(const Inkscape::Preferences::Entry& val) {
+ Glib::ustring path = val.getEntryName();
+
+ if (path == "tracebackground") {
+ this->trace_bg = val.getBool();
+ } else if (path == "keep_selected") {
+ this->keep_selected = val.getBool();
+ } else {
+ //pass on up to parent class to handle common attributes.
+ DynamicBase::set(val);
+ }
+
+ //g_print("DDC: %g %g %g %g\n", ddc->mass, ddc->drag, ddc->angle, ddc->width);
+}
+
+static double
+flerp(double f0, double f1, double p)
+{
+ return f0 + ( f1 - f0 ) * p;
+}
+
+///* Get normalized point */
+//Geom::Point CalligraphicTool::getNormalizedPoint(Geom::Point v) const {
+// Geom::Rect drect = desktop->get_display_area();
+//
+// double const max = MAX ( drect.dimensions()[Geom::X], drect.dimensions()[Geom::Y] );
+//
+// return Geom::Point(( v[Geom::X] - drect.min()[Geom::X] ) / max, ( v[Geom::Y] - drect.min()[Geom::Y] ) / max);
+//}
+//
+///* Get view point */
+//Geom::Point CalligraphicTool::getViewPoint(Geom::Point n) const {
+// Geom::Rect drect = desktop->get_display_area();
+//
+// double const max = MAX ( drect.dimensions()[Geom::X], drect.dimensions()[Geom::Y] );
+//
+// return Geom::Point(n[Geom::X] * max + drect.min()[Geom::X], n[Geom::Y] * max + drect.min()[Geom::Y]);
+//}
+
+void CalligraphicTool::reset(Geom::Point p) {
+ this->last = this->cur = this->getNormalizedPoint(p);
+
+ this->vel = Geom::Point(0,0);
+ this->vel_max = 0;
+ this->acc = Geom::Point(0,0);
+ this->ang = Geom::Point(0,0);
+ this->del = Geom::Point(0,0);
+}
+
+void CalligraphicTool::extinput(GdkEvent *event) {
+ if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &this->pressure)) {
+ this->pressure = CLAMP (this->pressure, DDC_MIN_PRESSURE, DDC_MAX_PRESSURE);
+ } else {
+ this->pressure = DDC_DEFAULT_PRESSURE;
+ }
+
+ if (gdk_event_get_axis (event, GDK_AXIS_XTILT, &this->xtilt)) {
+ this->xtilt = CLAMP (this->xtilt, DDC_MIN_TILT, DDC_MAX_TILT);
+ } else {
+ this->xtilt = DDC_DEFAULT_TILT;
+ }
+
+ if (gdk_event_get_axis (event, GDK_AXIS_YTILT, &this->ytilt)) {
+ this->ytilt = CLAMP (this->ytilt, DDC_MIN_TILT, DDC_MAX_TILT);
+ } else {
+ this->ytilt = DDC_DEFAULT_TILT;
+ }
+}
+
+
+bool CalligraphicTool::apply(Geom::Point p) {
+ Geom::Point n = this->getNormalizedPoint(p);
+
+ /* Calculate mass and drag */
+ double const mass = flerp(1.0, 160.0, this->mass);
+ double const drag = flerp(0.0, 0.5, this->drag * this->drag);
+
+ /* Calculate force and acceleration */
+ Geom::Point force = n - this->cur;
+
+ // If force is below the absolute threshold DYNA_EPSILON,
+ // or we haven't yet reached DYNA_VEL_START (i.e. at the beginning of stroke)
+ // _and_ the force is below the (higher) DYNA_EPSILON_START threshold,
+ // discard this move.
+ // This prevents flips, blobs, and jerks caused by microscopic tremor of the tablet pen,
+ // especially bothersome at the start of the stroke where we don't yet have the inertia to
+ // smooth them out.
+ if ( Geom::L2(force) < DYNA_EPSILON || (this->vel_max < DYNA_VEL_START && Geom::L2(force) < DYNA_EPSILON_START)) {
+ return FALSE;
+ }
+
+ this->acc = force / mass;
+
+ /* Calculate new velocity */
+ this->vel += this->acc;
+
+ if (Geom::L2(this->vel) > this->vel_max)
+ this->vel_max = Geom::L2(this->vel);
+
+ /* Calculate angle of drawing tool */
+
+ double a1;
+ if (this->usetilt) {
+ // 1a. calculate nib angle from input device tilt:
+ gdouble length = std::sqrt(this->xtilt*this->xtilt + this->ytilt*this->ytilt);;
+
+ if (length > 0) {
+ Geom::Point ang1 = Geom::Point(this->ytilt/length, this->xtilt/length);
+ a1 = atan2(ang1);
+ }
+ else
+ a1 = 0.0;
+ }
+ else {
+ // 1b. fixed dc->angle (absolutely flat nib):
+ double const radians = ( (this->angle - 90) / 180.0 ) * M_PI;
+ Geom::Point ang1 = Geom::Point(-sin(radians), cos(radians));
+ a1 = atan2(ang1);
+ }
+
+ // 2. perpendicular to dc->vel (absolutely non-flat nib):
+ gdouble const mag_vel = Geom::L2(this->vel);
+ if ( mag_vel < DYNA_EPSILON ) {
+ return FALSE;
+ }
+ Geom::Point ang2 = Geom::rot90(this->vel) / mag_vel;
+
+ // 3. Average them using flatness parameter:
+ // calculate angles
+ double a2 = atan2(ang2);
+ // flip a2 to force it to be in the same half-circle as a1
+ bool flipped = false;
+ if (fabs (a2-a1) > 0.5*M_PI) {
+ a2 += M_PI;
+ flipped = true;
+ }
+ // normalize a2
+ if (a2 > M_PI)
+ a2 -= 2*M_PI;
+ if (a2 < -M_PI)
+ a2 += 2*M_PI;
+ // find the flatness-weighted bisector angle, unflip if a2 was flipped
+ // FIXME: when dc->vel is oscillating around the fixed angle, the new_ang flips back and forth. How to avoid this?
+ double new_ang = a1 + (1 - this->flatness) * (a2 - a1) - (flipped? M_PI : 0);
+
+ // Try to detect a sudden flip when the new angle differs too much from the previous for the
+ // current velocity; in that case discard this move
+ double angle_delta = Geom::L2(Geom::Point (cos (new_ang), sin (new_ang)) - this->ang);
+ if ( angle_delta / Geom::L2(this->vel) > 4000 ) {
+ return FALSE;
+ }
+
+ // convert to point
+ this->ang = Geom::Point (cos (new_ang), sin (new_ang));
+
+// g_print ("force %g acc %g vel_max %g vel %g a1 %g a2 %g new_ang %g\n", Geom::L2(force), Geom::L2(dc->acc), dc->vel_max, Geom::L2(dc->vel), a1, a2, new_ang);
+
+ /* Apply drag */
+ this->vel *= 1.0 - drag;
+
+ /* Update position */
+ this->last = this->cur;
+ this->cur += this->vel;
+
+ return TRUE;
+}
+
+void CalligraphicTool::brush() {
+ g_assert( this->npoints >= 0 && this->npoints < SAMPLING_SIZE );
+
+ // How much velocity thins strokestyle
+ double vel_thin = flerp (0, 160, this->vel_thin);
+
+ // Influence of pressure on thickness
+ double pressure_thick = (this->usepressure ? this->pressure : 1.0);
+
+ // get the real brush point, not the same as pointer (affected by hatch tracking and/or mass
+ // drag)
+ Geom::Point brush = this->getViewPoint(this->cur);
+ Geom::Point brush_w = SP_EVENT_CONTEXT(this)->desktop->d2w(brush);
+
+ double trace_thick = 1;
+ if (this->trace_bg) {
+ // pick single pixel
+ double R, G, B, A;
+ Geom::IntRect area = Geom::IntRect::from_xywh(brush_w.floor(), Geom::IntPoint(1, 1));
+ cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
+ sp_canvas_arena_render_surface(SP_CANVAS_ARENA(sp_desktop_drawing(SP_EVENT_CONTEXT(this)->desktop)), s, area);
+ ink_cairo_surface_average_color_premul(s, R, G, B, A);
+ cairo_surface_destroy(s);
+ double max = MAX (MAX (R, G), B);
+ double min = MIN (MIN (R, G), B);
+ double L = A * (max + min)/2 + (1 - A); // blend with white bg
+ trace_thick = 1 - L;
+ //g_print ("L %g thick %g\n", L, trace_thick);
+ }
+
+ double width = (pressure_thick * trace_thick - vel_thin * Geom::L2(this->vel)) * this->width;
+
+ double tremble_left = 0, tremble_right = 0;
+ if (this->tremor > 0) {
+ // obtain two normally distributed random variables, using polar Box-Muller transform
+ double x1, x2, w, y1, y2;
+ do {
+ x1 = 2.0 * g_random_double_range(0,1) - 1.0;
+ x2 = 2.0 * g_random_double_range(0,1) - 1.0;
+ w = x1 * x1 + x2 * x2;
+ } while ( w >= 1.0 );
+ w = sqrt( (-2.0 * log( w ) ) / w );
+ y1 = x1 * w;
+ y2 = x2 * w;
+
+ // deflect both left and right edges randomly and independently, so that:
+ // (1) dc->tremor=1 corresponds to sigma=1, decreasing dc->tremor narrows the bell curve;
+ // (2) deflection depends on width, but is upped for small widths for better visual uniformity across widths;
+ // (3) deflection somewhat depends on speed, to prevent fast strokes looking
+ // comparatively smooth and slow ones excessively jittery
+ tremble_left = (y1)*this->tremor * (0.15 + 0.8*width) * (0.35 + 14*Geom::L2(this->vel));
+ tremble_right = (y2)*this->tremor * (0.15 + 0.8*width) * (0.35 + 14*Geom::L2(this->vel));
+ }
+
+ if ( width < 0.02 * this->width ) {
+ width = 0.02 * this->width;
+ }
+
+ double dezoomify_factor = 0.05 * 1000;
+ if (!this->abs_width) {
+ dezoomify_factor /= SP_EVENT_CONTEXT(this)->desktop->current_zoom();
+ }
+
+ Geom::Point del_left = dezoomify_factor * (width + tremble_left) * this->ang;
+ Geom::Point del_right = dezoomify_factor * (width + tremble_right) * this->ang;
+
+ this->point1[this->npoints] = brush + del_left;
+ this->point2[this->npoints] = brush - del_right;
+
+ this->del = 0.5*(del_left + del_right);
+
+ this->npoints++;
+}
+
+static void
+sp_ddc_update_toolbox (SPDesktop *desktop, const gchar *id, double value)
+{
+ desktop->setToolboxAdjustmentValue (id, value);
+}
+
+void CalligraphicTool::cancel() {
+ this->dragging = false;
+ this->is_drawing = false;
+
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), 0);
+
+ /* Remove all temporary line segments */
+ while (this->segments) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data));
+ this->segments = g_slist_remove(this->segments, this->segments->data);
+ }
+
+ /* reset accumulated curve */
+ this->accumulated->reset();
+ this->clear_current();
+
+ if (this->repr) {
+ this->repr = NULL;
+ }
+}
+
+bool CalligraphicTool::root_handler(GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+ if (Inkscape::have_viable_layer(desktop, this->message_context) == false) {
+ return TRUE;
+ }
+
+ this->accumulated->reset();
+
+ if (this->repr) {
+ this->repr = NULL;
+ }
+
+ /* initialize first point */
+ this->npoints = 0;
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ ( GDK_KEY_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_PRESS_MASK ),
+ NULL,
+ event->button.time);
+
+ ret = TRUE;
+
+ desktop->canvas->forceFullRedrawAfterInterruptions(3);
+ this->is_drawing = true;
+ this->just_started_drawing = true;
+ }
+ break;
+ case GDK_MOTION_NOTIFY:
+ {
+ Geom::Point const motion_w(event->motion.x,
+ event->motion.y);
+ Geom::Point motion_dt(desktop->w2d(motion_w));
+ this->extinput(event);
+
+ this->message_context->clear();
+
+ // for hatching:
+ double hatch_dist = 0;
+ Geom::Point hatch_unit_vector(0,0);
+ Geom::Point nearest(0,0);
+ Geom::Point pointer(0,0);
+ Geom::Affine motion_to_curve(Geom::identity());
+
+ if (event->motion.state & GDK_CONTROL_MASK) { // hatching - sense the item
+
+ SPItem *selected = sp_desktop_selection(desktop)->singleItem();
+ if (selected && (SP_IS_SHAPE(selected) || SP_IS_TEXT(selected))) {
+ // One item selected, and it's a path;
+ // let's try to track it as a guide
+
+ if (selected != this->hatch_item) {
+ this->hatch_item = selected;
+ if (this->hatch_livarot_path)
+ delete this->hatch_livarot_path;
+ this->hatch_livarot_path = Path_for_item (this->hatch_item, true, true);
+ this->hatch_livarot_path->ConvertWithBackData(0.01);
+ }
+
+ // calculate pointer point in the guide item's coords
+ motion_to_curve = selected->dt2i_affine() * selected->i2doc_affine();
+ pointer = motion_dt * motion_to_curve;
+
+ // calculate the nearest point on the guide path
+ boost::optional<Path::cut_position> position = get_nearest_position_on_Path(this->hatch_livarot_path, pointer);
+ nearest = get_point_on_Path(this->hatch_livarot_path, position->piece, position->t);
+
+
+ // distance from pointer to nearest
+ hatch_dist = Geom::L2(pointer - nearest);
+ // unit-length vector
+ hatch_unit_vector = (pointer - nearest)/hatch_dist;
+
+ this->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Guide path selected</b>; start drawing along the guide with <b>Ctrl</b>"));
+ } else {
+ this->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Select a guide path</b> to track with <b>Ctrl</b>"));
+ }
+ }
+
+ if ( this->is_drawing && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) {
+ this->dragging = TRUE;
+
+ if (event->motion.state & GDK_CONTROL_MASK && this->hatch_item) { // hatching
+
+#define HATCH_VECTOR_ELEMENTS 12
+#define INERTIA_ELEMENTS 24
+#define SPEED_ELEMENTS 12
+#define SPEED_MIN 0.3
+#define SPEED_NORMAL 0.35
+#define INERTIA_FORCE 0.5
+
+ // speed is the movement of the nearest point along the guide path, divided by
+ // the movement of the pointer at the same period; it is averaged for the last
+ // SPEED_ELEMENTS motion events. Normally, as you track the guide path, speed
+ // is about 1, i.e. the nearest point on the path is moved by about the same
+ // distance as the pointer. If the speed starts to decrease, we are losing
+ // contact with the guide; if it drops below SPEED_MIN, we are on our own and
+ // not attracted to guide anymore. Most often this happens when you have
+ // tracked to the end of a guide calligraphic stroke and keep moving
+ // further. We try to handle this situation gracefully: not stick with the
+ // guide forever but let go of it smoothly and without sharp jerks (non-zero
+ // mass recommended; with zero mass, jerks are still quite noticeable).
+
+ double speed = 1;
+ if (Geom::L2(this->hatch_last_nearest) != 0) {
+ // the distance nearest moved since the last motion event
+ double nearest_moved = Geom::L2(nearest - this->hatch_last_nearest);
+ // the distance pointer moved since the last motion event
+ double pointer_moved = Geom::L2(pointer - this->hatch_last_pointer);
+ // store them in stacks limited to SPEED_ELEMENTS
+ this->hatch_nearest_past.push_front(nearest_moved);
+ if (this->hatch_nearest_past.size() > SPEED_ELEMENTS)
+ this->hatch_nearest_past.pop_back();
+ this->hatch_pointer_past.push_front(pointer_moved);
+ if (this->hatch_pointer_past.size() > SPEED_ELEMENTS)
+ this->hatch_pointer_past.pop_back();
+
+ // If the stacks are full,
+ if (this->hatch_nearest_past.size() == SPEED_ELEMENTS) {
+ // calculate the sums of all stored movements
+ double nearest_sum = std::accumulate (this->hatch_nearest_past.begin(), this->hatch_nearest_past.end(), 0.0);
+ double pointer_sum = std::accumulate (this->hatch_pointer_past.begin(), this->hatch_pointer_past.end(), 0.0);
+ // and divide to get the speed
+ speed = nearest_sum/pointer_sum;
+ //g_print ("nearest sum %g pointer_sum %g speed %g\n", nearest_sum, pointer_sum, speed);
+ }
+ }
+
+ if ( this->hatch_escaped // already escaped, do not reattach
+ || (speed < SPEED_MIN) // stuck; most likely reached end of traced stroke
+ || (this->hatch_spacing > 0 && hatch_dist > 50 * this->hatch_spacing) // went too far from the guide
+ ) {
+ // We are NOT attracted to the guide!
+
+ //g_print ("\nlast_nearest %g %g nearest %g %g pointer %g %g pos %d %g\n", dc->last_nearest[Geom::X], dc->last_nearest[Geom::Y], nearest[Geom::X], nearest[Geom::Y], pointer[Geom::X], pointer[Geom::Y], position->piece, position->t);
+
+ // Remember hatch_escaped so we don't get
+ // attracted again until the end of this stroke
+ this->hatch_escaped = true;
+
+ if (this->inertia_vectors.size() >= INERTIA_ELEMENTS/2) { // move by inertia
+ Geom::Point moved_past_escape = motion_dt - this->inertia_vectors.front();
+ Geom::Point inertia =
+ this->inertia_vectors.front() - this->inertia_vectors.back();
+
+ double dot = Geom::dot (moved_past_escape, inertia);
+ dot /= Geom::L2(moved_past_escape) * Geom::L2(inertia);
+
+ if (dot > 0) { // mouse is still moving in approx the same direction
+ Geom::Point should_have_moved =
+ (inertia) * (1/Geom::L2(inertia)) * Geom::L2(moved_past_escape);
+ motion_dt = this->inertia_vectors.front() +
+ (INERTIA_FORCE * should_have_moved + (1 - INERTIA_FORCE) * moved_past_escape);
+ }
+ }
+
+ } else {
+
+ // Calculate angle cosine of this vector-to-guide and all past vectors
+ // summed, to detect if we accidentally flipped to the other side of the
+ // guide
+ Geom::Point hatch_vector_accumulated = std::accumulate
+ (this->hatch_vectors.begin(), this->hatch_vectors.end(), Geom::Point(0,0));
+ double dot = Geom::dot (pointer - nearest, hatch_vector_accumulated);
+ dot /= Geom::L2(pointer - nearest) * Geom::L2(hatch_vector_accumulated);
+
+ if (this->hatch_spacing != 0) { // spacing was already set
+ double target;
+ if (speed > SPEED_NORMAL) {
+ // all ok, strictly obey the spacing
+ target = this->hatch_spacing;
+ } else {
+ // looks like we're starting to lose speed,
+ // so _gradually_ let go attraction to prevent jerks
+ target = (this->hatch_spacing * speed + hatch_dist * (SPEED_NORMAL - speed))/SPEED_NORMAL;
+ }
+ if (!IS_NAN(dot) && dot < -0.5) {// flip
+ target = -target;
+ }
+
+ // This is the track pointer that we will use instead of the real one
+ Geom::Point new_pointer = nearest + target * hatch_unit_vector;
+
+ // some limited feedback: allow persistent pulling to slightly change
+ // the spacing
+ this->hatch_spacing += (hatch_dist - this->hatch_spacing)/3500;
+
+ // return it to the desktop coords
+ motion_dt = new_pointer * motion_to_curve.inverse();
+
+ if (speed >= SPEED_NORMAL) {
+ this->inertia_vectors.push_front(motion_dt);
+ if (this->inertia_vectors.size() > INERTIA_ELEMENTS)
+ this->inertia_vectors.pop_back();
+ }
+
+ } else {
+ // this is the first motion event, set the dist
+ this->hatch_spacing = hatch_dist;
+ }
+
+ // remember last points
+ this->hatch_last_pointer = pointer;
+ this->hatch_last_nearest = nearest;
+
+ this->hatch_vectors.push_front(pointer - nearest);
+ if (this->hatch_vectors.size() > HATCH_VECTOR_ELEMENTS)
+ this->hatch_vectors.pop_back();
+ }
+
+ this->message_context->set(Inkscape::NORMAL_MESSAGE, this->hatch_escaped? _("Tracking: <b>connection to guide path lost!</b>") : _("<b>Tracking</b> a guide path"));
+
+ } else {
+ this->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Drawing</b> a calligraphic stroke"));
+ }
+
+ if (this->just_started_drawing) {
+ this->just_started_drawing = false;
+ this->reset(motion_dt);
+ }
+
+ if (!this->apply(motion_dt)) {
+ ret = TRUE;
+ break;
+ }
+
+ if ( this->cur != this->last ) {
+ this->brush();
+ g_assert( this->npoints > 0 );
+ this->fit_and_split(false);
+ }
+ ret = TRUE;
+ }
+
+ // Draw the hatching circle if necessary
+ if (event->motion.state & GDK_CONTROL_MASK) {
+ if (this->hatch_spacing == 0 && hatch_dist != 0) {
+ // Haven't set spacing yet: gray, center free, update radius live
+ Geom::Point c = desktop->w2d(motion_w);
+ Geom::Affine const sm (Geom::Scale(hatch_dist, hatch_dist) * Geom::Translate(c));
+ sp_canvas_item_affine_absolute(this->hatch_area, sm);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0x7f7f7fff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_item_show(this->hatch_area);
+ } else if (this->dragging && !this->hatch_escaped) {
+ // Tracking: green, center snapped, fixed radius
+ Geom::Point c = motion_dt;
+ Geom::Affine const sm (Geom::Scale(this->hatch_spacing, this->hatch_spacing) * Geom::Translate(c));
+ sp_canvas_item_affine_absolute(this->hatch_area, sm);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0x00FF00ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_item_show(this->hatch_area);
+ } else if (this->dragging && this->hatch_escaped) {
+ // Tracking escaped: red, center free, fixed radius
+ Geom::Point c = motion_dt;
+ Geom::Affine const sm (Geom::Scale(this->hatch_spacing, this->hatch_spacing) * Geom::Translate(c));
+
+ sp_canvas_item_affine_absolute(this->hatch_area, sm);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0xFF0000ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_item_show(this->hatch_area);
+ } else {
+ // Not drawing but spacing set: gray, center snapped, fixed radius
+ Geom::Point c = (nearest + this->hatch_spacing * hatch_unit_vector) * motion_to_curve.inverse();
+ if (!IS_NAN(c[Geom::X]) && !IS_NAN(c[Geom::Y])) {
+ Geom::Affine const sm (Geom::Scale(this->hatch_spacing, this->hatch_spacing) * Geom::Translate(c));
+ sp_canvas_item_affine_absolute(this->hatch_area, sm);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0x7f7f7fff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_item_show(this->hatch_area);
+ }
+ }
+ } else {
+ sp_canvas_item_hide(this->hatch_area);
+ }
+ }
+ break;
+
+
+ case GDK_BUTTON_RELEASE:
+ {
+ Geom::Point const motion_w(event->button.x, event->button.y);
+ Geom::Point const motion_dt(desktop->w2d(motion_w));
+
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
+ desktop->canvas->endForcedFullRedraws();
+ this->is_drawing = false;
+
+ if (this->dragging && event->button.button == 1 && !this->space_panning) {
+ this->dragging = FALSE;
+
+ this->apply(motion_dt);
+
+ /* Remove all temporary line segments */
+ while (this->segments) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data));
+ this->segments = g_slist_remove(this->segments, this->segments->data);
+ }
+
+ /* Create object */
+ this->fit_and_split(true);
+ if (this->accumulate())
+ this->set_to_accumulated(event->button.state & GDK_SHIFT_MASK, event->button.state & GDK_MOD1_MASK); // performs document_done
+ else
+ g_warning ("Failed to create path: invalid data in dc->cal1 or dc->cal2");
+
+ /* reset accumulated curve */
+ this->accumulated->reset();
+
+ this->clear_current();
+ if (this->repr) {
+ this->repr = NULL;
+ }
+
+ if (!this->hatch_pointer_past.empty()) this->hatch_pointer_past.clear();
+ if (!this->hatch_nearest_past.empty()) this->hatch_nearest_past.clear();
+ if (!this->inertia_vectors.empty()) this->inertia_vectors.clear();
+ if (!this->hatch_vectors.empty()) this->hatch_vectors.clear();
+ this->hatch_last_nearest = Geom::Point(0,0);
+ this->hatch_last_pointer = Geom::Point(0,0);
+ this->hatch_escaped = false;
+ this->hatch_item = NULL;
+ this->hatch_livarot_path = NULL;
+ this->just_started_drawing = false;
+
+ if (this->hatch_spacing != 0 && !this->keep_selected) {
+ // we do not select the newly drawn path, so increase spacing by step
+ if (this->hatch_spacing_step == 0) {
+ this->hatch_spacing_step = this->hatch_spacing;
+ }
+ this->hatch_spacing += this->hatch_spacing_step;
+ }
+
+ this->message_context->clear();
+ ret = TRUE;
+ }
+ break;
+ }
+
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Up:
+ case GDK_KEY_KP_Up:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->angle += 5.0;
+ if (this->angle > 90.0)
+ this->angle = 90.0;
+ sp_ddc_update_toolbox (desktop, "calligraphy-angle", this->angle);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Down:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->angle -= 5.0;
+ if (this->angle < -90.0)
+ this->angle = -90.0;
+ sp_ddc_update_toolbox (desktop, "calligraphy-angle", this->angle);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Right:
+ case GDK_KEY_KP_Right:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->width += 0.01;
+ if (this->width > 1.0)
+ this->width = 1.0;
+ sp_ddc_update_toolbox (desktop, "altx-calligraphy", this->width * 100); // the same spinbutton is for alt+x
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Left:
+ case GDK_KEY_KP_Left:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->width -= 0.01;
+ if (this->width < 0.01)
+ this->width = 0.01;
+ sp_ddc_update_toolbox (desktop, "altx-calligraphy", this->width * 100);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Home:
+ case GDK_KEY_KP_Home:
+ this->width = 0.01;
+ sp_ddc_update_toolbox (desktop, "altx-calligraphy", this->width * 100);
+ ret = TRUE;
+ break;
+ case GDK_KEY_End:
+ case GDK_KEY_KP_End:
+ this->width = 1.0;
+ sp_ddc_update_toolbox (desktop, "altx-calligraphy", this->width * 100);
+ ret = TRUE;
+ break;
+ case GDK_KEY_x:
+ case GDK_KEY_X:
+ if (MOD__ALT_ONLY(event)) {
+ desktop->setToolboxFocusTo ("altx-calligraphy");
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Escape:
+ if (this->is_drawing) {
+ // if drawing, cancel, otherwise pass it up for deselecting
+ this->cancel();
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_z:
+ case GDK_KEY_Z:
+ if (MOD__CTRL_ONLY(event) && this->is_drawing) {
+ // if drawing, cancel, otherwise pass it up for undo
+ this->cancel();
+ ret = TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case GDK_KEY_RELEASE:
+ switch (get_group0_keyval(&event->key)) {
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ this->message_context->clear();
+ this->hatch_spacing = 0;
+ this->hatch_spacing_step = 0;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+// if ((SP_EVENT_CONTEXT_CLASS(sp_dyna_draw_context_parent_class))->root_handler) {
+// ret = (SP_EVENT_CONTEXT_CLASS(sp_dyna_draw_context_parent_class))->root_handler(event_context, event);
+// }
+ ret = DynamicBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+
+void CalligraphicTool::clear_current() {
+ /* reset bpath */
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), NULL);
+ /* reset curve */
+ this->currentcurve->reset();
+ this->cal1->reset();
+ this->cal2->reset();
+ /* reset points */
+ this->npoints = 0;
+}
+
+void CalligraphicTool::set_to_accumulated(bool unionize, bool subtract) {
+ if (!this->accumulated->is_empty()) {
+ if (!this->repr) {
+ /* Create object */
+ Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
+ Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
+
+ /* Set style */
+ sp_desktop_apply_style_tool (desktop, repr, "/tools/calligraphic", false);
+
+ this->repr = repr;
+
+ SPItem *item=SP_ITEM(desktop->currentLayer()->appendChildRepr(this->repr));
+ Inkscape::GC::release(this->repr);
+ item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
+ item->updateRepr();
+ }
+
+ Geom::PathVector pathv = this->accumulated->get_pathvector() * desktop->dt2doc();
+ gchar *str = sp_svg_write_path(pathv);
+ g_assert( str != NULL );
+ this->repr->setAttribute("d", str);
+ g_free(str);
+
+ if (unionize) {
+ sp_desktop_selection(desktop)->add(this->repr);
+ sp_selected_path_union_skip_undo(sp_desktop_selection(desktop), desktop);
+ } else if (subtract) {
+ sp_desktop_selection(desktop)->add(this->repr);
+ sp_selected_path_diff_skip_undo(sp_desktop_selection(desktop), desktop);
+ } else {
+ if (this->keep_selected) {
+ sp_desktop_selection(desktop)->set(this->repr);
+ }
+ }
+
+ // Now we need to write the transform information.
+ // First, find out whether our repr is still linked to a valid object. In this case,
+ // we need to write the transform data only for this element.
+ // Either there was no boolean op or it failed.
+ SPItem *result = SP_ITEM(desktop->doc()->getObjectByRepr(this->repr));
+
+ if (result == NULL) {
+ // The boolean operation succeeded.
+ // Now we fetch the single item, that has been set as selected by the boolean op.
+ // This is its result.
+ result = desktop->getSelection()->singleItem();
+ }
+
+ result->doWriteTransform(result->getRepr(), result->transform, NULL, true);
+ } else {
+ if (this->repr) {
+ sp_repr_unparent(this->repr);
+ }
+
+ this->repr = NULL;
+ }
+
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_CALLIGRAPHIC,
+ _("Draw calligraphic stroke"));
+}
+
+static void
+add_cap(SPCurve *curve,
+ Geom::Point const &from,
+ Geom::Point const &to,
+ double rounding)
+{
+ if (Geom::L2( to - from ) > DYNA_EPSILON) {
+ Geom::Point vel = rounding * Geom::rot90( to - from ) / sqrt(2.0);
+ double mag = Geom::L2(vel);
+
+ Geom::Point v = mag * Geom::rot90( to - from ) / Geom::L2( to - from );
+ curve->curveto(from + v, to + v, to);
+ }
+}
+
+bool CalligraphicTool::accumulate() {
+ if (
+ this->cal1->is_empty() ||
+ this->cal2->is_empty() ||
+ (this->cal1->get_segment_count() <= 0) ||
+ this->cal1->first_path()->closed()
+ ) {
+
+ this->cal1->reset();
+ this->cal2->reset();
+
+ return false; // failure
+ }
+
+ SPCurve *rev_cal2 = this->cal2->create_reverse();
+
+ if ((rev_cal2->get_segment_count() <= 0) || rev_cal2->first_path()->closed()) {
+ rev_cal2->unref();
+
+ this->cal1->reset();
+ this->cal2->reset();
+
+ return false; // failure
+ }
+
+ Geom::Curve const * dc_cal1_firstseg = this->cal1->first_segment();
+ Geom::Curve const * rev_cal2_firstseg = rev_cal2->first_segment();
+ Geom::Curve const * dc_cal1_lastseg = this->cal1->last_segment();
+ Geom::Curve const * rev_cal2_lastseg = rev_cal2->last_segment();
+
+ this->accumulated->reset(); /* Is this required ?? */
+
+ this->accumulated->append(this->cal1, false);
+
+ add_cap(this->accumulated, dc_cal1_lastseg->finalPoint(), rev_cal2_firstseg->initialPoint(), this->cap_rounding);
+
+ this->accumulated->append(rev_cal2, true);
+
+ add_cap(this->accumulated, rev_cal2_lastseg->finalPoint(), dc_cal1_firstseg->initialPoint(), this->cap_rounding);
+
+ this->accumulated->closepath();
+
+ rev_cal2->unref();
+
+ this->cal1->reset();
+ this->cal2->reset();
+
+ return true; // success
+}
+
+static double square(double const x)
+{
+ return x * x;
+}
+
+void CalligraphicTool::fit_and_split(bool release) {
+ double const tolerance_sq = square( desktop->w2d().descrim() * TOLERANCE_CALLIGRAPHIC );
+
+#ifdef DYNA_DRAW_VERBOSE
+ g_print("[F&S:R=%c]", release?'T':'F');
+#endif
+
+ if (!( this->npoints > 0 && this->npoints < SAMPLING_SIZE )) {
+ return; // just clicked
+ }
+
+ if ( this->npoints == SAMPLING_SIZE - 1 || release ) {
+#define BEZIER_SIZE 4
+#define BEZIER_MAX_BEZIERS 8
+#define BEZIER_MAX_LENGTH ( BEZIER_SIZE * BEZIER_MAX_BEZIERS )
+
+#ifdef DYNA_DRAW_VERBOSE
+ g_print("[F&S:#] dc->npoints:%d, release:%s\n",
+ this->npoints, release ? "TRUE" : "FALSE");
+#endif
+
+ /* Current calligraphic */
+ if ( this->cal1->is_empty() || this->cal2->is_empty() ) {
+ /* dc->npoints > 0 */
+ /* g_print("calligraphics(1|2) reset\n"); */
+ this->cal1->reset();
+ this->cal2->reset();
+
+ this->cal1->moveto(this->point1[0]);
+ this->cal2->moveto(this->point2[0]);
+ }
+
+ Geom::Point b1[BEZIER_MAX_LENGTH];
+ gint const nb1 = Geom::bezier_fit_cubic_r(b1, this->point1, this->npoints,
+ tolerance_sq, BEZIER_MAX_BEZIERS);
+ g_assert( nb1 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b1)) );
+
+ Geom::Point b2[BEZIER_MAX_LENGTH];
+ gint const nb2 = Geom::bezier_fit_cubic_r(b2, this->point2, this->npoints,
+ tolerance_sq, BEZIER_MAX_BEZIERS);
+ g_assert( nb2 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b2)) );
+
+ if ( nb1 != -1 && nb2 != -1 ) {
+ /* Fit and draw and reset state */
+#ifdef DYNA_DRAW_VERBOSE
+ g_print("nb1:%d nb2:%d\n", nb1, nb2);
+#endif
+ /* CanvasShape */
+ if (! release) {
+ this->currentcurve->reset();
+ this->currentcurve->moveto(b1[0]);
+ for (Geom::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) {
+ this->currentcurve->curveto(bp1[1], bp1[2], bp1[3]);
+ }
+ this->currentcurve->lineto(b2[BEZIER_SIZE*(nb2-1) + 3]);
+ for (Geom::Point *bp2 = b2 + BEZIER_SIZE * ( nb2 - 1 ); bp2 >= b2; bp2 -= BEZIER_SIZE) {
+ this->currentcurve->curveto(bp2[2], bp2[1], bp2[0]);
+ }
+ // FIXME: dc->segments is always NULL at this point??
+ if (!this->segments) { // first segment
+ add_cap(this->currentcurve, b2[0], b1[0], this->cap_rounding);
+ }
+ this->currentcurve->closepath();
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), this->currentcurve);
+ }
+
+ /* Current calligraphic */
+ for (Geom::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) {
+ this->cal1->curveto(bp1[1], bp1[2], bp1[3]);
+ }
+ for (Geom::Point *bp2 = b2; bp2 < b2 + BEZIER_SIZE * nb2; bp2 += BEZIER_SIZE) {
+ this->cal2->curveto(bp2[1], bp2[2], bp2[3]);
+ }
+ } else {
+ /* fixme: ??? */
+#ifdef DYNA_DRAW_VERBOSE
+ g_print("[fit_and_split] failed to fit-cubic.\n");
+#endif
+ this->draw_temporary_box();
+
+ for (gint i = 1; i < this->npoints; i++) {
+ this->cal1->lineto(this->point1[i]);
+ }
+ for (gint i = 1; i < this->npoints; i++) {
+ this->cal2->lineto(this->point2[i]);
+ }
+ }
+
+ /* Fit and draw and copy last point */
+#ifdef DYNA_DRAW_VERBOSE
+ g_print("[%d]Yup\n", this->npoints);
+#endif
+ if (!release) {
+ g_assert(!this->currentcurve->is_empty());
+
+ SPCanvasItem *cbp = sp_canvas_item_new(sp_desktop_sketch(desktop),
+ SP_TYPE_CANVAS_BPATH,
+ NULL);
+ SPCurve *curve = this->currentcurve->copy();
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH (cbp), curve);
+ curve->unref();
+
+ guint32 fillColor = sp_desktop_get_color_tool (desktop, "/tools/calligraphic", true);
+ //guint32 strokeColor = sp_desktop_get_color_tool (desktop, "/tools/calligraphic", false);
+ double opacity = sp_desktop_get_master_opacity_tool (desktop, "/tools/calligraphic");
+ double fillOpacity = sp_desktop_get_opacity_tool (desktop, "/tools/calligraphic", true);
+ //double strokeOpacity = sp_desktop_get_opacity_tool (desktop, "/tools/calligraphic", false);
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cbp), ((fillColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*fillOpacity)), SP_WIND_RULE_EVENODD);
+ //on second thougtht don't do stroke yet because we don't have stoke-width yet and because stoke appears between segments while drawing
+ //sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), ((strokeColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*strokeOpacity)), 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ /* fixme: Cannot we cascade it to root more clearly? */
+ g_signal_connect(G_OBJECT(cbp), "event", G_CALLBACK(sp_desktop_root_handler), desktop);
+
+ this->segments = g_slist_prepend(this->segments, cbp);
+ }
+
+ this->point1[0] = this->point1[this->npoints - 1];
+ this->point2[0] = this->point2[this->npoints - 1];
+ this->npoints = 1;
+ } else {
+ this->draw_temporary_box();
+ }
+}
+
+void CalligraphicTool::draw_temporary_box() {
+ this->currentcurve->reset();
+
+ this->currentcurve->moveto(this->point2[this->npoints-1]);
+
+ for (gint i = this->npoints-2; i >= 0; i--) {
+ this->currentcurve->lineto(this->point2[i]);
+ }
+
+ for (gint i = 0; i < this->npoints; i++) {
+ this->currentcurve->lineto(this->point1[i]);
+ }
+
+ if (this->npoints >= 2) {
+ add_cap(this->currentcurve, this->point1[this->npoints-1], this->point2[this->npoints-1], this->cap_rounding);
+ }
+
+ this->currentcurve->closepath();
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), this->currentcurve);
+}
+
+}
+}
+}
+
+
+/*
+ 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/tools/calligraphic-tool.h b/src/ui/tools/calligraphic-tool.h
new file mode 100644
index 000000000..926e9d126
--- /dev/null
+++ b/src/ui/tools/calligraphic-tool.h
@@ -0,0 +1,94 @@
+#ifndef SP_DYNA_DRAW_CONTEXT_H_SEEN
+#define SP_DYNA_DRAW_CONTEXT_H_SEEN
+
+/*
+ * Handwriting-like drawing mode
+ *
+ * Authors:
+ * Mitsuru Oka <oka326@parkcity.ne.jp>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * The original dynadraw code:
+ * Paul Haeberli <paul@sgi.com>
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ * Copyright (C) 1999-2002 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/tools/dynamic-base.h"
+#include "splivarot.h"
+
+#define DDC_MIN_PRESSURE 0.0
+#define DDC_MAX_PRESSURE 1.0
+#define DDC_DEFAULT_PRESSURE 1.0
+
+#define DDC_MIN_TILT -1.0
+#define DDC_MAX_TILT 1.0
+#define DDC_DEFAULT_TILT 0.0
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class CalligraphicTool : public DynamicBase {
+public:
+ CalligraphicTool();
+ virtual ~CalligraphicTool();
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual void set(const Inkscape::Preferences::Entry& val);
+ virtual bool root_handler(GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+private:
+ /** newly created object remain selected */
+ bool keep_selected;
+
+ double hatch_spacing;
+ double hatch_spacing_step;
+ SPItem *hatch_item;
+ Path *hatch_livarot_path;
+ std::list<double> hatch_nearest_past;
+ std::list<double> hatch_pointer_past;
+ std::list<Geom::Point> inertia_vectors;
+ Geom::Point hatch_last_nearest, hatch_last_pointer;
+ std::list<Geom::Point> hatch_vectors;
+ bool hatch_escaped;
+ SPCanvasItem *hatch_area;
+ bool just_started_drawing;
+ bool trace_bg;
+
+ void clear_current();
+ void set_to_accumulated(bool unionize, bool subtract);
+ bool accumulate();
+ void fit_and_split(bool release);
+ void draw_temporary_box();
+ void cancel();
+ void brush();
+ bool apply(Geom::Point p);
+ void extinput(GdkEvent *event);
+ void reset(Geom::Point p);
+};
+
+}
+}
+}
+
+#endif // SP_DYNA_DRAW_CONTEXT_H_SEEN
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/tools/connector-tool.cpp b/src/ui/tools/connector-tool.cpp
new file mode 100644
index 000000000..36e013ba8
--- /dev/null
+++ b/src/ui/tools/connector-tool.cpp
@@ -0,0 +1,1496 @@
+/*
+ * Connector creation tool
+ *
+ * Authors:
+ * Michael Wybrow <mjwybrow@users.sourceforge.net>
+ * Abhishek Sharma
+ * Jon A. Cruz <jon@joncruz.org>
+ * Martin Owens <doctormo@gmail.com>
+ *
+ * Copyright (C) 2005-2008 Michael Wybrow
+ * Copyright (C) 2009 Monash University
+ * Copyright (C) 2012 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ *
+ * TODO:
+ * o Show a visual indicator for objects with the 'avoid' property set.
+ * o Allow user to change a object between a path and connector through
+ * the interface.
+ * o Create an interface for setting markers (arrow heads).
+ * o Better distinguish between paths and connectors to prevent problems
+ * in the node tool and paths accidentally being turned into connectors
+ * in the connector tool. Perhaps have a way to convert between.
+ * o Only call libavoid's updateEndPoint as required. Currently we do it
+ * for both endpoints, even if only one is moving.
+ * o Deal sanely with connectors with both endpoints attached to the
+ * same connection point, and drawing of connectors attaching
+ * overlapping shapes (currently tries to adjust connector to be
+ * outside both bounding boxes).
+ * o Fix many special cases related to connectors updating,
+ * e.g., copying a couple of shapes and a connector that are
+ * attached to each other.
+ * e.g., detach connector when it is moved or transformed in
+ * one of the other contexts.
+ * o Cope with shapes whose ids change when they have attached
+ * connectors.
+ * o During dragging motion, gobble up to and use the final motion event.
+ * Gobbling away all duplicates after the current can occasionally result
+ * in the path lagging behind the mouse cursor if it is no longer being
+ * dragged.
+ * o Fix up libavoid's representation after undo actions. It doesn't see
+ * any transform signals and hence doesn't know shapes have moved back to
+ * there earlier positions.
+ *
+ * ----------------------------------------------------------------------------
+ *
+ * Notes:
+ *
+ * Much of the way connectors work for user-defined points has been
+ * changed so that it no longer defines special attributes to record
+ * the points. Instead it uses single node paths to define points
+ * who are then seperate objects that can be fixed on the canvas,
+ * grouped into objects and take full advantage of all transform, snap
+ * and align functionality of all other objects.
+ *
+ * I think that the style change between polyline and orthogonal
+ * would be much clearer with two buttons (radio behaviour -- just
+ * one is true).
+ *
+ * The other tools show a label change from "New:" to "Change:"
+ * depending on whether an object is selected. We could consider
+ * this but there may not be space.
+ *
+ * Likewise for the avoid/ignore shapes buttons. These should be
+ * inactive when a shape is not selected in the connector context.
+ *
+ */
+
+
+
+#include <gdk/gdkkeysyms.h>
+#include <string>
+#include <cstring>
+
+#include "ui/tools/connector-tool.h"
+#include "pixmaps/cursor-connector.xpm"
+#include "xml/node-event-vector.h"
+#include "xml/repr.h"
+#include "svg/svg.h"
+#include "desktop.h"
+#include "desktop-style.h"
+#include "desktop-handles.h"
+#include "document.h"
+#include "document-undo.h"
+#include "message-context.h"
+#include "message-stack.h"
+#include "selection.h"
+#include "inkscape.h"
+#include "preferences.h"
+#include "sp-path.h"
+#include "display/sp-canvas.h"
+#include "display/canvas-bpath.h"
+#include "display/sodipodi-ctrl.h"
+#include <glibmm/i18n.h>
+#include <glibmm/stringutils.h>
+#include "snap.h"
+#include "knot.h"
+#include "sp-conn-end.h"
+#include "sp-conn-end-pair.h"
+#include "conn-avoid-ref.h"
+#include "libavoid/vertices.h"
+#include "libavoid/router.h"
+#include "context-fns.h"
+#include "sp-namedview.h"
+#include "sp-text.h"
+#include "sp-flowtext.h"
+#include "display/curve.h"
+#include "verbs.h"
+
+using Inkscape::DocumentUndo;
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+// Stuff borrowed from DrawContext
+static void spcc_connector_set_initial_point(ConnectorTool *cc, Geom::Point const p);
+static void spcc_connector_set_subsequent_point(ConnectorTool *cc, Geom::Point const p);
+static void spcc_connector_finish_segment(ConnectorTool *cc, Geom::Point p);
+static void spcc_reset_colors(ConnectorTool *cc);
+static void spcc_connector_finish(ConnectorTool *cc);
+static void spcc_concat_colors_and_flush(ConnectorTool *cc);
+static void spcc_flush_white(ConnectorTool *cc, SPCurve *gc);
+
+// Context event handlers
+static gint connector_handle_button_press(ConnectorTool *const cc, GdkEventButton const &bevent);
+static gint connector_handle_motion_notify(ConnectorTool *const cc, GdkEventMotion const &mevent);
+static gint connector_handle_button_release(ConnectorTool *const cc, GdkEventButton const &revent);
+static gint connector_handle_key_press(ConnectorTool *const cc, guint const keyval);
+
+static void cc_active_shape_add_knot(ConnectorTool *cc, SPItem* item);
+static void cc_set_active_shape(ConnectorTool *cc, SPItem *item);
+static void cc_clear_active_knots(SPKnotList k);
+static void cc_clear_active_shape(ConnectorTool *cc);
+static void cc_set_active_conn(ConnectorTool *cc, SPItem *item);
+static void cc_clear_active_conn(ConnectorTool *cc);
+static bool conn_pt_handle_test(ConnectorTool *cc, Geom::Point& p, gchar **href);
+static void cc_select_handle(SPKnot* knot);
+static void cc_deselect_handle(SPKnot* knot);
+static bool cc_item_is_shape(SPItem *item);
+static void cc_connector_rerouting_finish(ConnectorTool *const cc,
+ Geom::Point *const p);
+
+static void shape_event_attr_deleted(Inkscape::XML::Node *repr,
+ Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data);
+static void shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
+ gchar const *old_value, gchar const *new_value, bool is_interactive,
+ gpointer data);
+
+/*static Geom::Point connector_drag_origin_w(0, 0);
+static bool connector_within_tolerance = false;*/
+
+static Inkscape::XML::NodeEventVector shape_repr_events = {
+ NULL, /* child_added */
+ NULL, /* child_added */
+ shape_event_attr_changed,
+ NULL, /* content_changed */
+ NULL /* order_changed */
+};
+
+static Inkscape::XML::NodeEventVector layer_repr_events = {
+ NULL, /* child_added */
+ shape_event_attr_deleted,
+ NULL, /* child_added */
+ NULL, /* content_changed */
+ NULL /* order_changed */
+};
+
+namespace {
+ ToolBase* createConnectorContext() {
+ return new ConnectorTool();
+ }
+
+ bool connectorContextRegistered = ToolFactory::instance().registerObject("/tools/connector", createConnectorContext);
+}
+
+const std::string& ConnectorTool::getPrefsPath() {
+ return ConnectorTool::prefsPath;
+}
+
+const std::string ConnectorTool::prefsPath = "/tools/connector";
+
+ConnectorTool::ConnectorTool() : ToolBase() {
+ this->red_curve = 0;
+ this->isOrthogonal = false;
+ this->c1 = 0;
+ this->red_bpath = 0;
+ this->green_curve = 0;
+ this->selection = 0;
+ this->cl0 = 0;
+ this->cl1 = 0;
+ this->c0 = 0;
+
+ this->cursor_shape = cursor_connector_xpm;
+ this->hot_x = 1;
+ this->hot_y = 1;
+ this->xp = 0;
+ this->yp = 0;
+
+ this->red_color = 0xff00007f;
+
+ this->newconn = NULL;
+ this->newConnRef = NULL;
+ this->curvature = 0.0;
+
+ this->sel_changed_connection = sigc::connection();
+
+ this->active_shape = NULL;
+ this->active_shape_repr = NULL;
+ this->active_shape_layer_repr = NULL;
+
+ this->active_conn = NULL;
+ this->active_conn_repr = NULL;
+
+ this->active_handle = NULL;
+
+ this->selected_handle = NULL;
+
+ this->clickeditem = NULL;
+ this->clickedhandle = NULL;
+
+ for (int i = 0; i < 2; ++i) {
+ this->endpt_handle[i] = NULL;
+ this->endpt_handler_id[i] = 0;
+ }
+
+ this->shref = NULL;
+ this->ehref = NULL;
+ this->npoints = 0;
+ this->state = SP_CONNECTOR_CONTEXT_IDLE;
+}
+
+ConnectorTool::~ConnectorTool() {
+ this->sel_changed_connection.disconnect();
+
+ for (int i = 0; i < 2; ++i) {
+ if (this->endpt_handle[1]) {
+ g_object_unref(this->endpt_handle[i]);
+ this->endpt_handle[i] = NULL;
+ }
+ }
+
+ if (this->shref) {
+ g_free(this->shref);
+ this->shref = NULL;
+ }
+
+ if (this->ehref) {
+ g_free(this->shref);
+ this->shref = NULL;
+ }
+
+ g_assert( this->newConnRef == NULL );
+}
+
+void ConnectorTool::setup() {
+ ToolBase::setup();
+
+ this->selection = sp_desktop_selection(this->desktop);
+
+ this->sel_changed_connection.disconnect();
+ this->sel_changed_connection = this->selection->connectChanged(
+ sigc::mem_fun(this, &ConnectorTool::selection_changed)
+ );
+
+ /* Create red bpath */
+ this->red_bpath = sp_canvas_bpath_new(sp_desktop_sketch(this->desktop), NULL);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->red_bpath), this->red_color,
+ 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->red_bpath), 0x00000000,
+ SP_WIND_RULE_NONZERO);
+ /* Create red curve */
+ this->red_curve = new SPCurve();
+
+ /* Create green curve */
+ this->green_curve = new SPCurve();
+
+ // Notice the initial selection.
+ //cc_selection_changed(this->selection, (gpointer) this);
+ this->selection_changed(this->selection);
+
+ this->within_tolerance = false;
+
+ sp_event_context_read(this, "curvature");
+ sp_event_context_read(this, "orthogonal");
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/connector/selcue", 0)) {
+ this->enableSelectionCue();
+ }
+
+ // Make sure we see all enter events for canvas items,
+ // even if a mouse button is depressed.
+ this->desktop->canvas->gen_all_enter_events = true;
+}
+
+void ConnectorTool::set(const Inkscape::Preferences::Entry& val) {
+ /* fixme: Proper error handling for non-numeric data. Use a locale-independent function like
+ * g_ascii_strtod (or a thin wrapper that does the right thing for invalid values inf/nan). */
+ Glib::ustring name = val.getEntryName();
+
+ if (name == "curvature") {
+ this->curvature = val.getDoubleLimited(); // prevents NaN and +/-Inf from messing up
+ } else if (name == "orthogonal") {
+ this->isOrthogonal = val.getBool();
+ }
+}
+
+void ConnectorTool::finish() {
+ spcc_connector_finish(this);
+ this->state = SP_CONNECTOR_CONTEXT_IDLE;
+
+ ToolBase::finish();
+
+ if (this->selection) {
+ this->selection = NULL;
+ }
+
+ cc_clear_active_shape(this);
+ cc_clear_active_conn(this);
+
+ // Restore the default event generating behaviour.
+ this->desktop->canvas->gen_all_enter_events = false;
+}
+
+//-----------------------------------------------------------------------------
+
+
+static void
+cc_clear_active_shape(ConnectorTool *cc)
+{
+ if (cc->active_shape == NULL) {
+ return;
+ }
+ g_assert( cc->active_shape_repr );
+ g_assert( cc->active_shape_layer_repr );
+
+ cc->active_shape = NULL;
+
+ if (cc->active_shape_repr) {
+ sp_repr_remove_listener_by_data(cc->active_shape_repr, cc);
+ Inkscape::GC::release(cc->active_shape_repr);
+ cc->active_shape_repr = NULL;
+
+ sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc);
+ Inkscape::GC::release(cc->active_shape_layer_repr);
+ cc->active_shape_layer_repr = NULL;
+ }
+
+ cc_clear_active_knots(cc->knots);
+}
+
+static void
+cc_clear_active_knots(SPKnotList k)
+{
+ // Hide the connection points if they exist.
+ if (k.size()) {
+ for (SPKnotList::iterator it = k.begin(); it != k.end(); ++it) {
+ sp_knot_hide(it->first);
+ }
+ }
+}
+
+static void
+cc_clear_active_conn(ConnectorTool *cc)
+{
+ if (cc->active_conn == NULL) {
+ return;
+ }
+ g_assert( cc->active_conn_repr );
+
+ cc->active_conn = NULL;
+
+ if (cc->active_conn_repr) {
+ sp_repr_remove_listener_by_data(cc->active_conn_repr, cc);
+ Inkscape::GC::release(cc->active_conn_repr);
+ cc->active_conn_repr = NULL;
+ }
+
+ // Hide the endpoint handles.
+ for (int i = 0; i < 2; ++i) {
+ if (cc->endpt_handle[i]) {
+ sp_knot_hide(cc->endpt_handle[i]);
+ }
+ }
+}
+
+
+static bool
+conn_pt_handle_test(ConnectorTool *cc, Geom::Point& p, gchar **href)
+{
+ if (cc->active_handle && (cc->knots.find(cc->active_handle) != cc->knots.end()))
+ {
+ p = cc->active_handle->pos;
+ *href = g_strdup_printf("#%s", cc->active_handle->owner->getId());
+ return true;
+ }
+ *href = NULL;
+ return false;
+}
+
+static void
+cc_select_handle(SPKnot* knot)
+{
+ knot->setShape(SP_KNOT_SHAPE_SQUARE);
+ knot->setSize(10);
+ knot->setAnchor(SP_ANCHOR_CENTER);
+ knot->setFill(0x0000ffff, 0x0000ffff, 0x0000ffff);
+ sp_knot_update_ctrl(knot);
+}
+
+static void
+cc_deselect_handle(SPKnot* knot)
+{
+ knot->setShape(SP_KNOT_SHAPE_SQUARE);
+ knot->setSize(8);
+ knot->setAnchor(SP_ANCHOR_CENTER);
+ knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff);
+ sp_knot_update_ctrl(knot);
+}
+
+bool ConnectorTool::item_handler(SPItem* item, GdkEvent* event) {
+ gint ret = FALSE;
+
+ Geom::Point p(event->button.x, event->button.y);
+
+ switch (event->type) {
+ case GDK_BUTTON_RELEASE:
+ if (event->button.button == 1 && !this->space_panning) {
+ if ((this->state == SP_CONNECTOR_CONTEXT_DRAGGING) && this->within_tolerance) {
+ spcc_reset_colors(this);
+ this->state = SP_CONNECTOR_CONTEXT_IDLE;
+ }
+
+ if (this->state != SP_CONNECTOR_CONTEXT_IDLE) {
+ // Doing something else like rerouting.
+ break;
+ }
+
+ // find out clicked item, honoring Alt
+ SPItem *item = sp_event_context_find_item(desktop, p, event->button.state & GDK_MOD1_MASK, FALSE);
+
+ if (event->button.state & GDK_SHIFT_MASK) {
+ this->selection->toggle(item);
+ } else {
+ this->selection->set(item);
+ /* When selecting a new item, do not allow showing
+ connection points on connectors. (yet?)
+ */
+
+ if (item != this->active_shape && !cc_item_is_connector(item)) {
+ cc_set_active_shape(this, item);
+ }
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_ENTER_NOTIFY:
+ if (!this->selected_handle) {
+ if (cc_item_is_shape(item)) {
+ cc_set_active_shape(this, item);
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+bool ConnectorTool::root_handler(GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ ret = connector_handle_button_press(this, event->button);
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ ret = connector_handle_motion_notify(this, event->motion);
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ ret = connector_handle_button_release(this, event->button);
+ break;
+
+ case GDK_KEY_PRESS:
+ ret = connector_handle_key_press(this, get_group0_keyval (&event->key));
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+
+static gint
+connector_handle_button_press(ConnectorTool *const cc, GdkEventButton const &bevent)
+{
+ Geom::Point const event_w(bevent.x, bevent.y);
+ /* Find desktop coordinates */
+ Geom::Point p = cc->desktop->w2d(event_w);
+ ToolBase *event_context = SP_EVENT_CONTEXT(cc);
+
+ gint ret = FALSE;
+
+ if ( bevent.button == 1 && !event_context->space_panning ) {
+
+ SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
+
+ if (Inkscape::have_viable_layer(desktop, cc->message_context) == false) {
+ return TRUE;
+ }
+
+ Geom::Point const event_w(bevent.x,
+ bevent.y);
+
+ cc->xp = bevent.x;
+ cc->yp = bevent.y;
+ cc->within_tolerance = true;
+
+ Geom::Point const event_dt = cc->desktop->w2d(event_w);
+
+ SnapManager &m = cc->desktop->namedview->snap_manager;
+
+ switch (cc->state) {
+ case SP_CONNECTOR_CONTEXT_STOP:
+ /* This is allowed, if we just canceled curve */
+ case SP_CONNECTOR_CONTEXT_IDLE:
+ {
+ if ( cc->npoints == 0 ) {
+ cc_clear_active_conn(cc);
+
+ SP_EVENT_CONTEXT_DESKTOP(cc)->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new connector"));
+
+ /* Set start anchor */
+ /* Create green anchor */
+ Geom::Point p = event_dt;
+
+ // Test whether we clicked on a connection point
+ bool found = conn_pt_handle_test(cc, p, &cc->shref);
+
+ if (!found) {
+ // This is the first point, so just snap it to the grid
+ // as there's no other points to go off.
+ m.setup(cc->desktop);
+ m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
+ m.unSetup();
+ }
+ spcc_connector_set_initial_point(cc, p);
+
+ }
+ cc->state = SP_CONNECTOR_CONTEXT_DRAGGING;
+ ret = TRUE;
+ break;
+ }
+ case SP_CONNECTOR_CONTEXT_DRAGGING:
+ {
+ // This is the second click of a connector creation.
+ m.setup(cc->desktop);
+ m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
+ m.unSetup();
+
+ spcc_connector_set_subsequent_point(cc, p);
+ spcc_connector_finish_segment(cc, p);
+
+ conn_pt_handle_test(cc, p, &cc->ehref);
+ if (cc->npoints != 0) {
+ spcc_connector_finish(cc);
+ }
+ cc_set_active_conn(cc, cc->newconn);
+ cc->state = SP_CONNECTOR_CONTEXT_IDLE;
+ ret = TRUE;
+ break;
+ }
+ case SP_CONNECTOR_CONTEXT_CLOSE:
+ {
+ g_warning("Button down in CLOSE state");
+ break;
+ }
+ default:
+ break;
+ }
+ } else if (bevent.button == 3) {
+ if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) {
+ // A context menu is going to be triggered here,
+ // so end the rerouting operation.
+ cc_connector_rerouting_finish(cc, &p);
+
+ cc->state = SP_CONNECTOR_CONTEXT_IDLE;
+
+ // Don't set ret to TRUE, so we drop through to the
+ // parent handler which will open the context menu.
+ }
+ else if (cc->npoints != 0) {
+ spcc_connector_finish(cc);
+ cc->state = SP_CONNECTOR_CONTEXT_IDLE;
+ ret = TRUE;
+ }
+ }
+ return ret;
+}
+
+static gint
+connector_handle_motion_notify(ConnectorTool *const cc, GdkEventMotion const &mevent)
+{
+ gint ret = FALSE;
+ ToolBase *event_context = SP_EVENT_CONTEXT(cc);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) {
+ // allow middle-button scrolling
+ return FALSE;
+ }
+
+ Geom::Point const event_w(mevent.x, mevent.y);
+
+ if (cc->within_tolerance) {
+ cc->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+ if ( ( abs( (gint) mevent.x - cc->xp ) < cc->tolerance ) &&
+ ( abs( (gint) mevent.y - cc->yp ) < cc->tolerance ) ) {
+ return FALSE; // Do not drag if we're within tolerance from origin.
+ }
+ }
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to move the object, not click), then always process
+ // the motion notify coordinates as given (no snapping back to origin)
+ cc->within_tolerance = false;
+
+ SPDesktop *const dt = cc->desktop;
+
+ /* Find desktop coordinates */
+ Geom::Point p = dt->w2d(event_w);
+
+ SnapManager &m = dt->namedview->snap_manager;
+
+ switch (cc->state) {
+ case SP_CONNECTOR_CONTEXT_DRAGGING:
+ {
+ gobble_motion_events(mevent.state);
+ // This is movement during a connector creation.
+ if ( cc->npoints > 0 ) {
+ m.setup(dt);
+ m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
+ m.unSetup();
+ cc->selection->clear();
+ spcc_connector_set_subsequent_point(cc, p);
+ ret = TRUE;
+ }
+ break;
+ }
+ case SP_CONNECTOR_CONTEXT_REROUTING:
+ {
+ gobble_motion_events(GDK_BUTTON1_MASK);
+ g_assert( SP_IS_PATH(cc->clickeditem));
+
+ m.setup(dt);
+ m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
+ m.unSetup();
+
+ // Update the hidden path
+ Geom::Affine i2d ( (cc->clickeditem)->i2dt_affine() );
+ Geom::Affine d2i = i2d.inverse();
+ SPPath *path = SP_PATH(cc->clickeditem);
+ SPCurve *curve = path->get_curve();
+ if (cc->clickedhandle == cc->endpt_handle[0]) {
+ Geom::Point o = cc->endpt_handle[1]->pos;
+ curve->stretch_endpoints(p * d2i, o * d2i);
+ }
+ else {
+ Geom::Point o = cc->endpt_handle[0]->pos;
+ curve->stretch_endpoints(o * d2i, p * d2i);
+ }
+ sp_conn_reroute_path_immediate(path);
+
+ // Copy this to the temporary visible path
+ cc->red_curve = path->get_curve_for_edit();
+ cc->red_curve->transform(i2d);
+
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
+ ret = TRUE;
+ break;
+ }
+ case SP_CONNECTOR_CONTEXT_STOP:
+ /* This is perfectly valid */
+ break;
+ default:
+ if (!sp_event_context_knot_mouseover(cc)) {
+ m.setup(dt);
+ m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_OTHER_HANDLE));
+ m.unSetup();
+ }
+ break;
+ }
+ return ret;
+}
+
+static gint
+connector_handle_button_release(ConnectorTool *const cc, GdkEventButton const &revent)
+{
+ gint ret = FALSE;
+ ToolBase *event_context = SP_EVENT_CONTEXT(cc);
+ if ( revent.button == 1 && !event_context->space_panning ) {
+
+ SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
+ SPDocument *doc = sp_desktop_document(desktop);
+
+ SnapManager &m = desktop->namedview->snap_manager;
+
+ Geom::Point const event_w(revent.x, revent.y);
+
+ /* Find desktop coordinates */
+ Geom::Point p = cc->desktop->w2d(event_w);
+
+ switch (cc->state) {
+ //case SP_CONNECTOR_CONTEXT_POINT:
+ case SP_CONNECTOR_CONTEXT_DRAGGING:
+ {
+ m.setup(desktop);
+ m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
+ m.unSetup();
+
+ if (cc->within_tolerance)
+ {
+ spcc_connector_finish_segment(cc, p);
+ return TRUE;
+ }
+ // Connector has been created via a drag, end it now.
+ spcc_connector_set_subsequent_point(cc, p);
+ spcc_connector_finish_segment(cc, p);
+ // Test whether we clicked on a connection point
+ conn_pt_handle_test(cc, p, &cc->ehref);
+ if (cc->npoints != 0) {
+ spcc_connector_finish(cc);
+ }
+ cc_set_active_conn(cc, cc->newconn);
+ cc->state = SP_CONNECTOR_CONTEXT_IDLE;
+ break;
+ }
+ case SP_CONNECTOR_CONTEXT_REROUTING:
+ {
+ m.setup(desktop);
+ m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
+ m.unSetup();
+ cc_connector_rerouting_finish(cc, &p);
+
+ doc->ensureUpToDate();
+ cc->state = SP_CONNECTOR_CONTEXT_IDLE;
+ return TRUE;
+ break;
+ }
+ case SP_CONNECTOR_CONTEXT_STOP:
+ /* This is allowed, if we just cancelled curve */
+ break;
+ default:
+ break;
+ }
+ ret = TRUE;
+ }
+ return ret;
+}
+
+static gint
+connector_handle_key_press(ConnectorTool *const cc, guint const keyval)
+{
+ gint ret = FALSE;
+
+ switch (keyval) {
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ if (cc->npoints != 0) {
+ spcc_connector_finish(cc);
+ cc->state = SP_CONNECTOR_CONTEXT_IDLE;
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Escape:
+ if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) {
+
+ SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
+ SPDocument *doc = sp_desktop_document(desktop);
+
+ cc_connector_rerouting_finish(cc, NULL);
+
+ DocumentUndo::undo(doc);
+
+ cc->state = SP_CONNECTOR_CONTEXT_IDLE;
+ desktop->messageStack()->flash( Inkscape::NORMAL_MESSAGE,
+ _("Connector endpoint drag cancelled."));
+ ret = TRUE;
+ }
+ else if (cc->npoints != 0) {
+ // if drawing, cancel, otherwise pass it up for deselecting
+ cc->state = SP_CONNECTOR_CONTEXT_STOP;
+ spcc_reset_colors(cc);
+ ret = TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+
+static void
+cc_connector_rerouting_finish(ConnectorTool *const cc, Geom::Point *const p)
+{
+ SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
+ SPDocument *doc = sp_desktop_document(desktop);
+
+ // Clear the temporary path:
+ cc->red_curve->reset();
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
+
+ if (p != NULL)
+ {
+ // Test whether we clicked on a connection point
+ gchar *shape_label;
+ bool found = conn_pt_handle_test(cc, *p, &shape_label);
+
+ if (found) {
+ if (cc->clickedhandle == cc->endpt_handle[0]) {
+ cc->clickeditem->setAttribute("inkscape:connection-start", shape_label, NULL);
+ }
+ else {
+ cc->clickeditem->setAttribute("inkscape:connection-end", shape_label, NULL);
+ }
+ g_free(shape_label);
+ }
+ }
+ cc->clickeditem->setHidden(false);
+ sp_conn_reroute_path_immediate(SP_PATH(cc->clickeditem));
+ cc->clickeditem->updateRepr();
+ DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR,
+ _("Reroute connector"));
+ cc_set_active_conn(cc, cc->clickeditem);
+}
+
+
+static void
+spcc_reset_colors(ConnectorTool *cc)
+{
+ /* Red */
+ cc->red_curve->reset();
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
+
+ cc->green_curve->reset();
+ cc->npoints = 0;
+}
+
+
+static void
+spcc_connector_set_initial_point(ConnectorTool *const cc, Geom::Point const p)
+{
+ g_assert( cc->npoints == 0 );
+
+ cc->p[0] = p;
+ cc->p[1] = p;
+ cc->npoints = 2;
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
+}
+
+
+static void
+spcc_connector_set_subsequent_point(ConnectorTool *const cc, Geom::Point const p)
+{
+ g_assert( cc->npoints != 0 );
+
+ SPDesktop *dt = cc->desktop;
+ Geom::Point o = dt->dt2doc(cc->p[0]);
+ Geom::Point d = dt->dt2doc(p);
+ Avoid::Point src(o[Geom::X], o[Geom::Y]);
+ Avoid::Point dst(d[Geom::X], d[Geom::Y]);
+
+ if (!cc->newConnRef) {
+ Avoid::Router *router = sp_desktop_document(dt)->router;
+ cc->newConnRef = new Avoid::ConnRef(router);
+ cc->newConnRef->setEndpoint(Avoid::VertID::src, src);
+ if (cc->isOrthogonal)
+ cc->newConnRef->setRoutingType(Avoid::ConnType_Orthogonal);
+ else
+ cc->newConnRef->setRoutingType(Avoid::ConnType_PolyLine);
+ }
+ // Set new endpoint.
+ cc->newConnRef->setEndpoint(Avoid::VertID::tar, dst);
+ // Immediately generate new routes for connector.
+ cc->newConnRef->makePathInvalid();
+ cc->newConnRef->router()->processTransaction();
+ // Recreate curve from libavoid route.
+ recreateCurve( cc->red_curve, cc->newConnRef, cc->curvature );
+ cc->red_curve->transform(dt->doc2dt());
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
+}
+
+
+/**
+ * Concats red, blue and green.
+ * If any anchors are defined, process these, optionally removing curves from white list
+ * Invoke _flush_white to write result back to object.
+ */
+static void
+spcc_concat_colors_and_flush(ConnectorTool *cc)
+{
+ SPCurve *c = cc->green_curve;
+ cc->green_curve = new SPCurve();
+
+ cc->red_curve->reset();
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
+
+ if (c->is_empty()) {
+ c->unref();
+ return;
+ }
+
+ spcc_flush_white(cc, c);
+
+ c->unref();
+}
+
+
+/*
+ * Flushes white curve(s) and additional curve into object
+ *
+ * No cleaning of colored curves - this has to be done by caller
+ * No rereading of white data, so if you cannot rely on ::modified, do it in caller
+ *
+ */
+
+static void
+spcc_flush_white(ConnectorTool *cc, SPCurve *gc)
+{
+ SPCurve *c;
+
+ if (gc) {
+ c = gc;
+ c->ref();
+ } else {
+ return;
+ }
+
+ /* Now we have to go back to item coordinates at last */
+ c->transform(SP_EVENT_CONTEXT_DESKTOP(cc)->dt2doc());
+
+ SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
+ SPDocument *doc = sp_desktop_document(desktop);
+ Inkscape::XML::Document *xml_doc = doc->getReprDoc();
+
+ if ( c && !c->is_empty() ) {
+ /* We actually have something to write */
+
+ Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
+ /* Set style */
+ sp_desktop_apply_style_tool(desktop, repr, "/tools/connector", false);
+
+ gchar *str = sp_svg_write_path( c->get_pathvector() );
+ g_assert( str != NULL );
+ repr->setAttribute("d", str);
+ g_free(str);
+
+ /* Attach repr */
+ cc->newconn = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
+ cc->newconn->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
+
+ bool connection = false;
+ cc->newconn->setAttribute( "inkscape:connector-type",
+ cc->isOrthogonal ? "orthogonal" : "polyline", NULL );
+ cc->newconn->setAttribute( "inkscape:connector-curvature",
+ Glib::Ascii::dtostr(cc->curvature).c_str(), NULL );
+ if (cc->shref)
+ {
+ cc->newconn->setAttribute( "inkscape:connection-start", cc->shref, NULL);
+ connection = true;
+ }
+
+ if (cc->ehref)
+ {
+ cc->newconn->setAttribute( "inkscape:connection-end", cc->ehref, NULL);
+ connection = true;
+ }
+ // Process pending updates.
+ cc->newconn->updateRepr();
+ doc->ensureUpToDate();
+
+ if (connection) {
+ // Adjust endpoints to shape edge.
+ sp_conn_reroute_path_immediate(SP_PATH(cc->newconn));
+ cc->newconn->updateRepr();
+ }
+
+ cc->newconn->doWriteTransform(cc->newconn->getRepr(), cc->newconn->transform, NULL, true);
+
+ // Only set the selection after we are finished with creating the attributes of
+ // the connector. Otherwise, the selection change may alter the defaults for
+ // values like curvature in the connector context, preventing subsequent lookup
+ // of their original values.
+ cc->selection->set(repr);
+ Inkscape::GC::release(repr);
+ }
+
+ c->unref();
+
+ DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR, _("Create connector"));
+}
+
+
+static void
+spcc_connector_finish_segment(ConnectorTool *const cc, Geom::Point const /*p*/)
+{
+ if (!cc->red_curve->is_empty()) {
+ cc->green_curve->append_continuous(cc->red_curve, 0.0625);
+
+ cc->p[0] = cc->p[3];
+ cc->p[1] = cc->p[4];
+ cc->npoints = 2;
+
+ cc->red_curve->reset();
+ }
+}
+
+
+static void
+spcc_connector_finish(ConnectorTool *const cc)
+{
+ SPDesktop *const desktop = cc->desktop;
+ desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing connector"));
+
+ cc->red_curve->reset();
+ spcc_concat_colors_and_flush(cc);
+
+ cc->npoints = 0;
+
+ if (cc->newConnRef) {
+ cc->newConnRef->removeFromGraph();
+ delete cc->newConnRef;
+ cc->newConnRef = NULL;
+ }
+}
+
+
+static gboolean
+cc_generic_knot_handler(SPCanvasItem *, GdkEvent *event, SPKnot *knot)
+{
+ g_assert (knot != NULL);
+
+ g_object_ref(knot);
+
+ ConnectorTool *cc = SP_CONNECTOR_CONTEXT(
+ knot->desktop->event_context);
+
+ gboolean consumed = FALSE;
+
+ gchar const *knot_tip = "Click to join at this point";
+ switch (event->type) {
+ case GDK_ENTER_NOTIFY:
+ sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, TRUE);
+
+ cc->active_handle = knot;
+ if (knot_tip)
+ {
+ knot->desktop->event_context->defaultMessageContext()->set(
+ Inkscape::NORMAL_MESSAGE, knot_tip);
+ }
+
+ consumed = TRUE;
+ break;
+ case GDK_LEAVE_NOTIFY:
+ sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE);
+
+ cc->active_handle = NULL;
+
+ if (knot_tip) {
+ knot->desktop->event_context->defaultMessageContext()->clear();
+ }
+
+ consumed = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ g_object_unref(knot);
+
+ return consumed;
+}
+
+
+static gboolean
+endpt_handler(SPKnot */*knot*/, GdkEvent *event, ConnectorTool *cc)
+{
+ //g_assert( SP_IS_CONNECTOR_CONTEXT(cc) );
+
+ gboolean consumed = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ g_assert( (cc->active_handle == cc->endpt_handle[0]) ||
+ (cc->active_handle == cc->endpt_handle[1]) );
+ if (cc->state == SP_CONNECTOR_CONTEXT_IDLE) {
+ cc->clickeditem = cc->active_conn;
+ cc->clickedhandle = cc->active_handle;
+ cc_clear_active_conn(cc);
+ cc->state = SP_CONNECTOR_CONTEXT_REROUTING;
+
+ // Disconnect from attached shape
+ unsigned ind = (cc->active_handle == cc->endpt_handle[0]) ? 0 : 1;
+ sp_conn_end_detach(cc->clickeditem, ind);
+
+ Geom::Point origin;
+ if (cc->clickedhandle == cc->endpt_handle[0]) {
+ origin = cc->endpt_handle[1]->pos;
+ }
+ else {
+ origin = cc->endpt_handle[0]->pos;
+ }
+
+ // Show the red path for dragging.
+ cc->red_curve = SP_PATH(cc->clickeditem)->get_curve_for_edit();
+ Geom::Affine i2d = (cc->clickeditem)->i2dt_affine();
+ cc->red_curve->transform(i2d);
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
+
+ cc->clickeditem->setHidden(true);
+
+ // The rest of the interaction rerouting the connector is
+ // handled by the context root handler.
+ consumed = TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return consumed;
+}
+
+static void cc_active_shape_add_knot(ConnectorTool *cc, SPItem* item)
+{
+ SPDesktop *desktop = cc->desktop;
+ SPKnot *knot = sp_knot_new(desktop, 0);
+
+ knot->owner = item;
+ knot->setShape(SP_KNOT_SHAPE_SQUARE);
+ knot->setSize(8);
+ knot->setAnchor(SP_ANCHOR_CENTER);
+ knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff);
+ sp_knot_update_ctrl(knot);
+
+ // We don't want to use the standard knot handler.
+ g_signal_handler_disconnect(G_OBJECT(knot->item),
+ knot->_event_handler_id);
+ knot->_event_handler_id = 0;
+
+ g_signal_connect(G_OBJECT(knot->item), "event",
+ G_CALLBACK(cc_generic_knot_handler), knot);
+ sp_knot_set_position(knot, item->avoidRef->getConnectionPointPos() * desktop->doc2dt(), 0);
+ sp_knot_show(knot);
+ cc->knots[knot] = 1;
+}
+
+static void cc_set_active_shape(ConnectorTool *cc, SPItem *item)
+{
+ g_assert(item != NULL );
+
+ if (cc->active_shape != item)
+ {
+ // The active shape has changed
+ // Rebuild everything
+ cc->active_shape = item;
+ // Remove existing active shape listeners
+ if (cc->active_shape_repr) {
+ sp_repr_remove_listener_by_data(cc->active_shape_repr, cc);
+ Inkscape::GC::release(cc->active_shape_repr);
+
+ sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc);
+ Inkscape::GC::release(cc->active_shape_layer_repr);
+ }
+
+ // Listen in case the active shape changes
+ cc->active_shape_repr = item->getRepr();
+ if (cc->active_shape_repr) {
+ Inkscape::GC::anchor(cc->active_shape_repr);
+ sp_repr_add_listener(cc->active_shape_repr, &shape_repr_events, cc);
+
+ cc->active_shape_layer_repr = cc->active_shape_repr->parent();
+ Inkscape::GC::anchor(cc->active_shape_layer_repr);
+ sp_repr_add_listener(cc->active_shape_layer_repr, &layer_repr_events, cc);
+ }
+
+ cc_clear_active_knots(cc->knots);
+
+ // The idea here is to try and add a group's children to solidify
+ // connection handling. We react to path objects with only one node.
+ for (SPObject *child = item->firstChild() ; child ; child = child->getNext() ) {
+ if (SP_IS_PATH(child) && SP_PATH(child)->nodesInPath() == 1) {
+ cc_active_shape_add_knot(cc, (SPItem *) child);
+ }
+ }
+ cc_active_shape_add_knot(cc, item);
+
+ }
+ else
+ {
+ // Ensure the item's connection_points map
+ // has been updated
+ item->document->ensureUpToDate();
+ }
+}
+
+
+static void
+cc_set_active_conn(ConnectorTool *cc, SPItem *item)
+{
+ g_assert( SP_IS_PATH(item) );
+
+ const SPCurve *curve = SP_PATH(item)->get_curve_reference();
+ Geom::Affine i2dt = item->i2dt_affine();
+
+ if (cc->active_conn == item)
+ {
+ if (curve->is_empty())
+ {
+ // Connector is invisible because it is clipped to the boundary of
+ // two overlpapping shapes.
+ sp_knot_hide(cc->endpt_handle[0]);
+ sp_knot_hide(cc->endpt_handle[1]);
+ }
+ else
+ {
+ // Just adjust handle positions.
+ Geom::Point startpt = *(curve->first_point()) * i2dt;
+ sp_knot_set_position(cc->endpt_handle[0], startpt, 0);
+
+ Geom::Point endpt = *(curve->last_point()) * i2dt;
+ sp_knot_set_position(cc->endpt_handle[1], endpt, 0);
+ }
+
+ return;
+ }
+
+ cc->active_conn = item;
+
+ // Remove existing active conn listeners
+ if (cc->active_conn_repr) {
+ sp_repr_remove_listener_by_data(cc->active_conn_repr, cc);
+ Inkscape::GC::release(cc->active_conn_repr);
+ cc->active_conn_repr = NULL;
+ }
+
+ // Listen in case the active conn changes
+ cc->active_conn_repr = item->getRepr();
+ if (cc->active_conn_repr) {
+ Inkscape::GC::anchor(cc->active_conn_repr);
+ sp_repr_add_listener(cc->active_conn_repr, &shape_repr_events, cc);
+ }
+
+ for (int i = 0; i < 2; ++i) {
+
+ // Create the handle if it doesn't exist
+ if ( cc->endpt_handle[i] == NULL ) {
+ SPKnot *knot = sp_knot_new(cc->desktop,
+ _("<b>Connector endpoint</b>: drag to reroute or connect to new shapes"));
+
+ knot->setShape(SP_KNOT_SHAPE_SQUARE);
+ knot->setSize(7);
+ knot->setAnchor(SP_ANCHOR_CENTER);
+ knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff);
+ knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
+ sp_knot_update_ctrl(knot);
+
+ // We don't want to use the standard knot handler,
+ // since we don't want this knot to be draggable.
+ g_signal_handler_disconnect(G_OBJECT(knot->item),
+ knot->_event_handler_id);
+ knot->_event_handler_id = 0;
+
+ g_signal_connect(G_OBJECT(knot->item), "event",
+ G_CALLBACK(cc_generic_knot_handler), knot);
+
+ cc->endpt_handle[i] = knot;
+ }
+
+ // Remove any existing handlers
+ if (cc->endpt_handler_id[i]) {
+ g_signal_handlers_disconnect_by_func(
+ G_OBJECT(cc->endpt_handle[i]->item),
+ (void*)G_CALLBACK(endpt_handler), (gpointer) cc );
+ cc->endpt_handler_id[i] = 0;
+ }
+
+ // Setup handlers for connector endpoints, this is
+ // is as 'after' so that cc_generic_knot_handler is
+ // triggered first for any endpoint.
+ cc->endpt_handler_id[i] = g_signal_connect_after(
+ G_OBJECT(cc->endpt_handle[i]->item), "event",
+ G_CALLBACK(endpt_handler), cc);
+ }
+
+ if (curve->is_empty())
+ {
+ // Connector is invisible because it is clipped to the boundary
+ // of two overlpapping shapes. So, it doesn't need endpoints.
+ return;
+ }
+
+ Geom::Point startpt = *(curve->first_point()) * i2dt;
+ sp_knot_set_position(cc->endpt_handle[0], startpt, 0);
+
+ Geom::Point endpt = *(curve->last_point()) * i2dt;
+ sp_knot_set_position(cc->endpt_handle[1], endpt, 0);
+
+ sp_knot_show(cc->endpt_handle[0]);
+ sp_knot_show(cc->endpt_handle[1]);
+}
+
+void cc_create_connection_point(ConnectorTool* cc)
+{
+ if (cc->active_shape && cc->state == SP_CONNECTOR_CONTEXT_IDLE)
+ {
+ if (cc->selected_handle)
+ {
+ cc_deselect_handle( cc->selected_handle );
+ }
+ SPKnot *knot = sp_knot_new(cc->desktop, 0);
+ // We do not process events on this knot.
+ g_signal_handler_disconnect(G_OBJECT(knot->item),
+ knot->_event_handler_id);
+ knot->_event_handler_id = 0;
+
+ cc_select_handle( knot );
+ cc->selected_handle = knot;
+ sp_knot_show(cc->selected_handle);
+ cc->state = SP_CONNECTOR_CONTEXT_NEWCONNPOINT;
+ }
+}
+
+static bool cc_item_is_shape(SPItem *item)
+{
+ if (SP_IS_PATH(item)) {
+ const SPCurve * curve = (SP_SHAPE(item))->_curve;
+ if ( curve && !(curve->is_closed()) ) {
+ // Open paths are connectors.
+ return false;
+ }
+ }
+ else if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/connector/ignoretext", true)) {
+ // Don't count text as a shape we can connect connector to.
+ return false;
+ }
+ }
+ return true;
+}
+
+
+bool cc_item_is_connector(SPItem *item)
+{
+ if (SP_IS_PATH(item)) {
+ bool closed = SP_PATH(item)->get_curve_reference()->is_closed();
+ if (SP_PATH(item)->connEndPair.isAutoRoutingConn() && !closed) {
+ // To be considered a connector, an object must be a non-closed
+ // path that is marked with a "inkscape:connector-type" attribute.
+ return true;
+ }
+ }
+ return false;
+}
+
+
+void cc_selection_set_avoid(bool const set_avoid)
+{
+ SPDesktop *desktop = inkscape_active_desktop();
+ if (desktop == NULL) {
+ return;
+ }
+
+ SPDocument *document = sp_desktop_document(desktop);
+
+ Inkscape::Selection *selection = sp_desktop_selection(desktop);
+
+ GSList *l = const_cast<GSList *>(selection->itemList());
+
+ int changes = 0;
+
+ while (l) {
+ SPItem *item = SP_ITEM(l->data);
+
+ char const *value = (set_avoid) ? "true" : NULL;
+
+ if (cc_item_is_shape(item)) {
+ item->setAttribute("inkscape:connector-avoid", value, NULL);
+ item->avoidRef->handleSettingChange();
+ changes++;
+ }
+
+ l = l->next;
+ }
+
+ if (changes == 0) {
+ desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE,
+ _("Select <b>at least one non-connector object</b>."));
+ return;
+ }
+
+ char *event_desc = (set_avoid) ?
+ _("Make connectors avoid selected objects") :
+ _("Make connectors ignore selected objects");
+ DocumentUndo::done(document, SP_VERB_CONTEXT_CONNECTOR, event_desc);
+}
+
+void ConnectorTool::selection_changed(Inkscape::Selection *selection) {
+ SPItem *item = selection->singleItem();
+
+ if (this->active_conn == item) {
+ // Nothing to change.
+ return;
+ }
+
+ if (item == NULL) {
+ cc_clear_active_conn(this);
+ return;
+ }
+
+ if (cc_item_is_connector(item)) {
+ cc_set_active_conn(this, item);
+ }
+}
+
+static void
+shape_event_attr_deleted(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child,
+ Inkscape::XML::Node */*ref*/, gpointer data)
+{
+ g_assert(data);
+ ConnectorTool *cc = SP_CONNECTOR_CONTEXT(data);
+
+ if (child == cc->active_shape_repr) {
+ // The active shape has been deleted. Clear active shape.
+ cc_clear_active_shape(cc);
+ }
+}
+
+
+static void
+shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
+ gchar const */*old_value*/, gchar const */*new_value*/,
+ bool /*is_interactive*/, gpointer data)
+{
+ g_assert(data);
+ ConnectorTool *cc = SP_CONNECTOR_CONTEXT(data);
+
+ // Look for changes that result in onscreen movement.
+ if (!strcmp(name, "d") || !strcmp(name, "x") || !strcmp(name, "y") ||
+ !strcmp(name, "width") || !strcmp(name, "height") ||
+ !strcmp(name, "transform"))
+ {
+ if (repr == cc->active_shape_repr) {
+ // Active shape has moved. Clear active shape.
+ cc_clear_active_shape(cc);
+ }
+ else if (repr == cc->active_conn_repr) {
+ // The active conn has been moved.
+ // Set it again, which just sets new handle positions.
+ cc_set_active_conn(cc, cc->active_conn);
+ }
+ }
+}
+
+}
+}
+}
+
+
+/*
+ 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/tools/connector-tool.h b/src/ui/tools/connector-tool.h
new file mode 100644
index 000000000..7cd6a6dad
--- /dev/null
+++ b/src/ui/tools/connector-tool.h
@@ -0,0 +1,135 @@
+#ifndef SEEN_CONNECTOR_CONTEXT_H
+#define SEEN_CONNECTOR_CONTEXT_H
+
+/*
+ * Connector creation tool
+ *
+ * Authors:
+ * Michael Wybrow <mjwybrow@users.sourceforge.net>
+ *
+ * Copyright (C) 2005 Michael Wybrow
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <stddef.h>
+#include <sigc++/sigc++.h>
+#include <sigc++/connection.h>
+#include "ui/tools/tool-base.h"
+#include <2geom/point.h>
+#include "libavoid/connector.h"
+#include <glibmm/i18n.h>
+
+#define SP_CONNECTOR_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::ConnectorTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+//#define SP_IS_CONNECTOR_CONTEXT(obj) (dynamic_cast<const ConnectorTool*>((const ToolBase*)obj) != NULL)
+
+struct SPKnot;
+class SPCurve;
+
+namespace Inkscape
+{
+ class Selection;
+}
+
+enum {
+ SP_CONNECTOR_CONTEXT_IDLE,
+ SP_CONNECTOR_CONTEXT_DRAGGING,
+ SP_CONNECTOR_CONTEXT_CLOSE,
+ SP_CONNECTOR_CONTEXT_STOP,
+ SP_CONNECTOR_CONTEXT_REROUTING,
+ SP_CONNECTOR_CONTEXT_NEWCONNPOINT
+};
+
+typedef std::map<SPKnot *, int> SPKnotList;
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class ConnectorTool : public ToolBase {
+public:
+ ConnectorTool();
+ virtual ~ConnectorTool();
+
+ Inkscape::Selection *selection;
+ Geom::Point p[5];
+
+ /** \invar npoints in {0, 2}. */
+ gint npoints;
+ unsigned int state : 4;
+
+ // Red curve
+ SPCanvasItem *red_bpath;
+ SPCurve *red_curve;
+ guint32 red_color;
+
+ // Green curve
+ SPCurve *green_curve;
+
+ // The new connector
+ SPItem *newconn;
+ Avoid::ConnRef *newConnRef;
+ gdouble curvature;
+ bool isOrthogonal;
+
+ // The active shape
+ SPItem *active_shape;
+ Inkscape::XML::Node *active_shape_repr;
+ Inkscape::XML::Node *active_shape_layer_repr;
+
+ // Same as above, but for the active connector
+ SPItem *active_conn;
+ Inkscape::XML::Node *active_conn_repr;
+ sigc::connection sel_changed_connection;
+
+ // The activehandle
+ SPKnot *active_handle;
+
+ // The selected handle, used in editing mode
+ SPKnot *selected_handle;
+
+ SPItem *clickeditem;
+ SPKnot *clickedhandle;
+
+ SPKnotList knots;
+ SPKnot *endpt_handle[2];
+ guint endpt_handler_id[2];
+ gchar *shref;
+ gchar *ehref;
+ SPCanvasItem *c0, *c1, *cl0, *cl1;
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual void finish();
+ virtual void set(const Inkscape::Preferences::Entry& val);
+ virtual bool root_handler(GdkEvent* event);
+ virtual bool item_handler(SPItem* item, GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+private:
+ void selection_changed(Inkscape::Selection *selection);
+};
+
+void cc_selection_set_avoid(bool const set_ignore);
+void cc_create_connection_point(ConnectorTool* cc);
+void cc_remove_connection_point(ConnectorTool* cc);
+bool cc_item_is_connector(SPItem *item);
+
+}
+}
+}
+
+#endif /* !SEEN_CONNECTOR_CONTEXT_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/tools/dropper-tool.cpp b/src/ui/tools/dropper-tool.cpp
new file mode 100644
index 000000000..9c47b50e9
--- /dev/null
+++ b/src/ui/tools/dropper-tool.cpp
@@ -0,0 +1,414 @@
+/*
+ * Tool for picking colors from drawing
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 1999-2005 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <glibmm/i18n.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <2geom/transforms.h>
+#include <2geom/circle.h>
+
+#include "macros.h"
+#include "display/canvas-bpath.h"
+#include "display/canvas-arena.h"
+#include "display/curve.h"
+#include "display/cairo-utils.h"
+#include "svg/svg-color.h"
+#include "color.h"
+#include "color-rgba.h"
+#include "desktop-style.h"
+#include "preferences.h"
+#include "sp-namedview.h"
+#include "sp-cursor.h"
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "selection.h"
+#include "document.h"
+#include "document-undo.h"
+
+#include "pixmaps/cursor-dropper-f.xpm"
+#include "pixmaps/cursor-dropper-s.xpm"
+
+#include "ui/tools/dropper-tool.h"
+#include "message-context.h"
+#include "verbs.h"
+#include "ui/tools/tool-base.h"
+
+using Inkscape::DocumentUndo;
+
+static GdkCursor *cursor_dropper_fill = NULL;
+static GdkCursor *cursor_dropper_stroke = NULL;
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+namespace {
+ ToolBase* createDropperContext() {
+ return new DropperTool();
+ }
+
+ bool dropperContextRegistered = ToolFactory::instance().registerObject("/tools/dropper", createDropperContext);
+}
+
+const std::string& DropperTool::getPrefsPath() {
+ return DropperTool::prefsPath;
+}
+
+const std::string DropperTool::prefsPath = "/tools/dropper";
+
+DropperTool::DropperTool() : ToolBase() {
+ this->R = 0;
+ this->G = 0;
+ this->B = 0;
+ this->alpha = 0;
+ this->dragging = false;
+
+ this->grabbed = 0;
+ this->area = 0;
+ this->centre = Geom::Point(0, 0);
+
+ this->cursor_shape = cursor_dropper_f_xpm;
+ this->hot_x = 7;
+ this->hot_y = 7;
+
+ cursor_dropper_fill = sp_cursor_new_from_xpm(cursor_dropper_f_xpm , 7, 7);
+ cursor_dropper_stroke = sp_cursor_new_from_xpm(cursor_dropper_s_xpm , 7, 7);
+}
+
+DropperTool::~DropperTool() {
+}
+
+void DropperTool::setup() {
+ ToolBase::setup();
+
+ /* TODO: have a look at CalligraphicTool::setup where the same is done.. generalize? */
+ Geom::PathVector path;
+ Geom::Circle(0, 0, 1).getPath(path);
+
+ SPCurve *c = new SPCurve(path);
+
+ this->area = sp_canvas_bpath_new(sp_desktop_controls(this->desktop), c);
+
+ c->unref();
+
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->area), 0x00000000,(SPWindRule)0);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->area), 0x0000007f, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_item_hide(this->area);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (prefs->getBool("/tools/dropper/selcue")) {
+ this->enableSelectionCue();
+ }
+
+ if (prefs->getBool("/tools/dropper/gradientdrag")) {
+ this->enableGrDrag();
+ }
+}
+
+void DropperTool::finish() {
+ this->enableGrDrag(false);
+
+ if (this->grabbed) {
+ sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME);
+ this->grabbed = NULL;
+ }
+
+ if (this->area) {
+ sp_canvas_item_destroy(this->area);
+ this->area = NULL;
+ }
+
+ if (cursor_dropper_fill) {
+#if GTK_CHECK_VERSION(3,0,0)
+ g_object_unref(cursor_dropper_fill);
+#else
+ gdk_cursor_unref (cursor_dropper_fill);
+#endif
+ cursor_dropper_fill = NULL;
+ }
+
+ if (cursor_dropper_stroke) {
+#if GTK_CHECK_VERSION(3,0,0)
+ g_object_unref(cursor_dropper_stroke);
+#else
+ gdk_cursor_unref (cursor_dropper_stroke);
+#endif
+ cursor_dropper_fill = NULL;
+ }
+}
+
+/**
+ * Returns the current dropper context color.
+ */
+guint32 DropperTool::get_color() {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ int pick = prefs->getInt("/tools/dropper/pick", SP_DROPPER_PICK_VISIBLE);
+ bool setalpha = prefs->getBool("/tools/dropper/setalpha", true);
+
+ return SP_RGBA32_F_COMPOSE(this->R,
+ this->G,
+ this->B,
+ (pick == SP_DROPPER_PICK_ACTUAL && setalpha) ? this->alpha : 1.0);
+}
+
+bool DropperTool::root_handler(GdkEvent* event) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ int ret = FALSE;
+
+ int pick = prefs->getInt("/tools/dropper/pick", SP_DROPPER_PICK_VISIBLE);
+ bool setalpha = prefs->getBool("/tools/dropper/setalpha", true);
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+ this->centre = Geom::Point(event->button.x, event->button.y);
+ this->dragging = true;
+ ret = TRUE;
+ }
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK,
+ NULL, event->button.time);
+ this->grabbed = SP_CANVAS_ITEM(desktop->acetate);
+
+ break;
+ case GDK_MOTION_NOTIFY:
+ if (event->motion.state & GDK_BUTTON2_MASK || event->motion.state & GDK_BUTTON3_MASK) {
+ // pass on middle and right drag
+ ret = FALSE;
+ break;
+ } else if (!this->space_panning) {
+ // otherwise, constantly calculate color no matter is any button pressed or not
+
+ // If one time pick with stroke set the pixmap
+ if (prefs->getBool("/tools/dropper/onetimepick", false) && prefs->getInt("/dialogs/fillstroke/page", 0) == 1) {
+ //TODO Only set when not set already
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(sp_desktop_canvas(desktop)));
+ gdk_window_set_cursor(window, cursor_dropper_stroke);
+ }
+
+ double rw = 0.0;
+ double R(0), G(0), B(0), A(0);
+
+ if (this->dragging) {
+ // calculate average
+
+ // radius
+ rw = std::min(Geom::L2(Geom::Point(event->button.x, event->button.y) - this->centre), 400.0);
+
+ if (rw == 0) { // happens sometimes, little idea why...
+ break;
+ }
+
+ Geom::Point const cd = desktop->w2d(this->centre);
+ Geom::Affine const w2dt = desktop->w2d();
+ const double scale = rw * w2dt.descrim();
+ Geom::Affine const sm( Geom::Scale(scale, scale) * Geom::Translate(cd) );
+ sp_canvas_item_affine_absolute(this->area, sm);
+ sp_canvas_item_show(this->area);
+
+ /* Get buffer */
+ Geom::Rect r(this->centre, this->centre);
+ r.expandBy(rw);
+ if (!r.hasZeroArea()) {
+ Geom::IntRect area = r.roundOutwards();
+ cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, area.width(), area.height());
+ sp_canvas_arena_render_surface(SP_CANVAS_ARENA(sp_desktop_drawing(desktop)), s, area);
+ ink_cairo_surface_average_color_premul(s, R, G, B, A);
+ cairo_surface_destroy(s);
+ }
+ } else {
+ // pick single pixel
+ Geom::IntRect area = Geom::IntRect::from_xywh(floor(event->button.x), floor(event->button.y), 1, 1);
+ cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
+ sp_canvas_arena_render_surface(SP_CANVAS_ARENA(sp_desktop_drawing(desktop)), s, area);
+ ink_cairo_surface_average_color_premul(s, R, G, B, A);
+ cairo_surface_destroy(s);
+ }
+
+ if (pick == SP_DROPPER_PICK_VISIBLE) {
+ // compose with page color
+ guint32 bg = sp_desktop_namedview(desktop)->pagecolor;
+ R = R + (SP_RGBA32_R_F(bg)) * (1 - A);
+ G = G + (SP_RGBA32_G_F(bg)) * (1 - A);
+ B = B + (SP_RGBA32_B_F(bg)) * (1 - A);
+ A = 1.0;
+ } else {
+ // un-premultiply color channels
+ if (A > 0) {
+ R /= A;
+ G /= A;
+ B /= A;
+ }
+ }
+
+ if (fabs(A) < 1e-4) {
+ A = 0; // suppress exponentials, CSS does not allow that
+ }
+
+ // remember color
+ this->R = R;
+ this->G = G;
+ this->B = B;
+ this->alpha = A;
+
+ // status message
+ double alpha_to_set = setalpha? this->alpha : 1.0;
+ guint32 c32 = SP_RGBA32_F_COMPOSE(R, G, B, alpha_to_set);
+
+ gchar c[64];
+ sp_svg_write_color(c, sizeof(c), c32);
+
+ // alpha of color under cursor, to show in the statusbar
+ // locale-sensitive printf is OK, since this goes to the UI, not into SVG
+ gchar *alpha = g_strdup_printf(_(" alpha %.3g"), alpha_to_set);
+ // where the color is picked, to show in the statusbar
+ gchar *where = this->dragging ? g_strdup_printf(_(", averaged with radius %d"), (int) rw) : g_strdup_printf("%s", _(" under cursor"));
+ // message, to show in the statusbar
+ const gchar *message = this->dragging ? _("<b>Release mouse</b> to set color.") : _("<b>Click</b> to set fill, <b>Shift+click</b> to set stroke; <b>drag</b> to average color in area; with <b>Alt</b> to pick inverse color; <b>Ctrl+C</b> to copy the color under mouse to clipboard");
+
+ this->defaultMessageContext()->setF(
+ Inkscape::NORMAL_MESSAGE,
+ "<b>%s%s</b>%s. %s", c,
+ (pick == SP_DROPPER_PICK_VISIBLE) ? "" : alpha, where, message);
+
+ g_free(where);
+ g_free(alpha);
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ if (event->button.button == 1 && !this->space_panning) {
+ sp_canvas_item_hide(this->area);
+ this->dragging = false;
+
+ if (this->grabbed) {
+ sp_canvas_item_ungrab(this->grabbed, event->button.time);
+ this->grabbed = NULL;
+ }
+
+ double alpha_to_set = setalpha? this->alpha : 1.0;
+
+ bool fill = !(event->button.state & GDK_SHIFT_MASK); // Stroke if Shift key held
+
+ if (prefs->getBool("/tools/dropper/onetimepick", false)) {
+ // "One time" pick from Fill/Stroke dialog stroke page, always apply fill or stroke (ignore <Shift> key)
+ fill = (prefs->getInt("/dialogs/fillstroke/page", 0) == 0) ? true : false;
+ }
+
+ // do the actual color setting
+ sp_desktop_set_color(desktop,
+ (event->button.state & GDK_MOD1_MASK)?
+ ColorRGBA(1 - this->R, 1 - this->G, 1 - this->B, alpha_to_set) : ColorRGBA(this->R, this->G, this->B, alpha_to_set),
+ false, fill);
+
+ // REJON: set aux. toolbar input to hex color!
+
+ if (event->button.state & GDK_SHIFT_MASK) {
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(sp_desktop_canvas(desktop)));
+ gdk_window_set_cursor(window, cursor_dropper_stroke);
+ }
+
+ if (!(sp_desktop_selection(desktop)->isEmpty())) {
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_DROPPER,
+ _("Set picked color"));
+ }
+
+ if (prefs->getBool("/tools/dropper/onetimepick", false)) {
+ prefs->setBool("/tools/dropper/onetimepick", false);
+ sp_toggle_dropper(desktop);
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval(&event->key)) {
+ case GDK_KEY_Up:
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_Down:
+ // prevent the zoom field from activation
+ if (!MOD__CTRL_ONLY(event)) {
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Escape:
+ sp_desktop_selection(desktop)->clear();
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ if (!desktop->isWaitingCursor() && !prefs->getBool("/tools/dropper/onetimepick", false)) {
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(sp_desktop_canvas(desktop)));
+ gdk_window_set_cursor(window, cursor_dropper_stroke);
+ }
+
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case GDK_KEY_RELEASE:
+ switch (get_group0_keyval(&event->key)) {
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ if (!desktop->isWaitingCursor() && !prefs->getBool("/tools/dropper/onetimepick", false)) {
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(sp_desktop_canvas(desktop)));
+ gdk_window_set_cursor(window, cursor_dropper_fill);
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+}
+}
+}
+
+
+/*
+ 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/tools/dropper-tool.h b/src/ui/tools/dropper-tool.h
new file mode 100644
index 000000000..dd6f25bb6
--- /dev/null
+++ b/src/ui/tools/dropper-tool.h
@@ -0,0 +1,73 @@
+#ifndef __SP_DROPPER_CONTEXT_H__
+#define __SP_DROPPER_CONTEXT_H__
+
+/*
+ * Tool for picking colors from drawing
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 1999-2002 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/tools/tool-base.h"
+
+#define SP_DROPPER_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::DropperTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_DROPPER_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::DropperTool*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+enum {
+ SP_DROPPER_PICK_VISIBLE,
+ SP_DROPPER_PICK_ACTUAL
+};
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class DropperTool : public ToolBase {
+public:
+ DropperTool();
+ virtual ~DropperTool();
+
+ static const std::string prefsPath;
+
+ virtual const std::string& getPrefsPath();
+
+ guint32 get_color();
+
+protected:
+ virtual void setup();
+ virtual void finish();
+ virtual bool root_handler(GdkEvent* event);
+
+private:
+ double R;
+ double G;
+ double B;
+ double alpha;
+
+ bool dragging;
+
+ SPCanvasItem* grabbed;
+ SPCanvasItem* area;
+ Geom::Point centre;
+};
+
+}
+}
+}
+
+#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/tools/dynamic-base.cpp b/src/ui/tools/dynamic-base.cpp
new file mode 100644
index 000000000..cec58dce9
--- /dev/null
+++ b/src/ui/tools/dynamic-base.cpp
@@ -0,0 +1,162 @@
+
+#include "ui/tools/dynamic-base.h"
+
+#include <gtk/gtk.h>
+
+#include "config.h"
+
+#include "message-context.h"
+#include "streq.h"
+#include "preferences.h"
+#include "display/sp-canvas-item.h"
+#include "desktop.h"
+
+#define MIN_PRESSURE 0.0
+#define MAX_PRESSURE 1.0
+#define DEFAULT_PRESSURE 1.0
+
+#define DRAG_MIN 0.0
+#define DRAG_DEFAULT 1.0
+#define DRAG_MAX 1.0
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+DynamicBase::DynamicBase() :
+ ToolBase(),
+ accumulated(NULL),
+ segments(NULL),
+ currentshape(NULL),
+ currentcurve(NULL),
+ cal1(NULL),
+ cal2(NULL),
+ point1(),
+ point2(),
+ repr(NULL),
+ cur(0,0),
+ vel(0,0),
+ vel_max(0),
+ acc(0,0),
+ ang(0,0),
+ last(0,0),
+ del(0,0),
+ pressure(DEFAULT_PRESSURE),
+ xtilt(0),
+ ytilt(0),
+ dragging(FALSE),
+ usepressure(FALSE),
+ usetilt(FALSE),
+ mass(0.3),
+ drag(DRAG_DEFAULT),
+ angle(30.0),
+ width(0.2),
+ vel_thin(0.1),
+ flatness(0.9),
+ tremor(0),
+ cap_rounding(0),
+ is_drawing(false),
+ abs_width(false)
+{
+}
+
+DynamicBase::~DynamicBase() {
+ if (this->accumulated) {
+ this->accumulated = this->accumulated->unref();
+ this->accumulated = 0;
+ }
+
+ while (this->segments) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data));
+ this->segments = g_slist_remove(this->segments, this->segments->data);
+ }
+
+ if (this->currentcurve) {
+ this->currentcurve = this->currentcurve->unref();
+ this->currentcurve = 0;
+ }
+
+ if (this->cal1) {
+ this->cal1 = this->cal1->unref();
+ this->cal1 = 0;
+ }
+
+ if (this->cal2) {
+ this->cal2 = this->cal2->unref();
+ this->cal2 = 0;
+ }
+
+ if (this->currentshape) {
+ sp_canvas_item_destroy(this->currentshape);
+ this->currentshape = 0;
+ }
+}
+
+void DynamicBase::set(const Inkscape::Preferences::Entry& value) {
+ Glib::ustring path = value.getEntryName();
+
+ // ignore preset modifications
+ static Glib::ustring const presets_path = this->pref_observer->observed_path + "/preset";
+ Glib::ustring const &full_path = value.getPath();
+
+ if (full_path.compare(0, presets_path.size(), presets_path) == 0) {
+ return;
+ }
+
+ if (path == "mass") {
+ this->mass = 0.01 * CLAMP(value.getInt(10), 0, 100);
+ } else if (path == "wiggle") {
+ this->drag = CLAMP((1 - 0.01 * value.getInt()), DRAG_MIN, DRAG_MAX); // drag is inverse to wiggle
+ } else if (path == "angle") {
+ this->angle = CLAMP(value.getDouble(), -90, 90);
+ } else if (path == "width") {
+ this->width = 0.01 * CLAMP(value.getInt(10), 1, 100);
+ } else if (path == "thinning") {
+ this->vel_thin = 0.01 * CLAMP(value.getInt(10), -100, 100);
+ } else if (path == "tremor") {
+ this->tremor = 0.01 * CLAMP(value.getInt(), 0, 100);
+ } else if (path == "flatness") {
+ this->flatness = 0.01 * CLAMP(value.getInt(), 0, 100);
+ } else if (path == "usepressure") {
+ this->usepressure = value.getBool();
+ } else if (path == "usetilt") {
+ this->usetilt = value.getBool();
+ } else if (path == "abs_width") {
+ this->abs_width = value.getBool();
+ } else if (path == "cap_rounding") {
+ this->cap_rounding = value.getDouble();
+ }
+}
+
+/* Get normalized point */
+Geom::Point DynamicBase::getNormalizedPoint(Geom::Point v) const {
+ Geom::Rect drect = this->desktop->get_display_area();
+
+ double const max = MAX ( drect.dimensions()[Geom::X], drect.dimensions()[Geom::Y] );
+
+ return Geom::Point(( v[Geom::X] - drect.min()[Geom::X] ) / max, ( v[Geom::Y] - drect.min()[Geom::Y] ) / max);
+}
+
+/* Get view point */
+Geom::Point DynamicBase::getViewPoint(Geom::Point n) const {
+ Geom::Rect drect = this->desktop->get_display_area();
+
+ double const max = MAX ( drect.dimensions()[Geom::X], drect.dimensions()[Geom::Y] );
+
+ return Geom::Point(n[Geom::X] * max + drect.min()[Geom::X], n[Geom::Y] * max + drect.min()[Geom::Y]);
+}
+
+}
+}
+}
+
+/*
+ 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/tools/dynamic-base.h b/src/ui/tools/dynamic-base.h
new file mode 100644
index 000000000..9218eabd3
--- /dev/null
+++ b/src/ui/tools/dynamic-base.h
@@ -0,0 +1,123 @@
+#ifndef COMMON_CONTEXT_H_SEEN
+#define COMMON_CONTEXT_H_SEEN
+
+/*
+ * Common drawing mode
+ *
+ * Authors:
+ * Mitsuru Oka <oka326@parkcity.ne.jp>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * The original dynadraw code:
+ * Paul Haeberli <paul@sgi.com>
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ * Copyright (C) 1999-2002 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ * Copyright (C) 2008 Jon A. Cruz
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/tools/tool-base.h"
+#include "display/curve.h"
+#include <2geom/point.h>
+
+#define SAMPLING_SIZE 8 /* fixme: ?? */
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class DynamicBase : public ToolBase {
+public:
+ DynamicBase();
+ virtual ~DynamicBase();
+
+ virtual void set(const Inkscape::Preferences::Entry& val);
+
+protected:
+ /** accumulated shape which ultimately goes in svg:path */
+ SPCurve *accumulated;
+
+ /** canvas items for "comitted" segments */
+ GSList *segments;
+
+ /** canvas item for red "leading" segment */
+ SPCanvasItem *currentshape;
+ /** shape of red "leading" segment */
+ SPCurve *currentcurve;
+
+ /** left edge of the stroke; combined to get accumulated */
+ SPCurve *cal1;
+
+ /** right edge of the stroke; combined to get accumulated */
+ SPCurve *cal2;
+
+ /** left edge points for this segment */
+ Geom::Point point1[SAMPLING_SIZE];
+
+ /** right edge points for this segment */
+ Geom::Point point2[SAMPLING_SIZE];
+
+ /** number of edge points for this segment */
+ gint npoints;
+
+ /* repr */
+ Inkscape::XML::Node *repr;
+
+ /* common */
+ Geom::Point cur;
+ Geom::Point vel;
+ double vel_max;
+ Geom::Point acc;
+ Geom::Point ang;
+ Geom::Point last;
+ Geom::Point del;
+
+ /* extended input data */
+ gdouble pressure;
+ gdouble xtilt;
+ gdouble ytilt;
+
+ /* attributes */
+ guint dragging : 1; /* mouse state: mouse is dragging */
+ guint usepressure : 1;
+ guint usetilt : 1;
+ double mass, drag;
+ double angle;
+ double width;
+
+ double vel_thin;
+ double flatness;
+ double tremor;
+ double cap_rounding;
+
+ //Inkscape::MessageContext *_message_context;
+
+ bool is_drawing;
+
+ /** uses absolute width independent of zoom */
+ bool abs_width;
+
+ Geom::Point getViewPoint(Geom::Point n) const;
+ Geom::Point getNormalizedPoint(Geom::Point v) const;
+};
+
+}
+}
+}
+
+#endif // COMMON_CONTEXT_H_SEEN
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
+
diff --git a/src/ui/tools/eraser-tool.cpp b/src/ui/tools/eraser-tool.cpp
new file mode 100644
index 000000000..270987d27
--- /dev/null
+++ b/src/ui/tools/eraser-tool.cpp
@@ -0,0 +1,1019 @@
+/*
+ * Eraser drawing mode
+ *
+ * Authors:
+ * Mitsuru Oka <oka326@parkcity.ne.jp>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * MenTaLguY <mental@rydia.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * The original dynadraw code:
+ * Paul Haeberli <paul@sgi.com>
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ * Copyright (C) 1999-2005 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ * Copyright (C) 2005-2007 bulia byak
+ * Copyright (C) 2006 MenTaLguY
+ * Copyright (C) 2008 Jon A. Cruz
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#define noERASER_VERBOSE
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glibmm/i18n.h>
+#include <string>
+#include <cstring>
+#include <numeric>
+
+#include "svg/svg.h"
+#include "display/sp-canvas.h"
+#include "display/canvas-bpath.h"
+#include <2geom/bezier-utils.h>
+
+#include <glib.h>
+#include "macros.h"
+#include "document.h"
+#include "selection.h"
+#include "desktop.h"
+#include "desktop-events.h"
+#include "desktop-handles.h"
+#include "desktop-style.h"
+#include "message-context.h"
+#include "preferences.h"
+#include "pixmaps/cursor-eraser.xpm"
+#include "xml/repr.h"
+#include "context-fns.h"
+#include "sp-item.h"
+#include "color.h"
+#include "rubberband.h"
+#include "splivarot.h"
+#include "sp-item-group.h"
+#include "sp-shape.h"
+#include "sp-path.h"
+#include "sp-text.h"
+#include "display/canvas-bpath.h"
+#include "display/canvas-arena.h"
+#include "livarot/Shape.h"
+#include "document-undo.h"
+#include "verbs.h"
+#include <2geom/math-utils.h>
+#include <2geom/pathvector.h>
+
+#include "ui/tools/eraser-tool.h"
+
+using Inkscape::DocumentUndo;
+
+#define ERC_RED_RGBA 0xff0000ff
+
+#define TOLERANCE_ERASER 0.1
+
+#define ERASER_EPSILON 0.5e-6
+#define ERASER_EPSILON_START 0.5e-2
+#define ERASER_VEL_START 1e-5
+
+#define DRAG_MIN 0.0
+#define DRAG_DEFAULT 1.0
+#define DRAG_MAX 1.0
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+namespace {
+ ToolBase* createEraserContext() {
+ return new EraserTool();
+ }
+
+ bool eraserContextRegistered = ToolFactory::instance().registerObject("/tools/eraser", createEraserContext);
+}
+
+const std::string& EraserTool::getPrefsPath() {
+ return EraserTool::prefsPath;
+}
+
+const std::string EraserTool::prefsPath = "/tools/eraser";
+
+EraserTool::EraserTool() : DynamicBase() {
+ this->cursor_shape = cursor_eraser_xpm;
+ this->hot_x = 4;
+ this->hot_y = 4;
+}
+
+EraserTool::~EraserTool() {
+}
+
+void EraserTool::setup() {
+ DynamicBase::setup();
+
+ this->accumulated = new SPCurve();
+ this->currentcurve = new SPCurve();
+
+ this->cal1 = new SPCurve();
+ this->cal2 = new SPCurve();
+
+ this->currentshape = sp_canvas_item_new(sp_desktop_sketch(desktop), SP_TYPE_CANVAS_BPATH, NULL);
+
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->currentshape), ERC_RED_RGBA, SP_WIND_RULE_EVENODD);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->currentshape), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+
+ /* fixme: Cannot we cascade it to root more clearly? */
+ g_signal_connect(G_OBJECT(this->currentshape), "event", G_CALLBACK(sp_desktop_root_handler), desktop);
+
+/*
+static ProfileFloatElement f_profile[PROFILE_FLOAT_SIZE] = {
+ {"mass",0.02, 0.0, 1.0},
+ {"wiggle",0.0, 0.0, 1.0},
+ {"angle",30.0, -90.0, 90.0},
+ {"thinning",0.1, -1.0, 1.0},
+ {"tremor",0.0, 0.0, 1.0},
+ {"flatness",0.9, 0.0, 1.0},
+ {"cap_rounding",0.0, 0.0, 5.0}
+};
+*/
+
+ sp_event_context_read(this, "mass");
+ sp_event_context_read(this, "wiggle");
+ sp_event_context_read(this, "angle");
+ sp_event_context_read(this, "width");
+ sp_event_context_read(this, "thinning");
+ sp_event_context_read(this, "tremor");
+ sp_event_context_read(this, "flatness");
+ sp_event_context_read(this, "tracebackground");
+ sp_event_context_read(this, "usepressure");
+ sp_event_context_read(this, "usetilt");
+ sp_event_context_read(this, "abs_width");
+ sp_event_context_read(this, "cap_rounding");
+
+ this->is_drawing = false;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/eraser/selcue", 0) != 0) {
+ this->enableSelectionCue();
+ }
+
+ // TODO temp force:
+ this->enableSelectionCue();
+}
+
+static double
+flerp(double f0, double f1, double p)
+{
+ return f0 + ( f1 - f0 ) * p;
+}
+
+void EraserTool::reset(Geom::Point p) {
+ this->last = this->cur = getNormalizedPoint(p);
+ this->vel = Geom::Point(0,0);
+ this->vel_max = 0;
+ this->acc = Geom::Point(0,0);
+ this->ang = Geom::Point(0,0);
+ this->del = Geom::Point(0,0);
+}
+
+void EraserTool::extinput(GdkEvent *event) {
+ if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &this->pressure))
+ this->pressure = CLAMP (this->pressure, ERC_MIN_PRESSURE, ERC_MAX_PRESSURE);
+ else
+ this->pressure = ERC_DEFAULT_PRESSURE;
+
+ if (gdk_event_get_axis (event, GDK_AXIS_XTILT, &this->xtilt))
+ this->xtilt = CLAMP (this->xtilt, ERC_MIN_TILT, ERC_MAX_TILT);
+ else
+ this->xtilt = ERC_DEFAULT_TILT;
+
+ if (gdk_event_get_axis (event, GDK_AXIS_YTILT, &this->ytilt))
+ this->ytilt = CLAMP (this->ytilt, ERC_MIN_TILT, ERC_MAX_TILT);
+ else
+ this->ytilt = ERC_DEFAULT_TILT;
+}
+
+
+bool EraserTool::apply(Geom::Point p) {
+ Geom::Point n = getNormalizedPoint(p);
+
+ /* Calculate mass and drag */
+ double const mass = flerp(1.0, 160.0, this->mass);
+ double const drag = flerp(0.0, 0.5, this->drag * this->drag);
+
+ /* Calculate force and acceleration */
+ Geom::Point force = n - this->cur;
+
+ // If force is below the absolute threshold ERASER_EPSILON,
+ // or we haven't yet reached ERASER_VEL_START (i.e. at the beginning of stroke)
+ // _and_ the force is below the (higher) ERASER_EPSILON_START threshold,
+ // discard this move.
+ // This prevents flips, blobs, and jerks caused by microscopic tremor of the tablet pen,
+ // especially bothersome at the start of the stroke where we don't yet have the inertia to
+ // smooth them out.
+ if ( Geom::L2(force) < ERASER_EPSILON || (this->vel_max < ERASER_VEL_START && Geom::L2(force) < ERASER_EPSILON_START)) {
+ return FALSE;
+ }
+
+ this->acc = force / mass;
+
+ /* Calculate new velocity */
+ this->vel += this->acc;
+
+ if (Geom::L2(this->vel) > this->vel_max)
+ this->vel_max = Geom::L2(this->vel);
+
+ /* Calculate angle of drawing tool */
+
+ double a1;
+ if (this->usetilt) {
+ // 1a. calculate nib angle from input device tilt:
+ gdouble length = std::sqrt(this->xtilt*this->xtilt + this->ytilt*this->ytilt);;
+
+ if (length > 0) {
+ Geom::Point ang1 = Geom::Point(this->ytilt/length, this->xtilt/length);
+ a1 = atan2(ang1);
+ }
+ else
+ a1 = 0.0;
+ }
+ else {
+ // 1b. fixed dc->angle (absolutely flat nib):
+ double const radians = ( (this->angle - 90) / 180.0 ) * M_PI;
+ Geom::Point ang1 = Geom::Point(-sin(radians), cos(radians));
+ a1 = atan2(ang1);
+ }
+
+ // 2. perpendicular to dc->vel (absolutely non-flat nib):
+ gdouble const mag_vel = Geom::L2(this->vel);
+ if ( mag_vel < ERASER_EPSILON ) {
+ return FALSE;
+ }
+ Geom::Point ang2 = Geom::rot90(this->vel) / mag_vel;
+
+ // 3. Average them using flatness parameter:
+ // calculate angles
+ double a2 = atan2(ang2);
+ // flip a2 to force it to be in the same half-circle as a1
+ bool flipped = false;
+ if (fabs (a2-a1) > 0.5*M_PI) {
+ a2 += M_PI;
+ flipped = true;
+ }
+ // normalize a2
+ if (a2 > M_PI)
+ a2 -= 2*M_PI;
+ if (a2 < -M_PI)
+ a2 += 2*M_PI;
+ // find the flatness-weighted bisector angle, unflip if a2 was flipped
+ // FIXME: when dc->vel is oscillating around the fixed angle, the new_ang flips back and forth. How to avoid this?
+ double new_ang = a1 + (1 - this->flatness) * (a2 - a1) - (flipped? M_PI : 0);
+
+ // Try to detect a sudden flip when the new angle differs too much from the previous for the
+ // current velocity; in that case discard this move
+ double angle_delta = Geom::L2(Geom::Point (cos (new_ang), sin (new_ang)) - this->ang);
+ if ( angle_delta / Geom::L2(this->vel) > 4000 ) {
+ return FALSE;
+ }
+
+ // convert to point
+ this->ang = Geom::Point (cos (new_ang), sin (new_ang));
+
+// g_print ("force %g acc %g vel_max %g vel %g a1 %g a2 %g new_ang %g\n", Geom::L2(force), Geom::L2(dc->acc), dc->vel_max, Geom::L2(dc->vel), a1, a2, new_ang);
+
+ /* Apply drag */
+ this->vel *= 1.0 - drag;
+
+ /* Update position */
+ this->last = this->cur;
+ this->cur += this->vel;
+
+ return TRUE;
+}
+
+void EraserTool::brush() {
+ g_assert( this->npoints >= 0 && this->npoints < SAMPLING_SIZE );
+
+ // How much velocity thins strokestyle
+ double vel_thin = flerp (0, 160, this->vel_thin);
+
+ // Influence of pressure on thickness
+ double pressure_thick = (this->usepressure ? this->pressure : 1.0);
+
+ // get the real brush point, not the same as pointer (affected by hatch tracking and/or mass
+ // drag)
+ Geom::Point brush = getViewPoint(this->cur);
+ //Geom::Point brush_w = SP_EVENT_CONTEXT(dc)->desktop->d2w(brush);
+
+ double trace_thick = 1;
+
+ double width = (pressure_thick * trace_thick - vel_thin * Geom::L2(this->vel)) * this->width;
+
+ double tremble_left = 0, tremble_right = 0;
+ if (this->tremor > 0) {
+ // obtain two normally distributed random variables, using polar Box-Muller transform
+ double x1, x2, w, y1, y2;
+ do {
+ x1 = 2.0 * g_random_double_range(0,1) - 1.0;
+ x2 = 2.0 * g_random_double_range(0,1) - 1.0;
+ w = x1 * x1 + x2 * x2;
+ } while ( w >= 1.0 );
+ w = sqrt( (-2.0 * log( w ) ) / w );
+ y1 = x1 * w;
+ y2 = x2 * w;
+
+ // deflect both left and right edges randomly and independently, so that:
+ // (1) dc->tremor=1 corresponds to sigma=1, decreasing dc->tremor narrows the bell curve;
+ // (2) deflection depends on width, but is upped for small widths for better visual uniformity across widths;
+ // (3) deflection somewhat depends on speed, to prevent fast strokes looking
+ // comparatively smooth and slow ones excessively jittery
+ tremble_left = (y1)*this->tremor * (0.15 + 0.8*width) * (0.35 + 14*Geom::L2(this->vel));
+ tremble_right = (y2)*this->tremor * (0.15 + 0.8*width) * (0.35 + 14*Geom::L2(this->vel));
+ }
+
+ if ( width < 0.02 * this->width ) {
+ width = 0.02 * this->width;
+ }
+
+ double dezoomify_factor = 0.05 * 1000;
+ if (!this->abs_width) {
+ dezoomify_factor /= SP_EVENT_CONTEXT(this)->desktop->current_zoom();
+ }
+
+ Geom::Point del_left = dezoomify_factor * (width + tremble_left) * this->ang;
+ Geom::Point del_right = dezoomify_factor * (width + tremble_right) * this->ang;
+
+ this->point1[this->npoints] = brush + del_left;
+ this->point2[this->npoints] = brush - del_right;
+
+ this->del = 0.5*(del_left + del_right);
+
+ this->npoints++;
+}
+
+static void
+sp_erc_update_toolbox (SPDesktop *desktop, const gchar *id, double value)
+{
+ desktop->setToolboxAdjustmentValue (id, value);
+}
+
+void EraserTool::cancel() {
+ SPDesktop *desktop = SP_EVENT_CONTEXT(this)->desktop;
+ this->dragging = FALSE;
+ this->is_drawing = false;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), 0);
+ /* Remove all temporary line segments */
+ while (this->segments) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data));
+ this->segments = g_slist_remove(this->segments, this->segments->data);
+ }
+ /* reset accumulated curve */
+ this->accumulated->reset();
+ this->clear_current();
+ if (this->repr) {
+ this->repr = NULL;
+ }
+}
+
+bool EraserTool::root_handler(GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+ if (Inkscape::have_viable_layer(desktop, this->message_context) == false) {
+ return TRUE;
+ }
+
+ Geom::Point const button_w(event->button.x, event->button.y);
+ Geom::Point const button_dt(desktop->w2d(button_w));
+
+ this->reset(button_dt);
+ this->extinput(event);
+ this->apply(button_dt);
+
+ this->accumulated->reset();
+
+ if (this->repr) {
+ this->repr = NULL;
+ }
+
+ Inkscape::Rubberband::get(desktop)->start(desktop, button_dt);
+ Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH);
+
+ /* initialize first point */
+ this->npoints = 0;
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ ( GDK_KEY_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_PRESS_MASK ),
+ NULL,
+ event->button.time);
+
+ ret = TRUE;
+
+ desktop->canvas->forceFullRedrawAfterInterruptions(3);
+ this->is_drawing = true;
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY: {
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point motion_dt(desktop->w2d(motion_w)
+ );
+ this->extinput(event);
+
+ this->message_context->clear();
+
+ if ( this->is_drawing && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) {
+ this->dragging = TRUE;
+
+ this->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Drawing</b> an eraser stroke"));
+
+ if (!this->apply(motion_dt)) {
+ ret = TRUE;
+ break;
+ }
+
+ if ( this->cur != this->last ) {
+ this->brush();
+ g_assert( this->npoints > 0 );
+ this->fit_and_split(false);
+ }
+
+ ret = TRUE;
+ }
+
+ Inkscape::Rubberband::get(desktop)->move(motion_dt);
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE: {
+ Geom::Point const motion_w(event->button.x, event->button.y);
+ Geom::Point const motion_dt(desktop->w2d(motion_w));
+
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
+ desktop->canvas->endForcedFullRedraws();
+ this->is_drawing = false;
+
+ if (this->dragging && event->button.button == 1 && !this->space_panning) {
+ this->dragging = FALSE;
+
+ this->apply(motion_dt);
+
+ /* Remove all temporary line segments */
+ while (this->segments) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data));
+ this->segments = g_slist_remove(this->segments, this->segments->data);
+ }
+
+ /* Create object */
+ this->fit_and_split(true);
+ this->accumulate();
+ this->set_to_accumulated(); // performs document_done
+
+ /* reset accumulated curve */
+ this->accumulated->reset();
+
+ this->clear_current();
+ if (this->repr) {
+ this->repr = NULL;
+ }
+
+ this->message_context->clear();
+ ret = TRUE;
+ }
+
+ if (Inkscape::Rubberband::get(desktop)->is_started()) {
+ Inkscape::Rubberband::get(desktop)->stop();
+ }
+
+ break;
+ }
+
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Up:
+ case GDK_KEY_KP_Up:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->angle += 5.0;
+
+ if (this->angle > 90.0) {
+ this->angle = 90.0;
+ }
+
+ sp_erc_update_toolbox (desktop, "eraser-angle", this->angle);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Down:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->angle -= 5.0;
+
+ if (this->angle < -90.0) {
+ this->angle = -90.0;
+ }
+
+ sp_erc_update_toolbox (desktop, "eraser-angle", this->angle);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Right:
+ case GDK_KEY_KP_Right:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->width += 0.01;
+
+ if (this->width > 1.0) {
+ this->width = 1.0;
+ }
+
+ sp_erc_update_toolbox (desktop, "altx-eraser", this->width * 100); // the same spinbutton is for alt+x
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Left:
+ case GDK_KEY_KP_Left:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->width -= 0.01;
+
+ if (this->width < 0.01) {
+ this->width = 0.01;
+ }
+
+ sp_erc_update_toolbox (desktop, "altx-eraser", this->width * 100);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Home:
+ case GDK_KEY_KP_Home:
+ this->width = 0.01;
+ sp_erc_update_toolbox (desktop, "altx-eraser", this->width * 100);
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_End:
+ case GDK_KEY_KP_End:
+ this->width = 1.0;
+ sp_erc_update_toolbox (desktop, "altx-eraser", this->width * 100);
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_x:
+ case GDK_KEY_X:
+ if (MOD__ALT_ONLY(event)) {
+ desktop->setToolboxFocusTo ("altx-eraser");
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Escape:
+ Inkscape::Rubberband::get(desktop)->stop();
+
+ if (this->is_drawing) {
+ // if drawing, cancel, otherwise pass it up for deselecting
+ this->cancel();
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_z:
+ case GDK_KEY_Z:
+ if (MOD__CTRL_ONLY(event) && this->is_drawing) {
+ // if drawing, cancel, otherwise pass it up for undo
+ this->cancel();
+ ret = TRUE;
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case GDK_KEY_RELEASE:
+ switch (get_group0_keyval(&event->key)) {
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ this->message_context->clear();
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = DynamicBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+void EraserTool::clear_current() {
+ // reset bpath
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), NULL);
+
+ // reset curve
+ this->currentcurve->reset();
+ this->cal1->reset();
+ this->cal2->reset();
+
+ // reset points
+ this->npoints = 0;
+}
+
+void EraserTool::set_to_accumulated() {
+ bool workDone = false;
+
+ if (!this->accumulated->is_empty()) {
+ if (!this->repr) {
+ /* Create object */
+ Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
+ Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
+
+ /* Set style */
+ sp_desktop_apply_style_tool (desktop, repr, "/tools/eraser", false);
+
+ this->repr = repr;
+
+ SPItem *item=SP_ITEM(desktop->currentLayer()->appendChildRepr(this->repr));
+ Inkscape::GC::release(this->repr);
+
+ item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
+ item->updateRepr();
+ }
+
+ Geom::PathVector pathv = this->accumulated->get_pathvector() * desktop->dt2doc();
+ gchar *str = sp_svg_write_path(pathv);
+ g_assert( str != NULL );
+ this->repr->setAttribute("d", str);
+ g_free(str);
+
+ if ( this->repr ) {
+ bool wasSelection = false;
+ Inkscape::Selection *selection = sp_desktop_selection(desktop);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ gint eraserMode = prefs->getBool("/tools/eraser/mode") ? 1 : 0;
+ Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
+
+ SPItem* acid = SP_ITEM(desktop->doc()->getObjectByRepr(this->repr));
+ Geom::OptRect eraserBbox = acid->visualBounds();
+ Geom::Rect bounds = (*eraserBbox) * desktop->doc2dt();
+ std::vector<SPItem*> remainingItems;
+ GSList* toWorkOn = 0;
+
+ if (selection->isEmpty()) {
+ if ( eraserMode ) {
+ toWorkOn = sp_desktop_document(desktop)->getItemsPartiallyInBox(desktop->dkey, bounds);
+ } else {
+ Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop);
+ toWorkOn = sp_desktop_document(desktop)->getItemsAtPoints(desktop->dkey, r->getPoints());
+ }
+
+ toWorkOn = g_slist_remove( toWorkOn, acid );
+ } else {
+ toWorkOn = g_slist_copy(const_cast<GSList*>(selection->itemList()));
+ wasSelection = true;
+ }
+
+ if ( g_slist_length(toWorkOn) > 0 ) {
+ if ( eraserMode ) {
+ for (GSList *i = toWorkOn ; i ; i = i->next ) {
+ SPItem *item = SP_ITEM(i->data);
+
+ if ( eraserMode ) {
+ Geom::OptRect bbox = item->visualBounds();
+
+ if (bbox && bbox->intersects(*eraserBbox)) {
+ Inkscape::XML::Node* dup = this->repr->duplicate(xml_doc);
+ this->repr->parent()->appendChild(dup);
+ Inkscape::GC::release(dup); // parent takes over
+
+ selection->set(item);
+ selection->add(dup);
+ sp_selected_path_diff_skip_undo(selection, desktop);
+ workDone = true; // TODO set this only if something was cut.
+
+ if ( !selection->isEmpty() ) {
+ // If the item was not completely erased, track the new remainder.
+ GSList *nowSel = g_slist_copy(const_cast<GSList *>(selection->itemList()));
+
+ for (GSList const *i2 = nowSel ; i2 ; i2 = i2->next ) {
+ remainingItems.push_back(SP_ITEM(i2->data));
+ }
+
+ g_slist_free(nowSel);
+ }
+ } else {
+ remainingItems.push_back(item);
+ }
+ }
+ }
+ } else {
+ for (GSList *i = toWorkOn ; i ; i = i->next ) {
+ sp_object_ref( SP_ITEM(i->data), 0 );
+ }
+
+ for (GSList *i = toWorkOn ; i ; i = i->next ) {
+ SPItem *item = SP_ITEM(i->data);
+ item->deleteObject(true);
+ sp_object_unref(item);
+ workDone = true;
+ }
+ }
+
+ g_slist_free(toWorkOn);
+
+ if ( !eraserMode ) {
+ //sp_selection_delete(desktop);
+ remainingItems.clear();
+ }
+
+ selection->clear();
+
+ if ( wasSelection ) {
+ if ( !remainingItems.empty() ) {
+ selection->add(remainingItems.begin(), remainingItems.end());
+ }
+ }
+ }
+
+ // Remove the eraser stroke itself:
+ sp_repr_unparent( this->repr );
+ this->repr = 0;
+ }
+ } else {
+ if (this->repr) {
+ sp_repr_unparent(this->repr);
+ this->repr = 0;
+ }
+ }
+
+
+ if ( workDone ) {
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_ERASER, _("Draw eraser stroke"));
+ } else {
+ DocumentUndo::cancel(sp_desktop_document(desktop));
+ }
+}
+
+static void
+add_cap(SPCurve *curve,
+ Geom::Point const &pre, Geom::Point const &from,
+ Geom::Point const &to, Geom::Point const &post,
+ double rounding)
+{
+ Geom::Point vel = rounding * Geom::rot90( to - from ) / sqrt(2.0);
+ double mag = Geom::L2(vel);
+
+ Geom::Point v_in = from - pre;
+ double mag_in = Geom::L2(v_in);
+
+ if ( mag_in > ERASER_EPSILON ) {
+ v_in = mag * v_in / mag_in;
+ } else {
+ v_in = Geom::Point(0, 0);
+ }
+
+ Geom::Point v_out = to - post;
+ double mag_out = Geom::L2(v_out);
+
+ if ( mag_out > ERASER_EPSILON ) {
+ v_out = mag * v_out / mag_out;
+ } else {
+ v_out = Geom::Point(0, 0);
+ }
+
+ if ( Geom::L2(v_in) > ERASER_EPSILON || Geom::L2(v_out) > ERASER_EPSILON ) {
+ curve->curveto(from + v_in, to + v_out, to);
+ }
+}
+
+void EraserTool::accumulate() {
+ if ( !this->cal1->is_empty() && !this->cal2->is_empty() ) {
+ this->accumulated->reset(); /* Is this required ?? */
+ SPCurve *rev_cal2 = this->cal2->create_reverse();
+
+ g_assert(this->cal1->get_segment_count() > 0);
+ g_assert(rev_cal2->get_segment_count() > 0);
+ g_assert( ! this->cal1->first_path()->closed() );
+ g_assert( ! rev_cal2->first_path()->closed() );
+
+ Geom::CubicBezier const * dc_cal1_firstseg = dynamic_cast<Geom::CubicBezier const *>( this->cal1->first_segment() );
+ Geom::CubicBezier const * rev_cal2_firstseg = dynamic_cast<Geom::CubicBezier const *>( rev_cal2->first_segment() );
+ Geom::CubicBezier const * dc_cal1_lastseg = dynamic_cast<Geom::CubicBezier const *>( this->cal1->last_segment() );
+ Geom::CubicBezier const * rev_cal2_lastseg = dynamic_cast<Geom::CubicBezier const *>( rev_cal2->last_segment() );
+
+ g_assert( dc_cal1_firstseg );
+ g_assert( rev_cal2_firstseg );
+ g_assert( dc_cal1_lastseg );
+ g_assert( rev_cal2_lastseg );
+
+ this->accumulated->append(this->cal1, FALSE);
+
+ add_cap(this->accumulated, (*dc_cal1_lastseg)[2], (*dc_cal1_lastseg)[3], (*rev_cal2_firstseg)[0], (*rev_cal2_firstseg)[1], this->cap_rounding);
+
+ this->accumulated->append(rev_cal2, TRUE);
+
+ add_cap(this->accumulated, (*rev_cal2_lastseg)[2], (*rev_cal2_lastseg)[3], (*dc_cal1_firstseg)[0], (*dc_cal1_firstseg)[1], this->cap_rounding);
+
+ this->accumulated->closepath();
+
+ rev_cal2->unref();
+
+ this->cal1->reset();
+ this->cal2->reset();
+ }
+}
+
+static double square(double const x)
+{
+ return x * x;
+}
+
+void EraserTool::fit_and_split(bool release) {
+ double const tolerance_sq = square( desktop->w2d().descrim() * TOLERANCE_ERASER );
+
+#ifdef ERASER_VERBOSE
+ g_print("[F&S:R=%c]", release?'T':'F');
+#endif
+
+ if (!( this->npoints > 0 && this->npoints < SAMPLING_SIZE ))
+ return; // just clicked
+
+ if ( this->npoints == SAMPLING_SIZE - 1 || release ) {
+#define BEZIER_SIZE 4
+#define BEZIER_MAX_BEZIERS 8
+#define BEZIER_MAX_LENGTH ( BEZIER_SIZE * BEZIER_MAX_BEZIERS )
+
+#ifdef ERASER_VERBOSE
+ g_print("[F&S:#] this->npoints:%d, release:%s\n",
+ dc->npoints, release ? "TRUE" : "FALSE");
+#endif
+
+ /* Current eraser */
+ if ( this->cal1->is_empty() || this->cal2->is_empty() ) {
+ /* dc->npoints > 0 */
+ /* g_print("erasers(1|2) reset\n"); */
+ this->cal1->reset();
+ this->cal2->reset();
+
+ this->cal1->moveto(this->point1[0]);
+ this->cal2->moveto(this->point2[0]);
+ }
+
+ Geom::Point b1[BEZIER_MAX_LENGTH];
+ gint const nb1 = Geom::bezier_fit_cubic_r(b1, this->point1, this->npoints, tolerance_sq, BEZIER_MAX_BEZIERS);
+ g_assert( nb1 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b1)) );
+
+ Geom::Point b2[BEZIER_MAX_LENGTH];
+ gint const nb2 = Geom::bezier_fit_cubic_r(b2, this->point2, this->npoints, tolerance_sq, BEZIER_MAX_BEZIERS);
+ g_assert( nb2 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b2)) );
+
+ if ( nb1 != -1 && nb2 != -1 ) {
+ /* Fit and draw and reset state */
+#ifdef ERASER_VERBOSE
+ g_print("nb1:%d nb2:%d\n", nb1, nb2);
+#endif
+
+ /* CanvasShape */
+ if (! release) {
+ this->currentcurve->reset();
+ this->currentcurve->moveto(b1[0]);
+
+ for (Geom::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) {
+ this->currentcurve->curveto(bp1[1], bp1[2], bp1[3]);
+ }
+
+ this->currentcurve->lineto(b2[BEZIER_SIZE*(nb2-1) + 3]);
+
+ for (Geom::Point *bp2 = b2 + BEZIER_SIZE * ( nb2 - 1 ); bp2 >= b2; bp2 -= BEZIER_SIZE) {
+ this->currentcurve->curveto(bp2[2], bp2[1], bp2[0]);
+ }
+
+ // FIXME: this->segments is always NULL at this point??
+ if (!this->segments) { // first segment
+ add_cap(this->currentcurve, b2[1], b2[0], b1[0], b1[1], this->cap_rounding);
+ }
+
+ this->currentcurve->closepath();
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), this->currentcurve);
+ }
+
+ /* Current eraser */
+ for (Geom::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) {
+ this->cal1->curveto(bp1[1], bp1[2], bp1[3]);
+ }
+
+ for (Geom::Point *bp2 = b2; bp2 < b2 + BEZIER_SIZE * nb2; bp2 += BEZIER_SIZE) {
+ this->cal2->curveto(bp2[1], bp2[2], bp2[3]);
+ }
+ } else {
+ /* fixme: ??? */
+#ifdef ERASER_VERBOSE
+ g_print("[fit_and_split] failed to fit-cubic.\n");
+#endif
+ this->draw_temporary_box();
+
+ for (gint i = 1; i < this->npoints; i++) {
+ this->cal1->lineto(this->point1[i]);
+ }
+
+ for (gint i = 1; i < this->npoints; i++) {
+ this->cal2->lineto(this->point2[i]);
+ }
+ }
+
+ /* Fit and draw and copy last point */
+#ifdef ERASER_VERBOSE
+ g_print("[%d]Yup\n", this->npoints);
+#endif
+ if (!release) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ gint eraserMode = prefs->getBool("/tools/eraser/mode") ? 1 : 0;
+ g_assert(!this->currentcurve->is_empty());
+
+ SPCanvasItem *cbp = sp_canvas_item_new(sp_desktop_sketch(desktop), SP_TYPE_CANVAS_BPATH, NULL);
+ SPCurve *curve = this->currentcurve->copy();
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH (cbp), curve);
+ curve->unref();
+
+ guint32 fillColor = sp_desktop_get_color_tool (desktop, "/tools/eraser", true);
+ //guint32 strokeColor = sp_desktop_get_color_tool (desktop, "/tools/eraser", false);
+ double opacity = sp_desktop_get_master_opacity_tool (desktop, "/tools/eraser");
+ double fillOpacity = sp_desktop_get_opacity_tool (desktop, "/tools/eraser", true);
+ //double strokeOpacity = sp_desktop_get_opacity_tool (desktop, "/tools/eraser", false);
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cbp), ((fillColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*fillOpacity)), SP_WIND_RULE_EVENODD);
+ //on second thougtht don't do stroke yet because we don't have stoke-width yet and because stoke appears between segments while drawing
+ //sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), ((strokeColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*strokeOpacity)), 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ /* fixme: Cannot we cascade it to root more clearly? */
+ g_signal_connect(G_OBJECT(cbp), "event", G_CALLBACK(sp_desktop_root_handler), desktop);
+
+ this->segments = g_slist_prepend(this->segments, cbp);
+
+ if ( !eraserMode ) {
+ sp_canvas_item_hide(cbp);
+ sp_canvas_item_hide(this->currentshape);
+ }
+ }
+
+ this->point1[0] = this->point1[this->npoints - 1];
+ this->point2[0] = this->point2[this->npoints - 1];
+ this->npoints = 1;
+ } else {
+ this->draw_temporary_box();
+ }
+}
+
+void EraserTool::draw_temporary_box() {
+ this->currentcurve->reset();
+
+ this->currentcurve->moveto(this->point1[this->npoints-1]);
+
+ for (gint i = this->npoints-2; i >= 0; i--) {
+ this->currentcurve->lineto(this->point1[i]);
+ }
+
+ for (gint i = 0; i < this->npoints; i++) {
+ this->currentcurve->lineto(this->point2[i]);
+ }
+
+ if (this->npoints >= 2) {
+ add_cap(this->currentcurve, this->point2[this->npoints-2], this->point2[this->npoints-1], this->point1[this->npoints-1], this->point1[this->npoints-2], this->cap_rounding);
+ }
+
+ this->currentcurve->closepath();
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), this->currentcurve);
+}
+
+}
+}
+}
+
+/*
+ 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/tools/eraser-tool.h b/src/ui/tools/eraser-tool.h
new file mode 100644
index 000000000..eb7eb16e8
--- /dev/null
+++ b/src/ui/tools/eraser-tool.h
@@ -0,0 +1,76 @@
+#ifndef SP_ERASER_CONTEXT_H_SEEN
+#define SP_ERASER_CONTEXT_H_SEEN
+
+/*
+ * Handwriting-like drawing mode
+ *
+ * Authors:
+ * Mitsuru Oka <oka326@parkcity.ne.jp>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * The original dynadraw code:
+ * Paul Haeberli <paul@sgi.com>
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ * Copyright (C) 1999-2002 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ * Copyright (C) 2008 Jon A. Cruz
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/tools/dynamic-base.h"
+
+#define ERC_MIN_PRESSURE 0.0
+#define ERC_MAX_PRESSURE 1.0
+#define ERC_DEFAULT_PRESSURE 1.0
+
+#define ERC_MIN_TILT -1.0
+#define ERC_MAX_TILT 1.0
+#define ERC_DEFAULT_TILT 0.0
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class EraserTool : public DynamicBase {
+public:
+ EraserTool();
+ virtual ~EraserTool();
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual bool root_handler(GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+private:
+ void reset(Geom::Point p);
+ void extinput(GdkEvent *event);
+ bool apply(Geom::Point p);
+ void brush();
+ void cancel();
+ void clear_current();
+ void set_to_accumulated();
+ void accumulate();
+ void fit_and_split(bool release);
+ void draw_temporary_box();
+};
+
+}
+}
+}
+
+#endif // SP_ERASER_CONTEXT_H_SEEN
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/tools/flood-tool.cpp b/src/ui/tools/flood-tool.cpp
new file mode 100644
index 000000000..0b72bc9f2
--- /dev/null
+++ b/src/ui/tools/flood-tool.cpp
@@ -0,0 +1,1263 @@
+/**
+ * @file
+ * Bucket fill drawing context, works by bitmap filling an area on a rendered version
+ * of the current display and then tracing the result using potrace.
+ */
+/* Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * John Bintz <jcoswell@coswellproductions.org>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl>
+ * Copyright (C) 2000-2005 authors
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "trace/potrace/inkscape-potrace.h"
+#include <2geom/pathvector.h>
+#include <gdk/gdkkeysyms.h>
+#include <queue>
+#include <deque>
+#include <glibmm/i18n.h>
+
+#include "color.h"
+#include "context-fns.h"
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "desktop-style.h"
+#include "display/cairo-utils.h"
+#include "display/drawing-context.h"
+#include "display/drawing-image.h"
+#include "display/drawing-item.h"
+#include "display/drawing.h"
+#include "display/sp-canvas.h"
+#include "document.h"
+#include "document-undo.h"
+#include "ui/tools/flood-tool.h"
+#include "livarot/Path.h"
+#include "livarot/Shape.h"
+#include "macros.h"
+#include "message-context.h"
+#include "message-stack.h"
+#include "preferences.h"
+#include "rubberband.h"
+#include "selection.h"
+#include "shape-editor.h"
+#include "sp-defs.h"
+#include "sp-item.h"
+#include "splivarot.h"
+#include "sp-namedview.h"
+#include "sp-object.h"
+#include "sp-path.h"
+#include "sp-rect.h"
+#include "sp-root.h"
+#include "svg/svg.h"
+#include "trace/imagemap.h"
+#include "trace/trace.h"
+#include "xml/node-event-vector.h"
+#include "xml/repr.h"
+#include "verbs.h"
+
+#include "pixmaps/cursor-paintbucket.xpm"
+
+using Inkscape::DocumentUndo;
+
+using Inkscape::Display::ExtractARGB32;
+using Inkscape::Display::ExtractRGB32;
+using Inkscape::Display::AssembleARGB32;
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+namespace {
+ ToolBase* createPaintbucketContext() {
+ return new FloodTool();
+ }
+
+ bool paintbucketContextRegistered = ToolFactory::instance().registerObject("/tools/paintbucket", createPaintbucketContext);
+}
+
+const std::string& FloodTool::getPrefsPath() {
+ return FloodTool::prefsPath;
+}
+
+const std::string FloodTool::prefsPath = "/tools/paintbucket";
+
+FloodTool::FloodTool() : ToolBase() {
+ this->cursor_shape = cursor_paintbucket_xpm;
+ this->hot_x = 11;
+ this->hot_y = 30;
+ this->xp = 0;
+ this->yp = 0;
+ this->tolerance = 4;
+ this->within_tolerance = false;
+ this->item_to_select = NULL;
+
+ this->item = NULL;
+}
+
+FloodTool::~FloodTool() {
+ this->sel_changed_connection.disconnect();
+
+ delete this->shape_editor;
+ this->shape_editor = NULL;
+
+ /* fixme: This is necessary because we do not grab */
+ if (this->item) {
+ this->finishItem();
+ }
+}
+
+/**
+ * Callback that processes the "changed" signal on the selection;
+ * destroys old and creates new knotholder.
+ */
+void FloodTool::selection_changed(Inkscape::Selection* selection) {
+ this->shape_editor->unset_item(SH_KNOTHOLDER);
+ this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER);
+}
+
+void FloodTool::setup() {
+ ToolBase::setup();
+
+ this->shape_editor = new ShapeEditor(this->desktop);
+
+ SPItem *item = sp_desktop_selection(this->desktop)->singleItem();
+ if (item) {
+ this->shape_editor->set_item(item, SH_KNOTHOLDER);
+ }
+
+ this->sel_changed_connection.disconnect();
+ this->sel_changed_connection = sp_desktop_selection(this->desktop)->connectChanged(
+ sigc::mem_fun(this, &FloodTool::selection_changed)
+ );
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (prefs->getBool("/tools/paintbucket/selcue")) {
+ this->enableSelectionCue();
+ }
+}
+
+
+// Changes from 0.48 -> 0.49 (Cairo)
+// 0.49: Ignores alpha in background
+// 0.48: RGBA, 0.49 ARGB
+// 0.49: premultiplied alpha
+inline static guint32 compose_onto(guint32 px, guint32 bg)
+{
+ guint ap = 0, rp = 0, gp = 0, bp = 0;
+ guint rb = 0, gb = 0, bb = 0;
+ ExtractARGB32(px, ap, rp, gp, bp);
+ ExtractRGB32(bg, rb, gb, bb);
+
+ // guint ao = 255*255 - (255-ap)*(255-bp); ao = (ao + 127) / 255;
+ // guint ao = (255-ap)*ab + 255*ap; ao = (ao + 127) / 255;
+ guint ao = 255; // Cairo version doesn't allow background to have alpha != 1.
+ guint ro = (255-ap)*rb + 255*rp; ro = (ro + 127) / 255;
+ guint go = (255-ap)*gb + 255*gp; go = (go + 127) / 255;
+ guint bo = (255-ap)*bb + 255*bp; bo = (bo + 127) / 255;
+
+ guint pxout = AssembleARGB32(ao, ro, go, bo);
+ return pxout;
+}
+
+/**
+ * Get the pointer to a pixel in a pixel buffer.
+ * @param px The pixel buffer.
+ * @param x The X coordinate.
+ * @param y The Y coordinate.
+ * @param stride The rowstride of the pixel buffer.
+ */
+inline guint32 get_pixel(guchar *px, int x, int y, int stride) {
+ return *reinterpret_cast<guint32*>(px + y * stride + x * 4);
+}
+
+inline unsigned char * get_trace_pixel(guchar *trace_px, int x, int y, int width) {
+ return trace_px + (x + y * width);
+}
+
+/**
+ * Generate the list of trace channel selection entries.
+ */
+GList * flood_channels_dropdown_items_list() {
+ GList *glist = NULL;
+
+ glist = g_list_append (glist, _("Visible Colors"));
+ glist = g_list_append (glist, _("Red"));
+ glist = g_list_append (glist, _("Green"));
+ glist = g_list_append (glist, _("Blue"));
+ glist = g_list_append (glist, _("Hue"));
+ glist = g_list_append (glist, _("Saturation"));
+ glist = g_list_append (glist, _("Lightness"));
+ glist = g_list_append (glist, _("Alpha"));
+
+ return glist;
+}
+
+/**
+ * Generate the list of autogap selection entries.
+ */
+GList * flood_autogap_dropdown_items_list() {
+ GList *glist = NULL;
+
+ glist = g_list_append (glist, (void*) C_("Flood autogap", "None"));
+ glist = g_list_append (glist, (void*) C_("Flood autogap", "Small"));
+ glist = g_list_append (glist, (void*) C_("Flood autogap", "Medium"));
+ glist = g_list_append (glist, (void*) C_("Flood autogap", "Large"));
+
+ return glist;
+}
+
+/**
+ * Compare a pixel in a pixel buffer with another pixel to determine if a point should be included in the fill operation.
+ * @param check The pixel in the pixel buffer to check.
+ * @param orig The original selected pixel to use as the fill target color.
+ * @param merged_orig_pixel The original pixel merged with the background.
+ * @param dtc The desktop background color.
+ * @param threshold The fill threshold.
+ * @param method The fill method to use as defined in PaintBucketChannels.
+ */
+static bool compare_pixels(guint32 check, guint32 orig, guint32 merged_orig_pixel, guint32 dtc, int threshold, PaintBucketChannels method)
+{
+ int diff = 0;
+ float hsl_check[3] = {0,0,0}, hsl_orig[3] = {0,0,0};
+
+ guint32 ac = 0, rc = 0, gc = 0, bc = 0;
+ ExtractARGB32(check, ac, rc, gc, bc);
+
+ guint32 ao = 0, ro = 0, go = 0, bo = 0;
+ ExtractARGB32(orig, ao, ro, go, bo);
+
+ guint32 ad = 0, rd = 0, gd = 0, bd = 0;
+ ExtractARGB32(dtc, ad, rd, gd, bd);
+
+ guint32 amop = 0, rmop = 0, gmop = 0, bmop = 0;
+ ExtractARGB32(merged_orig_pixel, amop, rmop, gmop, bmop);
+
+ if ((method == FLOOD_CHANNELS_H) ||
+ (method == FLOOD_CHANNELS_S) ||
+ (method == FLOOD_CHANNELS_L)) {
+ double dac = ac;
+ double dao = ao;
+ sp_color_rgb_to_hsl_floatv(hsl_check, rc / dac, gc / dac, bc / dac);
+ sp_color_rgb_to_hsl_floatv(hsl_orig, ro / dao, go / dao, bo / dao);
+ }
+
+ switch (method) {
+ case FLOOD_CHANNELS_ALPHA:
+ return abs(static_cast<int>(ac) - ao) <= threshold;
+ case FLOOD_CHANNELS_R:
+ return abs(static_cast<int>(ac ? unpremul_alpha(rc, ac) : 0) - (ao ? unpremul_alpha(ro, ao) : 0)) <= threshold;
+ case FLOOD_CHANNELS_G:
+ return abs(static_cast<int>(ac ? unpremul_alpha(gc, ac) : 0) - (ao ? unpremul_alpha(go, ao) : 0)) <= threshold;
+ case FLOOD_CHANNELS_B:
+ return abs(static_cast<int>(ac ? unpremul_alpha(bc, ac) : 0) - (ao ? unpremul_alpha(bo, ao) : 0)) <= threshold;
+ case FLOOD_CHANNELS_RGB:
+ guint32 amc, rmc, bmc, gmc;
+ //amc = 255*255 - (255-ac)*(255-ad); amc = (amc + 127) / 255;
+ //amc = (255-ac)*ad + 255*ac; amc = (amc + 127) / 255;
+ amc = 255; // Why are we looking at desktop? Cairo version ignores destop alpha
+ rmc = (255-ac)*rd + 255*rc; rmc = (rmc + 127) / 255;
+ gmc = (255-ac)*gd + 255*gc; gmc = (gmc + 127) / 255;
+ bmc = (255-ac)*bd + 255*bc; bmc = (bmc + 127) / 255;
+
+ diff += abs(static_cast<int>(amc ? unpremul_alpha(rmc, amc) : 0) - (amop ? unpremul_alpha(rmop, amop) : 0));
+ diff += abs(static_cast<int>(amc ? unpremul_alpha(gmc, amc) : 0) - (amop ? unpremul_alpha(gmop, amop) : 0));
+ diff += abs(static_cast<int>(amc ? unpremul_alpha(bmc, amc) : 0) - (amop ? unpremul_alpha(bmop, amop) : 0));
+ return ((diff / 3) <= ((threshold * 3) / 4));
+
+ case FLOOD_CHANNELS_H:
+ return ((int)(fabs(hsl_check[0] - hsl_orig[0]) * 100.0) <= threshold);
+ case FLOOD_CHANNELS_S:
+ return ((int)(fabs(hsl_check[1] - hsl_orig[1]) * 100.0) <= threshold);
+ case FLOOD_CHANNELS_L:
+ return ((int)(fabs(hsl_check[2] - hsl_orig[2]) * 100.0) <= threshold);
+ }
+
+ return false;
+}
+
+enum {
+ PIXEL_CHECKED = 1,
+ PIXEL_QUEUED = 2,
+ PIXEL_PAINTABLE = 4,
+ PIXEL_NOT_PAINTABLE = 8,
+ PIXEL_COLORED = 16
+};
+
+static inline bool is_pixel_checked(unsigned char *t) { return (*t & PIXEL_CHECKED) == PIXEL_CHECKED; }
+static inline bool is_pixel_queued(unsigned char *t) { return (*t & PIXEL_QUEUED) == PIXEL_QUEUED; }
+static inline bool is_pixel_paintability_checked(unsigned char *t) {
+ return !((*t & PIXEL_PAINTABLE) == 0) && ((*t & PIXEL_NOT_PAINTABLE) == 0);
+}
+static inline bool is_pixel_paintable(unsigned char *t) { return (*t & PIXEL_PAINTABLE) == PIXEL_PAINTABLE; }
+static inline bool is_pixel_colored(unsigned char *t) { return (*t & PIXEL_COLORED) == PIXEL_COLORED; }
+
+static inline void mark_pixel_checked(unsigned char *t) { *t |= PIXEL_CHECKED; }
+static inline void mark_pixel_unchecked(unsigned char *t) { *t ^= PIXEL_CHECKED; }
+static inline void mark_pixel_queued(unsigned char *t) { *t |= PIXEL_QUEUED; }
+static inline void mark_pixel_paintable(unsigned char *t) { *t |= PIXEL_PAINTABLE; *t ^= PIXEL_NOT_PAINTABLE; }
+static inline void mark_pixel_not_paintable(unsigned char *t) { *t |= PIXEL_NOT_PAINTABLE; *t ^= PIXEL_PAINTABLE; }
+static inline void mark_pixel_colored(unsigned char *t) { *t |= PIXEL_COLORED; }
+
+static inline void clear_pixel_paintability(unsigned char *t) { *t ^= PIXEL_PAINTABLE; *t ^= PIXEL_NOT_PAINTABLE; }
+
+struct bitmap_coords_info {
+ bool is_left;
+ unsigned int x;
+ unsigned int y;
+ int y_limit;
+ unsigned int width;
+ unsigned int height;
+ unsigned int stride;
+ unsigned int threshold;
+ unsigned int radius;
+ PaintBucketChannels method;
+ guint32 dtc;
+ guint32 merged_orig_pixel;
+ Geom::Rect bbox;
+ Geom::Rect screen;
+ unsigned int max_queue_size;
+ unsigned int current_step;
+};
+
+/**
+ * Check if a pixel can be included in the fill.
+ * @param px The rendered pixel buffer to check.
+ * @param trace_t The pixel in the trace pixel buffer to check or mark.
+ * @param x The X coordinate.
+ * @param y The y coordinate.
+ * @param orig_color The original selected pixel to use as the fill target color.
+ * @param bci The bitmap_coords_info structure.
+ */
+inline static bool check_if_pixel_is_paintable(guchar *px, unsigned char *trace_t, int x, int y, guint32 orig_color, bitmap_coords_info bci) {
+ if (is_pixel_paintability_checked(trace_t)) {
+ return is_pixel_paintable(trace_t);
+ } else {
+ guint32 pixel = get_pixel(px, x, y, bci.stride);
+ if (compare_pixels(pixel, orig_color, bci.merged_orig_pixel, bci.dtc, bci.threshold, bci.method)) {
+ mark_pixel_paintable(trace_t);
+ return true;
+ } else {
+ mark_pixel_not_paintable(trace_t);
+ return false;
+ }
+ }
+}
+
+/**
+ * Perform the bitmap-to-vector tracing and place the traced path onto the document.
+ * @param px The trace pixel buffer to trace to SVG.
+ * @param desktop The desktop on which to place the final SVG path.
+ * @param transform The transform to apply to the final SVG path.
+ * @param union_with_selection If true, merge the final SVG path with the current selection.
+ */
+static void do_trace(bitmap_coords_info bci, guchar *trace_px, SPDesktop *desktop, Geom::Affine transform, unsigned int min_x, unsigned int max_x, unsigned int min_y, unsigned int max_y, bool union_with_selection) {
+ SPDocument *document = sp_desktop_document(desktop);
+
+ unsigned char *trace_t;
+
+ GrayMap *gray_map = GrayMapCreate((max_x - min_x + 1), (max_y - min_y + 1));
+ unsigned int gray_map_y = 0;
+ for (unsigned int y = min_y; y <= max_y; y++) {
+ unsigned long *gray_map_t = gray_map->rows[gray_map_y];
+
+ trace_t = get_trace_pixel(trace_px, min_x, y, bci.width);
+ for (unsigned int x = min_x; x <= max_x; x++) {
+ *gray_map_t = is_pixel_colored(trace_t) ? GRAYMAP_BLACK : GRAYMAP_WHITE;
+ gray_map_t++;
+ trace_t++;
+ }
+ gray_map_y++;
+ }
+
+ Inkscape::Trace::Potrace::PotraceTracingEngine pte;
+ pte.keepGoing = 1;
+ std::vector<Inkscape::Trace::TracingEngineResult> results = pte.traceGrayMap(gray_map);
+ gray_map->destroy(gray_map);
+
+ //XML Tree being used here directly while it shouldn't be...."
+ Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
+
+ long totalNodeCount = 0L;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double offset = prefs->getDouble("/tools/paintbucket/offset", 0.0);
+
+ for (unsigned int i=0 ; i<results.size() ; i++) {
+ Inkscape::Trace::TracingEngineResult result = results[i];
+ totalNodeCount += result.getNodeCount();
+
+ Inkscape::XML::Node *pathRepr = xml_doc->createElement("svg:path");
+ /* Set style */
+ sp_desktop_apply_style_tool (desktop, pathRepr, "/tools/paintbucket", false);
+
+ Geom::PathVector pathv = sp_svg_read_pathv(result.getPathData().c_str());
+ Path *path = new Path;
+ path->LoadPathVector(pathv);
+
+ if (offset != 0) {
+
+ Shape *path_shape = new Shape();
+
+ path->ConvertWithBackData(0.03);
+ path->Fill(path_shape, 0);
+ delete path;
+
+ Shape *expanded_path_shape = new Shape();
+
+ expanded_path_shape->ConvertToShape(path_shape, fill_nonZero);
+ path_shape->MakeOffset(expanded_path_shape, offset * desktop->current_zoom(), join_round, 4);
+ expanded_path_shape->ConvertToShape(path_shape, fill_positive);
+
+ Path *expanded_path = new Path();
+
+ expanded_path->Reset();
+ expanded_path_shape->ConvertToForme(expanded_path);
+ expanded_path->ConvertEvenLines(1.0);
+ expanded_path->Simplify(1.0);
+
+ delete path_shape;
+ delete expanded_path_shape;
+
+ gchar *str = expanded_path->svg_dump_path();
+ if (str && *str) {
+ pathRepr->setAttribute("d", str);
+ g_free(str);
+ } else {
+ desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("<b>Too much inset</b>, the result is empty."));
+ Inkscape::GC::release(pathRepr);
+ g_free(str);
+ return;
+ }
+
+ delete expanded_path;
+
+ } else {
+ gchar *str = path->svg_dump_path();
+ delete path;
+ pathRepr->setAttribute("d", str);
+ g_free(str);
+ }
+
+ desktop->currentLayer()->addChild(pathRepr,NULL);
+
+ SPObject *reprobj = document->getObjectByRepr(pathRepr);
+ if (reprobj) {
+ SP_ITEM(reprobj)->doWriteTransform(pathRepr, transform, NULL);
+
+ // premultiply the item transform by the accumulated parent transform in the paste layer
+ Geom::Affine local (SP_GROUP(desktop->currentLayer())->i2doc_affine());
+ if (!local.isIdentity()) {
+ gchar const *t_str = pathRepr->attribute("transform");
+ Geom::Affine item_t (Geom::identity());
+ if (t_str)
+ sp_svg_transform_read(t_str, &item_t);
+ item_t *= local.inverse();
+ // (we're dealing with unattached repr, so we write to its attr instead of using sp_item_set_transform)
+ gchar *affinestr=sp_svg_transform_write(item_t);
+ pathRepr->setAttribute("transform", affinestr);
+ g_free(affinestr);
+ }
+
+ Inkscape::Selection *selection = sp_desktop_selection(desktop);
+
+ pathRepr->setPosition(-1);
+
+ if (union_with_selection) {
+ desktop->messageStack()->flashF( Inkscape::WARNING_MESSAGE,
+ ngettext("Area filled, path with <b>%d</b> node created and unioned with selection.","Area filled, path with <b>%d</b> nodes created and unioned with selection.",
+ SP_PATH(reprobj)->nodesInPath()), SP_PATH(reprobj)->nodesInPath() );
+ selection->add(reprobj);
+ sp_selected_path_union_skip_undo(sp_desktop_selection(desktop), desktop);
+ } else {
+ desktop->messageStack()->flashF( Inkscape::WARNING_MESSAGE,
+ ngettext("Area filled, path with <b>%d</b> node created.","Area filled, path with <b>%d</b> nodes created.",
+ SP_PATH(reprobj)->nodesInPath()), SP_PATH(reprobj)->nodesInPath() );
+ selection->set(reprobj);
+ }
+
+ }
+
+ Inkscape::GC::release(pathRepr);
+
+ }
+}
+
+/**
+ * The possible return states of perform_bitmap_scanline_check().
+ */
+enum ScanlineCheckResult {
+ SCANLINE_CHECK_OK,
+ SCANLINE_CHECK_ABORTED,
+ SCANLINE_CHECK_BOUNDARY
+};
+
+/**
+ * Determine if the provided coordinates are within the pixel buffer limits.
+ * @param x The X coordinate.
+ * @param y The Y coordinate.
+ * @param bci The bitmap_coords_info structure.
+ */
+inline static bool coords_in_range(unsigned int x, unsigned int y, bitmap_coords_info bci) {
+ return (x < bci.width) &&
+ (y < bci.height);
+}
+
+#define PAINT_DIRECTION_LEFT 1
+#define PAINT_DIRECTION_RIGHT 2
+#define PAINT_DIRECTION_UP 4
+#define PAINT_DIRECTION_DOWN 8
+#define PAINT_DIRECTION_ALL 15
+
+/**
+ * Paint a pixel or a square (if autogap is enabled) on the trace pixel buffer.
+ * @param px The rendered pixel buffer to check.
+ * @param trace_px The trace pixel buffer.
+ * @param orig_color The original selected pixel to use as the fill target color.
+ * @param bci The bitmap_coords_info structure.
+ * @param original_point_trace_t The original pixel in the trace pixel buffer to check.
+ */
+inline static unsigned int paint_pixel(guchar *px, guchar *trace_px, guint32 orig_color, bitmap_coords_info bci, unsigned char *original_point_trace_t) {
+ if (bci.radius == 0) {
+ mark_pixel_colored(original_point_trace_t);
+ return PAINT_DIRECTION_ALL;
+ } else {
+ unsigned char *trace_t;
+
+ bool can_paint_up = true;
+ bool can_paint_down = true;
+ bool can_paint_left = true;
+ bool can_paint_right = true;
+
+ for (unsigned int ty = bci.y - bci.radius; ty <= bci.y + bci.radius; ty++) {
+ for (unsigned int tx = bci.x - bci.radius; tx <= bci.x + bci.radius; tx++) {
+ if (coords_in_range(tx, ty, bci)) {
+ trace_t = get_trace_pixel(trace_px, tx, ty, bci.width);
+ if (!is_pixel_colored(trace_t)) {
+ if (check_if_pixel_is_paintable(px, trace_t, tx, ty, orig_color, bci)) {
+ mark_pixel_colored(trace_t);
+ } else {
+ if (tx < bci.x) { can_paint_left = false; }
+ if (tx > bci.x) { can_paint_right = false; }
+ if (ty < bci.y) { can_paint_up = false; }
+ if (ty > bci.y) { can_paint_down = false; }
+ }
+ }
+ }
+ }
+ }
+
+ unsigned int paint_directions = 0;
+ if (can_paint_left) { paint_directions += PAINT_DIRECTION_LEFT; }
+ if (can_paint_right) { paint_directions += PAINT_DIRECTION_RIGHT; }
+ if (can_paint_up) { paint_directions += PAINT_DIRECTION_UP; }
+ if (can_paint_down) { paint_directions += PAINT_DIRECTION_DOWN; }
+
+ return paint_directions;
+ }
+}
+
+/**
+ * Push a point to be checked onto the bottom of the rendered pixel buffer check queue.
+ * @param fill_queue The fill queue to add the point to.
+ * @param max_queue_size The maximum size of the fill queue.
+ * @param trace_t The trace pixel buffer pixel.
+ * @param x The X coordinate.
+ * @param y The Y coordinate.
+ */
+static void push_point_onto_queue(std::deque<Geom::Point> *fill_queue, unsigned int max_queue_size, unsigned char *trace_t, unsigned int x, unsigned int y) {
+ if (!is_pixel_queued(trace_t)) {
+ if ((fill_queue->size() < max_queue_size)) {
+ fill_queue->push_back(Geom::Point(x, y));
+ mark_pixel_queued(trace_t);
+ }
+ }
+}
+
+/**
+ * Shift a point to be checked onto the top of the rendered pixel buffer check queue.
+ * @param fill_queue The fill queue to add the point to.
+ * @param max_queue_size The maximum size of the fill queue.
+ * @param trace_t The trace pixel buffer pixel.
+ * @param x The X coordinate.
+ * @param y The Y coordinate.
+ */
+static void shift_point_onto_queue(std::deque<Geom::Point> *fill_queue, unsigned int max_queue_size, unsigned char *trace_t, unsigned int x, unsigned int y) {
+ if (!is_pixel_queued(trace_t)) {
+ if ((fill_queue->size() < max_queue_size)) {
+ fill_queue->push_front(Geom::Point(x, y));
+ mark_pixel_queued(trace_t);
+ }
+ }
+}
+
+/**
+ * Scan a row in the rendered pixel buffer and add points to the fill queue as necessary.
+ * @param fill_queue The fill queue to add the point to.
+ * @param px The rendered pixel buffer.
+ * @param trace_px The trace pixel buffer.
+ * @param orig_color The original selected pixel to use as the fill target color.
+ * @param bci The bitmap_coords_info structure.
+ */
+static ScanlineCheckResult perform_bitmap_scanline_check(std::deque<Geom::Point> *fill_queue, guchar *px, guchar *trace_px, guint32 orig_color, bitmap_coords_info bci, unsigned int *min_x, unsigned int *max_x) {
+ bool aborted = false;
+ bool reached_screen_boundary = false;
+ bool ok;
+
+ bool keep_tracing;
+ bool initial_paint = true;
+
+ unsigned char *current_trace_t = get_trace_pixel(trace_px, bci.x, bci.y, bci.width);
+ unsigned int paint_directions;
+
+ bool currently_painting_top = false;
+ bool currently_painting_bottom = false;
+
+ unsigned int top_ty = bci.y - 1;
+ unsigned int bottom_ty = bci.y + 1;
+
+ bool can_paint_top = (top_ty > 0);
+ bool can_paint_bottom = (bottom_ty < bci.height);
+
+ Geom::Point t = fill_queue->front();
+
+ do {
+ ok = false;
+ if (bci.is_left) {
+ keep_tracing = (bci.x != 0);
+ } else {
+ keep_tracing = (bci.x < bci.width);
+ }
+
+ *min_x = MIN(*min_x, bci.x);
+ *max_x = MAX(*max_x, bci.x);
+
+ if (keep_tracing) {
+ if (check_if_pixel_is_paintable(px, current_trace_t, bci.x, bci.y, orig_color, bci)) {
+ paint_directions = paint_pixel(px, trace_px, orig_color, bci, current_trace_t);
+ if (bci.radius == 0) {
+ mark_pixel_checked(current_trace_t);
+ if ((t[Geom::X] == bci.x) && (t[Geom::Y] == bci.y)) {
+ fill_queue->pop_front(); t = fill_queue->front();
+ }
+ }
+
+ if (can_paint_top) {
+ if (paint_directions & PAINT_DIRECTION_UP) {
+ unsigned char *trace_t = current_trace_t - bci.width;
+ if (!is_pixel_queued(trace_t)) {
+ bool ok_to_paint = check_if_pixel_is_paintable(px, trace_t, bci.x, top_ty, orig_color, bci);
+
+ if (initial_paint) { currently_painting_top = !ok_to_paint; }
+
+ if (ok_to_paint && (!currently_painting_top)) {
+ currently_painting_top = true;
+ push_point_onto_queue(fill_queue, bci.max_queue_size, trace_t, bci.x, top_ty);
+ }
+ if ((!ok_to_paint) && currently_painting_top) {
+ currently_painting_top = false;
+ }
+ }
+ }
+ }
+
+ if (can_paint_bottom) {
+ if (paint_directions & PAINT_DIRECTION_DOWN) {
+ unsigned char *trace_t = current_trace_t + bci.width;
+ if (!is_pixel_queued(trace_t)) {
+ bool ok_to_paint = check_if_pixel_is_paintable(px, trace_t, bci.x, bottom_ty, orig_color, bci);
+
+ if (initial_paint) { currently_painting_bottom = !ok_to_paint; }
+
+ if (ok_to_paint && (!currently_painting_bottom)) {
+ currently_painting_bottom = true;
+ push_point_onto_queue(fill_queue, bci.max_queue_size, trace_t, bci.x, bottom_ty);
+ }
+ if ((!ok_to_paint) && currently_painting_bottom) {
+ currently_painting_bottom = false;
+ }
+ }
+ }
+ }
+
+ if (bci.is_left) {
+ if (paint_directions & PAINT_DIRECTION_LEFT) {
+ bci.x--; current_trace_t--;
+ ok = true;
+ }
+ } else {
+ if (paint_directions & PAINT_DIRECTION_RIGHT) {
+ bci.x++; current_trace_t++;
+ ok = true;
+ }
+ }
+
+ initial_paint = false;
+ }
+ } else {
+ if (bci.bbox.min()[Geom::X] > bci.screen.min()[Geom::X]) {
+ aborted = true; break;
+ } else {
+ reached_screen_boundary = true;
+ }
+ }
+ } while (ok);
+
+ if (aborted) { return SCANLINE_CHECK_ABORTED; }
+ if (reached_screen_boundary) { return SCANLINE_CHECK_BOUNDARY; }
+ return SCANLINE_CHECK_OK;
+}
+
+/**
+ * Sort the rendered pixel buffer check queue vertically.
+ */
+static bool sort_fill_queue_vertical(Geom::Point a, Geom::Point b) {
+ return a[Geom::Y] > b[Geom::Y];
+}
+
+/**
+ * Sort the rendered pixel buffer check queue horizontally.
+ */
+static bool sort_fill_queue_horizontal(Geom::Point a, Geom::Point b) {
+ return a[Geom::X] > b[Geom::X];
+}
+
+/**
+ * Perform a flood fill operation.
+ * @param event_context The event context for this tool.
+ * @param event The details of this event.
+ * @param union_with_selection If true, union the new fill with the current selection.
+ * @param is_point_fill If false, use the Rubberband "touch selection" to get the initial points for the fill.
+ * @param is_touch_fill If true, use only the initial contact point in the Rubberband "touch selection" as the fill target color.
+ */
+static void sp_flood_do_flood_fill(ToolBase *event_context, GdkEvent *event, bool union_with_selection, bool is_point_fill, bool is_touch_fill) {
+ SPDesktop *desktop = event_context->desktop;
+ SPDocument *document = sp_desktop_document(desktop);
+
+ document->ensureUpToDate();
+
+ Geom::OptRect bbox = document->getRoot()->visualBounds();
+
+ if (!bbox) {
+ desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("<b>Area is not bounded</b>, cannot fill."));
+ return;
+ }
+
+ double zoom_scale = desktop->current_zoom();
+
+ // Render 160% of the physical display to the render pixel buffer, so that available
+ // fill areas off the screen can be included in the fill.
+ double padding = 1.6;
+
+ Geom::Rect screen = desktop->get_display_area();
+
+ unsigned int width = (int)ceil(screen.width() * zoom_scale * padding);
+ unsigned int height = (int)ceil(screen.height() * zoom_scale * padding);
+
+ Geom::Point origin(screen.min()[Geom::X],
+ document->getHeight().value("px") - screen.height() - screen.min()[Geom::Y]);
+
+ origin[Geom::X] += (screen.width() * ((1 - padding) / 2));
+ origin[Geom::Y] += (screen.height() * ((1 - padding) / 2));
+
+ Geom::Scale scale(zoom_scale, zoom_scale);
+ Geom::Affine affine = scale * Geom::Translate(-origin * scale);
+
+ int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
+ guchar *px = g_new(guchar, stride * height);
+ guint32 bgcolor, dtc;
+
+ // Draw image into data block px
+ { // this block limits the lifetime of Drawing and DrawingContext
+ /* Create DrawingItems and set transform */
+ unsigned dkey = SPItem::display_key_new(1);
+ Inkscape::Drawing drawing;
+ Inkscape::DrawingItem *root = document->getRoot()->invoke_show( drawing, dkey, SP_ITEM_SHOW_DISPLAY);
+ root->setTransform(affine);
+ drawing.setRoot(root);
+
+ Geom::IntRect final_bbox = Geom::IntRect::from_xywh(0, 0, width, height);
+ drawing.update(final_bbox);
+
+ cairo_surface_t *s = cairo_image_surface_create_for_data(
+ px, CAIRO_FORMAT_ARGB32, width, height, stride);
+ Inkscape::DrawingContext ct(s, Geom::Point(0,0));
+ // cairo_translate not necessary here - surface origin is at 0,0
+
+ SPNamedView *nv = sp_desktop_namedview(desktop);
+ bgcolor = nv->pagecolor;
+ // bgcolor is 0xrrggbbaa, we need 0xaarrggbb
+ dtc = (bgcolor >> 8) | (bgcolor << 24);
+
+ ct.setSource(bgcolor);
+ ct.setOperator(CAIRO_OPERATOR_SOURCE);
+ ct.paint();
+ ct.setOperator(CAIRO_OPERATOR_OVER);
+
+ drawing.render(ct, final_bbox);
+
+ //cairo_surface_write_to_png( s, "cairo.png" );
+
+ cairo_surface_flush(s);
+ cairo_surface_destroy(s);
+
+ // Hide items
+ document->getRoot()->invoke_hide(dkey);
+ }
+
+ // {
+ // // Dump data to png
+ // cairo_surface_t *s = cairo_image_surface_create_for_data(
+ // px, CAIRO_FORMAT_ARGB32, width, height, stride);
+ // cairo_surface_write_to_png( s, "cairo2.png" );
+ // std::cout << " Wrote cairo2.png" << std::endl;
+ // }
+
+ guchar *trace_px = g_new(guchar, width * height);
+ memset(trace_px, 0x00, width * height);
+
+ std::deque<Geom::Point> fill_queue;
+ std::queue<Geom::Point> color_queue;
+
+ std::vector<Geom::Point> fill_points;
+
+ bool aborted = false;
+ int y_limit = height - 1;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ PaintBucketChannels method = (PaintBucketChannels) prefs->getInt("/tools/paintbucket/channels", 0);
+ int threshold = prefs->getIntLimited("/tools/paintbucket/threshold", 1, 0, 100);
+
+ switch(method) {
+ case FLOOD_CHANNELS_ALPHA:
+ case FLOOD_CHANNELS_RGB:
+ case FLOOD_CHANNELS_R:
+ case FLOOD_CHANNELS_G:
+ case FLOOD_CHANNELS_B:
+ threshold = (255 * threshold) / 100;
+ break;
+ case FLOOD_CHANNELS_H:
+ case FLOOD_CHANNELS_S:
+ case FLOOD_CHANNELS_L:
+ break;
+ }
+
+ bitmap_coords_info bci;
+
+ bci.y_limit = y_limit;
+ bci.width = width;
+ bci.height = height;
+ bci.stride = stride;
+ bci.threshold = threshold;
+ bci.method = method;
+ bci.bbox = *bbox;
+ bci.screen = screen;
+ bci.dtc = dtc;
+ bci.radius = prefs->getIntLimited("/tools/paintbucket/autogap", 0, 0, 3);
+ bci.max_queue_size = (width * height) / 4;
+ bci.current_step = 0;
+
+ if (is_point_fill) {
+ fill_points.push_back(Geom::Point(event->button.x, event->button.y));
+ } else {
+ Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop);
+ fill_points = r->getPoints();
+ }
+
+ for (unsigned int i = 0; i < fill_points.size(); i++) {
+ Geom::Point pw = Geom::Point(fill_points[i][Geom::X] / zoom_scale, document->getHeight().value("px") + (fill_points[i][Geom::Y] / zoom_scale)) * affine;
+
+ pw[Geom::X] = (int)MIN(width - 1, MAX(0, pw[Geom::X]));
+ pw[Geom::Y] = (int)MIN(height - 1, MAX(0, pw[Geom::Y]));
+
+ if (is_touch_fill) {
+ if (i == 0) {
+ color_queue.push(pw);
+ } else {
+ unsigned char *trace_t = get_trace_pixel(trace_px, (int)pw[Geom::X], (int)pw[Geom::Y], width);
+ push_point_onto_queue(&fill_queue, bci.max_queue_size, trace_t, (int)pw[Geom::X], (int)pw[Geom::Y]);
+ }
+ } else {
+ color_queue.push(pw);
+ }
+ }
+
+ bool reached_screen_boundary = false;
+
+ bool first_run = true;
+
+ unsigned long sort_size_threshold = 5;
+
+ unsigned int min_y = height;
+ unsigned int max_y = 0;
+ unsigned int min_x = width;
+ unsigned int max_x = 0;
+
+ while (!color_queue.empty() && !aborted) {
+ Geom::Point color_point = color_queue.front();
+ color_queue.pop();
+
+ int cx = (int)color_point[Geom::X];
+ int cy = (int)color_point[Geom::Y];
+
+ guint32 orig_color = get_pixel(px, cx, cy, stride);
+ bci.merged_orig_pixel = compose_onto(orig_color, dtc);
+
+ unsigned char *trace_t = get_trace_pixel(trace_px, cx, cy, width);
+ if (!is_pixel_checked(trace_t) && !is_pixel_colored(trace_t)) {
+ if (check_if_pixel_is_paintable(px, trace_px, cx, cy, orig_color, bci)) {
+ shift_point_onto_queue(&fill_queue, bci.max_queue_size, trace_t, cx, cy);
+
+ if (!first_run) {
+ for (unsigned int y = 0; y < height; y++) {
+ trace_t = get_trace_pixel(trace_px, 0, y, width);
+ for (unsigned int x = 0; x < width; x++) {
+ clear_pixel_paintability(trace_t);
+ trace_t++;
+ }
+ }
+ }
+ first_run = false;
+ }
+ }
+
+ unsigned long old_fill_queue_size = fill_queue.size();
+
+ while (!fill_queue.empty() && !aborted) {
+ Geom::Point cp = fill_queue.front();
+
+ if (bci.radius == 0) {
+ unsigned long new_fill_queue_size = fill_queue.size();
+
+ /*
+ * To reduce the number of points in the fill queue, periodically
+ * resort all of the points in the queue so that scanline checks
+ * can complete more quickly. A point cannot be checked twice
+ * in a normal scanline checks, so forcing scanline checks to start
+ * from one corner of the rendered area as often as possible
+ * will reduce the number of points that need to be checked and queued.
+ */
+ if (new_fill_queue_size > sort_size_threshold) {
+ if (new_fill_queue_size > old_fill_queue_size) {
+ std::sort(fill_queue.begin(), fill_queue.end(), sort_fill_queue_vertical);
+
+ std::deque<Geom::Point>::iterator start_sort = fill_queue.begin();
+ std::deque<Geom::Point>::iterator end_sort = fill_queue.begin();
+ unsigned int sort_y = (unsigned int)cp[Geom::Y];
+ unsigned int current_y = sort_y;
+
+ for (std::deque<Geom::Point>::iterator i = fill_queue.begin(); i != fill_queue.end(); ++i) {
+ Geom::Point current = *i;
+ current_y = (unsigned int)current[Geom::Y];
+ if (current_y != sort_y) {
+ if (start_sort != end_sort) {
+ std::sort(start_sort, end_sort, sort_fill_queue_horizontal);
+ }
+ sort_y = current_y;
+ start_sort = i;
+ }
+ end_sort = i;
+ }
+ if (start_sort != end_sort) {
+ std::sort(start_sort, end_sort, sort_fill_queue_horizontal);
+ }
+
+ cp = fill_queue.front();
+ }
+ }
+
+ old_fill_queue_size = new_fill_queue_size;
+ }
+
+ fill_queue.pop_front();
+
+ int x = (int)cp[Geom::X];
+ int y = (int)cp[Geom::Y];
+
+ min_y = MIN((unsigned int)y, min_y);
+ max_y = MAX((unsigned int)y, max_y);
+
+ unsigned char *trace_t = get_trace_pixel(trace_px, x, y, width);
+ if (!is_pixel_checked(trace_t)) {
+ mark_pixel_checked(trace_t);
+
+ if (y == 0) {
+ if (bbox->min()[Geom::Y] > screen.min()[Geom::Y]) {
+ aborted = true; break;
+ } else {
+ reached_screen_boundary = true;
+ }
+ }
+
+ if (y == y_limit) {
+ if (bbox->max()[Geom::Y] < screen.max()[Geom::Y]) {
+ aborted = true; break;
+ } else {
+ reached_screen_boundary = true;
+ }
+ }
+
+ bci.is_left = true;
+ bci.x = x;
+ bci.y = y;
+
+ ScanlineCheckResult result = perform_bitmap_scanline_check(&fill_queue, px, trace_px, orig_color, bci, &min_x, &max_x);
+
+ switch (result) {
+ case SCANLINE_CHECK_ABORTED:
+ aborted = true;
+ break;
+ case SCANLINE_CHECK_BOUNDARY:
+ reached_screen_boundary = true;
+ break;
+ default:
+ break;
+ }
+
+ if (bci.x < width) {
+ trace_t++;
+ if (!is_pixel_checked(trace_t) && !is_pixel_queued(trace_t)) {
+ mark_pixel_checked(trace_t);
+ bci.is_left = false;
+ bci.x = x + 1;
+
+ result = perform_bitmap_scanline_check(&fill_queue, px, trace_px, orig_color, bci, &min_x, &max_x);
+
+ switch (result) {
+ case SCANLINE_CHECK_ABORTED:
+ aborted = true;
+ break;
+ case SCANLINE_CHECK_BOUNDARY:
+ reached_screen_boundary = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ bci.current_step++;
+
+ if (bci.current_step > bci.max_queue_size) {
+ aborted = true;
+ }
+ }
+ }
+
+ g_free(px);
+
+ if (aborted) {
+ g_free(trace_px);
+ desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("<b>Area is not bounded</b>, cannot fill."));
+ return;
+ }
+
+ if (reached_screen_boundary) {
+ desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("<b>Only the visible part of the bounded area was filled.</b> If you want to fill all of the area, undo, zoom out, and fill again."));
+ }
+
+ unsigned int trace_padding = bci.radius + 1;
+ if (min_y > trace_padding) { min_y -= trace_padding; }
+ if (max_y < (y_limit - trace_padding)) { max_y += trace_padding; }
+ if (min_x > trace_padding) { min_x -= trace_padding; }
+ if (max_x < (width - 1 - trace_padding)) { max_x += trace_padding; }
+
+ Geom::Point min_start = Geom::Point(min_x, min_y);
+
+ affine = scale * Geom::Translate(-origin * scale - min_start);
+ Geom::Affine inverted_affine = Geom::Affine(affine).inverse();
+
+ do_trace(bci, trace_px, desktop, inverted_affine, min_x, max_x, min_y, max_y, union_with_selection);
+
+ g_free(trace_px);
+
+ DocumentUndo::done(document, SP_VERB_CONTEXT_PAINTBUCKET, _("Fill bounded area"));
+}
+
+bool FloodTool::item_handler(SPItem* item, GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if ((event->button.state & GDK_CONTROL_MASK) && event->button.button == 1 && !this->space_panning) {
+ Geom::Point const button_w(event->button.x, event->button.y);
+
+ SPItem *item = sp_event_context_find_item (desktop, button_w, TRUE, TRUE);
+
+ // Set style
+ desktop->applyCurrentOrToolStyle(item, "/tools/paintbucket", false);
+
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_PAINTBUCKET, _("Set style on object"));
+
+ ret = TRUE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+// if (((ToolBaseClass *) sp_flood_context_parent_class)->item_handler) {
+// ret = ((ToolBaseClass *) sp_flood_context_parent_class)->item_handler(event_context, item, event);
+// }
+ // CPPIFY: ret is overwritten...
+ ret = ToolBase::item_handler(item, event);
+
+ return ret;
+}
+
+bool FloodTool::root_handler(GdkEvent* event) {
+ static bool dragging;
+
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+ if (!(event->button.state & GDK_CONTROL_MASK)) {
+ Geom::Point const button_w(event->button.x, event->button.y);
+
+ if (Inkscape::have_viable_layer(desktop, this->defaultMessageContext())) {
+ // save drag origin
+ this->xp = (gint) button_w[Geom::X];
+ this->yp = (gint) button_w[Geom::Y];
+ this->within_tolerance = true;
+
+ dragging = true;
+
+ Geom::Point const p(desktop->w2d(button_w));
+ Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH);
+ Inkscape::Rubberband::get(desktop)->start(desktop, p);
+ }
+ }
+ }
+
+ case GDK_MOTION_NOTIFY:
+ if ( dragging && ( event->motion.state & GDK_BUTTON1_MASK ) && !this->space_panning) {
+ if ( this->within_tolerance
+ && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance )
+ && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) {
+ break; // do not drag if we're within tolerance from origin
+ }
+
+ this->within_tolerance = false;
+
+ Geom::Point const motion_pt(event->motion.x, event->motion.y);
+ Geom::Point const p(desktop->w2d(motion_pt));
+
+ if (Inkscape::Rubberband::get(desktop)->is_started()) {
+ Inkscape::Rubberband::get(desktop)->move(p);
+ this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Draw over</b> areas to add to fill, hold <b>Alt</b> for touch fill"));
+ gobble_motion_events(GDK_BUTTON1_MASK);
+ }
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ if (event->button.button == 1 && !this->space_panning) {
+ Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop);
+
+ if (r->is_started()) {
+ // set "busy" cursor
+ desktop->setWaitingCursor();
+
+ if (SP_IS_EVENT_CONTEXT(this)) {
+ // Since setWaitingCursor runs main loop iterations, we may have already left this tool!
+ // So check if the tool is valid before doing anything
+ dragging = false;
+
+ bool is_point_fill = this->within_tolerance;
+ bool is_touch_fill = event->button.state & GDK_MOD1_MASK;
+
+ sp_flood_do_flood_fill(this, event, event->button.state & GDK_SHIFT_MASK, is_point_fill, is_touch_fill);
+
+ desktop->clearWaitingCursor();
+ // restore cursor when done; note that it may already be different if e.g. user
+ // switched to another tool during interruptible tracing or drawing, in which case do nothing
+
+ ret = TRUE;
+ }
+
+ r->stop();
+
+ //if (SP_IS_EVENT_CONTEXT(this)) {
+ this->defaultMessageContext()->clear();
+ //}
+ }
+ }
+ break;
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Up:
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_Down:
+ // prevent the zoom field from activation
+ if (!MOD__CTRL_ONLY(event))
+ ret = TRUE;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+void FloodTool::finishItem() {
+ this->message_context->clear();
+
+ if (this->item != NULL) {
+ this->item->updateRepr();
+
+ desktop->canvas->endForcedFullRedraws();
+
+ sp_desktop_selection(desktop)->set(this->item);
+
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_PAINTBUCKET, _("Fill bounded area"));
+
+ this->item = NULL;
+ }
+}
+
+void FloodTool::set_channels(gint channels) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt("/tools/paintbucket/channels", channels);
+}
+
+}
+}
+}
+
+/*
+ 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/tools/flood-tool.h b/src/ui/tools/flood-tool.h
new file mode 100644
index 000000000..77dd2f13a
--- /dev/null
+++ b/src/ui/tools/flood-tool.h
@@ -0,0 +1,74 @@
+#ifndef __SP_FLOOD_CONTEXT_H__
+#define __SP_FLOOD_CONTEXT_H__
+
+/*
+ * Flood fill drawing context
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * John Bintz <jcoswell@coswellproductions.org>
+ *
+ * Released under GNU GPL
+ */
+
+#include <stddef.h>
+#include <sigc++/sigc++.h>
+#include <gtk/gtk.h>
+#include "ui/tools/tool-base.h"
+
+#define SP_FLOOD_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::FloodTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_FLOOD_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::FloodTool*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+
+#define FLOOD_COLOR_CHANNEL_R 1
+#define FLOOD_COLOR_CHANNEL_G 2
+#define FLOOD_COLOR_CHANNEL_B 4
+#define FLOOD_COLOR_CHANNEL_A 8
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class FloodTool : public ToolBase {
+public:
+ FloodTool();
+ virtual ~FloodTool();
+
+ SPItem *item;
+
+ sigc::connection sel_changed_connection;
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual bool root_handler(GdkEvent* event);
+ virtual bool item_handler(SPItem* item, GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+ static void set_channels(gint channels);
+
+private:
+ void selection_changed(Inkscape::Selection* selection);
+ void finishItem();
+};
+
+GList* flood_channels_dropdown_items_list (void);
+GList* flood_autogap_dropdown_items_list (void);
+
+enum PaintBucketChannels {
+ FLOOD_CHANNELS_RGB,
+ FLOOD_CHANNELS_R,
+ FLOOD_CHANNELS_G,
+ FLOOD_CHANNELS_B,
+ FLOOD_CHANNELS_H,
+ FLOOD_CHANNELS_S,
+ FLOOD_CHANNELS_L,
+ FLOOD_CHANNELS_ALPHA
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/ui/tools/freehand-base.cpp b/src/ui/tools/freehand-base.cpp
new file mode 100644
index 000000000..fa9eaa6b4
--- /dev/null
+++ b/src/ui/tools/freehand-base.cpp
@@ -0,0 +1,818 @@
+/*
+ * Generic drawing context
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Abhishek Sharma
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2000 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2002 Lauris Kaplinski
+ * Copyright (C) 2012 Johan Engelen
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#define DRAW_VERBOSE
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "live_effects/lpe-patternalongpath.h"
+#include "display/canvas-bpath.h"
+#include "xml/repr.h"
+#include "svg/svg.h"
+#include <glibmm/i18n.h>
+#include "display/curve.h"
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "desktop-style.h"
+#include "document.h"
+#include "draw-anchor.h"
+#include "macros.h"
+#include "message-stack.h"
+#include "ui/tools/pen-tool.h"
+#include "ui/tools/lpe-tool.h"
+#include "preferences.h"
+#include "selection.h"
+#include "selection-chemistry.h"
+#include "snap.h"
+#include "sp-path.h"
+#include "sp-namedview.h"
+#include "live_effects/lpe-powerstroke.h"
+#include "style.h"
+#include "ui/control-manager.h"
+#include "ui/tools/freehand-base.h"
+
+#include <gdk/gdkkeysyms.h>
+
+using Inkscape::DocumentUndo;
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+static void spdc_selection_changed(Inkscape::Selection *sel, FreehandBase *dc);
+static void spdc_selection_modified(Inkscape::Selection *sel, guint flags, FreehandBase *dc);
+
+static void spdc_attach_selection(FreehandBase *dc, Inkscape::Selection *sel);
+
+/**
+ * Flushes white curve(s) and additional curve into object.
+ *
+ * No cleaning of colored curves - this has to be done by caller
+ * No rereading of white data, so if you cannot rely on ::modified, do it in caller
+ */
+static void spdc_flush_white(FreehandBase *dc, SPCurve *gc);
+
+static void spdc_reset_white(FreehandBase *dc);
+static void spdc_free_colors(FreehandBase *dc);
+
+FreehandBase::FreehandBase() : ToolBase() {
+ this->selection = 0;
+ this->grab = 0;
+ this->anchor_statusbar = false;
+
+ this->attach = FALSE;
+
+ this->red_color = 0xff00007f;
+ this->blue_color = 0x0000ff7f;
+ this->green_color = 0x00ff007f;
+ this->red_curve_is_valid = false;
+
+ this->red_bpath = NULL;
+ this->red_curve = NULL;
+
+ this->blue_bpath = NULL;
+ this->blue_curve = NULL;
+
+ this->green_bpaths = NULL;
+ this->green_curve = NULL;
+ this->green_anchor = NULL;
+ this->green_closed = false;
+
+ this->white_item = NULL;
+ this->white_curves = NULL;
+ this->white_anchors = NULL;
+
+ this->sa = NULL;
+ this->ea = NULL;
+
+ this->waiting_LPE_type = Inkscape::LivePathEffect::INVALID_LPE;
+}
+
+FreehandBase::~FreehandBase() {
+ if (this->grab) {
+ sp_canvas_item_ungrab(this->grab, GDK_CURRENT_TIME);
+ this->grab = NULL;
+ }
+
+ if (this->selection) {
+ this->selection = NULL;
+ }
+
+ spdc_free_colors(this);
+}
+
+void FreehandBase::setup() {
+ ToolBase::setup();
+
+ this->selection = sp_desktop_selection(desktop);
+
+ // Connect signals to track selection changes
+ this->sel_changed_connection = this->selection->connectChanged(
+ sigc::bind(sigc::ptr_fun(&spdc_selection_changed), this)
+ );
+ this->sel_modified_connection = this->selection->connectModified(
+ sigc::bind(sigc::ptr_fun(&spdc_selection_modified), this)
+ );
+
+ // Create red bpath
+ this->red_bpath = sp_canvas_bpath_new(sp_desktop_sketch(this->desktop), NULL);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->red_bpath), this->red_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+
+ // Create red curve
+ this->red_curve = new SPCurve();
+
+ // Create blue bpath
+ this->blue_bpath = sp_canvas_bpath_new(sp_desktop_sketch(this->desktop), NULL);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->blue_bpath), this->blue_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+
+ // Create blue curve
+ this->blue_curve = new SPCurve();
+
+ // Create green curve
+ this->green_curve = new SPCurve();
+
+ // No green anchor by default
+ this->green_anchor = NULL;
+ this->green_closed = FALSE;
+
+ this->attach = TRUE;
+ spdc_attach_selection(this, this->selection);
+}
+
+void FreehandBase::finish() {
+ this->sel_changed_connection.disconnect();
+ this->sel_modified_connection.disconnect();
+
+ if (this->grab) {
+ sp_canvas_item_ungrab(this->grab, GDK_CURRENT_TIME);
+ }
+
+ if (this->selection) {
+ this->selection = NULL;
+ }
+
+ spdc_free_colors(this);
+}
+
+void FreehandBase::set(const Inkscape::Preferences::Entry& /*value*/) {
+}
+
+bool FreehandBase::root_handler(GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Up:
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_Down:
+ // prevent the zoom field from activation
+ if (!MOD__CTRL_ONLY(event)) {
+ ret = TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+static Glib::ustring const tool_name(FreehandBase *dc)
+{
+ return ( SP_IS_PEN_CONTEXT(dc)
+ ? "/tools/freehand/pen"
+ : "/tools/freehand/pencil" );
+}
+
+static void spdc_paste_curve_as_freehand_shape(const SPCurve *c, FreehandBase *dc, SPItem *item)
+{
+ using namespace Inkscape::LivePathEffect;
+
+ // TODO: Don't paste path if nothing is on the clipboard
+
+ Effect::createAndApply(PATTERN_ALONG_PATH, dc->desktop->doc(), item);
+ Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE();
+ gchar *svgd = sp_svg_write_path(c->get_pathvector());
+ static_cast<LPEPatternAlongPath*>(lpe)->pattern.paste_param_path(svgd);
+}
+
+static void spdc_apply_powerstroke_shape(const std::vector<Geom::Point> & points, FreehandBase *dc, SPItem *item)
+{
+ using namespace Inkscape::LivePathEffect;
+
+ Effect::createAndApply(POWERSTROKE, dc->desktop->doc(), item);
+ Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE();
+ static_cast<LPEPowerStroke*>(lpe)->offset_points.param_set_and_write_new_value(points);
+
+ // write powerstroke parameters:
+ lpe->getRepr()->setAttribute("start_linecap_type", "zerowidth");
+ lpe->getRepr()->setAttribute("end_linecap_type", "zerowidth");
+ lpe->getRepr()->setAttribute("cusp_linecap_type", "round");
+ lpe->getRepr()->setAttribute("sort_points", "true");
+ lpe->getRepr()->setAttribute("interpolator_type", "CubicBezierJohan");
+ lpe->getRepr()->setAttribute("interpolator_beta", "0.2");
+}
+
+static void spdc_check_for_and_apply_waiting_LPE(FreehandBase *dc, SPItem *item, SPCurve *curve)
+{
+ using namespace Inkscape::LivePathEffect;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (item && SP_IS_LPE_ITEM(item)) {
+ if (prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 1) {
+ Effect::createAndApply(SPIRO, dc->desktop->doc(), item);
+ }
+ //BSpline
+ //Añadimos el modo BSpline a los efectos en espera
+ if (prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 2) {
+ Effect::createAndApply(BSPLINE, dc->desktop->doc(), item);
+ }
+ //BSPline End
+
+ int shape = prefs->getInt(tool_name(dc) + "/shape", 0);
+ bool shape_applied = false;
+ SPCSSAttr *css_item = sp_css_attr_from_object(item, SP_STYLE_FLAG_ALWAYS);
+ const char *cstroke = sp_repr_css_property(css_item, "stroke", "none");
+
+#define SHAPE_LENGTH 10
+#define SHAPE_HEIGHT 10
+
+ switch (shape) {
+ case 0:
+ // don't apply any shape
+ break;
+ case 1:
+ {
+ // "triangle in"
+ std::vector<Geom::Point> points(1);
+ points[0] = Geom::Point(0., SHAPE_HEIGHT/2);
+ spdc_apply_powerstroke_shape(points, dc, item);
+
+ shape_applied = true;
+ break;
+ }
+ case 2:
+ {
+ // "triangle out"
+ guint curve_length = curve->get_segment_count();
+ std::vector<Geom::Point> points(1);
+ points[0] = Geom::Point((double)curve_length, SHAPE_HEIGHT/2);
+ spdc_apply_powerstroke_shape(points, dc, item);
+
+ shape_applied = true;
+ break;
+ }
+ case 3:
+ {
+ // "ellipse"
+ SPCurve *c = new SPCurve();
+ const double C1 = 0.552;
+ c->moveto(0, SHAPE_HEIGHT/2);
+ c->curveto(0, (1 - C1) * SHAPE_HEIGHT/2, (1 - C1) * SHAPE_LENGTH/2, 0, SHAPE_LENGTH/2, 0);
+ c->curveto((1 + C1) * SHAPE_LENGTH/2, 0, SHAPE_LENGTH, (1 - C1) * SHAPE_HEIGHT/2, SHAPE_LENGTH, SHAPE_HEIGHT/2);
+ c->curveto(SHAPE_LENGTH, (1 + C1) * SHAPE_HEIGHT/2, (1 + C1) * SHAPE_LENGTH/2, SHAPE_HEIGHT, SHAPE_LENGTH/2, SHAPE_HEIGHT);
+ c->curveto((1 - C1) * SHAPE_LENGTH/2, SHAPE_HEIGHT, 0, (1 + C1) * SHAPE_HEIGHT/2, 0, SHAPE_HEIGHT/2);
+ c->closepath();
+ spdc_paste_curve_as_freehand_shape(c, dc, item);
+ c->unref();
+ shape_applied = true;
+ break;
+ }
+ case 4:
+ {
+ // take shape from clipboard; TODO: catch the case where clipboard is empty
+ Effect::createAndApply(PATTERN_ALONG_PATH, dc->desktop->doc(), item);
+ Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE();
+ static_cast<LPEPatternAlongPath*>(lpe)->pattern.on_paste_button_click();
+
+ shape_applied = true;
+ break;
+ }
+ default:
+ break;
+ }
+ if (shape_applied) {
+ // apply original stroke color as fill and unset stroke; then return
+ SPCSSAttr *css = sp_repr_css_attr_new();
+
+ if (!strcmp(cstroke, "none")){
+ sp_repr_css_set_property (css, "fill", "black");
+ } else {
+ sp_repr_css_set_property (css, "fill", cstroke);
+ }
+ sp_repr_css_set_property (css, "stroke", "none");
+ sp_desktop_apply_css_recursive(item, css, true);
+ sp_repr_css_attr_unref(css);
+ return;
+ }
+
+ if (dc->waiting_LPE_type != INVALID_LPE) {
+ Effect::createAndApply(dc->waiting_LPE_type, dc->desktop->doc(), item);
+ dc->waiting_LPE_type = INVALID_LPE;
+
+ if (SP_IS_LPETOOL_CONTEXT(dc)) {
+ // since a geometric LPE was applied, we switch back to "inactive" mode
+ lpetool_context_switch_mode(SP_LPETOOL_CONTEXT(dc), INVALID_LPE);
+ }
+ }
+ if (SP_IS_PEN_CONTEXT(dc)) {
+ sp_pen_context_set_polyline_mode(SP_PEN_CONTEXT(dc));
+ }
+ }
+}
+
+/*
+ * Selection handlers
+ */
+
+static void spdc_selection_changed(Inkscape::Selection *sel, FreehandBase *dc)
+{
+ if (dc->attach) {
+ spdc_attach_selection(dc, sel);
+ }
+}
+
+/* fixme: We have to ensure this is not delayed (Lauris) */
+
+static void spdc_selection_modified(Inkscape::Selection *sel, guint /*flags*/, FreehandBase *dc)
+{
+ if (dc->attach) {
+ spdc_attach_selection(dc, sel);
+ }
+}
+
+static void spdc_attach_selection(FreehandBase *dc, Inkscape::Selection */*sel*/)
+{
+ // We reset white and forget white/start/end anchors
+ spdc_reset_white(dc);
+ dc->sa = NULL;
+ dc->ea = NULL;
+
+ SPItem *item = dc->selection ? dc->selection->singleItem() : NULL;
+
+ if ( item && SP_IS_PATH(item) ) {
+ // Create new white data
+ // Item
+ dc->white_item = item;
+
+ // Curve list
+ // We keep it in desktop coordinates to eliminate calculation errors
+ SPCurve *norm = SP_PATH(item)->get_curve_for_edit();
+ norm->transform((dc->white_item)->i2dt_affine());
+ g_return_if_fail( norm != NULL );
+ dc->white_curves = g_slist_reverse(norm->split());
+ norm->unref();
+
+ // Anchor list
+ for (GSList *l = dc->white_curves; l != NULL; l = l->next) {
+ SPCurve *c;
+ c = static_cast<SPCurve*>(l->data);
+ g_return_if_fail( c->get_segment_count() > 0 );
+ if ( !c->is_closed() ) {
+ SPDrawAnchor *a;
+ a = sp_draw_anchor_new(dc, c, TRUE, *(c->first_point()));
+ if (a)
+ dc->white_anchors = g_slist_prepend(dc->white_anchors, a);
+ a = sp_draw_anchor_new(dc, c, FALSE, *(c->last_point()));
+ if (a)
+ dc->white_anchors = g_slist_prepend(dc->white_anchors, a);
+ }
+ }
+ // fixme: recalculate active anchor?
+ }
+}
+
+
+void spdc_endpoint_snap_rotation(ToolBase const *const ec, Geom::Point &p, Geom::Point const &o,
+ guint state)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ unsigned const snaps = abs(prefs->getInt("/options/rotationsnapsperpi/value", 12));
+
+ SnapManager &m = SP_EVENT_CONTEXT_DESKTOP(ec)->namedview->snap_manager;
+ m.setup(SP_EVENT_CONTEXT_DESKTOP(ec));
+
+ bool snap_enabled = m.snapprefs.getSnapEnabledGlobally();
+ if (state & GDK_SHIFT_MASK) {
+ // SHIFT disables all snapping, except the angular snapping. After all, the user explicitly asked for angular
+ // snapping by pressing CTRL, otherwise we wouldn't have arrived here. But although we temporarily disable
+ // the snapping here, we must still call for a constrained snap in order to apply the constraints (i.e. round
+ // to the nearest angle increment)
+ m.snapprefs.setSnapEnabledGlobally(false);
+ }
+
+ Inkscape::SnappedPoint dummy = m.constrainedAngularSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE), boost::optional<Geom::Point>(), o, snaps);
+ p = dummy.getPoint();
+
+ if (state & GDK_SHIFT_MASK) {
+ m.snapprefs.setSnapEnabledGlobally(snap_enabled); // restore the original setting
+ }
+
+ m.unSetup();
+}
+
+
+void spdc_endpoint_snap_free(ToolBase const * const ec, Geom::Point& p, boost::optional<Geom::Point> &start_of_line, guint const /*state*/)
+{
+ SPDesktop *dt = SP_EVENT_CONTEXT_DESKTOP(ec);
+ SnapManager &m = dt->namedview->snap_manager;
+ Inkscape::Selection *selection = sp_desktop_selection (dt);
+
+ // selection->singleItem() is the item that is currently being drawn. This item will not be snapped to (to avoid self-snapping)
+ // TODO: Allow snapping to the stationary parts of the item, and only ignore the last segment
+
+ m.setup(dt, true, selection->singleItem());
+ Inkscape::SnapCandidatePoint scp(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ if (start_of_line) {
+ scp.addOrigin(*start_of_line);
+ }
+
+ Inkscape::SnappedPoint sp = m.freeSnap(scp);
+ p = sp.getPoint();
+
+ m.unSetup();
+}
+
+static SPCurve *reverse_then_unref(SPCurve *orig)
+{
+ SPCurve *ret = orig->create_reverse();
+ orig->unref();
+ return ret;
+}
+
+void spdc_concat_colors_and_flush(FreehandBase *dc, gboolean forceclosed)
+{
+ // Concat RBG
+ SPCurve *c = dc->green_curve;
+
+ // Green
+ dc->green_curve = new SPCurve();
+ while (dc->green_bpaths) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->green_bpaths->data));
+ dc->green_bpaths = g_slist_remove(dc->green_bpaths, dc->green_bpaths->data);
+ }
+
+ // Blue
+ c->append_continuous(dc->blue_curve, 0.0625);
+ dc->blue_curve->reset();
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->blue_bpath), NULL);
+
+ // Red
+ if (dc->red_curve_is_valid) {
+ c->append_continuous(dc->red_curve, 0.0625);
+ }
+ dc->red_curve->reset();
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->red_bpath), NULL);
+
+
+ if (c->is_empty()) {
+ c->unref();
+ return;
+ }
+
+ // Step A - test, whether we ended on green anchor
+ if ( forceclosed || ( dc->green_anchor && dc->green_anchor->active ) ) {
+ // We hit green anchor, closing Green-Blue-Red
+ SP_EVENT_CONTEXT_DESKTOP(dc)->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Path is closed."));
+ c->closepath_current();
+ // Closed path, just flush
+ spdc_flush_white(dc, c);
+ c->unref();
+ return;
+ }
+
+ // Step B - both start and end anchored to same curve
+ if ( dc->sa && dc->ea
+ && ( dc->sa->curve == dc->ea->curve )
+ && ( ( dc->sa != dc->ea )
+ || dc->sa->curve->is_closed() ) )
+ {
+ // We hit bot start and end of single curve, closing paths
+ SP_EVENT_CONTEXT_DESKTOP(dc)->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Closing path."));
+ if (dc->sa->start && !(dc->sa->curve->is_closed()) ) {
+ c = reverse_then_unref(c);
+ }
+ dc->sa->curve->append_continuous(c, 0.0625);
+ c->unref();
+ dc->sa->curve->closepath_current();
+ //BSpline
+ //Si la curva tiene un LPE del tipo BSpline ejecutamos spdc_flush_white
+ //pasándole la curva de inicio necesaria
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if(prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 1 ||
+ prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 2){
+ dc->white_curves = g_slist_remove(dc->white_curves, dc->sa->curve);
+ spdc_flush_white(dc, dc->sa->curve);
+ }else
+ //BSpline End
+ spdc_flush_white(dc, NULL);
+ return;
+ }
+
+ // Step C - test start
+ if (dc->sa) {
+ SPCurve *s = dc->sa->curve;
+ dc->white_curves = g_slist_remove(dc->white_curves, s);
+ if (dc->sa->start) {
+ s = reverse_then_unref(s);
+ }
+ s->append_continuous(c, 0.0625);
+ c->unref();
+ c = s;
+ } else /* Step D - test end */ if (dc->ea) {
+ SPCurve *e = dc->ea->curve;
+ dc->white_curves = g_slist_remove(dc->white_curves, e);
+ if (!dc->ea->start) {
+ e = reverse_then_unref(e);
+ }
+ c->append_continuous(e, 0.0625);
+ e->unref();
+ }
+
+
+ spdc_flush_white(dc, c);
+
+ c->unref();
+}
+
+static void spdc_flush_white(FreehandBase *dc, SPCurve *gc)
+{
+ SPCurve *c;
+
+ if (dc->white_curves) {
+ g_assert(dc->white_item);
+ c = SPCurve::concat(dc->white_curves);
+ g_slist_free(dc->white_curves);
+ dc->white_curves = NULL;
+ if (gc) {
+ c->append(gc, FALSE);
+ }
+ } else if (gc) {
+ c = gc;
+ c->ref();
+ } else {
+ return;
+ }
+
+ // Now we have to go back to item coordinates at last
+ c->transform( dc->white_item
+ ? (dc->white_item)->dt2i_affine()
+ : SP_EVENT_CONTEXT_DESKTOP(dc)->dt2doc() );
+
+ SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(dc);
+ SPDocument *doc = sp_desktop_document(desktop);
+ Inkscape::XML::Document *xml_doc = doc->getReprDoc();
+
+ if ( c && !c->is_empty() ) {
+ // We actually have something to write
+
+ bool has_lpe = false;
+ Inkscape::XML::Node *repr;
+ if (dc->white_item) {
+ repr = dc->white_item->getRepr();
+ has_lpe = SP_LPE_ITEM(dc->white_item)->hasPathEffectRecursive();
+ } else {
+ repr = xml_doc->createElement("svg:path");
+ // Set style
+ sp_desktop_apply_style_tool(desktop, repr, tool_name(dc).data(), false);
+ }
+
+ gchar *str = sp_svg_write_path( c->get_pathvector() );
+ g_assert( str != NULL );
+ if (has_lpe)
+ repr->setAttribute("inkscape:original-d", str);
+ else
+ repr->setAttribute("d", str);
+ g_free(str);
+
+ if (!dc->white_item) {
+ // Attach repr
+ SPItem *item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
+
+ // we finished the path; now apply any waiting LPEs or freehand shapes
+ spdc_check_for_and_apply_waiting_LPE(dc, item, c);
+
+ dc->selection->set(repr);
+ Inkscape::GC::release(repr);
+ item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
+ item->doWriteTransform(item->getRepr(), item->transform, NULL, true);
+ item->updateRepr();
+ }
+
+ DocumentUndo::done(doc, SP_IS_PEN_CONTEXT(dc)? SP_VERB_CONTEXT_PEN : SP_VERB_CONTEXT_PENCIL,
+ _("Draw path"));
+
+ // When quickly drawing several subpaths with Shift, the next subpath may be finished and
+ // flushed before the selection_modified signal is fired by the previous change, which
+ // results in the tool losing all of the selected path's curve except that last subpath. To
+ // fix this, we force the selection_modified callback now, to make sure the tool's curve is
+ // in sync immediately.
+ spdc_selection_modified(sp_desktop_selection(desktop), 0, dc);
+ }
+
+ c->unref();
+
+ // Flush pending updates
+ doc->ensureUpToDate();
+}
+
+SPDrawAnchor *spdc_test_inside(FreehandBase *dc, Geom::Point p)
+{
+ SPDrawAnchor *active = NULL;
+
+ // Test green anchor
+ if (dc->green_anchor) {
+ active = sp_draw_anchor_test(dc->green_anchor, p, TRUE);
+ }
+
+ for (GSList *l = dc->white_anchors; l != NULL; l = l->next) {
+ SPDrawAnchor *na = sp_draw_anchor_test(static_cast<SPDrawAnchor*>(l->data), p, !active);
+ if ( !active && na ) {
+ active = na;
+ }
+ }
+
+ //BSpline
+ //Modificamos la curva del "anchor" final para que sea igual que la curva de inicio.
+ //Esta curva fue modificada al continuar la curva y necesitamos que sea igual que la curva en
+ //la que cerramos el trazado.
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if((prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 1 ||
+ prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 2) &&
+ dc->sa && !dc->red_curve->is_empty() && !dc->green_anchor){
+ if(active){
+ active->curve = dc->sa->curve;
+ active->curve->ref();
+ }
+ }
+ //BSpline End
+
+ return active;
+}
+
+static void spdc_reset_white(FreehandBase *dc)
+{
+ if (dc->white_item) {
+ // We do not hold refcount
+ dc->white_item = NULL;
+ }
+ while (dc->white_curves) {
+ reinterpret_cast<SPCurve *>(dc->white_curves->data)->unref();
+ dc->white_curves = g_slist_remove(dc->white_curves, dc->white_curves->data);
+ }
+ while (dc->white_anchors) {
+ sp_draw_anchor_destroy(static_cast<SPDrawAnchor*>(dc->white_anchors->data));
+ dc->white_anchors = g_slist_remove(dc->white_anchors, dc->white_anchors->data);
+ }
+}
+
+static void spdc_free_colors(FreehandBase *dc)
+{
+ // Red
+ if (dc->red_bpath) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->red_bpath));
+ dc->red_bpath = NULL;
+ }
+ if (dc->red_curve) {
+ dc->red_curve = dc->red_curve->unref();
+ }
+
+ // Blue
+ if (dc->blue_bpath) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->blue_bpath));
+ dc->blue_bpath = NULL;
+ }
+ if (dc->blue_curve) {
+ dc->blue_curve = dc->blue_curve->unref();
+ }
+
+ // Green
+ while (dc->green_bpaths) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->green_bpaths->data));
+ dc->green_bpaths = g_slist_remove(dc->green_bpaths, dc->green_bpaths->data);
+ }
+ if (dc->green_curve) {
+ dc->green_curve = dc->green_curve->unref();
+ }
+ if (dc->green_anchor) {
+ dc->green_anchor = sp_draw_anchor_destroy(dc->green_anchor);
+ }
+
+ // White
+ if (dc->white_item) {
+ // We do not hold refcount
+ dc->white_item = NULL;
+ }
+ while (dc->white_curves) {
+ reinterpret_cast<SPCurve *>(dc->white_curves->data)->unref();
+ dc->white_curves = g_slist_remove(dc->white_curves, dc->white_curves->data);
+ }
+ while (dc->white_anchors) {
+ sp_draw_anchor_destroy(static_cast<SPDrawAnchor *>(dc->white_anchors->data));
+ dc->white_anchors = g_slist_remove(dc->white_anchors, dc->white_anchors->data);
+ }
+}
+
+void spdc_create_single_dot(ToolBase *ec, Geom::Point const &pt, char const *tool, guint event_state) {
+ g_return_if_fail(!strcmp(tool, "/tools/freehand/pen") || !strcmp(tool, "/tools/freehand/pencil"));
+ Glib::ustring tool_path = tool;
+
+ SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(ec);
+ Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
+ Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
+ repr->setAttribute("sodipodi:type", "arc");
+ SPItem *item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
+ Inkscape::GC::release(repr);
+
+ // apply the tool's current style
+ sp_desktop_apply_style_tool(desktop, repr, tool, false);
+
+ // find out stroke width (TODO: is there an easier way??)
+ double stroke_width = 3.0;
+ gchar const *style_str = NULL;
+ style_str = repr->attribute("style");
+ if (style_str) {
+ SPStyle *style = sp_style_new(SP_ACTIVE_DOCUMENT);
+ sp_style_merge_from_style_string(style, style_str);
+ stroke_width = style->stroke_width.computed;
+ style->stroke_width.computed = 0;
+ sp_style_unref(style);
+ }
+
+ // unset stroke and set fill color to former stroke color
+ gchar * str;
+ str = g_strdup_printf("fill:#%06x;stroke:none;", sp_desktop_get_color_tool(desktop, tool, false) >> 8);
+ repr->setAttribute("style", str);
+ g_free(str);
+
+ // put the circle where the mouse click occurred and set the diameter to the
+ // current stroke width, multiplied by the amount specified in the preferences
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ Geom::Affine const i2d (item->i2dt_affine ());
+ Geom::Point pp = pt * i2d.inverse();
+ double rad = 0.5 * prefs->getDouble(tool_path + "/dot-size", 3.0);
+ if (event_state & GDK_MOD1_MASK) {
+ // TODO: We vary the dot size between 0.5*rad and 1.5*rad, where rad is the dot size
+ // as specified in prefs. Very simple, but it might be sufficient in practice. If not,
+ // we need to devise something more sophisticated.
+ double s = g_random_double_range(-0.5, 0.5);
+ rad *= (1 + s);
+ }
+ if (event_state & GDK_SHIFT_MASK) {
+ // double the point size
+ rad *= 2;
+ }
+
+ sp_repr_set_svg_double (repr, "sodipodi:cx", pp[Geom::X]);
+ sp_repr_set_svg_double (repr, "sodipodi:cy", pp[Geom::Y]);
+ sp_repr_set_svg_double (repr, "sodipodi:rx", rad * stroke_width);
+ sp_repr_set_svg_double (repr, "sodipodi:ry", rad * stroke_width);
+ item->updateRepr();
+
+ sp_desktop_selection(desktop)->set(item);
+
+ desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating single dot"));
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_NONE, _("Create single dot"));
+}
+
+}
+}
+}
+
+/*
+ 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/tools/freehand-base.h b/src/ui/tools/freehand-base.h
new file mode 100644
index 000000000..7d60e217f
--- /dev/null
+++ b/src/ui/tools/freehand-base.h
@@ -0,0 +1,146 @@
+#ifndef SEEN_SP_DRAW_CONTEXT_H
+#define SEEN_SP_DRAW_CONTEXT_H
+
+/*
+ * Generic drawing context
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2000 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL
+ */
+
+#include <stddef.h>
+#include <sigc++/sigc++.h>
+#include <2geom/point.h>
+#include "ui/tools/tool-base.h"
+#include "live_effects/effect.h"
+
+/* Freehand context */
+
+#define SP_DRAW_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::FreehandBase*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_DRAW_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::FreehandBase*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+struct SPDrawAnchor;
+namespace Inkscape
+{
+ class Selection;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class FreehandBase : public ToolBase {
+public:
+ FreehandBase();
+ virtual ~FreehandBase();
+
+ Inkscape::Selection *selection;
+ SPCanvasItem *grab;
+
+ guint attach : 1;
+
+ guint32 red_color;
+ guint32 blue_color;
+ guint32 green_color;
+
+ // Red
+ SPCanvasItem *red_bpath;
+ SPCurve *red_curve;
+
+ // Blue
+ SPCanvasItem *blue_bpath;
+ SPCurve *blue_curve;
+
+
+ // Green
+ GSList *green_bpaths;
+ SPCurve *green_curve;
+ SPDrawAnchor *green_anchor;
+ gboolean green_closed; // a flag meaning we hit the green anchor, so close the path on itself
+
+ // White
+ SPItem *white_item;
+ GSList *white_curves;
+ GSList *white_anchors;
+
+ // Start anchor
+ SPDrawAnchor *sa;
+
+ // End anchor
+ SPDrawAnchor *ea;
+
+ /* type of the LPE that is to be applied automatically to a finished path (if any) */
+ Inkscape::LivePathEffect::EffectType waiting_LPE_type;
+
+ sigc::connection sel_changed_connection;
+ sigc::connection sel_modified_connection;
+
+ bool red_curve_is_valid;
+
+ bool anchor_statusbar;
+
+protected:
+ virtual void setup();
+ virtual void finish();
+ virtual void set(const Inkscape::Preferences::Entry& val);
+ virtual bool root_handler(GdkEvent* event);
+};
+
+/**
+ * Returns FIRST active anchor (the activated one).
+ */
+SPDrawAnchor *spdc_test_inside(FreehandBase *dc, Geom::Point p);
+
+/**
+ * Concats red, blue and green.
+ * If any anchors are defined, process these, optionally removing curves from white list
+ * Invoke _flush_white to write result back to object.
+ */
+void spdc_concat_colors_and_flush(FreehandBase *dc, gboolean forceclosed);
+
+/**
+ * Snaps node or handle to PI/rotationsnapsperpi degree increments.
+ *
+ * @param dc draw context.
+ * @param p cursor point (to be changed by snapping).
+ * @param o origin point.
+ * @param state keyboard state to check if ctrl or shift was pressed.
+ */
+void spdc_endpoint_snap_rotation(ToolBase const *const ec, Geom::Point &p, Geom::Point const &o, guint state);
+
+void spdc_endpoint_snap_free(ToolBase const *ec, Geom::Point &p, boost::optional<Geom::Point> &start_of_line, guint state);
+
+/**
+ * If we have an item and a waiting LPE, apply the effect to the item
+ * (spiro spline mode is treated separately).
+ */
+void spdc_check_for_and_apply_waiting_LPE(FreehandBase *dc, SPItem *item);
+
+/**
+ * Create a single dot represented by a circle.
+ */
+void spdc_create_single_dot(ToolBase *ec, Geom::Point const &pt, char const *tool, guint event_state);
+
+}
+}
+}
+
+#endif // SEEN_SP_DRAW_CONTEXT_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/tools/gradient-tool.cpp b/src/ui/tools/gradient-tool.cpp
new file mode 100644
index 000000000..e4ab7b424
--- /dev/null
+++ b/src/ui/tools/gradient-tool.cpp
@@ -0,0 +1,977 @@
+/*
+ * Gradient drawing and editing tool
+ *
+ * Authors:
+ * bulia byak <buliabyak@users.sf.net>
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2007 Johan Engelen
+ * Copyright (C) 2005 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+
+#include <gdk/gdkkeysyms.h>
+
+#include "macros.h"
+#include "document.h"
+#include "selection.h"
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "message-context.h"
+#include "message-stack.h"
+#include "pixmaps/cursor-gradient.xpm"
+#include "pixmaps/cursor-gradient-add.xpm"
+#include "ui/tools/gradient-tool.h"
+#include "gradient-chemistry.h"
+#include <glibmm/i18n.h>
+#include "preferences.h"
+#include "gradient-drag.h"
+#include "gradient-chemistry.h"
+#include "xml/repr.h"
+#include "sp-item.h"
+#include "display/sp-ctrlline.h"
+#include "sp-linear-gradient.h"
+#include "sp-radial-gradient.h"
+#include "sp-stop.h"
+#include "svg/css-ostringstream.h"
+#include "svg/svg-color.h"
+#include "snap.h"
+#include "sp-namedview.h"
+#include "rubberband.h"
+#include "document-undo.h"
+#include "verbs.h"
+#include "selection-chemistry.h"
+
+using Inkscape::DocumentUndo;
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+static void sp_gradient_drag(GradientTool &rc, Geom::Point const pt, guint state, guint32 etime);
+
+namespace {
+ ToolBase* createGradientContext() {
+ return new GradientTool();
+ }
+
+ bool gradientContextRegistered = ToolFactory::instance().registerObject("/tools/gradient", createGradientContext);
+}
+
+const std::string& GradientTool::getPrefsPath() {
+ return GradientTool::prefsPath;
+}
+
+const std::string GradientTool::prefsPath = "/tools/gradient";
+
+
+GradientTool::GradientTool() : ToolBase() {
+ this->node_added = false;
+ this->subselcon = 0;
+ this->selcon = 0;
+
+ this->cursor_addnode = false;
+ this->cursor_shape = cursor_gradient_xpm;
+ this->hot_x = 4;
+ this->hot_y = 4;
+ this->xp = 0;
+ this->yp = 0;
+ this->tolerance = 6;
+ this->within_tolerance = false;
+ this->item_to_select = NULL;
+}
+
+GradientTool::~GradientTool() {
+ this->enableGrDrag(false);
+
+ this->selcon->disconnect();
+ delete this->selcon;
+
+ this->subselcon->disconnect();
+ delete this->subselcon;
+}
+
+const gchar *gr_handle_descr [] = {
+ N_("Linear gradient <b>start</b>"), //POINT_LG_BEGIN
+ N_("Linear gradient <b>end</b>"),
+ N_("Linear gradient <b>mid stop</b>"),
+ N_("Radial gradient <b>center</b>"),
+ N_("Radial gradient <b>radius</b>"),
+ N_("Radial gradient <b>radius</b>"),
+ N_("Radial gradient <b>focus</b>"), // POINT_RG_FOCUS
+ N_("Radial gradient <b>mid stop</b>"),
+ N_("Radial gradient <b>mid stop</b>")
+};
+
+void GradientTool::selection_changed(Inkscape::Selection*) {
+ GradientTool *rc = (GradientTool *) this;
+
+ GrDrag *drag = rc->_grdrag;
+ Inkscape::Selection *selection = sp_desktop_selection(SP_EVENT_CONTEXT(rc)->desktop);
+ if (selection == NULL) {
+ return;
+ }
+ guint n_obj = g_slist_length((GSList *) selection->itemList());
+
+ if (!drag->isNonEmpty() || selection->isEmpty())
+ return;
+ guint n_tot = drag->numDraggers();
+ guint n_sel = drag->numSelected();
+
+ //The use of ngettext in the following code is intentional even if the English singular form would never be used
+ if (n_sel == 1) {
+ if (drag->singleSelectedDraggerNumDraggables() == 1) {
+ gchar * message = g_strconcat(
+ //TRANSLATORS: %s will be substituted with the point name (see previous messages); This is part of a compound message
+ _("%s selected"),
+ //TRANSLATORS: Mind the space in front. This is part of a compound message
+ ngettext(" out of %d gradient handle"," out of %d gradient handles",n_tot),
+ ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL);
+ rc->message_context->setF(Inkscape::NORMAL_MESSAGE,
+ message,_(gr_handle_descr[drag->singleSelectedDraggerSingleDraggableType()]), n_tot, n_obj);
+ } else {
+ gchar * message = g_strconcat(
+ //TRANSLATORS: This is a part of a compound message (out of two more indicating: grandint handle count & object count)
+ ngettext("One handle merging %d stop (drag with <b>Shift</b> to separate) selected",
+ "One handle merging %d stops (drag with <b>Shift</b> to separate) selected",drag->singleSelectedDraggerNumDraggables()),
+ ngettext(" out of %d gradient handle"," out of %d gradient handles",n_tot),
+ ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL);
+ rc->message_context->setF(Inkscape::NORMAL_MESSAGE,message,drag->singleSelectedDraggerNumDraggables(), n_tot, n_obj);
+ }
+ } else if (n_sel > 1) {
+ //TRANSLATORS: The plural refers to number of selected gradient handles. This is part of a compound message (part two indicates selected object count)
+ gchar * message = g_strconcat(ngettext("<b>%d</b> gradient handle selected out of %d","<b>%d</b> gradient handles selected out of %d",n_sel),
+ //TRANSLATORS: Mind the space in front. (Refers to gradient handles selected). This is part of a compound message
+ ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL);
+ rc->message_context->setF(Inkscape::NORMAL_MESSAGE,message, n_sel, n_tot, n_obj);
+ } else if (n_sel == 0) {
+ rc->message_context->setF(Inkscape::NORMAL_MESSAGE,
+ //TRANSLATORS: The plural refers to number of selected objects
+ ngettext("<b>No</b> gradient handles selected out of %d on %d selected object",
+ "<b>No</b> gradient handles selected out of %d on %d selected objects",n_obj), n_tot, n_obj);
+ }
+}
+
+void GradientTool::setup() {
+ ToolBase::setup();
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (prefs->getBool("/tools/gradient/selcue", true)) {
+ this->enableSelectionCue();
+ }
+
+ this->enableGrDrag();
+ Inkscape::Selection *selection = sp_desktop_selection(this->desktop);
+
+ this->selcon = new sigc::connection(selection->connectChanged(
+ sigc::mem_fun(this, &GradientTool::selection_changed)
+ ));
+
+ this->subselcon = new sigc::connection(this->desktop->connectToolSubselectionChanged(
+ sigc::hide(sigc::bind(
+ sigc::mem_fun(this, &GradientTool::selection_changed),
+ (Inkscape::Selection*)NULL
+ ))
+ ));
+
+ this->selection_changed(selection);
+}
+
+void
+sp_gradient_context_select_next (ToolBase *event_context)
+{
+ GrDrag *drag = event_context->_grdrag;
+ g_assert (drag);
+
+ GrDragger *d = drag->select_next();
+
+ event_context->desktop->scroll_to_point(d->point, 1.0);
+}
+
+void
+sp_gradient_context_select_prev (ToolBase *event_context)
+{
+ GrDrag *drag = event_context->_grdrag;
+ g_assert (drag);
+
+ GrDragger *d = drag->select_prev();
+
+ event_context->desktop->scroll_to_point(d->point, 1.0);
+}
+
+static bool
+sp_gradient_context_is_over_line (GradientTool *rc, SPItem *item, Geom::Point event_p)
+{
+ SPDesktop *desktop = SP_EVENT_CONTEXT (rc)->desktop;
+
+ //Translate mouse point into proper coord system
+ rc->mousepoint_doc = desktop->w2d(event_p);
+
+ SPCtrlLine* line = SP_CTRLLINE(item);
+
+ Geom::LineSegment ls(line->s, line->e);
+ Geom::Point nearest = ls.pointAt(ls.nearestPoint(rc->mousepoint_doc));
+ double dist_screen = Geom::L2 (rc->mousepoint_doc - nearest) * desktop->current_zoom();
+
+ double tolerance = (double) SP_EVENT_CONTEXT(rc)->tolerance;
+
+ bool close = (dist_screen < tolerance);
+
+ return close;
+}
+
+static std::vector<Geom::Point>
+sp_gradient_context_get_stop_intervals (GrDrag *drag, GSList **these_stops, GSList **next_stops)
+{
+ std::vector<Geom::Point> coords;
+
+ // for all selected draggers
+ for (GList *i = drag->selected; i != NULL; i = i->next) {
+ GrDragger *dragger = (GrDragger *) i->data;
+ // remember the coord of the dragger to reselect it later
+ coords.push_back(dragger->point);
+ // for all draggables of dragger
+ for (GSList const* j = dragger->draggables; j != NULL; j = j->next) {
+ GrDraggable *d = (GrDraggable *) j->data;
+
+ // find the gradient
+ SPGradient *gradient = getGradient(d->item, d->fill_or_stroke);
+ SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (gradient, false);
+
+ // these draggable types cannot have a next draggabe to insert a stop between them
+ if (d->point_type == POINT_LG_END ||
+ d->point_type == POINT_RG_FOCUS ||
+ d->point_type == POINT_RG_R1 ||
+ d->point_type == POINT_RG_R2) {
+ continue;
+ }
+
+ // from draggables to stops
+ SPStop *this_stop = sp_get_stop_i (vector, d->point_i);
+ SPStop *next_stop = this_stop->getNextStop();
+ SPStop *last_stop = sp_last_stop (vector);
+
+ Inkscape::PaintTarget fs = d->fill_or_stroke;
+ SPItem *item = d->item;
+ gint type = d->point_type;
+ gint p_i = d->point_i;
+
+ // if there's a next stop,
+ if (next_stop) {
+ GrDragger *dnext = NULL;
+ // find its dragger
+ // (complex because it may have different types, and because in radial,
+ // more than one dragger may correspond to a stop, so we must distinguish)
+ if (type == POINT_LG_BEGIN || type == POINT_LG_MID) {
+ if (next_stop == last_stop) {
+ dnext = drag->getDraggerFor(item, POINT_LG_END, p_i+1, fs);
+ } else {
+ dnext = drag->getDraggerFor(item, POINT_LG_MID, p_i+1, fs);
+ }
+ } else { // radial
+ if (type == POINT_RG_CENTER || type == POINT_RG_MID1) {
+ if (next_stop == last_stop) {
+ dnext = drag->getDraggerFor(item, POINT_RG_R1, p_i+1, fs);
+ } else {
+ dnext = drag->getDraggerFor(item, POINT_RG_MID1, p_i+1, fs);
+ }
+ }
+ if ((type == POINT_RG_MID2) ||
+ (type == POINT_RG_CENTER && dnext && !dnext->isSelected())) {
+ if (next_stop == last_stop) {
+ dnext = drag->getDraggerFor(item, POINT_RG_R2, p_i+1, fs);
+ } else {
+ dnext = drag->getDraggerFor(item, POINT_RG_MID2, p_i+1, fs);
+ }
+ }
+ }
+
+ // if both adjacent draggers selected,
+ if (!g_slist_find(*these_stops, this_stop) && dnext && dnext->isSelected()) {
+
+ // remember the coords of the future dragger to select it
+ coords.push_back(0.5*(dragger->point + dnext->point));
+
+ // do not insert a stop now, it will confuse the loop;
+ // just remember the stops
+ *these_stops = g_slist_prepend (*these_stops, this_stop);
+ *next_stops = g_slist_prepend (*next_stops, next_stop);
+ }
+ }
+ }
+ }
+ return coords;
+}
+
+void
+sp_gradient_context_add_stops_between_selected_stops (GradientTool *rc)
+{
+ SPDocument *doc = NULL;
+ GrDrag *drag = rc->_grdrag;
+
+ GSList *these_stops = NULL;
+ GSList *next_stops = NULL;
+
+ std::vector<Geom::Point> coords = sp_gradient_context_get_stop_intervals (drag, &these_stops, &next_stops);
+
+ if (g_slist_length(these_stops) == 0 && drag->numSelected() == 1) {
+ // if a single stop is selected, add between that stop and the next one
+ GrDragger *dragger = (GrDragger *) drag->selected->data;
+ for (GSList const* j = dragger->draggables; j != NULL; j = j->next) {
+ GrDraggable *d = (GrDraggable *) j->data;
+ if (d->point_type == POINT_RG_FOCUS) {
+ /*
+ * There are 2 draggables at the center (start) of a radial gradient
+ * To avoid creating 2 seperate stops, ignore this draggable point type
+ */
+ continue;
+ }
+ SPGradient *gradient = getGradient(d->item, d->fill_or_stroke);
+ SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (gradient, false);
+ SPStop *this_stop = sp_get_stop_i (vector, d->point_i);
+ SPStop *next_stop = this_stop->getNextStop();
+ if (this_stop && next_stop) {
+ these_stops = g_slist_prepend (these_stops, this_stop);
+ next_stops = g_slist_prepend (next_stops, next_stop);
+ }
+ }
+ }
+
+ // now actually create the new stops
+ GSList *i = these_stops;
+ GSList *j = next_stops;
+ GSList *new_stops = NULL;
+
+ for (; i != NULL && j != NULL; i = i->next, j = j->next) {
+ SPStop *this_stop = (SPStop *) i->data;
+ SPStop *next_stop = (SPStop *) j->data;
+ gfloat offset = 0.5*(this_stop->offset + next_stop->offset);
+ SPObject *parent = this_stop->parent;
+ if (SP_IS_GRADIENT (parent)) {
+ doc = parent->document;
+ SPStop *new_stop = sp_vector_add_stop (SP_GRADIENT (parent), this_stop, next_stop, offset);
+ new_stops = g_slist_prepend (new_stops, new_stop);
+ SP_GRADIENT(parent)->ensureVector();
+ }
+ }
+
+ if (g_slist_length(these_stops) > 0 && doc) {
+ DocumentUndo::done(doc, SP_VERB_CONTEXT_GRADIENT, _("Add gradient stop"));
+ drag->updateDraggers();
+ // so that it does not automatically update draggers in idle loop, as this would deselect
+ drag->local_change = true;
+
+ // select the newly created stops
+ for (GSList *s = new_stops; s != NULL; s = s->next) {
+ drag->selectByStop((SPStop *)s->data);
+ }
+
+ }
+
+ g_slist_free (these_stops);
+ g_slist_free (next_stops);
+ g_slist_free (new_stops);
+}
+
+static double sqr(double x) {return x*x;}
+
+static void
+sp_gradient_simplify(GradientTool *rc, double tolerance)
+{
+ SPDocument *doc = NULL;
+ GrDrag *drag = rc->_grdrag;
+
+ GSList *these_stops = NULL;
+ GSList *next_stops = NULL;
+
+ std::vector<Geom::Point> coords = sp_gradient_context_get_stop_intervals (drag, &these_stops, &next_stops);
+
+ GSList *todel = NULL;
+
+ GSList *i = these_stops;
+ GSList *j = next_stops;
+ for (; i != NULL && j != NULL; i = i->next, j = j->next) {
+ SPStop *stop0 = (SPStop *) i->data;
+ SPStop *stop1 = (SPStop *) j->data;
+
+ gint i1 = g_slist_index(these_stops, stop1);
+ if (i1 != -1) {
+ GSList *next_next = g_slist_nth (next_stops, i1);
+ if (next_next) {
+ SPStop *stop2 = (SPStop *) next_next->data;
+
+ if (g_slist_find(todel, stop0) || g_slist_find(todel, stop2))
+ continue;
+
+ guint32 const c0 = stop0->get_rgba32();
+ guint32 const c2 = stop2->get_rgba32();
+ guint32 const c1r = stop1->get_rgba32();
+ guint32 c1 = average_color (c0, c2,
+ (stop1->offset - stop0->offset) / (stop2->offset - stop0->offset));
+
+ double diff =
+ sqr(SP_RGBA32_R_F(c1) - SP_RGBA32_R_F(c1r)) +
+ sqr(SP_RGBA32_G_F(c1) - SP_RGBA32_G_F(c1r)) +
+ sqr(SP_RGBA32_B_F(c1) - SP_RGBA32_B_F(c1r)) +
+ sqr(SP_RGBA32_A_F(c1) - SP_RGBA32_A_F(c1r));
+
+ if (diff < tolerance)
+ todel = g_slist_prepend (todel, stop1);
+ }
+ }
+ }
+
+ for (i = todel; i != NULL; i = i->next) {
+ SPStop *stop = (SPStop*) i->data;
+ doc = stop->document;
+ Inkscape::XML::Node * parent = stop->getRepr()->parent();
+ parent->removeChild( stop->getRepr() );
+ }
+
+ if (g_slist_length(todel) > 0) {
+ DocumentUndo::done(doc, SP_VERB_CONTEXT_GRADIENT, _("Simplify gradient"));
+ drag->local_change = true;
+ drag->updateDraggers();
+ drag->selectByCoords(coords);
+ }
+
+ g_slist_free (todel);
+ g_slist_free (these_stops);
+ g_slist_free (next_stops);
+}
+
+
+static void
+sp_gradient_context_add_stop_near_point (GradientTool *rc, SPItem *item, Geom::Point mouse_p, guint32 /*etime*/)
+{
+ // item is the selected item. mouse_p the location in doc coordinates of where to add the stop
+
+ ToolBase *ec = SP_EVENT_CONTEXT(rc);
+ SPDesktop *desktop = SP_EVENT_CONTEXT (rc)->desktop;
+
+ double tolerance = (double) ec->tolerance;
+
+ SPStop *newstop = ec->get_drag()->addStopNearPoint (item, mouse_p, tolerance/desktop->current_zoom());
+
+ DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_GRADIENT,
+ _("Add gradient stop"));
+
+ ec->get_drag()->updateDraggers();
+ ec->get_drag()->local_change = true;
+ ec->get_drag()->selectByStop(newstop);
+}
+
+bool GradientTool::root_handler(GdkEvent* event) {
+ static bool dragging;
+
+ Inkscape::Selection *selection = sp_desktop_selection (desktop);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+ double const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px
+
+ GrDrag *drag = this->_grdrag;
+ g_assert (drag);
+
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_2BUTTON_PRESS:
+ if ( event->button.button == 1 ) {
+ bool over_line = false;
+ SPCtrlLine *line = NULL;
+
+ if (drag->lines) {
+ for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) {
+ line = (SPCtrlLine*) l->data;
+ over_line |= sp_gradient_context_is_over_line (this, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y));
+ }
+ }
+
+ if (over_line) {
+ // we take the first item in selection, because with doubleclick, the first click
+ // always resets selection to the single object under cursor
+ sp_gradient_context_add_stop_near_point(this, SP_ITEM(selection->itemList()->data), this->mousepoint_doc, event->button.time);
+ } else {
+ for (GSList const* i = selection->itemList(); i != NULL; i = i->next) {
+ SPItem *item = SP_ITEM(i->data);
+ SPGradientType new_type = (SPGradientType) prefs->getInt("/tools/gradient/newgradient", SP_GRADIENT_TYPE_LINEAR);
+ Inkscape::PaintTarget fsmode = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE;
+
+ SPGradient *vector = sp_gradient_vector_for_object(sp_desktop_document(desktop), desktop, item, fsmode);
+
+ SPGradient *priv = sp_item_set_gradient(item, vector, new_type, fsmode);
+ sp_gradient_reset_to_userspace(priv, item);
+ }
+
+ DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_GRADIENT,
+ _("Create default gradient"));
+ }
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_BUTTON_PRESS:
+ if ( event->button.button == 1 && !this->space_panning ) {
+ Geom::Point button_w(event->button.x, event->button.y);
+
+ // save drag origin
+ this->xp = (gint) button_w[Geom::X];
+ this->yp = (gint) button_w[Geom::Y];
+ this->within_tolerance = true;
+
+ dragging = true;
+
+ Geom::Point button_dt = desktop->w2d(button_w);
+ if (event->button.state & GDK_SHIFT_MASK) {
+ Inkscape::Rubberband::get(desktop)->start(desktop, button_dt);
+ } else {
+ // remember clicked item, disregarding groups, honoring Alt; do nothing with Crtl to
+ // enable Ctrl+doubleclick of exactly the selected item(s)
+ if (!(event->button.state & GDK_CONTROL_MASK)) {
+ this->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE);
+ }
+
+ if (!selection->isEmpty()) {
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+ m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ m.unSetup();
+ }
+
+ this->origin = button_dt;
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ if (dragging && ( event->motion.state & GDK_BUTTON1_MASK ) && !this->space_panning) {
+ if ( this->within_tolerance
+ && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance )
+ && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) {
+ break; // do not drag if we're within tolerance from origin
+ }
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to draw, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ this->within_tolerance = false;
+
+ Geom::Point const motion_w(event->motion.x,
+ event->motion.y);
+ Geom::Point const motion_dt = this->desktop->w2d(motion_w);
+
+ if (Inkscape::Rubberband::get(desktop)->is_started()) {
+ Inkscape::Rubberband::get(desktop)->move(motion_dt);
+ this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Draw around</b> handles to select them"));
+ } else {
+ sp_gradient_drag(*this, motion_dt, event->motion.state, event->motion.time);
+ }
+
+ gobble_motion_events(GDK_BUTTON1_MASK);
+
+ ret = TRUE;
+ } else {
+ if (!drag->mouseOver() && !selection->isEmpty()) {
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point const motion_dt = this->desktop->w2d(motion_w);
+
+ m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE));
+ m.unSetup();
+ }
+
+ bool over_line = false;
+
+ if (drag->lines) {
+ for (GSList *l = drag->lines; l != NULL; l = l->next) {
+ over_line |= sp_gradient_context_is_over_line (this, (SPItem*) l->data, Geom::Point(event->motion.x, event->motion.y));
+ }
+ }
+
+ if (this->cursor_addnode && !over_line) {
+ this->cursor_shape = cursor_gradient_xpm;
+ this->sp_event_context_update_cursor();
+ this->cursor_addnode = false;
+ } else if (!this->cursor_addnode && over_line) {
+ this->cursor_shape = cursor_gradient_add_xpm;
+ this->sp_event_context_update_cursor();
+ this->cursor_addnode = true;
+ }
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ this->xp = this->yp = 0;
+
+ if ( event->button.button == 1 && !this->space_panning ) {
+ bool over_line = false;
+ SPCtrlLine *line = NULL;
+
+ if (drag->lines) {
+ for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) {
+ line = (SPCtrlLine*) l->data;
+ over_line = sp_gradient_context_is_over_line (this, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y));
+ if (over_line)
+ break;
+ }
+ }
+
+ if ( (event->button.state & GDK_CONTROL_MASK) && (event->button.state & GDK_MOD1_MASK ) ) {
+ if (over_line && line) {
+ sp_gradient_context_add_stop_near_point(this, line->item, this->mousepoint_doc, 0);
+ ret = TRUE;
+ }
+ } else {
+ dragging = false;
+
+ // unless clicked with Ctrl (to enable Ctrl+doubleclick).
+ if (event->button.state & GDK_CONTROL_MASK) {
+ ret = TRUE;
+ break;
+ }
+
+ if (!this->within_tolerance) {
+ // we've been dragging, either do nothing (grdrag handles that),
+ // or rubberband-select if we have rubberband
+ Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop);
+
+ if (r->is_started() && !this->within_tolerance) {
+ // this was a rubberband drag
+ if (r->getMode() == RUBBERBAND_MODE_RECT) {
+ Geom::OptRect const b = r->getRectangle();
+ drag->selectRect(*b);
+ }
+ }
+ } else if (this->item_to_select) {
+ if (over_line && line) {
+ // Clicked on an existing gradient line, dont change selection. This stops
+ // possible change in selection during a double click with overlapping objects
+ } else {
+ // no dragging, select clicked item if any
+ if (event->button.state & GDK_SHIFT_MASK) {
+ selection->toggle(this->item_to_select);
+ } else {
+ drag->deselectAll();
+ selection->set(this->item_to_select);
+ }
+ }
+ } else {
+ // click in an empty space; do the same as Esc
+ if (drag->selected) {
+ drag->deselectAll();
+ } else {
+ selection->clear();
+ }
+ }
+
+ this->item_to_select = NULL;
+ ret = TRUE;
+ }
+
+ Inkscape::Rubberband::get(desktop)->stop();
+ }
+ break;
+
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Alt_L:
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine)
+ case GDK_KEY_Meta_R:
+ sp_event_show_modifier_tip (this->defaultMessageContext(), event,
+ _("<b>Ctrl</b>: snap gradient angle"),
+ _("<b>Shift</b>: draw gradient around the starting point"),
+ NULL);
+ break;
+
+ case GDK_KEY_x:
+ case GDK_KEY_X:
+ if (MOD__ALT_ONLY(event)) {
+ desktop->setToolboxFocusTo ("altx-grad");
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_A:
+ case GDK_KEY_a:
+ if (MOD__CTRL_ONLY(event) && drag->isNonEmpty()) {
+ drag->selectAll();
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_L:
+ case GDK_KEY_l:
+ if (MOD__CTRL_ONLY(event) && drag->isNonEmpty() && drag->hasSelection()) {
+ sp_gradient_simplify(this, 1e-4);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Escape:
+ if (drag->selected) {
+ drag->deselectAll();
+ } else {
+ Inkscape::SelectionHelper::selectNone(desktop);
+ }
+ ret = TRUE;
+ //TODO: make dragging escapable by Esc
+ break;
+
+ case GDK_KEY_Left: // move handle left
+ case GDK_KEY_KP_Left:
+ case GDK_KEY_KP_4:
+ if (!MOD__CTRL(event)) { // not ctrl
+ gint mul = 1 + gobble_key_events(
+ get_group0_keyval(&event->key), 0); // with any mask
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move_screen(mul*-10, 0); // shift
+ } else {
+ drag->selected_move_screen(mul*-1, 0); // no shift
+ }
+ } else { // no alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move(mul*-10*nudge, 0); // shift
+ } else {
+ drag->selected_move(mul*-nudge, 0); // no shift
+ }
+ }
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Up: // move handle up
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_8:
+ if (!MOD__CTRL(event)) { // not ctrl
+ gint mul = 1 + gobble_key_events(
+ get_group0_keyval(&event->key), 0); // with any mask
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move_screen(0, mul*10); // shift
+ } else {
+ drag->selected_move_screen(0, mul*1); // no shift
+ }
+ } else { // no alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move(0, mul*10*nudge); // shift
+ } else {
+ drag->selected_move(0, mul*nudge); // no shift
+ }
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Right: // move handle right
+ case GDK_KEY_KP_Right:
+ case GDK_KEY_KP_6:
+ if (!MOD__CTRL(event)) { // not ctrl
+ gint mul = 1 + gobble_key_events(
+ get_group0_keyval(&event->key), 0); // with any mask
+
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move_screen(mul*10, 0); // shift
+ } else {
+ drag->selected_move_screen(mul*1, 0); // no shift
+ }
+ } else { // no alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move(mul*10*nudge, 0); // shift
+ } else {
+ drag->selected_move(mul*nudge, 0); // no shift
+ }
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Down: // move handle down
+ case GDK_KEY_KP_Down:
+ case GDK_KEY_KP_2:
+ if (!MOD__CTRL(event)) { // not ctrl
+ gint mul = 1 + gobble_key_events(
+ get_group0_keyval(&event->key), 0); // with any mask
+
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move_screen(0, mul*-10); // shift
+ } else {
+ drag->selected_move_screen(0, mul*-1); // no shift
+ }
+ } else { // no alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move(0, mul*-10*nudge); // shift
+ } else {
+ drag->selected_move(0, mul*-nudge); // no shift
+ }
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_r:
+ case GDK_KEY_R:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_gradient_reverse_selected_gradients(desktop);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Insert:
+ case GDK_KEY_KP_Insert:
+ // with any modifiers:
+ sp_gradient_context_add_stops_between_selected_stops (this);
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ case GDK_KEY_BackSpace:
+ ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event));
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case GDK_KEY_RELEASE:
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Alt_L:
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt
+ case GDK_KEY_Meta_R:
+ this->defaultMessageContext()->clear();
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+static void sp_gradient_drag(GradientTool &rc, Geom::Point const pt, guint /*state*/, guint32 etime)
+{
+ SPDesktop *desktop = SP_EVENT_CONTEXT(&rc)->desktop;
+ Inkscape::Selection *selection = sp_desktop_selection(desktop);
+ SPDocument *document = sp_desktop_document(desktop);
+ ToolBase *ec = SP_EVENT_CONTEXT(&rc);
+
+ if (!selection->isEmpty()) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int type = prefs->getInt("/tools/gradient/newgradient", 1);
+ Inkscape::PaintTarget fill_or_stroke = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE;
+
+ SPGradient *vector;
+ if (ec->item_to_select) {
+ // pick color from the object where drag started
+ vector = sp_gradient_vector_for_object(document, desktop, ec->item_to_select, fill_or_stroke);
+ } else {
+ // Starting from empty space:
+ // Sort items so that the topmost comes last
+ GSList *items = g_slist_copy ((GSList *) selection->itemList());
+ items = g_slist_sort(items, (GCompareFunc) sp_item_repr_compare_position);
+ // take topmost
+ vector = sp_gradient_vector_for_object(document, desktop, SP_ITEM(g_slist_last(items)->data), fill_or_stroke);
+ g_slist_free (items);
+ }
+
+ // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ sp_repr_css_set_property(css, "fill-opacity", "1.0");
+
+ for (GSList const *i = selection->itemList(); i != NULL; i = i->next) {
+
+ //FIXME: see above
+ sp_repr_css_change_recursive(SP_OBJECT(i->data)->getRepr(), css, "style");
+
+ sp_item_set_gradient(SP_ITEM(i->data), vector, (SPGradientType) type, fill_or_stroke);
+
+ if (type == SP_GRADIENT_TYPE_LINEAR) {
+ sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_LG_BEGIN, 0, rc.origin, fill_or_stroke, true, false);
+ sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_LG_END, 0, pt, fill_or_stroke, true, false);
+ } else if (type == SP_GRADIENT_TYPE_RADIAL) {
+ sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_RG_CENTER, 0, rc.origin, fill_or_stroke, true, false);
+ sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_RG_R1, 0, pt, fill_or_stroke, true, false);
+ }
+ SP_OBJECT(i->data)->requestModified(SP_OBJECT_MODIFIED_FLAG);
+ }
+ if (ec->_grdrag) {
+ ec->_grdrag->updateDraggers();
+ // prevent regenerating draggers by selection modified signal, which sometimes
+ // comes too late and thus destroys the knot which we will now grab:
+ ec->_grdrag->local_change = true;
+ // give the grab out-of-bounds values of xp/yp because we're already dragging
+ // and therefore are already out of tolerance
+ ec->_grdrag->grabKnot (SP_ITEM(selection->itemList()->data),
+ type == SP_GRADIENT_TYPE_LINEAR? POINT_LG_END : POINT_RG_R1,
+ -1, // ignore number (though it is always 1)
+ fill_or_stroke, 99999, 99999, etime);
+ }
+ // We did an undoable action, but SPDocumentUndo::done will be called by the knot when released
+
+ // status text; we do not track coords because this branch is run once, not all the time
+ // during drag
+ int n_objects = g_slist_length((GSList *) selection->itemList());
+ rc.message_context->setF(Inkscape::NORMAL_MESSAGE,
+ ngettext("<b>Gradient</b> for %d object; with <b>Ctrl</b> to snap angle",
+ "<b>Gradient</b> for %d objects; with <b>Ctrl</b> to snap angle", n_objects),
+ n_objects);
+ } else {
+ sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>objects</b> on which to create gradient."));
+ }
+}
+
+}
+}
+}
+
+
+/*
+ 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/tools/gradient-tool.h b/src/ui/tools/gradient-tool.h
new file mode 100644
index 000000000..6fe3bca9f
--- /dev/null
+++ b/src/ui/tools/gradient-tool.h
@@ -0,0 +1,76 @@
+#ifndef __SP_GRADIENT_CONTEXT_H__
+#define __SP_GRADIENT_CONTEXT_H__
+
+/*
+ * Gradient drawing and editing tool
+ *
+ * Authors:
+ * bulia byak <buliabyak@users.sf.net>
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ * Jon A. Cruz <jon@joncruz.org.
+ *
+ * Copyright (C) 2007 Johan Engelen
+ * Copyright (C) 2005,2010 Authors
+ *
+ * Released under GNU GPL
+ */
+
+#include <stddef.h>
+#include <sigc++/sigc++.h>
+#include "ui/tools/tool-base.h"
+
+#define SP_GRADIENT_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::GradientTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_GRADIENT_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::GradientTool*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class GradientTool : public ToolBase {
+public:
+ GradientTool();
+ virtual ~GradientTool();
+
+ Geom::Point origin;
+
+ bool cursor_addnode;
+
+ bool node_added;
+
+ Geom::Point mousepoint_doc; // stores mousepoint when over_line in doc coords
+
+ sigc::connection *selcon;
+ sigc::connection *subselcon;
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual bool root_handler(GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+private:
+ void selection_changed(Inkscape::Selection*);
+};
+
+void sp_gradient_context_select_next (ToolBase *event_context);
+void sp_gradient_context_select_prev (ToolBase *event_context);
+void sp_gradient_context_add_stops_between_selected_stops (GradientTool *rc);
+
+}
+}
+}
+
+#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/tools/lpe-tool.cpp b/src/ui/tools/lpe-tool.cpp
new file mode 100644
index 000000000..a5406f1c5
--- /dev/null
+++ b/src/ui/tools/lpe-tool.cpp
@@ -0,0 +1,503 @@
+/*
+ * LPEToolContext: a context for a generic tool composed of subtools that are given by LPEs
+ *
+ * Authors:
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ * Copyright (C) 1999-2005 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ * Copyright (C) 2008 Maximilian Albert
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <2geom/sbasis-geometric.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <glibmm/i18n.h>
+#include "macros.h"
+#include "pixmaps/cursor-crosshairs.xpm"
+#include <gtk/gtk.h>
+#include "desktop.h"
+#include "message-context.h"
+#include "preferences.h"
+#include "shape-editor.h"
+#include "selection.h"
+#include "desktop-handles.h"
+#include "document.h"
+#include "display/curve.h"
+#include "display/canvas-bpath.h"
+#include "display/canvas-text.h"
+#include "message-stack.h"
+#include "sp-path.h"
+#include "util/units.h"
+
+#include "ui/tools/lpe-tool.h"
+
+using Inkscape::Util::unit_table;
+using Inkscape::UI::Tools::PenTool;
+
+const int num_subtools = 8;
+
+SubtoolEntry lpesubtools[] = {
+ // this must be here to account for the "all inactive" action
+ {Inkscape::LivePathEffect::INVALID_LPE, "draw-geometry-inactive"},
+ {Inkscape::LivePathEffect::LINE_SEGMENT, "draw-geometry-line-segment"},
+ {Inkscape::LivePathEffect::CIRCLE_3PTS, "draw-geometry-circle-from-three-points"},
+ {Inkscape::LivePathEffect::CIRCLE_WITH_RADIUS, "draw-geometry-circle-from-radius"},
+ {Inkscape::LivePathEffect::PARALLEL, "draw-geometry-line-parallel"},
+ {Inkscape::LivePathEffect::PERP_BISECTOR, "draw-geometry-line-perpendicular"},
+ {Inkscape::LivePathEffect::ANGLE_BISECTOR, "draw-geometry-angle-bisector"},
+ {Inkscape::LivePathEffect::MIRROR_SYMMETRY, "draw-geometry-mirror"}
+};
+
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+void sp_lpetool_context_selection_changed(Inkscape::Selection *selection, gpointer data);
+
+namespace {
+ ToolBase* createLPEToolContext() {
+ return new LpeTool();
+ }
+
+ bool lpetoolContextRegistered = ToolFactory::instance().registerObject("/tools/lpetool", createLPEToolContext);
+}
+
+const std::string& LpeTool::getPrefsPath() {
+ return LpeTool::prefsPath;
+}
+
+const std::string LpeTool::prefsPath = "/tools/lpetool";
+
+LpeTool::LpeTool() : PenTool() {
+ this->mode = Inkscape::LivePathEffect::BEND_PATH;
+ this->shape_editor = 0;
+
+ this->cursor_shape = cursor_crosshairs_xpm;
+ this->hot_x = 7;
+ this->hot_y = 7;
+
+ this->canvas_bbox = NULL;
+ this->measuring_items = new std::map<SPPath *, SPCanvasItem*>;
+}
+
+LpeTool::~LpeTool() {
+ delete this->shape_editor;
+ this->shape_editor = NULL;
+
+ if (this->canvas_bbox) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(this->canvas_bbox));
+ this->canvas_bbox = NULL;
+ }
+
+ lpetool_delete_measuring_items(this);
+ delete this->measuring_items;
+ this->measuring_items = NULL;
+
+ this->sel_changed_connection.disconnect();
+}
+
+void LpeTool::setup() {
+ PenTool::setup();
+
+ Inkscape::Selection *selection = sp_desktop_selection (this->desktop);
+ SPItem *item = selection->singleItem();
+
+ this->sel_changed_connection.disconnect();
+ this->sel_changed_connection =
+ selection->connectChanged(sigc::bind(sigc::ptr_fun(&sp_lpetool_context_selection_changed), (gpointer)this));
+
+ this->shape_editor = new ShapeEditor(this->desktop);
+
+ lpetool_context_switch_mode(this, Inkscape::LivePathEffect::INVALID_LPE);
+ lpetool_context_reset_limiting_bbox(this);
+ lpetool_create_measuring_items(this);
+
+// TODO temp force:
+ this->enableSelectionCue();
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (item) {
+ this->shape_editor->set_item(item, SH_NODEPATH);
+ this->shape_editor->set_item(item, SH_KNOTHOLDER);
+ }
+
+ if (prefs->getBool("/tools/lpetool/selcue")) {
+ this->enableSelectionCue();
+ }
+}
+
+/**
+ * Callback that processes the "changed" signal on the selection;
+ * destroys old and creates new nodepath and reassigns listeners to the new selected item's repr.
+ */
+void sp_lpetool_context_selection_changed(Inkscape::Selection *selection, gpointer data)
+{
+ LpeTool *lc = SP_LPETOOL_CONTEXT(data);
+
+ lc->shape_editor->unset_item(SH_KNOTHOLDER);
+ SPItem *item = selection->singleItem();
+ lc->shape_editor->set_item(item, SH_KNOTHOLDER);
+}
+
+void LpeTool::set(const Inkscape::Preferences::Entry& val) {
+ if (val.getEntryName() == "mode") {
+ Inkscape::Preferences::get()->setString("/tools/geometric/mode", "drag");
+ SP_PEN_CONTEXT(this)->mode = PenTool::MODE_DRAG;
+ }
+}
+
+bool LpeTool::item_handler(SPItem* item, GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ {
+ // select the clicked item but do nothing else
+ Inkscape::Selection * const selection = sp_desktop_selection(this->desktop);
+ selection->clear();
+ selection->add(item);
+ ret = TRUE;
+ break;
+ }
+ case GDK_BUTTON_RELEASE:
+ // TODO: do we need to catch this or can we pass it on to the parent handler?
+ ret = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = PenTool::item_handler(item, event);
+ }
+
+ return ret;
+}
+
+bool LpeTool::root_handler(GdkEvent* event) {
+ Inkscape::Selection *selection = sp_desktop_selection (desktop);
+
+ bool ret = false;
+
+ if (sp_pen_context_has_waiting_LPE(this)) {
+ // quit when we are waiting for a LPE to be applied
+ //ret = ((ToolBaseClass *) sp_lpetool_context_parent_class)->root_handler(event_context, event);
+ return PenTool::root_handler(event);
+ }
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+ if (this->mode == Inkscape::LivePathEffect::INVALID_LPE) {
+ // don't do anything for now if we are inactive (except clearing the selection
+ // since this was a click into empty space)
+ selection->clear();
+ desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Choose a construction tool from the toolbar."));
+ ret = true;
+ break;
+ }
+
+ // save drag origin
+ this->xp = (gint) event->button.x;
+ this->yp = (gint) event->button.y;
+ this->within_tolerance = true;
+
+ using namespace Inkscape::LivePathEffect;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int mode = prefs->getInt("/tools/lpetool/mode");
+ EffectType type = lpesubtools[mode].type;
+
+ //bool over_stroke = lc->shape_editor->is_over_stroke(Geom::Point(event->button.x, event->button.y), true);
+
+ sp_pen_context_wait_for_LPE_mouse_clicks(this, type, Inkscape::LivePathEffect::Effect::acceptsNumClicks(type));
+
+ // we pass the mouse click on to pen tool as the first click which it should collect
+ //ret = ((ToolBaseClass *) sp_lpetool_context_parent_class)->root_handler(event_context, event);
+ ret = PenTool::root_handler(event);
+ }
+ break;
+
+
+ case GDK_BUTTON_RELEASE:
+ {
+ /**
+ break;
+ **/
+ }
+
+ case GDK_KEY_PRESS:
+ /**
+ switch (get_group0_keyval (&event->key)) {
+ }
+ break;
+ **/
+
+ case GDK_KEY_RELEASE:
+ /**
+ switch (get_group0_keyval(&event->key)) {
+ case GDK_Control_L:
+ case GDK_Control_R:
+ dc->_message_context->clear();
+ break;
+ default:
+ break;
+ }
+ **/
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = PenTool::root_handler(event);
+ }
+
+ return ret;
+}
+
+/*
+ * Finds the index in the list of geometric subtools corresponding to the given LPE type.
+ * Returns -1 if no subtool is found.
+ */
+int
+lpetool_mode_to_index(Inkscape::LivePathEffect::EffectType const type) {
+ for (int i = 0; i < num_subtools; ++i) {
+ if (lpesubtools[i].type == type) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/*
+ * Checks whether an item has a construction applied as LPE and if so returns the index in
+ * lpesubtools of this construction
+ */
+int lpetool_item_has_construction(LpeTool */*lc*/, SPItem *item)
+{
+ if (!SP_IS_LPE_ITEM(item)) {
+ return -1;
+ }
+
+ Inkscape::LivePathEffect::Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE();
+ if (!lpe) {
+ return -1;
+ }
+ return lpetool_mode_to_index(lpe->effectType());
+}
+
+/*
+ * Attempts to perform the construction of the given type (i.e., to apply the corresponding LPE) to
+ * a single selected item. Returns whether we succeeded.
+ */
+bool
+lpetool_try_construction(LpeTool *lc, Inkscape::LivePathEffect::EffectType const type)
+{
+ Inkscape::Selection *selection = sp_desktop_selection(lc->desktop);
+ SPItem *item = selection->singleItem();
+
+ // TODO: should we check whether type represents a valid geometric construction?
+ if (item && SP_IS_LPE_ITEM(item) && Inkscape::LivePathEffect::Effect::acceptsNumClicks(type) == 0) {
+ Inkscape::LivePathEffect::Effect::createAndApply(type, sp_desktop_document(lc->desktop), item);
+ return true;
+ }
+ return false;
+}
+
+void
+lpetool_context_switch_mode(LpeTool *lc, Inkscape::LivePathEffect::EffectType const type)
+{
+ int index = lpetool_mode_to_index(type);
+ if (index != -1) {
+ lc->mode = type;
+ lc->desktop->setToolboxSelectOneValue ("lpetool_mode_action", index);
+ } else {
+ g_warning ("Invalid mode selected: %d", type);
+ return;
+ }
+}
+
+void
+lpetool_get_limiting_bbox_corners(SPDocument *document, Geom::Point &A, Geom::Point &B) {
+ Geom::Coord w = document->getWidth().value("px");
+ Geom::Coord h = document->getHeight().value("px");
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ double ulx = prefs->getDouble("/tools/lpetool/bbox_upperleftx", 0);
+ double uly = prefs->getDouble("/tools/lpetool/bbox_upperlefty", 0);
+ double lrx = prefs->getDouble("/tools/lpetool/bbox_lowerrightx", w);
+ double lry = prefs->getDouble("/tools/lpetool/bbox_lowerrighty", h);
+
+ A = Geom::Point(ulx, uly);
+ B = Geom::Point(lrx, lry);
+}
+
+/*
+ * Reads the limiting bounding box from preferences and draws it on the screen
+ */
+// TODO: Note that currently the bbox is not user-settable; we simply use the page borders
+void
+lpetool_context_reset_limiting_bbox(LpeTool *lc)
+{
+ if (lc->canvas_bbox) {
+ sp_canvas_item_destroy(lc->canvas_bbox);
+ lc->canvas_bbox = NULL;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (!prefs->getBool("/tools/lpetool/show_bbox", true))
+ return;
+
+ SPDocument *document = sp_desktop_document(lc->desktop);
+
+ Geom::Point A, B;
+ lpetool_get_limiting_bbox_corners(document, A, B);
+ Geom::Affine doc2dt(lc->desktop->doc2dt());
+ A *= doc2dt;
+ B *= doc2dt;
+
+ Geom::Rect rect(A, B);
+ SPCurve *curve = SPCurve::new_from_rect(rect);
+
+ lc->canvas_bbox = sp_canvas_bpath_new (sp_desktop_controls(lc->desktop), curve);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(lc->canvas_bbox), 0x0000ffff, 0.8, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT, 5, 5);
+}
+
+static void
+set_pos_and_anchor(SPCanvasText *canvas_text, const Geom::Piecewise<Geom::D2<Geom::SBasis> > &pwd2,
+ const double t, const double length, bool /*use_curvature*/ = false)
+{
+ using namespace Geom;
+
+ Piecewise<D2<SBasis> > pwd2_reparam = arc_length_parametrization(pwd2, 2 , 0.1);
+ double t_reparam = pwd2_reparam.cuts.back() * t;
+ Point pos = pwd2_reparam.valueAt(t_reparam);
+ Point dir = unit_vector(derivative(pwd2_reparam).valueAt(t_reparam));
+ Point n = -rot90(dir);
+ double angle = Geom::angle_between(dir, Point(1,0));
+
+ sp_canvastext_set_coords(canvas_text, pos + n * length);
+ sp_canvastext_set_anchor_manually(canvas_text, std::sin(angle), -std::cos(angle));
+}
+
+void
+lpetool_create_measuring_items(LpeTool *lc, Inkscape::Selection *selection)
+{
+ if (!selection) {
+ selection = sp_desktop_selection(lc->desktop);
+ }
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool show = prefs->getBool("/tools/lpetool/show_measuring_info", true);
+
+ SPPath *path;
+ SPCurve *curve;
+ SPCanvasText *canvas_text;
+ SPCanvasGroup *tmpgrp = sp_desktop_tempgroup(lc->desktop);
+ gchar *arc_length;
+ double lengthval;
+
+ for (GSList const *i = selection->itemList(); i != NULL; i = i->next) {
+ if (SP_IS_PATH(i->data)) {
+ path = SP_PATH(i->data);
+ curve = path->getCurve();
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2 = paths_to_pw(curve->get_pathvector());
+ canvas_text = (SPCanvasText *) sp_canvastext_new(tmpgrp, lc->desktop, Geom::Point(0,0), "");
+ if (!show)
+ sp_canvas_item_hide(SP_CANVAS_ITEM(canvas_text));
+
+ Inkscape::Util::Unit const * unit = NULL;
+ if (prefs->getString("/tools/lpetool/unit").compare("")) {
+ unit = unit_table.getUnit(prefs->getString("/tools/lpetool/unit"));
+ } else {
+ unit = unit_table.getUnit("px");
+ }
+
+ lengthval = Geom::length(pwd2);
+ lengthval = Inkscape::Util::Quantity::convert(lengthval, "px", unit);
+ arc_length = g_strdup_printf("%.2f %s", lengthval, unit->abbr.c_str());
+ sp_canvastext_set_text (canvas_text, arc_length);
+ set_pos_and_anchor(canvas_text, pwd2, 0.5, 10);
+ // TODO: must we free arc_length?
+ (*lc->measuring_items)[path] = SP_CANVAS_ITEM(canvas_text);
+ }
+ }
+}
+
+void
+lpetool_delete_measuring_items(LpeTool *lc)
+{
+ std::map<SPPath *, SPCanvasItem*>::iterator i;
+ for (i = lc->measuring_items->begin(); i != lc->measuring_items->end(); ++i) {
+ sp_canvas_item_destroy(i->second);
+ }
+ lc->measuring_items->clear();
+}
+
+void
+lpetool_update_measuring_items(LpeTool *lc)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ for ( std::map<SPPath *, SPCanvasItem*>::iterator i = lc->measuring_items->begin();
+ i != lc->measuring_items->end();
+ ++i )
+ {
+ SPPath *path = i->first;
+ SPCurve *curve = path->getCurve();
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2 = Geom::paths_to_pw(curve->get_pathvector());
+ Inkscape::Util::Unit const * unit = NULL;
+ if (prefs->getString("/tools/lpetool/unit").compare("")) {
+ unit = unit_table.getUnit(prefs->getString("/tools/lpetool/unit"));
+ } else {
+ unit = unit_table.getUnit("px");
+ }
+ double lengthval = Geom::length(pwd2);
+ lengthval = Inkscape::Util::Quantity::convert(lengthval, "px", unit);
+ gchar *arc_length = g_strdup_printf("%.2f %s", lengthval, unit->abbr.c_str());
+ sp_canvastext_set_text (SP_CANVASTEXT(i->second), arc_length);
+ set_pos_and_anchor(SP_CANVASTEXT(i->second), pwd2, 0.5, 10);
+ // TODO: must we free arc_length?
+ }
+}
+
+void
+lpetool_show_measuring_info(LpeTool *lc, bool show)
+{
+ std::map<SPPath *, SPCanvasItem*>::iterator i;
+ for (i = lc->measuring_items->begin(); i != lc->measuring_items->end(); ++i) {
+ if (show) {
+ sp_canvas_item_show(i->second);
+ } else {
+ sp_canvas_item_hide(i->second);
+ }
+ }
+}
+
+}
+}
+}
+
+/*
+ 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/tools/lpe-tool.h b/src/ui/tools/lpe-tool.h
new file mode 100644
index 000000000..df78c205c
--- /dev/null
+++ b/src/ui/tools/lpe-tool.h
@@ -0,0 +1,99 @@
+#ifndef SP_LPETOOL_CONTEXT_H_SEEN
+#define SP_LPETOOL_CONTEXT_H_SEEN
+
+/*
+ * LPEToolContext: a context for a generic tool composed of subtools that are given by LPEs
+ *
+ * Authors:
+ * Maximilian Albert <maximilian.albert@gmail.com>
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ * Copyright (C) 1999-2002 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ * Copyright (C) 2008 Maximilian Albert
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/tools/pen-tool.h"
+
+#define SP_LPETOOL_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::LpeTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_LPETOOL_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::LpeTool*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+/* This is the list of subtools from which the toolbar of the LPETool is built automatically */
+extern const int num_subtools;
+
+struct SubtoolEntry {
+ Inkscape::LivePathEffect::EffectType type;
+ gchar const *icon_name;
+};
+
+extern SubtoolEntry lpesubtools[];
+
+enum LPEToolState {
+ LPETOOL_STATE_PEN,
+ LPETOOL_STATE_NODE
+};
+
+namespace Inkscape {
+class Selection;
+}
+
+class ShapeEditor;
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class LpeTool : public PenTool {
+public:
+ LpeTool();
+ virtual ~LpeTool();
+
+ ShapeEditor* shape_editor;
+ SPCanvasItem *canvas_bbox;
+ Inkscape::LivePathEffect::EffectType mode;
+
+ std::map<SPPath *, SPCanvasItem*> *measuring_items;
+
+ sigc::connection sel_changed_connection;
+ sigc::connection sel_modified_connection;
+
+ static const std::string prefsPath;
+
+ virtual const std::string& getPrefsPath();
+
+protected:
+ virtual void setup();
+ virtual void set(const Inkscape::Preferences::Entry& val);
+ virtual bool root_handler(GdkEvent* event);
+ virtual bool item_handler(SPItem* item, GdkEvent* event);
+};
+
+int lpetool_mode_to_index(Inkscape::LivePathEffect::EffectType const type);
+int lpetool_item_has_construction(LpeTool *lc, SPItem *item);
+bool lpetool_try_construction(LpeTool *lc, Inkscape::LivePathEffect::EffectType const type);
+void lpetool_context_switch_mode(LpeTool *lc, Inkscape::LivePathEffect::EffectType const type);
+void lpetool_get_limiting_bbox_corners(SPDocument *document, Geom::Point &A, Geom::Point &B);
+void lpetool_context_reset_limiting_bbox(LpeTool *lc);
+void lpetool_create_measuring_items(LpeTool *lc, Inkscape::Selection *selection = NULL);
+void lpetool_delete_measuring_items(LpeTool *lc);
+void lpetool_update_measuring_items(LpeTool *lc);
+void lpetool_show_measuring_info(LpeTool *lc, bool show = true);
+
+}
+}
+}
+
+#endif // SP_LPETOOL_CONTEXT_H_SEEN
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/ui/tools/measure-tool.cpp b/src/ui/tools/measure-tool.cpp
new file mode 100644
index 000000000..0d823dfda
--- /dev/null
+++ b/src/ui/tools/measure-tool.cpp
@@ -0,0 +1,777 @@
+/*
+ * Our nice measuring tool
+ *
+ * Authors:
+ * Felipe Correa da Silva Sanches <juca@members.fsf.org>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2011 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#include <gdk/gdkkeysyms.h>
+#include <boost/none_t.hpp>
+#include "util/units.h"
+#include "macros.h"
+#include "display/curve.h"
+#include "sp-shape.h"
+#include "sp-text.h"
+#include "sp-flowtext.h"
+#include "text-editing.h"
+#include "display/sp-ctrlline.h"
+#include "display/sodipodi-ctrl.h"
+#include "display/sp-canvas-item.h"
+#include "display/sp-canvas-util.h"
+#include "desktop.h"
+#include "document.h"
+#include "pixmaps/cursor-measure.xpm"
+#include "preferences.h"
+#include "inkscape.h"
+#include "desktop-handles.h"
+#include "ui/tools/measure-tool.h"
+#include "ui/tools/freehand-base.h"
+#include "display/canvas-text.h"
+#include "path-chemistry.h"
+#include "2geom/line.h"
+#include <2geom/path-intersection.h>
+#include <2geom/pathvector.h>
+#include <2geom/crossing.h>
+#include <2geom/angle.h>
+#include "snap.h"
+#include "sp-namedview.h"
+#include "enums.h"
+#include "ui/control-manager.h"
+
+using Inkscape::ControlManager;
+using Inkscape::CTLINE_SECONDARY;
+using Inkscape::Util::unit_table;
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+std::vector<Inkscape::Display::TemporaryItem*> measure_tmp_items;
+
+namespace {
+ ToolBase* createMeasureContext() {
+ return new MeasureTool();
+ }
+
+ bool measureContextRegistered = ToolFactory::instance().registerObject("/tools/measure", createMeasureContext);
+}
+
+const std::string& MeasureTool::getPrefsPath() {
+ return MeasureTool::prefsPath;
+}
+
+const std::string MeasureTool::prefsPath = "/tools/measure";
+
+namespace
+{
+
+gint const DIMENSION_OFFSET = 35;
+
+/**
+ * Simple class to use for removing label overlap.
+ */
+class LabelPlacement {
+public:
+
+ double lengthVal;
+ double offset;
+ Geom::Point start;
+ Geom::Point end;
+};
+
+bool SortLabelPlacement(LabelPlacement const &first, LabelPlacement const &second)
+{
+ if (first.end[Geom::Y] == second.end[Geom::Y]) {
+ return first.end[Geom::X] < second.end[Geom::X];
+ } else {
+ return first.end[Geom::Y] < second.end[Geom::Y];
+ }
+}
+
+void repositionOverlappingLabels(std::vector<LabelPlacement> &placements, SPDesktop *desktop, Geom::Point const &normal, double fontsize)
+{
+ std::sort(placements.begin(), placements.end(), SortLabelPlacement);
+
+ double border = 3;
+ Geom::Rect box;
+ {
+ Geom::Point tmp(fontsize * 8 + (border * 2), fontsize + (border * 2));
+ tmp = desktop->w2d(tmp);
+ box = Geom::Rect(-tmp[Geom::X] / 2, -tmp[Geom::Y] / 2, tmp[Geom::X] / 2, tmp[Geom::Y] / 2);
+ }
+
+ // Using index since vector may be re-ordered as we go.
+ // Starting at one, since the first item can't overlap itself
+ for (size_t i = 1; i < placements.size(); i++) {
+ LabelPlacement &place = placements[i];
+
+ bool changed = false;
+ do {
+ Geom::Rect current(box + place.end);
+
+ changed = false;
+ bool overlaps = false;
+ for (size_t j = i; (j > 0) && !overlaps; --j) {
+ LabelPlacement &otherPlace = placements[j - 1];
+ Geom::Rect target(box + otherPlace.end);
+ if (current.intersects(target)) {
+ overlaps = true;
+ }
+ }
+ if (overlaps) {
+ place.offset += (fontsize + border);
+ place.end = place.start - desktop->w2d(normal * place.offset);
+ changed = true;
+ }
+ } while (changed);
+
+ std::sort(placements.begin(), placements.begin() + i + 1, SortLabelPlacement);
+ }
+}
+
+/**
+ * Calculates where to place the anchor for the display text and arc.
+ *
+ * @param desktop the desktop that is being used.
+ * @param angle the angle to be displaying.
+ * @param baseAngle the angle of the initial baseline.
+ * @param startPoint the point that is the vertex of the selected angle.
+ * @param endPoint the point that is the end the user is manipulating for measurement.
+ * @param fontsize the size to display the text label at.
+ */
+Geom::Point calcAngleDisplayAnchor(SPDesktop *desktop, double angle, double baseAngle,
+ Geom::Point const &startPoint, Geom::Point const &endPoint,
+ double fontsize)
+{
+ // Time for the trick work of figuring out where things should go, and how.
+ double lengthVal = (endPoint - startPoint).length();
+ double effective = baseAngle + (angle / 2);
+ Geom::Point where(lengthVal, 0);
+ where *= Geom::Affine(Geom::Rotate(effective)) * Geom::Affine(Geom::Translate(startPoint));
+
+ // When the angle is tight, the label would end up under the cursor and/or lines. Bump it
+ double scaledFontsize = std::abs(fontsize * desktop->w2d(Geom::Point(0, 1.0))[Geom::Y]);
+ if (std::abs((where - endPoint).length()) < scaledFontsize) {
+ where[Geom::Y] += scaledFontsize * 2;
+ }
+
+ // We now have the ideal position, but need to see if it will fit/work.
+
+ Geom::Rect visibleArea = desktop->get_display_area();
+ // Bring it in to "title safe" for the anchor point
+ Geom::Point textBox = desktop->w2d(Geom::Point(fontsize * 3, fontsize / 2));
+ textBox[Geom::Y] = std::abs(textBox[Geom::Y]);
+
+ visibleArea = Geom::Rect(visibleArea.min()[Geom::X] + textBox[Geom::X],
+ visibleArea.min()[Geom::Y] + textBox[Geom::Y],
+ visibleArea.max()[Geom::X] - textBox[Geom::X],
+ visibleArea.max()[Geom::Y] - textBox[Geom::Y]);
+
+ where[Geom::X] = std::min(where[Geom::X], visibleArea.max()[Geom::X]);
+ where[Geom::X] = std::max(where[Geom::X], visibleArea.min()[Geom::X]);
+ where[Geom::Y] = std::min(where[Geom::Y], visibleArea.max()[Geom::Y]);
+ where[Geom::Y] = std::max(where[Geom::Y], visibleArea.min()[Geom::Y]);
+
+ return where;
+}
+
+/**
+ * Given an angle, the arc center and edge point, draw an arc segment centered around that edge point.
+ *
+ * @param desktop the desktop that is being used.
+ * @param center the center point for the arc.
+ * @param end the point that ends at the edge of the arc segment.
+ * @param anchor the anchor point for displaying the text label.
+ * @param angle the angle of the arc segment to draw.
+ */
+void createAngleDisplayCurve(SPDesktop *desktop, Geom::Point const &center, Geom::Point const &end, Geom::Point const &anchor, double angle)
+{
+ // Given that we have a point on the arc's edge and the angle of the arc, we need to get the two endpoints.
+
+ double textLen = std::abs((anchor - center).length());
+ double sideLen = std::abs((end - center).length());
+ if (sideLen > 0.0) {
+ double factor = std::min(1.0, textLen / sideLen);
+
+ // arc start
+ Geom::Point p1 = end * (Geom::Affine(Geom::Translate(-center))
+ * Geom::Affine(Geom::Scale(factor))
+ * Geom::Affine(Geom::Translate(center)));
+
+ // arc end
+ Geom::Point p4 = p1 * (Geom::Affine(Geom::Translate(-center))
+ * Geom::Affine(Geom::Rotate(-angle))
+ * Geom::Affine(Geom::Translate(center)));
+
+ // from Riskus
+ double xc = center[Geom::X];
+ double yc = center[Geom::Y];
+ double ax = p1[Geom::X] - xc;
+ double ay = p1[Geom::Y] - yc;
+ double bx = p4[Geom::X] - xc;
+ double by = p4[Geom::Y] - yc;
+ double q1 = (ax * ax) + (ay * ay);
+ double q2 = q1 + (ax * bx) + (ay * by);
+
+ double k2 = (4.0 / 3.0) * (std::sqrt(2 * q1 * q2) - q2) / ((ax * by) - (ay * bx));
+
+ Geom::Point p2(xc + ax - (k2 * ay),
+ yc + ay + (k2 * ax));
+ Geom::Point p3(xc + bx + (k2 * by),
+ yc + by - (k2 * bx));
+ SPCtrlCurve *curve = ControlManager::getManager().createControlCurve(sp_desktop_tempgroup(desktop), p1, p2, p3, p4, CTLINE_SECONDARY);
+
+ measure_tmp_items.push_back(desktop->add_temporary_canvasitem(SP_CANVAS_ITEM(curve), 0, true));
+ }
+}
+
+} // namespace
+
+
+MeasureTool::MeasureTool() : ToolBase() {
+ this->grabbed = 0;
+
+ this->cursor_shape = cursor_measure_xpm;
+ this->hot_x = 4;
+ this->hot_y = 4;
+}
+
+MeasureTool::~MeasureTool() {
+}
+
+void MeasureTool::finish() {
+ this->enableGrDrag(false);
+
+ if (this->grabbed) {
+ sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME);
+ this->grabbed = NULL;
+ }
+}
+
+//void MeasureTool::setup() {
+// ToolBase* ec = this;
+//
+//// if (SP_EVENT_CONTEXT_CLASS(sp_measure_context_parent_class)->setup) {
+//// SP_EVENT_CONTEXT_CLASS(sp_measure_context_parent_class)->setup(ec);
+//// }
+// ToolBase::setup();
+//}
+
+//gint MeasureTool::item_handler(SPItem* item, GdkEvent* event) {
+// gint ret = FALSE;
+//
+//// if (SP_EVENT_CONTEXT_CLASS(sp_measure_context_parent_class)->item_handler) {
+//// ret = SP_EVENT_CONTEXT_CLASS(sp_measure_context_parent_class)->item_handler(event_context, item, event);
+//// }
+// ret = ToolBase::item_handler(item, event);
+//
+// return ret;
+//}
+
+static bool GeomPointSortPredicate(const Geom::Point& p1, const Geom::Point& p2)
+{
+ if (p1[Geom::Y] == p2[Geom::Y]) {
+ return p1[Geom::X] < p2[Geom::X];
+ } else {
+ return p1[Geom::Y] < p2[Geom::Y];
+ }
+}
+
+static void calculate_intersections(SPDesktop * /*desktop*/, SPItem* item, Geom::PathVector const &lineseg, SPCurve *curve, std::vector<Geom::Point> &intersections)
+{
+ curve->transform(item->i2doc_affine());
+
+ // Find all intersections of the control-line with this shape
+ Geom::CrossingSet cs = Geom::crossings(lineseg, curve->get_pathvector());
+
+ // Reconstruct and store the points of intersection
+ for (Geom::Crossings::const_iterator m = cs[0].begin(); m != cs[0].end(); ++m) {
+#if 0
+//TODO: consider only visible intersections
+ Geom::Point intersection = lineseg[0].pointAt((*m).ta);
+ double eps = 0.0001;
+ SPDocument* doc = sp_desktop_document(desktop);
+ if (((*m).ta > eps &&
+ item == doc->getItemAtPoint(desktop->dkey, lineseg[0].pointAt((*m).ta - eps), false, NULL)) ||
+ ((*m).ta + eps < 1 &&
+ item == doc->getItemAtPoint(desktop->dkey, lineseg[0].pointAt((*m).ta + eps), false, NULL)) ) {
+ intersections.push_back(intersection);
+ }
+#else
+ intersections.push_back(lineseg[0].pointAt((*m).ta));
+#endif
+ }
+}
+
+bool MeasureTool::root_handler(GdkEvent* event) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS: {
+ Geom::Point const button_w(event->button.x, event->button.y);
+ explicitBase = boost::none;
+ lastEnd = boost::none;
+ start_point = desktop->w2d(button_w);
+
+ if (event->button.button == 1 && !this->space_panning) {
+ // save drag origin
+ xp = static_cast<gint>(event->button.x);
+ yp = static_cast<gint>(event->button.y);
+ within_tolerance = true;
+
+ ret = TRUE;
+ }
+
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+ m.freeSnapReturnByRef(start_point, Inkscape::SNAPSOURCE_OTHER_HANDLE);
+ m.unSetup();
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK,
+ NULL, event->button.time);
+ this->grabbed = SP_CANVAS_ITEM(desktop->acetate);
+ break;
+ }
+ case GDK_KEY_PRESS: {
+ if ((event->key.keyval == GDK_KEY_Shift_L) || (event->key.keyval == GDK_KEY_Shift_R)) {
+ if (lastEnd) {
+ explicitBase = lastEnd;
+ }
+ }
+ break;
+ }
+ case GDK_MOTION_NOTIFY: {
+ if (!((event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning)) {
+ if (!(event->motion.state & GDK_SHIFT_MASK)) {
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point const motion_dt(desktop->w2d(motion_w));
+
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+
+ Inkscape::SnapCandidatePoint scp(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE);
+ scp.addOrigin(start_point);
+
+ m.preSnap(scp);
+ m.unSetup();
+ }
+ } else {
+ ret = TRUE;
+
+ if ( within_tolerance
+ && ( abs( static_cast<gint>(event->motion.x) - xp ) < tolerance )
+ && ( abs( static_cast<gint>(event->motion.y) - yp ) < tolerance ) ) {
+ break; // do not drag if we're within tolerance from origin
+ }
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to move the object, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ within_tolerance = false;
+
+ //clear previous temporary canvas items, we'll draw new ones
+ for (size_t idx = 0; idx < measure_tmp_items.size(); ++idx) {
+ desktop->remove_temporary_canvasitem(measure_tmp_items[idx]);
+ }
+
+ measure_tmp_items.clear();
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point const motion_dt(desktop->w2d(motion_w));
+ Geom::Point end_point = motion_dt;
+
+ if (event->motion.state & GDK_CONTROL_MASK) {
+ spdc_endpoint_snap_rotation(this, end_point, start_point, event->motion.state);
+ } else {
+ if (!(event->motion.state & GDK_SHIFT_MASK)) {
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+ Inkscape::SnapCandidatePoint scp(end_point, Inkscape::SNAPSOURCE_OTHER_HANDLE);
+ scp.addOrigin(start_point);
+ Inkscape::SnappedPoint sp = m.freeSnap(scp);
+ end_point = sp.getPoint();
+ m.unSetup();
+ }
+ }
+
+ Geom::PathVector lineseg;
+ Geom::Path p;
+ p.start(desktop->dt2doc(start_point));
+ p.appendNew<Geom::LineSegment>(desktop->dt2doc(end_point));
+ lineseg.push_back(p);
+
+ double deltax = end_point[Geom::X] - start_point[Geom::X];
+ double deltay = end_point[Geom::Y] - start_point[Geom::Y];
+ double angle = atan2(deltay, deltax);
+ double baseAngle = 0;
+
+ if (explicitBase) {
+ double deltax2 = explicitBase.get()[Geom::X] - start_point[Geom::X];
+ double deltay2 = explicitBase.get()[Geom::Y] - start_point[Geom::Y];
+
+ baseAngle = atan2(deltay2, deltax2);
+ angle -= baseAngle;
+
+ if (angle < -M_PI) {
+ angle += 2 * M_PI;
+ } else if (angle > M_PI) {
+ angle -= 2 * M_PI;
+ }
+ }
+
+//TODO: calculate NPOINTS
+//800 seems to be a good value for 800x600 resolution
+#define NPOINTS 800
+
+ std::vector<Geom::Point> points;
+
+ for (double i = 0; i < NPOINTS; i++) {
+ points.push_back(desktop->d2w(start_point + (i / NPOINTS) * (end_point - start_point)));
+ }
+
+// TODO: Felipe, why don't you simply iterate over all items, and test whether their bounding boxes intersect
+// with the measurement line, instead of interpolating? E.g. bbox_of_measurement_line.intersects(*bbox_of_item).
+// That's also how the object-snapper works, see _findCandidates() in object-snapper.cpp.
+
+ //select elements crossed by line segment:
+ GSList *items = sp_desktop_document(desktop)->getItemsAtPoints(desktop->dkey, points);
+ std::vector<Geom::Point> intersections;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool ignore_1st_and_last = prefs->getBool("/tools/measure/ignore_1st_and_last", true);
+
+ if (!ignore_1st_and_last) {
+ intersections.push_back(desktop->dt2doc(start_point));
+ }
+
+ std::vector<LabelPlacement> placements;
+
+ // TODO switch to a different variable name. The single letter 'l' is easy to misread.
+ for (GSList *l = items; l != NULL; l = l->next) {
+ SPItem *item = static_cast<SPItem*>(l->data);
+
+ if (SP_IS_SHAPE(item)) {
+ calculate_intersections(desktop, item, lineseg, SP_SHAPE(item)->getCurve(), intersections);
+ } else {
+ if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
+ Inkscape::Text::Layout::iterator iter = te_get_layout(item)->begin();
+ do {
+ Inkscape::Text::Layout::iterator iter_next = iter;
+ iter_next.nextGlyph(); // iter_next is one glyph ahead from iter
+ if (iter == iter_next) {
+ break;
+ }
+
+ // get path from iter to iter_next:
+ SPCurve *curve = te_get_layout(item)->convertToCurves(iter, iter_next);
+ iter = iter_next; // shift to next glyph
+ if (!curve) {
+ continue; // error converting this glyph
+ }
+ if (curve->is_empty()) { // whitespace glyph?
+ curve->unref();
+ continue;
+ }
+
+ curve->transform(item->i2doc_affine());
+
+ calculate_intersections(desktop, item, lineseg, curve, intersections);
+
+ if (iter == te_get_layout(item)->end()) {
+ break;
+ }
+ } while (true);
+ }
+ }
+ }
+
+ if (!ignore_1st_and_last) {
+ intersections.push_back(desktop->dt2doc(end_point));
+ }
+
+ //sort intersections
+ if (intersections.size() > 2) {
+ std::sort(intersections.begin(), intersections.end(), GeomPointSortPredicate);
+ }
+
+ Glib::ustring unit_name = prefs->getString("/tools/measure/unit");
+ if (!unit_name.compare("")) {
+ unit_name = "px";
+ }
+
+ double fontsize = prefs->getInt("/tools/measure/fontsize");
+
+ // Normal will be used for lines and text
+ Geom::Point windowNormal = Geom::unit_vector(Geom::rot90(desktop->d2w(end_point - start_point)));
+ Geom::Point normal = desktop->w2d(windowNormal);
+
+ for (size_t idx = 1; idx < intersections.size(); ++idx) {
+ LabelPlacement placement;
+ placement.lengthVal = (intersections[idx] - intersections[idx - 1]).length();
+ placement.lengthVal = Inkscape::Util::Quantity::convert(placement.lengthVal, "px", unit_name);
+ placement.offset = DIMENSION_OFFSET;
+ placement.start = desktop->doc2dt( (intersections[idx - 1] + intersections[idx]) / 2 );
+ placement.end = placement.start - (normal * placement.offset);
+
+ placements.push_back(placement);
+ }
+
+ // Adjust positions
+ repositionOverlappingLabels(placements, desktop, windowNormal, fontsize);
+
+ for (std::vector<LabelPlacement>::iterator it = placements.begin(); it != placements.end(); ++it)
+ {
+ LabelPlacement &place = *it;
+
+ // TODO cleanup memory, Glib::ustring, etc.:
+ gchar *measure_str = g_strdup_printf("%.2f %s", place.lengthVal, unit_name.c_str());
+ SPCanvasText *canvas_tooltip = sp_canvastext_new(sp_desktop_tempgroup(desktop),
+ desktop,
+ place.end,
+ measure_str);
+ sp_canvastext_set_fontsize(canvas_tooltip, fontsize);
+ canvas_tooltip->rgba = 0xffffffff;
+ canvas_tooltip->rgba_background = 0x0000007f;
+ canvas_tooltip->outline = false;
+ canvas_tooltip->background = true;
+ canvas_tooltip->anchor_position = TEXT_ANCHOR_CENTER;
+
+ measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvas_tooltip, 0));
+ g_free(measure_str);
+ }
+
+ Geom::Point angleDisplayPt = calcAngleDisplayAnchor(desktop, angle, baseAngle,
+ start_point, end_point,
+ fontsize);
+
+ {
+ // TODO cleanup memory, Glib::ustring, etc.:
+ gchar *angle_str = g_strdup_printf("%.2f °", angle * 180/M_PI);
+
+ SPCanvasText *canvas_tooltip = sp_canvastext_new(sp_desktop_tempgroup(desktop),
+ desktop,
+ angleDisplayPt,
+ angle_str);
+ sp_canvastext_set_fontsize(canvas_tooltip, fontsize);
+ canvas_tooltip->rgba = 0xffffffff;
+ canvas_tooltip->rgba_background = 0x337f337f;
+ canvas_tooltip->outline = false;
+ canvas_tooltip->background = true;
+ canvas_tooltip->anchor_position = TEXT_ANCHOR_CENTER;
+
+ measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvas_tooltip, 0));
+ g_free(angle_str);
+ }
+
+ {
+ double totallengthval = (end_point - start_point).length();
+ totallengthval = Inkscape::Util::Quantity::convert(totallengthval, "px", unit_name);
+
+ // TODO cleanup memory, Glib::ustring, etc.:
+ gchar *totallength_str = g_strdup_printf("%.2f %s", totallengthval, unit_name.c_str());
+ SPCanvasText *canvas_tooltip = sp_canvastext_new(sp_desktop_tempgroup(desktop),
+ desktop,
+ end_point + desktop->w2d(Geom::Point(3*fontsize, -fontsize)),
+ totallength_str);
+ sp_canvastext_set_fontsize(canvas_tooltip, fontsize);
+ canvas_tooltip->rgba = 0xffffffff;
+ canvas_tooltip->rgba_background = 0x3333337f;
+ canvas_tooltip->outline = false;
+ canvas_tooltip->background = true;
+ canvas_tooltip->anchor_position = TEXT_ANCHOR_LEFT;
+
+ measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvas_tooltip, 0));
+ g_free(totallength_str);
+ }
+
+ if (intersections.size() > 2) {
+ double totallengthval = (intersections[intersections.size()-1] - intersections[0]).length();
+ totallengthval = Inkscape::Util::Quantity::convert(totallengthval, "px", unit_name);
+
+ // TODO cleanup memory, Glib::ustring, etc.:
+ gchar *total_str = g_strdup_printf("%.2f %s", totallengthval, unit_name.c_str());
+ SPCanvasText *canvas_tooltip = sp_canvastext_new(sp_desktop_tempgroup(desktop),
+ desktop,
+ desktop->doc2dt((intersections[0] + intersections[intersections.size()-1])/2) + normal * 60,
+ total_str);
+ sp_canvastext_set_fontsize(canvas_tooltip, fontsize);
+ canvas_tooltip->rgba = 0xffffffff;
+ canvas_tooltip->rgba_background = 0x33337f7f;
+ canvas_tooltip->outline = false;
+ canvas_tooltip->background = true;
+ canvas_tooltip->anchor_position = TEXT_ANCHOR_CENTER;
+
+ measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvas_tooltip, 0));
+ g_free(total_str);
+ }
+
+ // Now that text has been added, we can add lines and controls so that they go underneath
+
+ for (size_t idx = 0; idx < intersections.size(); ++idx) {
+ // Display the intersection indicator (i.e. the cross)
+ SPCanvasItem * canvasitem = sp_canvas_item_new(sp_desktop_tempgroup(desktop),
+ SP_TYPE_CTRL,
+ "anchor", SP_ANCHOR_CENTER,
+ "size", 8.0,
+ "stroked", TRUE,
+ "stroke_color", 0xff0000ff,
+ "mode", SP_KNOT_MODE_XOR,
+ "shape", SP_KNOT_SHAPE_CROSS,
+ NULL );
+
+ SP_CTRL(canvasitem)->moveto(desktop->doc2dt(intersections[idx]));
+ measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvasitem, 0));
+ }
+
+ // Since adding goes to the bottom, do all lines last.
+
+ // draw main control line
+ {
+ SPCtrlLine *control_line = ControlManager::getManager().createControlLine(sp_desktop_tempgroup(desktop),
+ start_point,
+ end_point);
+ measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0));
+
+ if ((end_point[Geom::X] != start_point[Geom::X]) && (end_point[Geom::Y] != start_point[Geom::Y])) {
+ double length = std::abs((end_point - start_point).length());
+ Geom::Point anchorEnd = start_point;
+ anchorEnd[Geom::X] += length;
+ if (explicitBase) {
+ anchorEnd *= (Geom::Affine(Geom::Translate(-start_point))
+ * Geom::Affine(Geom::Rotate(baseAngle))
+ * Geom::Affine(Geom::Translate(start_point)));
+ }
+
+ SPCtrlLine *control_line = ControlManager::getManager().createControlLine(sp_desktop_tempgroup(desktop),
+ start_point,
+ anchorEnd,
+ CTLINE_SECONDARY);
+ measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0));
+
+ createAngleDisplayCurve(desktop, start_point, end_point, angleDisplayPt, angle);
+ }
+ }
+
+ if (intersections.size() > 2) {
+ ControlManager &mgr = ControlManager::getManager();
+ SPCtrlLine *control_line = 0;
+ control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop),
+ desktop->doc2dt(intersections[0]) + normal * 60,
+ desktop->doc2dt(intersections[intersections.size() - 1]) + normal * 60);
+ measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0));
+
+ control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop),
+ desktop->doc2dt(intersections[0]),
+ desktop->doc2dt(intersections[0]) + normal * 65);
+ measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0));
+
+ control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop),
+ desktop->doc2dt(intersections[intersections.size() - 1]),
+ desktop->doc2dt(intersections[intersections.size() - 1]) + normal * 65);
+ measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0));
+ }
+
+ // call-out lines
+ for (std::vector<LabelPlacement>::iterator it = placements.begin(); it != placements.end(); ++it)
+ {
+ LabelPlacement &place = *it;
+
+ ControlManager &mgr = ControlManager::getManager();
+ SPCtrlLine *control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop),
+ place.start,
+ place.end,
+ CTLINE_SECONDARY);
+ measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0));
+ }
+
+ {
+ for (size_t idx = 1; idx < intersections.size(); ++idx) {
+ Geom::Point measure_text_pos = (intersections[idx - 1] + intersections[idx]) / 2;
+
+ ControlManager &mgr = ControlManager::getManager();
+ SPCtrlLine *control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop),
+ desktop->doc2dt(measure_text_pos),
+ desktop->doc2dt(measure_text_pos) - (normal * DIMENSION_OFFSET),
+ CTLINE_SECONDARY);
+ measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0));
+ }
+ }
+
+ // Initial point
+ {
+ SPCanvasItem * canvasitem = sp_canvas_item_new(sp_desktop_tempgroup(desktop),
+ SP_TYPE_CTRL,
+ "anchor", SP_ANCHOR_CENTER,
+ "size", 8.0,
+ "stroked", TRUE,
+ "stroke_color", 0xff0000ff,
+ "mode", SP_KNOT_MODE_XOR,
+ "shape", SP_KNOT_SHAPE_CROSS,
+ NULL );
+
+ SP_CTRL(canvasitem)->moveto(start_point);
+ measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvasitem, 0));
+ }
+
+ lastEnd = end_point; // track in case we get a anchoring key-press later
+
+ gobble_motion_events(GDK_BUTTON1_MASK);
+ }
+ break;
+ }
+ case GDK_BUTTON_RELEASE: {
+ sp_event_context_discard_delayed_snap_event(this);
+ explicitBase = boost::none;
+ lastEnd = boost::none;
+
+ //clear all temporary canvas items related to the measurement tool.
+ for (size_t idx = 0; idx < measure_tmp_items.size(); ++idx) {
+ desktop->remove_temporary_canvasitem(measure_tmp_items[idx]);
+ }
+
+ measure_tmp_items.clear();
+
+ if (this->grabbed) {
+ sp_canvas_item_ungrab(this->grabbed, event->button.time);
+ this->grabbed = NULL;
+ }
+
+ xp = 0;
+ yp = 0;
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+}
+}
+}
+
+/*
+ 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/tools/measure-tool.h b/src/ui/tools/measure-tool.h
new file mode 100644
index 000000000..9701ba6ea
--- /dev/null
+++ b/src/ui/tools/measure-tool.h
@@ -0,0 +1,50 @@
+#ifndef SEEN_SP_MEASURING_CONTEXT_H
+#define SEEN_SP_MEASURING_CONTEXT_H
+
+/*
+ * Our fine measuring tool
+ *
+ * Authors:
+ * Felipe Correa da Silva Sanches <juca@members.fsf.org>
+ *
+ * Copyright (C) 2011 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/tools/tool-base.h"
+#include <2geom/point.h>
+#include <boost/optional.hpp>
+
+#define SP_MEASURE_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::MeasureTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_MEASURE_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::MeasureTool*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class MeasureTool : public ToolBase {
+public:
+ MeasureTool();
+ virtual ~MeasureTool();
+
+ static const std::string prefsPath;
+
+ virtual void finish();
+ virtual bool root_handler(GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+private:
+ SPCanvasItem* grabbed;
+
+ Geom::Point start_point;
+ boost::optional<Geom::Point> explicitBase;
+ boost::optional<Geom::Point> lastEnd;
+};
+
+}
+}
+}
+
+#endif // SEEN_SP_MEASURING_CONTEXT_H
diff --git a/src/ui/tools/mesh-tool.cpp b/src/ui/tools/mesh-tool.cpp
new file mode 100644
index 000000000..4e7617f44
--- /dev/null
+++ b/src/ui/tools/mesh-tool.cpp
@@ -0,0 +1,1017 @@
+/*
+ * Mesh drawing and editing tool
+ *
+ * Authors:
+ * bulia byak <buliabyak@users.sf.net>
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ * Abhishek Sharma
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2012 Tavmjong Bah
+ * Copyright (C) 2007 Johan Engelen
+ * Copyright (C) 2005 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+//#define DEBUG_MESH
+
+
+// Libraries
+#include <gdk/gdkkeysyms.h>
+#include <glibmm/i18n.h>
+
+// General
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "document.h"
+#include "document-undo.h"
+#include "macros.h"
+#include "message-context.h"
+#include "message-stack.h"
+#include "preferences.h"
+#include "rubberband.h"
+#include "selection.h"
+#include "snap.h"
+#include "sp-namedview.h"
+#include "verbs.h"
+
+// Gradient specific
+#include "gradient-drag.h"
+#include "gradient-chemistry.h"
+#include "pixmaps/cursor-gradient.xpm"
+#include "pixmaps/cursor-gradient-add.xpm"
+
+// Mesh specific
+#include "ui/tools/mesh-tool.h"
+#include "sp-mesh-gradient.h"
+#include "display/sp-ctrlcurve.h"
+
+using Inkscape::DocumentUndo;
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+static void sp_mesh_drag(MeshTool &rc, Geom::Point const pt, guint state, guint32 etime);
+
+namespace {
+ ToolBase* createMeshContext() {
+ return new MeshTool();
+ }
+
+ bool meshContextRegistered = ToolFactory::instance().registerObject("/tools/mesh", createMeshContext);
+}
+
+const std::string& MeshTool::getPrefsPath() {
+ return MeshTool::prefsPath;
+}
+
+const std::string MeshTool::prefsPath = "/tools/mesh";
+
+MeshTool::MeshTool() : ToolBase() {
+ this->selcon = 0;
+ this->node_added = false;
+ this->subselcon = 0;
+
+ this->cursor_addnode = false;
+ this->cursor_shape = cursor_gradient_xpm;
+ this->hot_x = 4;
+ this->hot_y = 4;
+ this->xp = 0;
+ this->yp = 0;
+ this->tolerance = 6;
+ this->within_tolerance = false;
+ this->item_to_select = NULL;
+}
+
+MeshTool::~MeshTool() {
+ this->enableGrDrag(false);
+
+ this->selcon->disconnect();
+ delete this->selcon;
+
+ this->subselcon->disconnect();
+ delete this->subselcon;
+}
+
+const gchar *ms_handle_descr [] = {
+ N_("Mesh gradient <b>corner</b>"),
+ N_("Mesh gradient <b>handle</b>"),
+ N_("Mesh gradient <b>tensor</b>")
+};
+
+void MeshTool::selection_changed(Inkscape::Selection* /*sel*/) {
+ GrDrag *drag = this->_grdrag;
+ Inkscape::Selection *selection = sp_desktop_selection(this->desktop);
+
+ if (selection == NULL) {
+ return;
+ }
+
+ guint n_obj = g_slist_length((GSList *) selection->itemList());
+
+ if (!drag->isNonEmpty() || selection->isEmpty()) {
+ return;
+ }
+
+ guint n_tot = drag->numDraggers();
+ guint n_sel = drag->numSelected();
+
+ //The use of ngettext in the following code is intentional even if the English singular form would never be used
+ if (n_sel == 1) {
+ if (drag->singleSelectedDraggerNumDraggables() == 1) {
+ gchar * message = g_strconcat(
+ //TRANSLATORS: %s will be substituted with the point name (see previous messages); This is part of a compound message
+ _("%s selected"),
+ //TRANSLATORS: Mind the space in front. This is part of a compound message
+ ngettext(" out of %d mesh handle"," out of %d mesh handles",n_tot),
+ ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL);
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE,
+ message,_(ms_handle_descr[drag->singleSelectedDraggerSingleDraggableType()]), n_tot, n_obj);
+ } else {
+ gchar * message =
+ g_strconcat(
+ //TRANSLATORS: This is a part of a compound message (out of two more indicating: grandint handle count & object count)
+ ngettext("One handle merging %d stop (drag with <b>Shift</b> to separate) selected",
+ "One handle merging %d stops (drag with <b>Shift</b> to separate) selected",
+ drag->singleSelectedDraggerNumDraggables()),
+ ngettext(" out of %d mesh handle"," out of %d mesh handles",n_tot),
+ ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL);
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE,message,drag->singleSelectedDraggerNumDraggables(), n_tot, n_obj);
+ }
+ } else if (n_sel > 1) {
+ //TRANSLATORS: The plural refers to number of selected mesh handles. This is part of a compound message (part two indicates selected object count)
+ gchar * message =
+ g_strconcat(ngettext("<b>%d</b> mesh handle selected out of %d","<b>%d</b> mesh handles selected out of %d",n_sel),
+ //TRANSLATORS: Mind the space in front. (Refers to gradient handles selected). This is part of a compound message
+ ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL);
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE,message, n_sel, n_tot, n_obj);
+ } else if (n_sel == 0) {
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE,
+ //TRANSLATORS: The plural refers to number of selected objects
+ ngettext("<b>No</b> mesh handles selected out of %d on %d selected object",
+ "<b>No</b> mesh handles selected out of %d on %d selected objects",n_obj), n_tot, n_obj);
+ }
+
+ // FIXME
+ // We need to update mesh gradient handles.
+ // Get gradient this drag belongs too..
+ // std::cout << "mesh_selection_changed: selection: objects: " << n_obj << std::endl;
+ // GSList *itemList = (GSList *) selection->itemList();
+ // while( itemList ) {
+
+ // SPItem *item = SP_ITEM( itemList->data );
+ // // std::cout << " item: " << SP_OBJECT(item)->getId() << std::endl;
+
+ // SPStyle *style = item->style;
+ // if (style && (style->fill.isPaintserver())) {
+
+ // SPPaintServer *server = item->style->getFillPaintServer();
+ // if ( SP_IS_MESHGRADIENT(server) ) {
+
+ // SPMeshGradient *mg = SP_MESHGRADIENT(server);
+
+ // guint rows = 0;//mg->array.patches.size();
+ // for ( guint i = 0; i < rows; ++i ) {
+ // guint columns = 0;//mg->array.patches[0].size();
+ // for ( guint j = 0; j < columns; ++j ) {
+ // }
+ // }
+ // }
+ // }
+ // itemList = itemList->next;
+ // }
+
+ // GList* dragger_ptr = drag->draggers; // Points to GrDragger class (group of GrDraggable)
+ // guint count = 0;
+ // while( dragger_ptr ) {
+
+ // std::cout << "mesh_selection_changed: dragger: " << ++count << std::endl;
+ // GSList* draggable_ptr = ((GrDragger *) dragger_ptr->data)->draggables;
+
+ // while( draggable_ptr ) {
+
+ // std::cout << "mesh_selection_changed: draggable: " << draggable_ptr << std::endl;
+ // GrDraggable *draggable = (GrDraggable *) draggable_ptr->data;
+
+ // gint point_type = draggable->point_type;
+ // gint point_i = draggable->point_i;
+ // bool fill_or_stroke = draggable->fill_or_stroke;
+
+ // if( point_type == POINT_MG_CORNER ) {
+
+ // //std::cout << "mesh_selection_changed: POINT_MG_CORNER: " << point_i << std::endl;
+ // // Now we must create or destroy corresponding handles.
+
+ // if( g_list_find( drag->selected, dragger_ptr->data ) ) {
+ // //std::cout << "gradient_selection_changed: Selected: " << point_i << std::endl;
+ // // Which meshes does this point belong to?
+
+ // } else {
+ // //std::cout << "mesh_selection_changed: Not Selected: " << point_i << std::endl;
+ // }
+ // }
+
+ // draggable_ptr = draggable_ptr->next;
+
+ // }
+
+ // dragger_ptr = dragger_ptr->next;
+ // }
+}
+
+void MeshTool::setup() {
+ ToolBase::setup();
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/mesh/selcue", true)) {
+ this->enableSelectionCue();
+ }
+
+ this->enableGrDrag();
+ Inkscape::Selection *selection = sp_desktop_selection(this->desktop);
+
+ this->selcon = new sigc::connection(selection->connectChanged(
+ sigc::mem_fun(this, &MeshTool::selection_changed)
+ ));
+
+ this->subselcon = new sigc::connection(this->desktop->connectToolSubselectionChanged(
+ sigc::hide(sigc::bind(
+ sigc::mem_fun(*this, &MeshTool::selection_changed),
+ (Inkscape::Selection*)NULL)
+ )
+ ));
+
+ this->selection_changed(selection);
+}
+
+void
+sp_mesh_context_select_next (ToolBase *event_context)
+{
+ GrDrag *drag = event_context->_grdrag;
+ g_assert (drag);
+
+ GrDragger *d = drag->select_next();
+
+ event_context->desktop->scroll_to_point(d->point, 1.0);
+}
+
+void
+sp_mesh_context_select_prev (ToolBase *event_context)
+{
+ GrDrag *drag = event_context->_grdrag;
+ g_assert (drag);
+
+ GrDragger *d = drag->select_prev();
+
+ event_context->desktop->scroll_to_point(d->point, 1.0);
+}
+
+/**
+Returns true if mouse cursor over mesh edge.
+*/
+static bool
+sp_mesh_context_is_over_line (MeshTool *rc, SPItem *item, Geom::Point event_p)
+{
+ SPDesktop *desktop = SP_EVENT_CONTEXT (rc)->desktop;
+
+ //Translate mouse point into proper coord system
+ rc->mousepoint_doc = desktop->w2d(event_p);
+
+ SPCtrlCurve *curve = SP_CTRLCURVE(item);
+ Geom::BezierCurveN<3> b( curve->p0, curve->p1, curve->p2, curve->p3 );
+ Geom::Coord coord = b.nearestPoint( rc->mousepoint_doc ); // Coord == double
+ Geom::Point nearest = b( coord );
+
+ double dist_screen = Geom::L2 (rc->mousepoint_doc - nearest) * desktop->current_zoom();
+
+ double tolerance = (double) SP_EVENT_CONTEXT(rc)->tolerance;
+
+ bool close = (dist_screen < tolerance);
+
+ return close;
+}
+
+
+/**
+Split row/column near the mouse point.
+*/
+static void sp_mesh_context_split_near_point(MeshTool *rc, SPItem *item, Geom::Point mouse_p, guint32 /*etime*/)
+{
+
+#ifdef DEBUG_MESH
+ std::cout << "sp_mesh_context_split_near_point: entrance: " << mouse_p << std::endl;
+#endif
+
+ // item is the selected item. mouse_p the location in doc coordinates of where to add the stop
+
+ ToolBase *ec = SP_EVENT_CONTEXT(rc);
+ SPDesktop *desktop = SP_EVENT_CONTEXT (rc)->desktop;
+
+ double tolerance = (double) ec->tolerance;
+
+ ec->get_drag()->addStopNearPoint (item, mouse_p, tolerance/desktop->current_zoom());
+
+ DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_MESH,
+ _("Split mesh row/column"));
+
+ ec->get_drag()->updateDraggers();
+}
+
+/**
+Wrapper for various mesh operations that require a list of selected corner nodes.
+ */
+static void
+sp_mesh_context_corner_operation (MeshTool *rc, MeshCornerOperation operation )
+{
+
+#ifdef DEBUG_MESH
+ std::cout << "sp_mesh_corner_operation: entrance: " << operation << std::endl;
+#endif
+
+ SPDocument *doc = NULL;
+ GrDrag *drag = rc->_grdrag;
+
+ std::map<SPMeshGradient*, std::vector<guint> > points;
+ std::map<SPMeshGradient*, SPItem*> items;
+
+ // Get list of selected draggers for each mesh.
+ // For all selected draggers
+ for (GList *i = drag->selected; i != NULL; i = i->next) {
+ GrDragger *dragger = (GrDragger *) i->data;
+ // For all draggables of dragger
+ for (GSList const* j = dragger->draggables; j != NULL; j = j->next) {
+ GrDraggable *d = (GrDraggable *) j->data;
+
+ // Only mesh corners
+ if( d->point_type != POINT_MG_CORNER ) continue;
+
+ // Find the gradient
+ SPMeshGradient *gradient = SP_MESHGRADIENT( getGradient (d->item, d->fill_or_stroke) );
+
+ // Collect points together for same gradient
+ points[gradient].push_back( d->point_i );
+ items[gradient] = d->item;
+ }
+ }
+
+ // Loop over meshes.
+ for( std::map<SPMeshGradient*, std::vector<guint> >::const_iterator iter = points.begin(); iter != points.end(); ++iter) {
+ SPMeshGradient *mg = SP_MESHGRADIENT( iter->first );
+ if( iter->second.size() > 0 ) {
+ guint noperation = 0;
+ switch (operation) {
+
+ case MG_CORNER_SIDE_TOGGLE:
+ // std::cout << "SIDE_TOGGLE" << std::endl;
+ noperation += mg->array.side_toggle( iter->second );
+ break;
+
+ case MG_CORNER_SIDE_ARC:
+ // std::cout << "SIDE_ARC" << std::endl;
+ noperation += mg->array.side_arc( iter->second );
+ break;
+
+ case MG_CORNER_TENSOR_TOGGLE:
+ // std::cout << "TENSOR_TOGGLE" << std::endl;
+ noperation += mg->array.tensor_toggle( iter->second );
+ break;
+
+ case MG_CORNER_COLOR_SMOOTH:
+ // std::cout << "COLOR_SMOOTH" << std::endl;
+ noperation += mg->array.color_smooth( iter->second );
+ break;
+
+ case MG_CORNER_COLOR_PICK:
+ // std::cout << "COLOR_PICK" << std::endl;
+ noperation += mg->array.color_pick( iter->second, items[iter->first] );
+ break;
+
+ default:
+ std::cout << "sp_mesh_corner_operation: unknown operation" << std::endl;
+ }
+
+ if( noperation > 0 ) {
+ mg->array.write( mg );
+ mg->requestModified(SP_OBJECT_MODIFIED_FLAG);
+ doc = mg->document;
+
+ switch (operation) {
+
+ case MG_CORNER_SIDE_TOGGLE:
+ DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Toggled mesh path type."));
+ break;
+
+ case MG_CORNER_SIDE_ARC:
+ DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Approximated arc for mesh side."));
+ break;
+
+ case MG_CORNER_TENSOR_TOGGLE:
+ DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Toggled mesh tensors."));
+ break;
+
+ case MG_CORNER_COLOR_SMOOTH:
+ DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Smoothed mesh corner color."));
+ break;
+
+ case MG_CORNER_COLOR_PICK:
+ DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Picked mesh corner color."));
+ break;
+
+ default:
+ std::cout << "sp_mesh_corner_operation: unknown operation" << std::endl;
+ }
+ }
+ }
+ }
+ drag->updateDraggers();
+
+}
+
+
+/**
+Handles all keyboard and mouse input for meshs.
+*/
+bool MeshTool::root_handler(GdkEvent* event) {
+ static bool dragging;
+
+ Inkscape::Selection *selection = sp_desktop_selection (desktop);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+ double const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px
+
+ GrDrag *drag = this->_grdrag;
+ g_assert (drag);
+
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_2BUTTON_PRESS:
+
+#ifdef DEBUG_MESH
+ std::cout << "sp_mesh_context_root_handler: GDK_2BUTTON_PRESS" << std::endl;
+#endif
+
+ // Double click:
+ // If over a mesh line, divide mesh row/column
+ // If not over a line, create new gradients for selected objects.
+
+ if ( event->button.button == 1 ) {
+ // Are we over a mesh line?
+ bool over_line = false;
+ SPCtrlCurve *line = NULL;
+
+ if (drag->lines) {
+ for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) {
+ line = (SPCtrlCurve*) l->data;
+ over_line |= sp_mesh_context_is_over_line (this, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y));
+ }
+ }
+
+ if (over_line) {
+ // We take the first item in selection, because with doubleclick, the first click
+ // always resets selection to the single object under cursor
+ sp_mesh_context_split_near_point(this, SP_ITEM(selection->itemList()->data), this->mousepoint_doc, event->button.time);
+ } else {
+ // Create a new gradient with default coordinates.
+ for (GSList const* i = selection->itemList(); i != NULL; i = i->next) {
+ SPItem *item = SP_ITEM(i->data);
+ SPGradientType new_type = SP_GRADIENT_TYPE_MESH;
+ Inkscape::PaintTarget fsmode = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE;
+
+#ifdef DEBUG_MESH
+ std::cout << "sp_mesh_context_root_handler: creating new mesh on: " << (fsmode == Inkscape::FOR_FILL ? "Fill" : "Stroke") << std::endl;
+#endif
+ SPGradient *vector = sp_gradient_vector_for_object(sp_desktop_document(desktop), desktop, item, fsmode);
+
+ SPGradient *priv = sp_item_set_gradient(item, vector, new_type, fsmode);
+ sp_gradient_reset_to_userspace(priv, item);
+ }
+
+ DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_MESH,
+ _("Create default mesh"));
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_BUTTON_PRESS:
+
+#ifdef DEBUG_MESH
+ std::cout << "sp_mesh_context_root_handler: GDK_BUTTON_PRESS" << std::endl;
+#endif
+ // Button down
+ // If Shift key down: do rubber band selection
+ // Else set origin for drag. A drag creates a new gradient if one does not exist
+ if ( event->button.button == 1 && !this->space_panning ) {
+ Geom::Point button_w(event->button.x, event->button.y);
+
+ // save drag origin
+ this->xp = (gint) button_w[Geom::X];
+ this->yp = (gint) button_w[Geom::Y];
+ this->within_tolerance = true;
+
+ dragging = true;
+
+ Geom::Point button_dt = desktop->w2d(button_w);
+ if (event->button.state & GDK_SHIFT_MASK) {
+ Inkscape::Rubberband::get(desktop)->start(desktop, button_dt);
+ } else {
+ // remember clicked item, disregarding groups, honoring Alt; do nothing with Crtl to
+ // enable Ctrl+doubleclick of exactly the selected item(s)
+ if (!(event->button.state & GDK_CONTROL_MASK)) {
+ this->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE);
+ }
+
+ if (!selection->isEmpty()) {
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+ m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ m.unSetup();
+ }
+
+ this->origin = button_dt;
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ // Mouse move
+ if ( dragging && ( event->motion.state & GDK_BUTTON1_MASK ) && !this->space_panning ) {
+
+#ifdef DEBUG_MESH
+ std::cout << "sp_mesh_context_root_handler: GDK_MOTION_NOTIFY: Dragging" << std::endl;
+#endif
+ if ( this->within_tolerance
+ && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance )
+ && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) {
+ break; // do not drag if we're within tolerance from origin
+ }
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to draw, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ this->within_tolerance = false;
+
+ Geom::Point const motion_w(event->motion.x,
+ event->motion.y);
+ Geom::Point const motion_dt = this->desktop->w2d(motion_w);
+
+ if (Inkscape::Rubberband::get(desktop)->is_started()) {
+ Inkscape::Rubberband::get(desktop)->move(motion_dt);
+ this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Draw around</b> handles to select them"));
+ } else {
+ // Create new gradient with coordinates determined by drag.
+ sp_mesh_drag(*this, motion_dt, event->motion.state, event->motion.time);
+ }
+
+ gobble_motion_events(GDK_BUTTON1_MASK);
+
+ ret = TRUE;
+ } else {
+ // Not dragging
+
+ // Do snapping
+ if (!drag->mouseOver() && !selection->isEmpty()) {
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point const motion_dt = this->desktop->w2d(motion_w);
+
+ m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE));
+ m.unSetup();
+ }
+
+ // Highlight corner node corresponding to side or tensor node
+ if( drag->mouseOver() ) {
+ // MESH FIXME: Light up corresponding corner node corresponding to node we are over.
+ // See "pathflash" in ui/tools/node-tool.cpp for ideas.
+ // Use desktop->add_temporary_canvasitem( SPCanvasItem, milliseconds );
+ }
+
+ // Change cursor shape if over line
+ bool over_line = false;
+
+ if (drag->lines) {
+ for (GSList *l = drag->lines; l != NULL; l = l->next) {
+ over_line |= sp_mesh_context_is_over_line (this, (SPItem*) l->data, Geom::Point(event->motion.x, event->motion.y));
+ }
+ }
+
+ if (this->cursor_addnode && !over_line) {
+ this->cursor_shape = cursor_gradient_xpm;
+ this->sp_event_context_update_cursor();
+ this->cursor_addnode = false;
+ } else if (!this->cursor_addnode && over_line) {
+ this->cursor_shape = cursor_gradient_add_xpm;
+ this->sp_event_context_update_cursor();
+ this->cursor_addnode = true;
+ }
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+
+#ifdef DEBUG_MESH
+ std::cout << "sp_mesh_context_root_handler: GDK_BUTTON_RELEASE" << std::endl;
+#endif
+
+ this->xp = this->yp = 0;
+
+ if ( event->button.button == 1 && !this->space_panning ) {
+ // Check if over line
+ bool over_line = false;
+ SPCtrlLine *line = NULL;
+
+ if (drag->lines) {
+ for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) {
+ line = (SPCtrlLine*) l->data;
+ over_line = sp_mesh_context_is_over_line (this, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y));
+
+ if (over_line) {
+ break;
+ }
+ }
+ }
+
+ if ( (event->button.state & GDK_CONTROL_MASK) && (event->button.state & GDK_MOD1_MASK ) ) {
+ if (over_line && line) {
+ sp_mesh_context_split_near_point(this, line->item, this->mousepoint_doc, 0);
+ ret = TRUE;
+ }
+ } else {
+ dragging = false;
+
+ // unless clicked with Ctrl (to enable Ctrl+doubleclick).
+ if (event->button.state & GDK_CONTROL_MASK) {
+ ret = TRUE;
+ break;
+ }
+
+ if (!this->within_tolerance) {
+ // we've been dragging, either do nothing (grdrag handles that),
+ // or rubberband-select if we have rubberband
+ Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop);
+
+ if (r->is_started() && !this->within_tolerance) {
+ // this was a rubberband drag
+ if (r->getMode() == RUBBERBAND_MODE_RECT) {
+ Geom::OptRect const b = r->getRectangle();
+ drag->selectRect(*b);
+ }
+ }
+ } else if (this->item_to_select) {
+ if (over_line && line) {
+ // Clicked on an existing mesh line, don't change selection. This stops
+ // possible change in selection during a double click with overlapping objects
+ } else {
+ // no dragging, select clicked item if any
+ if (event->button.state & GDK_SHIFT_MASK) {
+ selection->toggle(this->item_to_select);
+ } else {
+ selection->set(this->item_to_select);
+ }
+ }
+ } else {
+ // click in an empty space; do the same as Esc
+ if (drag->selected) {
+ drag->deselectAll();
+ } else {
+ selection->clear();
+ }
+ }
+
+ this->item_to_select = NULL;
+ ret = TRUE;
+ }
+
+ Inkscape::Rubberband::get(desktop)->stop();
+ }
+ break;
+
+ case GDK_KEY_PRESS:
+
+#ifdef DEBUG_MESH
+ std::cout << "sp_mesh_context_root_handler: GDK_KEY_PRESS" << std::endl;
+#endif
+
+ // FIXME: tip
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Alt_L:
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine)
+ case GDK_KEY_Meta_R:
+ sp_event_show_modifier_tip (this->defaultMessageContext(), event,
+ _("FIXME<b>Ctrl</b>: snap mesh angle"),
+ _("FIXME<b>Shift</b>: draw mesh around the starting point"),
+ NULL);
+ break;
+
+ case GDK_KEY_A:
+ case GDK_KEY_a:
+ if (MOD__CTRL_ONLY(event) && drag->isNonEmpty()) {
+ drag->selectAll();
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Escape:
+ if (drag->selected) {
+ drag->deselectAll();
+ } else {
+ selection->clear();
+ }
+
+ ret = TRUE;
+ //TODO: make dragging escapable by Esc
+ break;
+
+ case GDK_KEY_Left: // move handle left
+ case GDK_KEY_KP_Left:
+ case GDK_KEY_KP_4:
+ if (!MOD__CTRL(event)) { // not ctrl
+ gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask
+
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move_screen(mul*-10, 0); // shift
+ } else {
+ drag->selected_move_screen(mul*-1, 0); // no shift
+ }
+ } else { // no alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move(mul*-10*nudge, 0); // shift
+ } else {
+ drag->selected_move(mul*-nudge, 0); // no shift
+ }
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Up: // move handle up
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_8:
+ if (!MOD__CTRL(event)) { // not ctrl
+ gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask
+
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move_screen(0, mul*10); // shift
+ } else {
+ drag->selected_move_screen(0, mul*1); // no shift
+ }
+ } else { // no alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move(0, mul*10*nudge); // shift
+ } else {
+ drag->selected_move(0, mul*nudge); // no shift
+ }
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Right: // move handle right
+ case GDK_KEY_KP_Right:
+ case GDK_KEY_KP_6:
+ if (!MOD__CTRL(event)) { // not ctrl
+ gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask
+
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move_screen(mul*10, 0); // shift
+ } else {
+ drag->selected_move_screen(mul*1, 0); // no shift
+ }
+ } else { // no alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move(mul*10*nudge, 0); // shift
+ } else {
+ drag->selected_move(mul*nudge, 0); // no shift
+ }
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Down: // move handle down
+ case GDK_KEY_KP_Down:
+ case GDK_KEY_KP_2:
+ if (!MOD__CTRL(event)) { // not ctrl
+ gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask
+
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move_screen(0, mul*-10); // shift
+ } else {
+ drag->selected_move_screen(0, mul*-1); // no shift
+ }
+ } else { // no alt
+ if (MOD__SHIFT(event)) {
+ drag->selected_move(0, mul*-10*nudge); // shift
+ } else {
+ drag->selected_move(0, mul*-nudge); // no shift
+ }
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Insert:
+ case GDK_KEY_KP_Insert:
+ // with any modifiers:
+ //sp_gradient_context_add_stops_between_selected_stops (rc);
+ std::cout << "Inserting stops between selected stops not implemented yet" << std::endl;
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ case GDK_KEY_BackSpace:
+ if ( drag->selected ) {
+ std::cout << "Deleting mesh stops not implemented yet" << std::endl;
+ ret = TRUE;
+ }
+ break;
+
+ // Mesh Operations --------------------------------------------
+
+ case GDK_KEY_b: // Toggle mesh side between lineto and curveto.
+ case GDK_KEY_B:
+ if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) {
+ sp_mesh_context_corner_operation ( this, MG_CORNER_SIDE_TOGGLE );
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_c: // Convert mesh side from generic Bezier to Bezier approximating arc,
+ case GDK_KEY_C: // preserving handle direction.
+ if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) {
+ sp_mesh_context_corner_operation ( this, MG_CORNER_SIDE_ARC );
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_g: // Toggle mesh tensor points on/off
+ case GDK_KEY_G:
+ if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) {
+ sp_mesh_context_corner_operation ( this, MG_CORNER_TENSOR_TOGGLE );
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_j: // Smooth corner color
+ case GDK_KEY_J:
+ if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) {
+ sp_mesh_context_corner_operation ( this, MG_CORNER_COLOR_SMOOTH );
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_k: // Pick corner color
+ case GDK_KEY_K:
+ if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) {
+ sp_mesh_context_corner_operation ( this, MG_CORNER_COLOR_PICK );
+ ret = TRUE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ case GDK_KEY_RELEASE:
+
+#ifdef DEBUG_MESH
+ std::cout << "sp_mesh_context_root_handler: GDK_KEY_RELEASE" << std::endl;
+#endif
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Alt_L:
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt
+ case GDK_KEY_Meta_R:
+ this->defaultMessageContext()->clear();
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+static void sp_mesh_drag(MeshTool &rc, Geom::Point const /*pt*/, guint /*state*/, guint32 /*etime*/) {
+ SPDesktop *desktop = SP_EVENT_CONTEXT(&rc)->desktop;
+ Inkscape::Selection *selection = sp_desktop_selection(desktop);
+ SPDocument *document = sp_desktop_document(desktop);
+ ToolBase *ec = SP_EVENT_CONTEXT(&rc);
+
+ if (!selection->isEmpty()) {
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int type = SP_GRADIENT_TYPE_MESH;
+ Inkscape::PaintTarget fill_or_stroke = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE;
+
+ SPGradient *vector;
+ if (ec->item_to_select) {
+ // pick color from the object where drag started
+ vector = sp_gradient_vector_for_object(document, desktop, ec->item_to_select, fill_or_stroke);
+ } else {
+ // Starting from empty space:
+ // Sort items so that the topmost comes last
+ GSList *items = g_slist_copy ((GSList *) selection->itemList());
+ items = g_slist_sort(items, (GCompareFunc) sp_item_repr_compare_position);
+ // take topmost
+ vector = sp_gradient_vector_for_object(document, desktop, SP_ITEM(g_slist_last(items)->data), fill_or_stroke);
+ g_slist_free (items);
+ }
+
+ // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ sp_repr_css_set_property(css, "fill-opacity", "1.0");
+
+ for (GSList const *i = selection->itemList(); i != NULL; i = i->next) {
+
+ //FIXME: see above
+ sp_repr_css_change_recursive(SP_OBJECT(i->data)->getRepr(), css, "style");
+
+ sp_item_set_gradient(SP_ITEM(i->data), vector, (SPGradientType) type, fill_or_stroke);
+
+ // We don't need to do anything. Mesh is already sized appropriately.
+
+ SP_OBJECT(i->data)->requestModified(SP_OBJECT_MODIFIED_FLAG);
+ }
+ // if (ec->_grdrag) {
+ // ec->_grdrag->updateDraggers();
+ // // prevent regenerating draggers by selection modified signal, which sometimes
+ // // comes too late and thus destroys the knot which we will now grab:
+ // ec->_grdrag->local_change = true;
+ // // give the grab out-of-bounds values of xp/yp because we're already dragging
+ // // and therefore are already out of tolerance
+ // ec->_grdrag->grabKnot (SP_ITEM(selection->itemList()->data),
+ // type == SP_GRADIENT_TYPE_LINEAR? POINT_LG_END : POINT_RG_R1,
+ // -1, // ignore number (though it is always 1)
+ // fill_or_stroke, 99999, 99999, etime);
+ // }
+ // We did an undoable action, but SPDocumentUndo::done will be called by the knot when released
+
+ // status text; we do not track coords because this branch is run once, not all the time
+ // during drag
+ int n_objects = g_slist_length((GSList *) selection->itemList());
+ rc.message_context->setF(Inkscape::NORMAL_MESSAGE,
+ ngettext("<b>Gradient</b> for %d object; with <b>Ctrl</b> to snap angle",
+ "<b>Gradient</b> for %d objects; with <b>Ctrl</b> to snap angle", n_objects),
+ n_objects);
+ } else {
+ sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>objects</b> on which to create gradient."));
+ }
+
+}
+
+}
+}
+}
+
+/*
+ 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/tools/mesh-tool.h b/src/ui/tools/mesh-tool.h
new file mode 100644
index 000000000..d952c9010
--- /dev/null
+++ b/src/ui/tools/mesh-tool.h
@@ -0,0 +1,77 @@
+#ifndef SEEN_SP_MESH_CONTEXT_H
+#define SEEN_SP_MESH_CONTEXT_H
+
+/*
+ * Mesh drawing and editing tool
+ *
+ * Authors:
+ * bulia byak <buliabyak@users.sf.net>
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ * Jon A. Cruz <jon@joncruz.org.
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 2012 Tavmjong Bah
+ * Copyright (C) 2007 Johan Engelen
+ * Copyright (C) 2005,2010 Authors
+ *
+ * Released under GNU GPL
+ */
+
+#include <stddef.h>
+#include <sigc++/sigc++.h>
+#include "ui/tools/tool-base.h"
+
+#define SP_MESH_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::MeshTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_MESH_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::MeshTool*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class MeshTool : public ToolBase {
+public:
+ MeshTool();
+ virtual ~MeshTool();
+
+ Geom::Point origin;
+
+ bool cursor_addnode;
+
+ bool node_added;
+
+ Geom::Point mousepoint_doc; // stores mousepoint when over_line in doc coords
+
+ sigc::connection *selcon;
+ sigc::connection *subselcon;
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual bool root_handler(GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+private:
+ void selection_changed(Inkscape::Selection* sel);
+};
+
+void sp_mesh_context_select_next(ToolBase *event_context);
+void sp_mesh_context_select_prev(ToolBase *event_context);
+
+}
+}
+}
+
+#endif // SEEN_SP_MESH_CONTEXT_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/tools/node-tool.cpp b/src/ui/tools/node-tool.cpp
new file mode 100644
index 000000000..8a950b528
--- /dev/null
+++ b/src/ui/tools/node-tool.cpp
@@ -0,0 +1,705 @@
+/**
+ * @file
+ * New node tool - implementation.
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk@gmail.com>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/tool/curve-drag-point.h"
+#include <glib/gi18n.h>
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "display/sp-canvas-group.h"
+#include "display/canvas-bpath.h"
+#include "display/curve.h"
+#include "display/sp-canvas.h"
+#include "document.h"
+#include "live_effects/lpeobject.h"
+#include "message-context.h"
+#include "selection.h"
+#include "shape-editor.h" // temporary!
+#include "sp-clippath.h"
+#include "sp-item-group.h"
+#include "sp-mask.h"
+#include "sp-object-group.h"
+#include "sp-path.h"
+#include "sp-text.h"
+#include "ui/control-manager.h"
+#include "ui/tools/node-tool.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/manipulator.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tool/path-manipulator.h"
+#include "ui/tool/selector.h"
+#include "ui/tool/shape-record.h"
+
+#include "pixmaps/cursor-node.xpm"
+#include "pixmaps/cursor-node-d.xpm"
+#include "selection-chemistry.h"
+
+#include <gdk/gdkkeysyms.h>
+
+/** @struct NodeTool
+ *
+ * Node tool event context.
+ *
+ * @par Architectural overview of the tool
+ * @par
+ * Here's a breakdown of what each object does.
+ * - Handle: shows a handle and keeps the node type constraint (smooth / symmetric) by updating
+ * the other handle's position when dragged. Its move() method cannot violate the constraints.
+ * - Node: keeps node type constraints for auto nodes and smooth nodes at ends of linear segments.
+ * Its move() method cannot violate constraints. Handles linear grow and dispatches spatial grow
+ * to MultiPathManipulator. Keeps a reference to its NodeList.
+ * - NodeList: exposes an iterator-based interface to nodes. It is possible to obtain an iterator
+ * to a node from the node. Keeps a reference to its SubpathList.
+ * - SubpathList: list of NodeLists that represents an editable pathvector. Keeps a reference
+ * to its PathManipulator.
+ * - PathManipulator: performs most of the single-path actions like reverse subpaths,
+ * delete segment, shift selection, etc. Keeps a reference to MultiPathManipulator.
+ * - MultiPathManipulator: performs additional operations for actions that are not per-path,
+ * for example node joins and segment joins. Tracks the control transforms for PMs that edit
+ * clipping paths and masks. It is more or less equivalent to ShapeEditor and in the future
+ * it might handle all shapes. Handles XML commit of actions that affect all paths or
+ * the node selection and removes PathManipulators that have no nodes left after e.g. node
+ * deletes.
+ * - ControlPointSelection: keeps track of node selection and a set of nodes that can potentially
+ * be selected. There can be more than one selection. Performs actions that require no
+ * knowledge about the path, only about the nodes, like dragging and transforms. It is not
+ * specific to nodes and can accomodate any control point derived from SelectableControlPoint.
+ * Transforms nodes in response to transform handle events.
+ * - TransformHandleSet: displays nodeset transform handles and emits transform events. The aim
+ * is to eventually use a common class for object and control point transforms.
+ * - SelectableControlPoint: base for any type of selectable point. It can belong to only one
+ * selection.
+ *
+ * @par Functionality that resides in weird places
+ * @par
+ *
+ * This list is probably incomplete.
+ * - Curve dragging: CurveDragPoint, controlled by PathManipulator
+ * - Single handle shortcuts: MultiPathManipulator::event(), ModifierTracker
+ * - Linear and spatial grow: Node, spatial grow routed to ControlPointSelection
+ * - Committing handle actions performed with the mouse: PathManipulator
+ * - Sculpting: ControlPointSelection
+ *
+ * @par Plans for the future
+ * @par
+ * - MultiPathManipulator should become a generic shape editor that manages all active manipulator,
+ * more or less like the old ShapeEditor.
+ * - Knotholder should be rewritten into one manipulator class per shape, using the control point
+ * classes. Interesting features like dragging rectangle sides could be added along the way.
+ * - Better handling of clip and mask editing, particularly in response to undo.
+ * - High level refactoring of the event context hierarchy. All aspects of tools, like toolbox
+ * controls, icons, event handling should be collected in one class, though each aspect
+ * of a tool might be in an separate class for better modularity. The long term goal is to allow
+ * tools to be defined in extensions or shared library plugins.
+ */
+
+using Inkscape::ControlManager;
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+namespace {
+ ToolBase* createNodesContext() {
+ return new NodeTool();
+ }
+
+ bool nodesContextRegistered = ToolFactory::instance().registerObject("/tools/nodes", createNodesContext);
+}
+
+const std::string& NodeTool::getPrefsPath() {
+ return NodeTool::prefsPath;
+}
+
+const std::string NodeTool::prefsPath = "/tools/nodes";
+
+SPCanvasGroup *create_control_group(SPDesktop *d);
+
+NodeTool::NodeTool() : ToolBase() {
+ this->show_handles = false;
+ this->single_node_transform_handles = false;
+ this->show_transform_handles = false;
+ this->cursor_drag = false;
+ this->live_objects = false;
+ this->edit_clipping_paths = false;
+ this->live_outline = false;
+ this->flashed_item = 0;
+ this->_transform_handle_group = 0;
+ this->show_path_direction = false;
+ this->_last_over = 0;
+ this->edit_masks = false;
+ this->show_outline = false;
+ this->flash_tempitem = 0;
+
+ this->cursor_shape = cursor_node_xpm;
+ this->hot_x = 1;
+ this->hot_y = 1;
+
+ this->_selected_nodes = 0;
+ this->_multipath = 0;
+ this->_selector = 0;
+ this->_path_data = 0;
+}
+
+SPCanvasGroup *create_control_group(SPDesktop *d)
+{
+ return reinterpret_cast<SPCanvasGroup*>(sp_canvas_item_new(
+ sp_desktop_controls(d), SP_TYPE_CANVAS_GROUP, NULL));
+}
+
+void destroy_group(SPCanvasGroup *g)
+{
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(g));
+}
+
+NodeTool::~NodeTool() {
+ this->enableGrDrag(false);
+
+ if (this->flash_tempitem) {
+ this->desktop->remove_temporary_canvasitem(this->flash_tempitem);
+ }
+
+ this->_selection_changed_connection.disconnect();
+ //this->_selection_modified_connection.disconnect();
+ this->_mouseover_changed_connection.disconnect();
+ this->_sizeUpdatedConn.disconnect();
+
+ delete this->_multipath;
+ delete this->_selected_nodes;
+ delete this->_selector;
+
+ Inkscape::UI::PathSharedData &data = *this->_path_data;
+ destroy_group(data.node_data.node_group);
+ destroy_group(data.node_data.handle_group);
+ destroy_group(data.node_data.handle_line_group);
+ destroy_group(data.outline_group);
+ destroy_group(data.dragpoint_group);
+ destroy_group(this->_transform_handle_group);
+}
+
+void NodeTool::setup() {
+ ToolBase::setup();
+
+ this->_path_data = new Inkscape::UI::PathSharedData();
+
+ Inkscape::UI::PathSharedData &data = *this->_path_data;
+ data.node_data.desktop = this->desktop;
+
+ // selector has to be created here, so that its hidden control point is on the bottom
+ this->_selector = new Inkscape::UI::Selector(this->desktop);
+
+ // Prepare canvas groups for controls. This guarantees correct z-order, so that
+ // for example a dragpoint won't obscure a node
+ data.outline_group = create_control_group(this->desktop);
+ data.node_data.handle_line_group = create_control_group(this->desktop);
+ data.dragpoint_group = create_control_group(this->desktop);
+ this->_transform_handle_group = create_control_group(this->desktop);
+ data.node_data.node_group = create_control_group(this->desktop);
+ data.node_data.handle_group = create_control_group(this->desktop);
+
+ Inkscape::Selection *selection = sp_desktop_selection (this->desktop);
+
+ this->_selection_changed_connection.disconnect();
+ this->_selection_changed_connection =
+ selection->connectChanged(sigc::mem_fun(this, &NodeTool::selection_changed));
+
+ this->_mouseover_changed_connection.disconnect();
+ this->_mouseover_changed_connection =
+ Inkscape::UI::ControlPoint::signal_mouseover_change.connect(sigc::mem_fun(this, &NodeTool::mouseover_changed));
+
+ this->_sizeUpdatedConn = ControlManager::getManager().connectCtrlSizeChanged(
+ sigc::mem_fun(this, &NodeTool::handleControlUiStyleChange)
+ );
+
+ this->_selected_nodes = new Inkscape::UI::ControlPointSelection(this->desktop, this->_transform_handle_group);
+
+ data.node_data.selection = this->_selected_nodes;
+
+ this->_multipath = new Inkscape::UI::MultiPathManipulator(data, this->_selection_changed_connection);
+
+ this->_selector->signal_point.connect(sigc::mem_fun(this, &NodeTool::select_point));
+ this->_selector->signal_area.connect(sigc::mem_fun(this, &NodeTool::select_area));
+
+ this->_multipath->signal_coords_changed.connect(
+ sigc::bind(
+ sigc::mem_fun(*this->desktop, &SPDesktop::emitToolSubselectionChanged),
+ (void*)NULL
+ )
+ );
+
+ this->_selected_nodes->signal_point_changed.connect(
+ // Hide both signal parameters and bind the function parameter to 0
+ // sigc::signal<void, SelectableControlPoint *, bool>
+ // <=>
+ // void update_tip(GdkEvent *event)
+ sigc::hide(sigc::hide(sigc::bind(
+ sigc::mem_fun(this, &NodeTool::update_tip),
+ (GdkEvent*)NULL
+ )))
+ );
+
+ this->cursor_drag = false;
+ this->show_transform_handles = true;
+ this->single_node_transform_handles = false;
+ this->flash_tempitem = NULL;
+ this->flashed_item = NULL;
+ this->_last_over = NULL;
+
+ // read prefs before adding items to selection to prevent momentarily showing the outline
+ sp_event_context_read(this, "show_handles");
+ sp_event_context_read(this, "show_outline");
+ sp_event_context_read(this, "live_outline");
+ sp_event_context_read(this, "live_objects");
+ sp_event_context_read(this, "show_path_direction");
+ sp_event_context_read(this, "show_transform_handles");
+ sp_event_context_read(this, "single_node_transform_handles");
+ sp_event_context_read(this, "edit_clipping_paths");
+ sp_event_context_read(this, "edit_masks");
+
+ this->selection_changed(selection);
+ this->update_tip(NULL);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (prefs->getBool("/tools/nodes/selcue")) {
+ this->enableSelectionCue();
+ }
+
+ if (prefs->getBool("/tools/nodes/gradientdrag")) {
+ this->enableGrDrag();
+ }
+
+ this->desktop->emitToolSubselectionChanged(NULL); // sets the coord entry fields to inactive
+}
+
+void NodeTool::set(const Inkscape::Preferences::Entry& value) {
+ Glib::ustring entry_name = value.getEntryName();
+
+ if (entry_name == "show_handles") {
+ this->show_handles = value.getBool(true);
+ this->_multipath->showHandles(this->show_handles);
+ } else if (entry_name == "show_outline") {
+ this->show_outline = value.getBool();
+ this->_multipath->showOutline(this->show_outline);
+ } else if (entry_name == "live_outline") {
+ this->live_outline = value.getBool();
+ this->_multipath->setLiveOutline(this->live_outline);
+ } else if (entry_name == "live_objects") {
+ this->live_objects = value.getBool();
+ this->_multipath->setLiveObjects(this->live_objects);
+ } else if (entry_name == "show_path_direction") {
+ this->show_path_direction = value.getBool();
+ this->_multipath->showPathDirection(this->show_path_direction);
+ } else if (entry_name == "show_transform_handles") {
+ this->show_transform_handles = value.getBool(true);
+ this->_selected_nodes->showTransformHandles(
+ this->show_transform_handles, this->single_node_transform_handles);
+ } else if (entry_name == "single_node_transform_handles") {
+ this->single_node_transform_handles = value.getBool();
+ this->_selected_nodes->showTransformHandles(
+ this->show_transform_handles, this->single_node_transform_handles);
+ } else if (entry_name == "edit_clipping_paths") {
+ this->edit_clipping_paths = value.getBool();
+ this->selection_changed(this->desktop->selection);
+ } else if (entry_name == "edit_masks") {
+ this->edit_masks = value.getBool();
+ this->selection_changed(this->desktop->selection);
+ } else {
+ ToolBase::set(value);
+ }
+}
+
+/** Recursively collect ShapeRecords */
+void gather_items(NodeTool *nt, SPItem *base, SPObject *obj, Inkscape::UI::ShapeRole role,
+ std::set<Inkscape::UI::ShapeRecord> &s)
+{
+ using namespace Inkscape::UI;
+
+ if (!obj) {
+ return;
+ }
+
+ //XML Tree being used directly here while it shouldn't be.
+ if (SP_IS_PATH(obj) && obj->getRepr()->attribute("inkscape:original-d") != NULL) {
+ ShapeRecord r;
+ r.item = static_cast<SPItem*>(obj);
+ r.edit_transform = Geom::identity(); // TODO wrong?
+ r.role = role;
+ s.insert(r);
+ } else if (role != SHAPE_ROLE_NORMAL && (SP_IS_GROUP(obj) || SP_IS_OBJECTGROUP(obj))) {
+ for (SPObject *c = obj->children; c; c = c->next) {
+ gather_items(nt, base, c, role, s);
+ }
+ } else if (SP_IS_ITEM(obj)) {
+ SPItem *item = static_cast<SPItem*>(obj);
+ ShapeRecord r;
+ r.item = item;
+ // TODO add support for objectBoundingBox
+ r.edit_transform = base ? base->i2doc_affine() : Geom::identity();
+ r.role = role;
+
+ if (s.insert(r).second) {
+ // this item was encountered the first time
+ if (nt->edit_clipping_paths && item->clip_ref) {
+ gather_items(nt, item, item->clip_ref->getObject(), SHAPE_ROLE_CLIPPING_PATH, s);
+ }
+
+ if (nt->edit_masks && item->mask_ref) {
+ gather_items(nt, item, item->mask_ref->getObject(), SHAPE_ROLE_MASK, s);
+ }
+ }
+ }
+}
+
+void NodeTool::selection_changed(Inkscape::Selection *sel) {
+ using namespace Inkscape::UI;
+
+ std::set<ShapeRecord> shapes;
+
+ GSList const *ilist = sel->itemList();
+
+ for (GSList *i = const_cast<GSList*>(ilist); i; i = i->next) {
+ SPObject *obj = static_cast<SPObject*>(i->data);
+
+ if (SP_IS_ITEM(obj)) {
+ gather_items(this, NULL, static_cast<SPItem*>(obj), SHAPE_ROLE_NORMAL, shapes);
+ }
+ }
+
+ // use multiple ShapeEditors for now, to allow editing many shapes at once
+ // needs to be rethought
+ for (boost::ptr_map<SPItem*, ShapeEditor>::iterator i = this->_shape_editors.begin();
+ i != this->_shape_editors.end(); )
+ {
+ ShapeRecord s;
+ s.item = i->first;
+
+ if (shapes.find(s) == shapes.end()) {
+ this->_shape_editors.erase(i++);
+ } else {
+ ++i;
+ }
+ }
+
+ for (std::set<ShapeRecord>::iterator i = shapes.begin(); i != shapes.end(); ++i) {
+ ShapeRecord const &r = *i;
+
+ if ((SP_IS_SHAPE(r.item) || SP_IS_TEXT(r.item)) &&
+ this->_shape_editors.find(r.item) == this->_shape_editors.end())
+ {
+ ShapeEditor *si = new ShapeEditor(this->desktop);
+ si->set_item(r.item, SH_KNOTHOLDER);
+ this->_shape_editors.insert(const_cast<SPItem*&>(r.item), si);
+ }
+ }
+
+ this->_multipath->setItems(shapes);
+ this->update_tip(NULL);
+ this->desktop->updateNow();
+}
+
+bool NodeTool::root_handler(GdkEvent* event) {
+ /* things to handle here:
+ * 1. selection of items
+ * 2. passing events to manipulators
+ * 3. some keybindings
+ */
+ using namespace Inkscape::UI; // pull in event helpers
+
+ Inkscape::Selection *selection = desktop->selection;
+ static Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (this->_multipath->event(this, event)) {
+ return true;
+ }
+
+ if (this->_selector->event(this, event)) {
+ return true;
+ }
+
+ if (this->_selected_nodes->event(this, event)) {
+ return true;
+ }
+
+ switch (event->type)
+ {
+ case GDK_MOTION_NOTIFY: {
+ combine_motion_events(desktop->canvas, event->motion, 0);
+ SPItem *over_item = sp_event_context_find_item (desktop, event_point(event->button),
+ FALSE, TRUE);
+
+ if (over_item != this->_last_over) {
+ this->_last_over = over_item;
+ //ink_node_tool_update_tip(nt, event);
+ this->update_tip(event);
+ }
+
+ // create pathflash outline
+ if (prefs->getBool("/tools/nodes/pathflash_enabled")) {
+ if (over_item == this->flashed_item) {
+ break;
+ }
+
+ if (!prefs->getBool("/tools/nodes/pathflash_selected") && selection->includes(over_item)) {
+ break;
+ }
+
+ if (this->flash_tempitem) {
+ desktop->remove_temporary_canvasitem(this->flash_tempitem);
+ this->flash_tempitem = NULL;
+ this->flashed_item = NULL;
+ }
+
+ if (!SP_IS_SHAPE(over_item)) {
+ break; // for now, handle only shapes
+ }
+
+ this->flashed_item = over_item;
+ SPCurve *c = SP_SHAPE(over_item)->getCurveBeforeLPE();
+
+ if (!c) {
+ break; // break out when curve doesn't exist
+ }
+
+ c->transform(over_item->i2dt_affine());
+ SPCanvasItem *flash = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), c);
+
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(flash),
+ prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff), 1.0,
+ SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(flash), 0, SP_WIND_RULE_NONZERO);
+
+ this->flash_tempitem = desktop->add_temporary_canvasitem(flash,
+ prefs->getInt("/tools/nodes/pathflash_timeout", 500));
+
+ c->unref();
+ }
+ } break; // do not return true, because we need to pass this event to the parent context
+ // otherwise some features cease to work
+
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval(&event->key))
+ {
+ case GDK_KEY_Escape: // deselect everything
+ if (this->_selected_nodes->empty()) {
+ Inkscape::SelectionHelper::selectNone(desktop);
+ } else {
+ this->_selected_nodes->clear();
+ }
+ //ink_node_tool_update_tip(nt, event);
+ this->update_tip(event);
+ return TRUE;
+
+ case GDK_KEY_a:
+ case GDK_KEY_A:
+ if (held_control(event->key) && held_alt(event->key)) {
+ this->_selected_nodes->selectAll();
+ // Ctrl+A is handled in selection-chemistry.cpp via verb
+ //ink_node_tool_update_tip(nt, event);
+ this->update_tip(event);
+ return TRUE;
+ }
+ break;
+
+ case GDK_KEY_h:
+ case GDK_KEY_H:
+ if (held_only_control(event->key)) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/tools/nodes/show_handles", !this->show_handles);
+ return TRUE;
+ }
+ break;
+
+ default:
+ break;
+ }
+ //ink_node_tool_update_tip(nt, event);
+ this->update_tip(event);
+ break;
+
+ case GDK_KEY_RELEASE:
+ //ink_node_tool_update_tip(nt, event);
+ this->update_tip(event);
+ break;
+
+ default:
+ break;
+ }
+
+ return ToolBase::root_handler(event);
+}
+
+void NodeTool::update_tip(GdkEvent *event) {
+ using namespace Inkscape::UI;
+
+ if (event && (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE)) {
+ unsigned new_state = state_after_event(event);
+
+ if (new_state == event->key.state) {
+ return;
+ }
+
+ if (state_held_shift(new_state)) {
+ if (this->_last_over) {
+ this->message_context->set(Inkscape::NORMAL_MESSAGE,
+ C_("Node tool tip", "<b>Shift</b>: drag to add nodes to the selection, "
+ "click to toggle object selection"));
+ } else {
+ this->message_context->set(Inkscape::NORMAL_MESSAGE,
+ C_("Node tool tip", "<b>Shift</b>: drag to add nodes to the selection"));
+ }
+
+ return;
+ }
+ }
+
+ unsigned sz = this->_selected_nodes->size();
+ unsigned total = this->_selected_nodes->allPoints().size();
+
+ if (sz != 0) {
+ char *nodestring = g_strdup_printf(
+ ngettext("<b>%u of %u</b> node selected.", "<b>%u of %u</b> nodes selected.", total),
+ sz, total);
+
+ if (this->_last_over) {
+ // TRANSLATORS: The %s below is where the "%u of %u nodes selected" sentence gets put
+ char *dyntip = g_strdup_printf(C_("Node tool tip",
+ "%s Drag to select nodes, click to edit only this object (more: Shift)"),
+ nodestring);
+ this->message_context->set(Inkscape::NORMAL_MESSAGE, dyntip);
+ g_free(dyntip);
+ } else {
+ char *dyntip = g_strdup_printf(C_("Node tool tip",
+ "%s Drag to select nodes, click clear the selection"),
+ nodestring);
+ this->message_context->set(Inkscape::NORMAL_MESSAGE, dyntip);
+ g_free(dyntip);
+ }
+ g_free(nodestring);
+ } else if (!this->_multipath->empty()) {
+ if (this->_last_over) {
+ this->message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip",
+ "Drag to select nodes, click to edit only this object"));
+ } else {
+ this->message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip",
+ "Drag to select nodes, click to clear the selection"));
+ }
+ } else {
+ if (this->_last_over) {
+ this->message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip",
+ "Drag to select objects to edit, click to edit this object (more: Shift)"));
+ } else {
+ this->message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip",
+ "Drag to select objects to edit"));
+ }
+ }
+}
+
+void NodeTool::select_area(Geom::Rect const &sel, GdkEventButton *event) {
+ using namespace Inkscape::UI;
+
+ if (this->_multipath->empty()) {
+ // if multipath is empty, select rubberbanded items rather than nodes
+ Inkscape::Selection *selection = this->desktop->selection;
+ GSList *items = sp_desktop_document(this->desktop)->getItemsInBox(this->desktop->dkey, sel);
+ selection->setList(items);
+ g_slist_free(items);
+ } else {
+ if (!held_shift(*event)) {
+ this->_selected_nodes->clear();
+ }
+
+ this->_selected_nodes->selectArea(sel);
+ }
+}
+
+void NodeTool::select_point(Geom::Point const &sel, GdkEventButton *event) {
+ using namespace Inkscape::UI; // pull in event helpers
+
+ if (!event) {
+ return;
+ }
+
+ if (event->button != 1) {
+ return;
+ }
+
+ Inkscape::Selection *selection = this->desktop->selection;
+
+ SPItem *item_clicked = sp_event_context_find_item (this->desktop, event_point(*event),
+ (event->state & GDK_MOD1_MASK) && !(event->state & GDK_CONTROL_MASK), TRUE);
+
+ if (item_clicked == NULL) { // nothing under cursor
+ // if no Shift, deselect
+ // if there are nodes selected, the first click should deselect the nodes
+ // and the second should deselect the items
+ if (!state_held_shift(event->state)) {
+ if (this->_selected_nodes->empty()) {
+ selection->clear();
+ } else {
+ this->_selected_nodes->clear();
+ }
+ }
+ } else {
+ if (held_shift(*event)) {
+ selection->toggle(item_clicked);
+ } else {
+ selection->set(item_clicked);
+ }
+
+ this->desktop->updateNow();
+ }
+}
+
+void NodeTool::mouseover_changed(Inkscape::UI::ControlPoint *p) {
+ using Inkscape::UI::CurveDragPoint;
+
+ CurveDragPoint *cdp = dynamic_cast<CurveDragPoint*>(p);
+
+ if (cdp && !this->cursor_drag) {
+ this->cursor_shape = cursor_node_d_xpm;
+ this->hot_x = 1;
+ this->hot_y = 1;
+ this->sp_event_context_update_cursor();
+ this->cursor_drag = true;
+ } else if (!cdp && this->cursor_drag) {
+ this->cursor_shape = cursor_node_xpm;
+ this->hot_x = 1;
+ this->hot_y = 1;
+ this->sp_event_context_update_cursor();
+ this->cursor_drag = false;
+ }
+}
+
+void NodeTool::handleControlUiStyleChange() {
+ this->_multipath->updateHandles();
+}
+
+}
+}
+}
+
+//} // anonymous namespace
+
+/*
+ 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/tools/node-tool.h b/src/ui/tools/node-tool.h
new file mode 100644
index 000000000..4d15ab70e
--- /dev/null
+++ b/src/ui/tools/node-tool.h
@@ -0,0 +1,105 @@
+/** @file
+ * @brief New node tool with support for multiple path editing
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_NODE_TOOL_H
+#define SEEN_UI_TOOL_NODE_TOOL_H
+
+#include <boost/ptr_container/ptr_map.hpp>
+#include <glib.h>
+#include "ui/tools/tool-base.h"
+
+namespace Inkscape {
+ namespace Display {
+ class TemporaryItem;
+ }
+
+ namespace UI {
+ class MultiPathManipulator;
+ class ControlPointSelection;
+ class Selector;
+ class ControlPoint;
+
+ struct PathSharedData;
+ }
+}
+
+#define INK_NODE_TOOL(obj) (dynamic_cast<Inkscape::UI::Tools::NodeTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define INK_IS_NODE_TOOL(obj) (dynamic_cast<const Inkscape::UI::Tools::NodeTool*>((const Inkscape::UI::Tools::ToolBase*)obj))
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class NodeTool : public ToolBase {
+public:
+ NodeTool();
+ virtual ~NodeTool();
+
+ Inkscape::UI::ControlPointSelection* _selected_nodes;
+ Inkscape::UI::MultiPathManipulator* _multipath;
+
+ bool edit_clipping_paths;
+ bool edit_masks;
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual void set(const Inkscape::Preferences::Entry& val);
+ virtual bool root_handler(GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+private:
+ sigc::connection _selection_changed_connection;
+ sigc::connection _mouseover_changed_connection;
+ sigc::connection _sizeUpdatedConn;
+
+ SPItem *flashed_item;
+ Inkscape::Display::TemporaryItem *flash_tempitem;
+ Inkscape::UI::Selector* _selector;
+ Inkscape::UI::PathSharedData* _path_data;
+ SPCanvasGroup *_transform_handle_group;
+ SPItem *_last_over;
+ boost::ptr_map<SPItem*, ShapeEditor> _shape_editors;
+
+ bool cursor_drag;
+ bool show_handles;
+ bool show_outline;
+ bool live_outline;
+ bool live_objects;
+ bool show_path_direction;
+ bool show_transform_handles;
+ bool single_node_transform_handles;
+
+ void selection_changed(Inkscape::Selection *sel);
+
+ void select_area(Geom::Rect const &sel, GdkEventButton *event);
+ void select_point(Geom::Point const &sel, GdkEventButton *event);
+ void mouseover_changed(Inkscape::UI::ControlPoint *p);
+ void update_tip(GdkEvent *event);
+ void handleControlUiStyleChange();
+};
+
+}
+}
+}
+
+#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/tools/pen-tool.cpp b/src/ui/tools/pen-tool.cpp
new file mode 100644
index 000000000..32b1b47ef
--- /dev/null
+++ b/src/ui/tools/pen-tool.cpp
@@ -0,0 +1,2371 @@
+ /** \file
+ * Pen event context implementation.
+ */
+
+/*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2000 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2002 Lauris Kaplinski
+ * Copyright (C) 2004 Monash University
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <gdk/gdkkeysyms.h>
+#include <cstring>
+#include <string>
+
+
+#include "ui/tools/pen-tool.h"
+#include "sp-namedview.h"
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "selection.h"
+#include "selection-chemistry.h"
+#include "draw-anchor.h"
+#include "message-stack.h"
+#include "message-context.h"
+#include "preferences.h"
+#include "sp-path.h"
+#include "display/sp-canvas.h"
+#include "display/curve.h"
+#include "pixmaps/cursor-pen.xpm"
+#include "display/canvas-bpath.h"
+#include "display/sp-ctrlline.h"
+#include "display/sodipodi-ctrl.h"
+#include <glibmm/i18n.h>
+#include "macros.h"
+#include "context-fns.h"
+#include "tools-switch.h"
+#include "ui/control-manager.h"
+//BSpline
+//Incluimos los archivos necesarios para las BSpline y Spiro
+#define INKSCAPE_LPE_SPIRO_C
+#include "live_effects/lpe-spiro.h"
+
+
+#include <typeinfo>
+#include <2geom/pathvector.h>
+#include <2geom/affine.h>
+#include <2geom/bezier-curve.h>
+#include <2geom/hvlinesegment.h>
+#include "helper/geom-nodetype.h"
+#include "helper/geom-curves.h"
+
+// For handling un-continuous paths:
+#include "message-stack.h"
+#include "inkscape.h"
+#include "desktop.h"
+
+#include "live_effects/spiro.h"
+
+
+#define INKSCAPE_LPE_BSPLINE_C
+#include "live_effects/lpe-bspline.h"
+#include <2geom/nearest-point.h>
+//BSpline End
+
+#include "tool-factory.h"
+
+
+using Inkscape::ControlManager;
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+static void spdc_pen_set_initial_point(PenTool *pc, Geom::Point const p);
+/*
+ *BSpline
+ *Added functions
+*/
+//Añade los modos spiro y bspline
+static void sp_pen_context_set_mode(PenTool *const pc, guint mode);
+//Esta función cambia los colores rojo,verde y azul haciendolos transparentes o no en función de si se usa spiro
+static void bspline_spiro_color(PenTool *const pc);
+//Crea un nodo en modo bspline o spiro
+static void bspline_spiro(PenTool *const pc,bool shift);
+//Crea un nodo de modo spiro o bspline
+static void bspline_spiro_on(PenTool *const pc);
+//Crea un nodo de tipo CUSP
+static void bspline_spiro_off(PenTool *const pc);
+//Continua una curva existente en modo bspline o spiro
+static void bspline_spiro_start_anchor(PenTool *const pc,bool shift);
+//Continua una curva exsitente con el nodo de union en modo bspline o spiro
+static void bspline_spiro_start_anchor_on(PenTool *const pc);
+//Continua una curva existente con el nodo de union en modo CUSP
+static void bspline_spiro_start_anchor_off(PenTool *const pc);
+//Modifica la "red_curve" cuando se detecta movimiento
+static void bspline_spiro_motion(PenTool *const pc,bool shift);
+//Cierra la curva con el último nodo en modo bspline o spiro
+static void bspline_spiro_end_anchor_on(PenTool *const pc);
+//Cierra la curva con el último nodo en modo CUSP
+static void bspline_spiro_end_anchor_off(PenTool *const pc);
+//Unimos todas las curvas en juego y llamamos a la función doEffect.
+static void bspline_spiro_build(PenTool *const pc);
+//function bspline cloned from lpe-bspline.cpp
+static void bspline_doEffect(SPCurve * curve);
+//function spiro cloned from lpe-spiro.cpp
+static void spiro_doEffect(SPCurve * curve);
+//BSpline end
+static void spdc_pen_set_subsequent_point(PenTool *const pc, Geom::Point const p, bool statusbar, guint status = 0);
+static void spdc_pen_set_ctrl(PenTool *pc, Geom::Point const p, guint state);
+static void spdc_pen_finish_segment(PenTool *pc, Geom::Point p, guint state);
+
+static void spdc_pen_finish(PenTool *pc, gboolean closed);
+
+static gint pen_handle_button_press(PenTool *const pc, GdkEventButton const &bevent);
+static gint pen_handle_motion_notify(PenTool *const pc, GdkEventMotion const &mevent);
+static gint pen_handle_button_release(PenTool *const pc, GdkEventButton const &revent);
+static gint pen_handle_2button_press(PenTool *const pc, GdkEventButton const &bevent);
+static gint pen_handle_key_press(PenTool *const pc, GdkEvent *event);
+static void spdc_reset_colors(PenTool *pc);
+
+static void pen_disable_events(PenTool *const pc);
+static void pen_enable_events(PenTool *const pc);
+
+static Geom::Point pen_drag_origin_w(0, 0);
+static bool pen_within_tolerance = false;
+
+static int pen_next_paraxial_direction(const PenTool *const pc, Geom::Point const &pt, Geom::Point const &origin, guint state);
+static void pen_set_to_nearest_horiz_vert(const PenTool *const pc, Geom::Point &pt, guint const state, bool snap);
+
+static int pen_last_paraxial_dir = 0; // last used direction in horizontal/vertical mode; 0 = horizontal, 1 = vertical
+
+namespace {
+ ToolBase* createPenContext() {
+ return new PenTool();
+ }
+
+ bool penContextRegistered = ToolFactory::instance().registerObject("/tools/freehand/pen", createPenContext);
+}
+
+const std::string& PenTool::getPrefsPath() {
+ return PenTool::prefsPath;
+}
+
+const std::string PenTool::prefsPath = "/tools/freehand/pen";
+
+PenTool::PenTool() : FreehandBase() {
+ this->polylines_only = false;
+ this->polylines_paraxial = false;
+ this->expecting_clicks_for_LPE = 0;
+
+ this->cursor_shape = cursor_pen_xpm;
+ this->hot_x = 4;
+ this->hot_y = 4;
+
+ this->npoints = 0;
+ this->mode = MODE_CLICK;
+ this->state = POINT;
+
+ this->c0 = NULL;
+ this->c1 = NULL;
+ this->cl0 = NULL;
+ this->cl1 = NULL;
+
+ this->events_disabled = 0;
+
+ this->num_clicks = 0;
+ this->waiting_LPE = NULL;
+ this->waiting_item = NULL;
+}
+
+PenTool::~PenTool() {
+ if (this->c0) {
+ sp_canvas_item_destroy(this->c0);
+ this->c0 = NULL;
+ }
+ if (this->c1) {
+ sp_canvas_item_destroy(this->c1);
+ this->c1 = NULL;
+ }
+ if (this->cl0) {
+ sp_canvas_item_destroy(this->cl0);
+ this->cl0 = NULL;
+ }
+ if (this->cl1) {
+ sp_canvas_item_destroy(this->cl1);
+ this->cl1 = NULL;
+ }
+
+ if (this->expecting_clicks_for_LPE > 0) {
+ // we received too few clicks to sanely set the parameter path so we remove the LPE from the item
+ this->waiting_item->removeCurrentPathEffect(false);
+ }
+}
+
+void sp_pen_context_set_polyline_mode(PenTool *const pc) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ guint mode = prefs->getInt("/tools/freehand/pen/freehand-mode", 0);
+ pc->polylines_only = (mode == 3 || mode == 4);
+ pc->polylines_paraxial = (mode == 4);
+ //BSpline
+ //we call the function which defines the Spiro modes and the BSpline
+ //todo: merge to one function only
+ sp_pen_context_set_mode(pc, mode);
+ //BSpline End
+}
+
+//BSpline
+/*
+*.Set the mode of draw spiro, and bsplines
+*/
+void sp_pen_context_set_mode(PenTool *const pc, guint mode) {
+ pc->spiro = (mode == 1);
+ pc->bspline = (mode == 2);
+}
+//BSpline End
+
+/**
+ * Callback to initialize PenTool object.
+ */
+void PenTool::setup() {
+ FreehandBase::setup();
+
+ ControlManager &mgr = ControlManager::getManager();
+
+ // Pen indicators
+ this->c0 = mgr.createControl(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(this)), Inkscape::CTRL_TYPE_ADJ_HANDLE);
+ mgr.track(this->c0);
+
+ this->c1 = mgr.createControl(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(this)), Inkscape::CTRL_TYPE_ADJ_HANDLE);
+ mgr.track(this->c1);
+
+ this->cl0 = mgr.createControlLine(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(this)));
+ this->cl1 = mgr.createControlLine(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(this)));
+
+ sp_canvas_item_hide(this->c0);
+ sp_canvas_item_hide(this->c1);
+ sp_canvas_item_hide(this->cl0);
+ sp_canvas_item_hide(this->cl1);
+
+ sp_event_context_read(this, "mode");
+
+ this->anchor_statusbar = false;
+
+ sp_pen_context_set_polyline_mode(this);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/freehand/pen/selcue")) {
+ this->enableSelectionCue();
+ }
+}
+
+static void pen_cancel (PenTool *const pc)
+{
+ pc->num_clicks = 0;
+ pc->state = PenTool::STOP;
+ spdc_reset_colors(pc);
+ sp_canvas_item_hide(pc->c0);
+ sp_canvas_item_hide(pc->c1);
+ sp_canvas_item_hide(pc->cl0);
+ sp_canvas_item_hide(pc->cl1);
+ pc->message_context->clear();
+ pc->message_context->flash(Inkscape::NORMAL_MESSAGE, _("Drawing cancelled"));
+
+ pc->desktop->canvas->endForcedFullRedraws();
+}
+
+/**
+ * Finalization callback.
+ */
+void PenTool::finish() {
+ sp_event_context_discard_delayed_snap_event(this);
+
+ if (this->npoints != 0) {
+ pen_cancel(this);
+ }
+
+ FreehandBase::finish();
+}
+
+/**
+ * Callback that sets key to value in pen context.
+ */
+void PenTool::set(const Inkscape::Preferences::Entry& val) {
+ Glib::ustring name = val.getEntryName();
+
+ if (name == "mode") {
+ if ( val.getString() == "drag" ) {
+ this->mode = MODE_DRAG;
+ } else {
+ this->mode = MODE_CLICK;
+ }
+ }
+}
+
+/**
+ * Snaps new node relative to the previous node.
+ */
+static void spdc_endpoint_snap(PenTool const *const pc, Geom::Point &p, guint const state)
+{
+ if ((state & GDK_CONTROL_MASK) && !pc->polylines_paraxial) { //CTRL enables angular snapping
+ if (pc->npoints > 0) {
+ spdc_endpoint_snap_rotation(pc, p, pc->p[0], state);
+ }
+ } else {
+ // We cannot use shift here to disable snapping because the shift-key is already used
+ // to toggle the paraxial direction; if the user wants to disable snapping (s)he will
+ // have to use the %-key, the menu, or the snap toolbar
+ if ((pc->npoints > 0) && pc->polylines_paraxial) {
+ // snap constrained
+ pen_set_to_nearest_horiz_vert(pc, p, state, true);
+ } else {
+ // snap freely
+ boost::optional<Geom::Point> origin = pc->npoints > 0 ? pc->p[0] : boost::optional<Geom::Point>();
+ spdc_endpoint_snap_free(pc, p, origin, state); // pass the origin, to allow for perpendicular / tangential snapping
+ }
+ }
+}
+
+/**
+ * Snaps new node's handle relative to the new node.
+ */
+static void spdc_endpoint_snap_handle(PenTool const *const pc, Geom::Point &p, guint const state)
+{
+ g_return_if_fail(( pc->npoints == 2 ||
+ pc->npoints == 5 ));
+
+ if ((state & GDK_CONTROL_MASK)) { //CTRL enables angular snapping
+ spdc_endpoint_snap_rotation(pc, p, pc->p[pc->npoints - 2], state);
+ } else {
+ if (!(state & GDK_SHIFT_MASK)) { //SHIFT disables all snapping, except the angular snapping above
+ boost::optional<Geom::Point> origin = pc->p[pc->npoints - 2];
+ spdc_endpoint_snap_free(pc, p, origin, state);
+ }
+ }
+}
+
+bool PenTool::item_handler(SPItem* item, GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ ret = pen_handle_button_press(this, event->button);
+ break;
+ case GDK_BUTTON_RELEASE:
+ ret = pen_handle_button_release(this, event->button);
+ break;
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = FreehandBase::item_handler(item, event);
+ }
+
+ return ret;
+}
+
+/**
+ * Callback to handle all pen events.
+ */
+bool PenTool::root_handler(GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ ret = pen_handle_button_press(this, event->button);
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ ret = pen_handle_motion_notify(this, event->motion);
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ ret = pen_handle_button_release(this, event->button);
+ break;
+
+ case GDK_2BUTTON_PRESS:
+ ret = pen_handle_2button_press(this, event->button);
+ break;
+
+ case GDK_KEY_PRESS:
+ ret = pen_handle_key_press(this, event);
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = FreehandBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+/**
+ * Handle mouse button press event.
+ */
+static gint pen_handle_button_press(PenTool *const pc, GdkEventButton const &bevent)
+{
+ if (pc->events_disabled) {
+ // skip event processing if events are disabled
+ return FALSE;
+ }
+
+ FreehandBase * const dc = SP_DRAW_CONTEXT(pc);
+ SPDesktop * const desktop = SP_EVENT_CONTEXT_DESKTOP(dc);
+ Geom::Point const event_w(bevent.x, bevent.y);
+ Geom::Point event_dt(desktop->w2d(event_w));
+ ToolBase *event_context = SP_EVENT_CONTEXT(pc);
+ //Test whether we hit any anchor.
+ SPDrawAnchor * const anchor = spdc_test_inside(pc, event_w);
+ //with this we avoid creating a new point over the existing one
+ if(!bevent.button == 3 && (pc->spiro || pc->bspline) && pc->npoints > 0 && pc->p[0] == pc->p[3]){
+ return FALSE;
+ }
+ //BSpline end
+
+ gint ret = FALSE;
+ if (bevent.button == 1 && !event_context->space_panning
+ // make sure this is not the last click for a waiting LPE (otherwise we want to finish the path)
+ && (pc->expecting_clicks_for_LPE != 1)) {
+
+ if (Inkscape::have_viable_layer(desktop, dc->message_context) == false) {
+ return TRUE;
+ }
+
+ if (!pc->grab ) {
+ // Grab mouse, so release will not pass unnoticed
+ pc->grab = SP_CANVAS_ITEM(desktop->acetate);
+ sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK ),
+ NULL, bevent.time);
+ }
+
+ pen_drag_origin_w = event_w;
+ pen_within_tolerance = true;
+
+ switch (pc->mode) {
+ case PenTool::MODE_CLICK:
+ // In click mode we add point on release
+ switch (pc->state) {
+ case PenTool::POINT:
+ case PenTool::CONTROL:
+ case PenTool::CLOSE:
+ break;
+ case PenTool::STOP:
+ // This is allowed, if we just canceled curve
+ pc->state = PenTool::POINT;
+ break;
+ default:
+ break;
+ }
+ break;
+ case PenTool::MODE_DRAG:
+ switch (pc->state) {
+ case PenTool::STOP:
+ // This is allowed, if we just canceled curve
+ case PenTool::POINT:
+ if (pc->npoints == 0) {
+
+ Geom::Point p;
+ if ((bevent.state & GDK_CONTROL_MASK) && (pc->polylines_only || pc->polylines_paraxial)) {
+ p = event_dt;
+ if (!(bevent.state & GDK_SHIFT_MASK)) {
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+ m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ m.unSetup();
+ }
+ spdc_create_single_dot(event_context, p, "/tools/freehand/pen", bevent.state);
+ ret = TRUE;
+ break;
+ }
+
+ // TODO: Perhaps it would be nicer to rearrange the following case
+ // distinction so that the case of a waiting LPE is treated separately
+
+ // Set start anchor
+ pc->sa = anchor;
+ //BSpline
+ //Continuamos una curva existente
+ if(anchor){
+ bspline_spiro_start_anchor(pc,(bevent.state & GDK_SHIFT_MASK));
+ }
+ //BSpline End
+ if (anchor && !sp_pen_context_has_waiting_LPE(pc)) {
+ // Adjust point to anchor if needed; if we have a waiting LPE, we need
+ // a fresh path to be created so don't continue an existing one
+ p = anchor->dp;
+ desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path"));
+ } else {
+ // This is the first click of a new curve; deselect item so that
+ // this curve is not combined with it (unless it is drawn from its
+ // anchor, which is handled by the sibling branch above)
+ Inkscape::Selection * const selection = sp_desktop_selection(desktop);
+ if (!(bevent.state & GDK_SHIFT_MASK) || sp_pen_context_has_waiting_LPE(pc)) {
+ // if we have a waiting LPE, we need a fresh path to be created
+ // so don't append to an existing one
+ selection->clear();
+ desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path"));
+ } else if (selection->singleItem() && SP_IS_PATH(selection->singleItem())) {
+ desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Appending to selected path"));
+ }
+
+ // Create green anchor
+ p = event_dt;
+ spdc_endpoint_snap(pc, p, bevent.state);
+ pc->green_anchor = sp_draw_anchor_new(pc, pc->green_curve, TRUE, p);
+ }
+ spdc_pen_set_initial_point(pc, p);
+ } else {
+
+ // Set end anchor
+ pc->ea = anchor;
+ Geom::Point p;
+ if (anchor) {
+ p = anchor->dp;
+ // we hit an anchor, will finish the curve (either with or without closing)
+ // in release handler
+ pc->state = PenTool::CLOSE;
+
+ if (pc->green_anchor && pc->green_anchor->active) {
+ // we clicked on the current curve start, so close it even if
+ // we drag a handle away from it
+ dc->green_closed = TRUE;
+ }
+ ret = TRUE;
+ break;
+
+ } else {
+ p = event_dt;
+ spdc_endpoint_snap(pc, p, bevent.state); // Snap node only if not hitting anchor.
+ spdc_pen_set_subsequent_point(pc, p, true);
+ }
+ }
+
+ //BSpline
+ //Evitamos la creación de un punto de control para que se cree el nodo en el evento de soltar
+ pc->state = (pc->spiro || pc->bspline || pc->polylines_only) ? PenTool::POINT : PenTool::CONTROL;
+ //BSpline End
+
+ ret = TRUE;
+ break;
+ case PenTool::CONTROL:
+ g_warning("Button down in CONTROL state");
+ break;
+ case PenTool::CLOSE:
+ g_warning("Button down in CLOSE state");
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ } else if (pc->expecting_clicks_for_LPE == 1 && pc->npoints != 0) {
+ // when the last click for a waiting LPE occurs we want to finish the path
+ spdc_pen_finish_segment(pc, event_dt, bevent.state);
+ if (pc->green_closed) {
+ // finishing at the start anchor, close curve
+ spdc_pen_finish(pc, TRUE);
+ } else {
+ // finishing at some other anchor, finish curve but not close
+ spdc_pen_finish(pc, FALSE);
+ }
+
+ ret = TRUE;
+ } else if (bevent.button == 3 && pc->npoints != 0) {
+ // right click - finish path
+ spdc_pen_finish(pc, FALSE);
+ ret = TRUE;
+ }
+
+ if (pc->expecting_clicks_for_LPE > 0) {
+ --pc->expecting_clicks_for_LPE;
+ }
+ return ret;
+}
+
+/**
+ * Handle motion_notify event.
+ */
+static gint pen_handle_motion_notify(PenTool *const pc, GdkEventMotion const &mevent)
+{
+ gint ret = FALSE;
+
+ ToolBase *event_context = SP_EVENT_CONTEXT(pc);
+ SPDesktop * const dt = SP_EVENT_CONTEXT_DESKTOP(event_context);
+
+ if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) {
+ // allow scrolling
+ return FALSE;
+ }
+
+ if (pc->events_disabled) {
+ // skip motion events if pen events are disabled
+ return FALSE;
+ }
+
+ Geom::Point const event_w(mevent.x,
+ mevent.y);
+ //BSpline
+ //we take out the function the const "tolerance" because we need it later
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+ //"spiro_color" lo ejecutamos siempre sea o no spiro
+ if (pen_within_tolerance) {
+ if ( Geom::LInfty( event_w - pen_drag_origin_w ) < tolerance) {
+ return FALSE; // Do not drag if we're within tolerance from origin.
+ }
+ }
+ //BSpline END
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to move the object, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ pen_within_tolerance = false;
+
+ // Find desktop coordinates
+ Geom::Point p = dt->w2d(event_w);
+ // Test, whether we hit any anchor
+ SPDrawAnchor *anchor = spdc_test_inside(pc, event_w);
+ switch (pc->mode) {
+ case PenTool::MODE_CLICK:
+ switch (pc->state) {
+ case PenTool::POINT:
+ if ( pc->npoints != 0 ) {
+ // Only set point, if we are already appending
+ spdc_endpoint_snap(pc, p, mevent.state);
+ spdc_pen_set_subsequent_point(pc, p, true);
+ ret = TRUE;
+ } else if (!sp_event_context_knot_mouseover(pc)) {
+ SnapManager &m = dt->namedview->snap_manager;
+ m.setup(dt);
+ m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE));
+ m.unSetup();
+ }
+ break;
+ case PenTool::CONTROL:
+ case PenTool::CLOSE:
+ // Placing controls is last operation in CLOSE state
+ spdc_endpoint_snap(pc, p, mevent.state);
+ spdc_pen_set_ctrl(pc, p, mevent.state);
+ ret = TRUE;
+ break;
+ case PenTool::STOP:
+ // This is perfectly valid
+ break;
+ default:
+ break;
+ }
+ break;
+ case PenTool::MODE_DRAG:
+ switch (pc->state) {
+ case PenTool::POINT:
+ if ( pc->npoints > 0 ) {
+ // Only set point, if we are already appending
+
+ if (!anchor) { // Snap node only if not hitting anchor
+ spdc_endpoint_snap(pc, p, mevent.state);
+ spdc_pen_set_subsequent_point(pc, p, true, mevent.state);
+ } else {
+ spdc_pen_set_subsequent_point(pc, anchor->dp, false, mevent.state);
+ }
+
+ if (anchor && !pc->anchor_statusbar) {
+ if(!pc->spiro && !pc->bspline){
+ pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> or <b>click and drag</b> to close and finish the path."));
+ }else{
+ pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> or <b>click and drag</b> to close and finish the path. Shift to cusp node"));
+ }
+ pc->anchor_statusbar = true;
+ } else if (!anchor && pc->anchor_statusbar) {
+ pc->message_context->clear();
+ pc->anchor_statusbar = false;
+ }
+
+ ret = TRUE;
+ } else {
+ if (anchor && !pc->anchor_statusbar) {
+ if(!pc->spiro && !pc->bspline){
+ pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> or <b>click and drag</b> to continue the path from this point."));
+ }else{
+ pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> or <b>click and drag</b> to continue the path from this point. Shift to cusp node"));
+ }
+ pc->anchor_statusbar = true;
+ } else if (!anchor && pc->anchor_statusbar) {
+ pc->message_context->clear();
+ pc->anchor_statusbar = false;
+ }
+ if (!sp_event_context_knot_mouseover(pc)) {
+ SnapManager &m = dt->namedview->snap_manager;
+ m.setup(dt);
+ m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE));
+ m.unSetup();
+ }
+ }
+ break;
+ case PenTool::CONTROL:
+ case PenTool::CLOSE:
+ // Placing controls is last operation in CLOSE state
+ // snap the handle
+ spdc_endpoint_snap_handle(pc, p, mevent.state);
+ if (!pc->polylines_only) {
+ spdc_pen_set_ctrl(pc, p, mevent.state);
+ } else {
+ spdc_pen_set_ctrl(pc, pc->p[1], mevent.state);
+ }
+
+ gobble_motion_events(GDK_BUTTON1_MASK);
+ ret = TRUE;
+ break;
+ case PenTool::STOP:
+ // This is perfectly valid
+ break;
+ default:
+ if (!sp_event_context_knot_mouseover(pc)) {
+ SnapManager &m = dt->namedview->snap_manager;
+ m.setup(dt);
+ m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE));
+ m.unSetup();
+ }
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ //BSpline
+ //Lanzamos la función "bspline_spiro_motion" al moverse el ratón o cuando se para.
+ if(pc->bspline){
+ bspline_spiro_color(pc);
+ bspline_spiro_motion(pc,(mevent.state & GDK_SHIFT_MASK));
+ }else{
+ if ( Geom::LInfty( event_w - pen_drag_origin_w ) > tolerance || mevent.time == 0) {
+ bspline_spiro_color(pc);
+ bspline_spiro_motion(pc,(mevent.state & GDK_SHIFT_MASK));
+ pen_drag_origin_w = event_w;
+ }
+ }
+ //BSpline End
+ return ret;
+}
+
+/**
+ * Handle mouse button release event.
+ */
+static gint pen_handle_button_release(PenTool *const pc, GdkEventButton const &revent)
+{
+ if (pc->events_disabled) {
+ // skip event processing if events are disabled
+ return FALSE;
+ }
+
+ gint ret = FALSE;
+
+ ToolBase *event_context = SP_EVENT_CONTEXT(pc);
+ if ( revent.button == 1 && !event_context->space_panning) {
+
+ FreehandBase *dc = SP_DRAW_CONTEXT (pc);
+
+ Geom::Point const event_w(revent.x,
+ revent.y);
+ // Find desktop coordinates
+ Geom::Point p = pc->desktop->w2d(event_w);
+
+ // Test whether we hit any anchor.
+ SPDrawAnchor *anchor = spdc_test_inside(pc, event_w);
+ //BSpline
+ //with this we avoid creating a new point over the existing one
+ if(pc->spiro || pc->bspline){
+ //Si intentamos crear un nodo en el mismo sitio que el origen, paramos.
+ if(pc->npoints > 0 && pc->p[0] == pc->p[3]){
+ return FALSE;
+ }
+ }
+ //BSpline End
+ switch (pc->mode) {
+ case PenTool::MODE_CLICK:
+ switch (pc->state) {
+ case PenTool::POINT:
+ if ( pc->npoints == 0 ) {
+ // Start new thread only with button release
+ if (anchor) {
+ p = anchor->dp;
+ }
+ pc->sa = anchor;
+ //BSpline
+ //continuamos una curva existente
+ if (anchor) {
+ if(pc->bspline || pc->spiro){
+ bspline_spiro_start_anchor(pc,(revent.state & GDK_SHIFT_MASK));
+ }
+ }
+ //BSpline End
+ spdc_pen_set_initial_point(pc, p);
+ } else {
+ // Set end anchor here
+ pc->ea = anchor;
+ if (anchor) {
+ p = anchor->dp;
+ }
+ }
+ pc->state = PenTool::CONTROL;
+ ret = TRUE;
+ break;
+ case PenTool::CONTROL:
+ // End current segment
+ spdc_endpoint_snap(pc, p, revent.state);
+ spdc_pen_finish_segment(pc, p, revent.state);
+ pc->state = PenTool::POINT;
+ ret = TRUE;
+ break;
+ case PenTool::CLOSE:
+ // End current segment
+ if (!anchor) { // Snap node only if not hitting anchor
+ spdc_endpoint_snap(pc, p, revent.state);
+ }
+ spdc_pen_finish_segment(pc, p, revent.state);
+ //BSpline
+ //Ocultamos la guia del penultimo nodo al cerrar la curva
+ if(pc->spiro){
+ sp_canvas_item_hide(pc->c1);
+ }
+ //BSpline End
+ spdc_pen_finish(pc, TRUE);
+ pc->state = PenTool::POINT;
+ ret = TRUE;
+ break;
+ case PenTool::STOP:
+ // This is allowed, if we just canceled curve
+ pc->state = PenTool::POINT;
+ ret = TRUE;
+ break;
+ default:
+ break;
+ }
+ break;
+ case PenTool::MODE_DRAG:
+ switch (pc->state) {
+ case PenTool::POINT:
+ case PenTool::CONTROL:
+ spdc_endpoint_snap(pc, p, revent.state);
+ spdc_pen_finish_segment(pc, p, revent.state);
+ break;
+ case PenTool::CLOSE:
+ spdc_endpoint_snap(pc, p, revent.state);
+ spdc_pen_finish_segment(pc, p, revent.state);
+ //BSpline
+ //Ocultamos la guia del penultimo nodo al cerrar la curva
+ if(pc->spiro){
+ sp_canvas_item_hide(pc->c1);
+ }
+ //BSpline End
+ if (pc->green_closed) {
+ // finishing at the start anchor, close curve
+ spdc_pen_finish(pc, TRUE);
+ } else {
+ // finishing at some other anchor, finish curve but not close
+ spdc_pen_finish(pc, FALSE);
+ }
+ break;
+ case PenTool::STOP:
+ // This is allowed, if we just cancelled curve
+ break;
+ default:
+ break;
+ }
+ pc->state = PenTool::POINT;
+ ret = TRUE;
+ break;
+ default:
+ break;
+ }
+ if (pc->grab) {
+ // Release grab now
+ sp_canvas_item_ungrab(pc->grab, revent.time);
+ pc->grab = NULL;
+ }
+
+ ret = TRUE;
+
+ dc->green_closed = FALSE;
+ }
+
+ // TODO: can we be sure that the path was created correctly?
+ // TODO: should we offer an option to collect the clicks in a list?
+ if (pc->expecting_clicks_for_LPE == 0 && sp_pen_context_has_waiting_LPE(pc)) {
+ sp_pen_context_set_polyline_mode(pc);
+
+ ToolBase *ec = SP_EVENT_CONTEXT(pc);
+ Inkscape::Selection *selection = sp_desktop_selection (ec->desktop);
+
+ if (pc->waiting_LPE) {
+ // we have an already created LPE waiting for a path
+ pc->waiting_LPE->acceptParamPath(SP_PATH(selection->singleItem()));
+ selection->add(SP_OBJECT(pc->waiting_item));
+ pc->waiting_LPE = NULL;
+ } else {
+ // the case that we need to create a new LPE and apply it to the just-drawn path is
+ // handled in spdc_check_for_and_apply_waiting_LPE() in draw-context.cpp
+ }
+ }
+ return ret;
+}
+
+static gint pen_handle_2button_press(PenTool *const pc, GdkEventButton const &bevent)
+{
+ gint ret = FALSE;
+ // only end on LMB double click. Otherwise horizontal scrolling causes ending of the path
+ if (pc->npoints != 0 && bevent.button == 1) {
+ spdc_pen_finish(pc, FALSE);
+ ret = TRUE;
+ }
+ return ret;
+}
+
+static void pen_redraw_all (PenTool *const pc)
+{
+ // green
+ if (pc->green_bpaths) {
+ // remove old piecewise green canvasitems
+ while (pc->green_bpaths) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(pc->green_bpaths->data));
+ pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data);
+ }
+ // one canvas bpath for all of green_curve
+ SPCanvasItem *cshape = sp_canvas_bpath_new(sp_desktop_sketch(pc->desktop), pc->green_curve);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cshape), 0, SP_WIND_RULE_NONZERO);
+ pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape);
+ }
+ if (pc->green_anchor)
+ SP_CTRL(pc->green_anchor->ctrl)->moveto(pc->green_anchor->dp);
+ pc->red_curve->reset();
+ pc->red_curve->moveto(pc->p[0]);
+ pc->red_curve->curveto(pc->p[1], pc->p[2], pc->p[3]);
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve);
+
+ // handles
+ //BSpline
+ //Ocultamos los tiradores en modo BSpline y spiro
+ if (pc->p[0] != pc->p[1] && !pc->spiro && !pc->bspline) {
+ //BSpline End
+ SP_CTRL(pc->c1)->moveto(pc->p[1]);
+ pc->cl1->setCoords(pc->p[0], pc->p[1]);
+ sp_canvas_item_show(pc->c1);
+ sp_canvas_item_show(pc->cl1);
+ } else {
+ sp_canvas_item_hide(pc->c1);
+ sp_canvas_item_hide(pc->cl1);
+ }
+
+ Geom::Curve const * last_seg = pc->green_curve->last_segment();
+ if (last_seg) {
+ Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const *>( last_seg );
+ //BSpline
+ //Ocultamos los tiradores en modo BSpline y spiro
+ if ( cubic &&
+ (*cubic)[2] != pc->p[0] && !pc->spiro && !pc->bspline )
+ {
+ //BSpline End
+ Geom::Point p2 = (*cubic)[2];
+ SP_CTRL(pc->c0)->moveto(p2);
+ pc->cl0->setCoords(p2, pc->p[0]);
+ sp_canvas_item_show(pc->c0);
+ sp_canvas_item_show(pc->cl0);
+ } else {
+ sp_canvas_item_hide(pc->c0);
+ sp_canvas_item_hide(pc->cl0);
+ }
+ }
+ //BSpline
+ //Simplemente redibujamos la spiro teniendo en cuenta si el nodo es cusp o symm.
+ //como es un redibujo simplemente no llamamos a la función global
+ //sino al final de esta
+
+ //BSpline
+ //Lanzamos solamente el redibujado
+ bspline_spiro_build(pc);
+ //BSpline End
+}
+
+static void pen_lastpoint_move (PenTool *const pc, gdouble x, gdouble y)
+{
+ if (pc->npoints != 5)
+ return;
+
+ // green
+ if (!pc->green_curve->is_empty()) {
+ pc->green_curve->last_point_additive_move( Geom::Point(x,y) );
+ } else {
+ // start anchor too
+ if (pc->green_anchor) {
+ pc->green_anchor->dp += Geom::Point(x, y);
+ }
+ }
+
+ // red
+ pc->p[0] += Geom::Point(x, y);
+ pc->p[1] += Geom::Point(x, y);
+ pen_redraw_all(pc);
+}
+
+static void pen_lastpoint_move_screen (PenTool *const pc, gdouble x, gdouble y)
+{
+ pen_lastpoint_move (pc, x / pc->desktop->current_zoom(), y / pc->desktop->current_zoom());
+}
+
+static void pen_lastpoint_tocurve (PenTool *const pc)
+{
+ //BSpline
+ //Evitamos que si la "red_curve" tiene solo dos puntos -recta- no se pare aqui.
+ if (pc->npoints != 5 && !pc->spiro && !pc->bspline)
+ return;
+ //BSpline
+ Geom::CubicBezier const * cubic;
+ pc->p[1] = pc->red_curve->last_segment()->initialPoint() + (1./3)* (Geom::Point)(pc->red_curve->last_segment()->finalPoint() - pc->red_curve->last_segment()->initialPoint());
+ //Modificamos el último segmento de la curva verde para que forme el tipo de nodo que deseamos
+ if(pc->spiro||pc->bspline){
+ if(!pc->green_curve->is_empty()){
+ Geom::Point A(0,0);
+ Geom::Point B(0,0);
+ Geom::Point C(0,0);
+ Geom::Point D(0,0);
+ SPCurve * previous = new SPCurve();
+ cubic = dynamic_cast<Geom::CubicBezier const *>( pc->green_curve->last_segment() );
+ //We obtain the last segment 4 points in the previous curve
+ if ( cubic ){
+ A = (*cubic)[0];
+ B = (*cubic)[1];
+ if(pc->spiro){
+ C = pc->p[0] + (Geom::Point)(pc->p[0] - pc->p[1]);
+ }else
+ C = pc->green_curve->last_segment()->finalPoint() + (1./3)* (Geom::Point)(pc->green_curve->last_segment()->initialPoint() - pc->green_curve->last_segment()->finalPoint());
+ D = (*cubic)[3];
+ }else{
+ A = pc->green_curve->last_segment()->initialPoint();
+ B = pc->green_curve->last_segment()->initialPoint();
+ if(pc->spiro)
+ C = pc->p[0] + (Geom::Point)(pc->p[0] - pc->p[1]);
+ else
+ C = pc->green_curve->last_segment()->finalPoint() + (1./3)* (Geom::Point)(pc->green_curve->last_segment()->initialPoint() - pc->green_curve->last_segment()->finalPoint());
+ D = pc->green_curve->last_segment()->finalPoint();
+ }
+ previous->moveto(A);
+ previous->curveto(B, C, D);
+ if( pc->green_curve->get_segment_count() == 1){
+ pc->green_curve = previous;
+ }else{
+ //we eliminate the last segment
+ pc->green_curve->backspace();
+ //and we add it again with the recreation
+ pc->green_curve->append_continuous(previous, 0.0625);
+ }
+ }
+ //Si el último nodo es una union con otra curva
+ if(pc->green_curve->is_empty() && pc->sa && !pc->sa->curve->is_empty()){
+ bspline_spiro_start_anchor(pc, false);
+ }
+ }
+
+ //Spiro Live
+ pen_redraw_all(pc);
+}
+
+static void pen_lastpoint_toline (PenTool *const pc)
+{
+ //BSpline
+ //Evitamos que si la "red_curve" tiene solo dos puntos -recta- no se pare aqui.
+ if (pc->npoints != 5 && !pc->bspline)
+ return;
+
+ //Modificamos el último segmento de la curva verde para que forme el tipo de nodo que deseamos
+ if(pc->spiro || pc->bspline){
+ if(!pc->green_curve->is_empty()){
+ Geom::Point A(0,0);
+ Geom::Point B(0,0);
+ Geom::Point C(0,0);
+ Geom::Point D(0,0);
+ SPCurve * previous = new SPCurve();
+ Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const *>( pc->green_curve->last_segment() );
+ if ( cubic ) {
+ A = pc->green_curve->last_segment()->initialPoint();
+ B = (*cubic)[1];
+ C = pc->green_curve->last_segment()->finalPoint();
+ D = C;
+ } else {
+ //We obtain the last segment 4 points in the previous curve
+ A = pc->green_curve->last_segment()->initialPoint();
+ B = A;
+ C = pc->green_curve->last_segment()->finalPoint();
+ D = C;
+ }
+ previous->moveto(A);
+ previous->curveto(B, C, D);
+ if( pc->green_curve->get_segment_count() == 1){
+ pc->green_curve = previous;
+ }else{
+ //we eliminate the last segment
+ pc->green_curve->backspace();
+ //and we add it again with the recreation
+ pc->green_curve->append_continuous(previous, 0.0625);
+ }
+ }
+ //Si el último nodo es una union con otra curva
+ if(pc->green_curve->is_empty() && pc->sa && !pc->sa->curve->is_empty()){
+ bspline_spiro_start_anchor(pc, true);
+ }
+ }
+
+ pc->p[1] = pc->p[0];
+ pen_redraw_all(pc);
+}
+
+
+static gint pen_handle_key_press(PenTool *const pc, GdkEvent *event)
+{
+
+ gint ret = FALSE;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ gdouble const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px
+
+ switch (get_group0_keyval (&event->key)) {
+
+ case GDK_KEY_Left: // move last point left
+ case GDK_KEY_KP_Left:
+ if (!MOD__CTRL(event)) { // not ctrl
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) pen_lastpoint_move_screen(pc, -10, 0); // shift
+ else pen_lastpoint_move_screen(pc, -1, 0); // no shift
+ }
+ else { // no alt
+ if (MOD__SHIFT(event)) pen_lastpoint_move(pc, -10*nudge, 0); // shift
+ else pen_lastpoint_move(pc, -nudge, 0); // no shift
+ }
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Up: // move last point up
+ case GDK_KEY_KP_Up:
+ if (!MOD__CTRL(event)) { // not ctrl
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) pen_lastpoint_move_screen(pc, 0, 10); // shift
+ else pen_lastpoint_move_screen(pc, 0, 1); // no shift
+ }
+ else { // no alt
+ if (MOD__SHIFT(event)) pen_lastpoint_move(pc, 0, 10*nudge); // shift
+ else pen_lastpoint_move(pc, 0, nudge); // no shift
+ }
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Right: // move last point right
+ case GDK_KEY_KP_Right:
+ if (!MOD__CTRL(event)) { // not ctrl
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) pen_lastpoint_move_screen(pc, 10, 0); // shift
+ else pen_lastpoint_move_screen(pc, 1, 0); // no shift
+ }
+ else { // no alt
+ if (MOD__SHIFT(event)) pen_lastpoint_move(pc, 10*nudge, 0); // shift
+ else pen_lastpoint_move(pc, nudge, 0); // no shift
+ }
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Down: // move last point down
+ case GDK_KEY_KP_Down:
+ if (!MOD__CTRL(event)) { // not ctrl
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) pen_lastpoint_move_screen(pc, 0, -10); // shift
+ else pen_lastpoint_move_screen(pc, 0, -1); // no shift
+ }
+ else { // no alt
+ if (MOD__SHIFT(event)) pen_lastpoint_move(pc, 0, -10*nudge); // shift
+ else pen_lastpoint_move(pc, 0, -nudge); // no shift
+ }
+ ret = TRUE;
+ }
+ break;
+
+/*TODO: this is not yet enabled?? looks like some traces of the Geometry tool
+ case GDK_KEY_P:
+ case GDK_KEY_p:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::PARALLEL, 2);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_C:
+ case GDK_KEY_c:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::CIRCLE_3PTS, 3);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_B:
+ case GDK_KEY_b:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::PERP_BISECTOR, 2);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_A:
+ case GDK_KEY_a:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::ANGLE_BISECTOR, 3);
+ ret = TRUE;
+ }
+ break;
+*/
+
+ case GDK_KEY_U:
+ case GDK_KEY_u:
+ if (MOD__SHIFT_ONLY(event)) {
+ pen_lastpoint_tocurve(pc);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_L:
+ case GDK_KEY_l:
+ if (MOD__SHIFT_ONLY(event)) {
+ pen_lastpoint_toline(pc);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ if (pc->npoints != 0) {
+ spdc_pen_finish(pc, FALSE);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Escape:
+ if (pc->npoints != 0) {
+ // if drawing, cancel, otherwise pass it up for deselecting
+ pen_cancel (pc);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_z:
+ case GDK_KEY_Z:
+ if (MOD__CTRL_ONLY(event) && pc->npoints != 0) {
+ // if drawing, cancel, otherwise pass it up for undo
+ pen_cancel (pc);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_g:
+ case GDK_KEY_G:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_selection_to_guides(SP_EVENT_CONTEXT(pc)->desktop);
+ ret = true;
+ }
+ break;
+ case GDK_KEY_BackSpace:
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ if ( pc->green_curve->is_empty() || (pc->green_curve->last_segment() == NULL) ) {
+ if (!pc->red_curve->is_empty()) {
+ pen_cancel (pc);
+ ret = TRUE;
+ } else {
+ // do nothing; this event should be handled upstream
+ }
+ } else {
+ // Reset red curve
+ Geom::CubicBezier const * cubic = NULL;
+ pc->red_curve->reset();
+ // Destroy topmost green bpath
+ if (pc->green_bpaths) {
+ if (pc->green_bpaths->data)
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(pc->green_bpaths->data));
+ pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data);
+ }
+ // Get last segment
+ if ( pc->green_curve->is_empty() ) {
+ g_warning("pen_handle_key_press, case GDK_KP_Delete: Green curve is empty");
+ break;
+ }
+ // The code below assumes that pc->green_curve has only ONE path !
+ Geom::Curve const * crv = pc->green_curve->last_segment();
+ pc->p[0] = crv->initialPoint();
+ cubic = dynamic_cast<Geom::CubicBezier const *>(crv);
+ if ( cubic ) {
+ pc->p[1] = (*cubic)[1];
+ } else {
+ pc->p[1] = pc->p[0];
+ }
+ //Asignamos el valor a un tercio de distancia del último segmento.
+ if(pc->bspline){
+ pc->p[1] = pc->p[0] + (1./3)*(pc->p[3] - pc->p[0]);
+ }
+
+ Geom::Point const pt((pc->npoints < 4
+ ? (Geom::Point)(crv->finalPoint())
+ : pc->p[3]));
+
+ pc->npoints = 2;
+ //BSpline
+ //Eliminamos el último segmento de la curva verde
+ if( pc->green_curve->get_segment_count() == 1){
+ pc->npoints = 5;
+ if (pc->green_bpaths) {
+ if (pc->green_bpaths->data)
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(pc->green_bpaths->data));
+ pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data);
+ }
+ pc->green_curve->reset();
+ }else{
+ pc->green_curve->backspace();
+ }
+ //Asignamos el valor de pc->p[1] a el opuesto de el ultimo segmento de la línea verde
+ if(pc->spiro){
+ cubic = dynamic_cast<Geom::CubicBezier const *>(pc->green_curve->last_segment());
+ if ( cubic ) {
+ pc->p[1] = (*cubic)[3] + (Geom::Point)((*cubic)[3] - (*cubic)[2]);
+ SP_CTRL(pc->c1)->moveto(pc->p[0]);
+ } else {
+ pc->p[1] = pc->p[0];
+ }
+ }
+ //BSpline End
+ sp_canvas_item_hide(pc->cl0);
+ sp_canvas_item_hide(pc->cl1);
+ pc->state = PenTool::POINT;
+ spdc_pen_set_subsequent_point(pc, pt, true);
+ pen_last_paraxial_dir = !pen_last_paraxial_dir;
+ //BSpline
+ //Redibujamos
+ bspline_spiro_build(pc);
+ //BSpline End
+ ret = TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static void spdc_reset_colors(PenTool *pc)
+{
+ // Red
+ pc->red_curve->reset();
+
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL);
+ // Blue
+ pc->blue_curve->reset();
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->blue_bpath), NULL);
+
+ // Green
+ while (pc->green_bpaths) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(pc->green_bpaths->data));
+ pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data);
+ }
+ pc->green_curve->reset();
+ if (pc->green_anchor) {
+ pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor);
+ }
+ pc->sa = NULL;
+ pc->ea = NULL;
+ pc->npoints = 0;
+ pc->red_curve_is_valid = false;
+}
+
+
+static void spdc_pen_set_initial_point(PenTool *const pc, Geom::Point const p)
+{
+ g_assert( pc->npoints == 0 );
+
+ pc->p[0] = p;
+ pc->p[1] = p;
+ pc->npoints = 2;
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL);
+
+ pc->desktop->canvas->forceFullRedrawAfterInterruptions(5);
+}
+
+/**
+ * Show the status message for the current line/curve segment.
+ * This type of message always shows angle/distance as the last
+ * two parameters ("angle %3.2f&#176;, distance %s").
+ */
+static void spdc_pen_set_angle_distance_status_message(PenTool *const pc, Geom::Point const p, int pc_point_to_compare, gchar const *message)
+{
+ g_assert(pc != NULL);
+ g_assert((pc_point_to_compare == 0) || (pc_point_to_compare == 3)); // exclude control handles
+ g_assert(message != NULL);
+
+ SPDesktop *desktop = SP_EVENT_CONTEXT(pc)->desktop;
+ Geom::Point rel = p - pc->p[pc_point_to_compare];
+ Inkscape::Util::Quantity q = Inkscape::Util::Quantity(Geom::L2(rel), "px");
+ GString *dist = g_string_new(q.string(desktop->namedview->doc_units).c_str());
+ double angle = atan2(rel[Geom::Y], rel[Geom::X]) * 180 / M_PI;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/options/compassangledisplay/value", 0) != 0) {
+ angle = 90 - angle;
+ if (angle < 0) {
+ angle += 360;
+ }
+ }
+
+ pc->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, message, angle, dist->str);
+ g_string_free(dist, FALSE);
+}
+
+
+//Esta función cambia los colores rojo,verde y azul haciendolos transparentes o no en función de si se usa spiro
+static void bspline_spiro_color(PenTool *const pc)
+{
+ bool remake_green_bpaths = false;
+ if(pc->spiro){
+ //If the colour is not defined as trasparent, por example when changing
+ //from drawing to spiro mode or when selecting the pen tool
+ if(pc->green_color != 0x00ff000){
+ //We change the green and red colours to transparent, so this lines are not necessary
+ //to the drawing with spiro
+ pc->red_color = 0xff00000;
+ pc->green_color = 0x00ff000;
+ remake_green_bpaths = true;
+ }
+ }else{
+ //If we come from working with the spiro curve and change the mode the "green_curve" colour is transparent
+ if(pc->green_color != 0x00ff007f){
+ //since we are not im spiro mode, we assign the original colours
+ //to the red and the green curve, removing their transparency
+ pc->red_color = 0xff00007f;
+ pc->green_color = 0x00ff007f;
+ remake_green_bpaths = true;
+ }
+ //we hide the spiro/bspline rests
+ if(!pc->bspline){
+ sp_canvas_item_hide(pc->blue_bpath);
+ }
+ }
+ //We erase all the "green_bpaths" to recreate them after with the colour
+ //transparency recently modified
+ if (pc->green_bpaths && remake_green_bpaths) {
+ // remove old piecewise green canvasitems
+ while (pc->green_bpaths) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(pc->green_bpaths->data));
+ pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data);
+ }
+ // one canvas bpath for all of green_curve
+ SPCanvasItem *cshape = sp_canvas_bpath_new(sp_desktop_sketch(pc->desktop), pc->green_curve);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cshape), 0, SP_WIND_RULE_NONZERO);
+ pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape);
+ }
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(pc->red_bpath), pc->red_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+}
+
+
+static void bspline_spiro(PenTool *const pc, bool shift)
+{
+ if(!pc->spiro && !pc->bspline)
+ return;
+
+ if(!pc->anchor_statusbar)
+ shift?bspline_spiro_off(pc):bspline_spiro_on(pc);
+
+ bspline_spiro_build(pc);
+}
+
+static void bspline_spiro_on(PenTool *const pc)
+{
+ if(!pc->red_curve->is_empty()){
+ using Geom::X;
+ using Geom::Y;
+ pc->npoints = 5;
+ pc->p[0] = pc->red_curve->first_segment()->initialPoint();
+ pc->p[3] = pc->red_curve->first_segment()->finalPoint();
+ pc->p[2] = pc->p[3] + (1./3)*(pc->p[0] - pc->p[3]);
+ pc->p[2] = Geom::Point(pc->p[2][X] + 0.0001,pc->p[2][Y] + 0.0001);
+ }
+}
+
+static void bspline_spiro_off(PenTool *const pc)
+{
+ if(!pc->red_curve->is_empty()){
+ pc->npoints = 5;
+ pc->p[0] = pc->red_curve->first_segment()->initialPoint();
+ pc->p[3] = pc->red_curve->first_segment()->finalPoint();
+ pc->p[2] = pc->p[3];
+ }
+}
+
+static void bspline_spiro_start_anchor(PenTool *const pc, bool shift)
+{
+ if(!pc->spiro && !pc->bspline)
+ return;
+
+ if(pc->sa->curve->is_empty())
+ return;
+
+ if(shift)
+ bspline_spiro_start_anchor_off(pc);
+ else
+ bspline_spiro_start_anchor_on(pc);
+}
+
+static void bspline_spiro_start_anchor_on(PenTool *const pc)
+{
+ using Geom::X;
+ using Geom::Y;
+ SPCurve *tmpCurve = new SPCurve();
+ tmpCurve = pc->sa->curve->copy();
+ if(pc->sa->start)
+ tmpCurve = tmpCurve->create_reverse();
+ Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*tmpCurve->last_segment());
+ SPCurve *lastSeg = new SPCurve();
+ Geom::Point A = tmpCurve->last_segment()->initialPoint();
+ Geom::Point D = tmpCurve->last_segment()->finalPoint();
+ Geom::Point C = D + (1./3)*(A - D);
+ C = Geom::Point(C[X] + 0.0001,C[Y] + 0.0001);
+ if(cubic){
+ lastSeg->moveto(A);
+ lastSeg->curveto((*cubic)[1],C,D);
+ }else{
+ lastSeg->moveto(A);
+ lastSeg->curveto(A,C,D);
+ }
+ if( tmpCurve->get_segment_count() == 1){
+ tmpCurve = lastSeg;
+ }else{
+ //we eliminate the last segment
+ tmpCurve->backspace();
+ //and we add it again with the recreation
+ tmpCurve->append_continuous(lastSeg, 0.0625);
+ }
+ if (pc->sa->start) {
+ tmpCurve = tmpCurve->create_reverse();
+ }
+ pc->sa->curve->reset();
+ pc->sa->curve = tmpCurve;
+}
+
+static void bspline_spiro_start_anchor_off(PenTool *const pc)
+{
+ SPCurve *tmpCurve = new SPCurve();
+ tmpCurve = pc->sa->curve->copy();
+ if(pc->sa->start)
+ tmpCurve = tmpCurve->create_reverse();
+ Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*tmpCurve->last_segment());
+ if(cubic){
+ SPCurve *lastSeg = new SPCurve();
+ lastSeg->moveto((*cubic)[0]);
+ lastSeg->curveto((*cubic)[1],(*cubic)[3],(*cubic)[3]);
+ if( tmpCurve->get_segment_count() == 1){
+ tmpCurve = lastSeg;
+ }else{
+ //we eliminate the last segment
+ tmpCurve->backspace();
+ //and we add it again with the recreation
+ tmpCurve->append_continuous(lastSeg, 0.0625);
+ }
+ if (pc->sa->start) {
+ tmpCurve = tmpCurve->create_reverse();
+ }
+ pc->sa->curve->reset();
+ pc->sa->curve = tmpCurve;
+ }
+
+}
+
+static void bspline_spiro_motion(PenTool *const pc, bool shift){
+ if(!pc->spiro && !pc->bspline)
+ return;
+
+ using Geom::X;
+ using Geom::Y;
+ SPCurve *tmpCurve = new SPCurve();
+ pc->p[2] = pc->p[3] + (1./3)*(pc->p[0] - pc->p[3]);
+ if(pc->green_curve->is_empty() && !pc->sa){
+ pc->p[1] = pc->p[0] + (1./3)*(pc->p[3] - pc->p[0]);
+ }else if(!pc->green_curve->is_empty()){
+ tmpCurve = pc->green_curve->copy();
+ }else{
+ tmpCurve = pc->sa->curve->copy();
+ if(pc->sa->start)
+ tmpCurve = tmpCurve->create_reverse();
+
+ }
+ if(!tmpCurve->is_empty() && !pc->red_curve->is_empty()){
+ Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*tmpCurve->last_segment());
+ if(cubic){
+ if(pc->bspline){
+ SPCurve * WPower = new SPCurve();
+ Geom::D2< Geom::SBasis > SBasisWPower;
+ WPower->moveto(tmpCurve->last_segment()->finalPoint());
+ WPower->lineto(tmpCurve->last_segment()->initialPoint());
+ float WP = Geom::nearest_point((*cubic)[2],*WPower->first_segment());
+ WPower->reset();
+ WPower->moveto(pc->red_curve->last_segment()->initialPoint());
+ WPower->lineto(pc->red_curve->last_segment()->finalPoint());
+ SBasisWPower = WPower->first_segment()->toSBasis();
+ WPower->reset();
+ pc->p[1] = SBasisWPower.valueAt(WP);
+ if(!Geom::are_near(pc->p[1],pc->p[0]))
+ pc->p[1] = Geom::Point(pc->p[1][X] + 0.0001,pc->p[1][Y] + 0.0001);
+ if(shift)
+ pc->p[2]=pc->p[3];
+ else
+ pc->p[2] = pc->p[3] + (1./3)*(pc->p[0] - pc->p[3]);
+ }else{
+ pc->p[1] = (*cubic)[3] + (Geom::Point)((*cubic)[3] - (*cubic)[2] );
+ }
+ }else{
+ pc->p[1] = pc->p[0];
+ }
+ }
+
+ if(pc->anchor_statusbar && !pc->red_curve->is_empty()){
+ if(shift)
+ bspline_spiro_end_anchor_off(pc);
+ else
+ bspline_spiro_end_anchor_on(pc);
+ }
+
+ bspline_spiro_build(pc);
+}
+
+static void bspline_spiro_end_anchor_on(PenTool *const pc)
+{
+
+ using Geom::X;
+ using Geom::Y;
+ pc->p[2] = pc->p[3] + (1./3)*(pc->p[0] - pc->p[3]);
+ pc->p[2] = Geom::Point(pc->p[2][X] + 0.0001,pc->p[2][Y] + 0.0001);
+ SPCurve *tmpCurve = new SPCurve();
+ SPCurve *lastSeg = new SPCurve();
+ Geom::Point C(0,0);
+ if(!pc->sa || pc->sa->curve->is_empty()){
+ tmpCurve = pc->green_curve->create_reverse();
+ Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*tmpCurve->last_segment());
+ if(pc->bspline){
+ C = tmpCurve->last_segment()->finalPoint() + (1./3)*(tmpCurve->last_segment()->initialPoint() - tmpCurve->last_segment()->finalPoint());
+ C = Geom::Point(C[X] + 0.0001,C[Y] + 0.0001);
+ }else{
+ C = pc->p[3] + (Geom::Point)(pc->p[3] - pc->p[2] );
+ }
+ if(cubic){
+ lastSeg->moveto((*cubic)[0]);
+ lastSeg->curveto((*cubic)[1],C,(*cubic)[3]);
+ }else{
+ lastSeg->moveto(tmpCurve->last_segment()->initialPoint());
+ lastSeg->curveto(tmpCurve->last_segment()->initialPoint(),C,tmpCurve->last_segment()->finalPoint());
+ }
+ if( tmpCurve->get_segment_count() == 1){
+ tmpCurve = lastSeg;
+ }else{
+ //we eliminate the last segment
+ tmpCurve->backspace();
+ //and we add it again with the recreation
+ tmpCurve->append_continuous(lastSeg, 0.0625);
+ }
+ tmpCurve = tmpCurve->create_reverse();
+ pc->green_curve->reset();
+ pc->green_curve = tmpCurve;
+ }else{
+ tmpCurve = pc->sa->curve->copy();
+ if(!pc->sa->start)
+ tmpCurve = tmpCurve->create_reverse();
+ Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*tmpCurve->last_segment());
+ if(pc->bspline){
+ C = tmpCurve->last_segment()->finalPoint() + (1./3)*(tmpCurve->last_segment()->initialPoint() - tmpCurve->last_segment()->finalPoint());
+ C = Geom::Point(C[X] + 0.0001,C[Y] + 0.0001);
+ }else{
+ C = pc->p[3] + (Geom::Point)(pc->p[3] - pc->p[2] );
+ }
+ if(cubic){
+ lastSeg->moveto((*cubic)[0]);
+ lastSeg->curveto((*cubic)[1],C,(*cubic)[3]);
+ }else{
+ lastSeg->moveto(tmpCurve->last_segment()->initialPoint());
+ lastSeg->curveto(tmpCurve->last_segment()->initialPoint(),C,tmpCurve->last_segment()->finalPoint());
+ }
+ if( tmpCurve->get_segment_count() == 1){
+ tmpCurve = lastSeg;
+ }else{
+ //we eliminate the last segment
+ tmpCurve->backspace();
+ //and we add it again with the recreation
+ tmpCurve->append_continuous(lastSeg, 0.0625);
+ }
+ if (!pc->sa->start) {
+ tmpCurve = tmpCurve->create_reverse();
+ }
+ pc->sa->curve->reset();
+ pc->sa->curve = tmpCurve;
+ }
+}
+
+static void bspline_spiro_end_anchor_off(PenTool *const pc)
+{
+
+ pc->p[2] = pc->p[3];
+ SPCurve *tmpCurve = new SPCurve();
+ SPCurve *lastSeg = new SPCurve();
+ if(!pc->sa || pc->sa->curve->is_empty()){
+ tmpCurve = pc->green_curve->create_reverse();
+ Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*tmpCurve->last_segment());
+ if(cubic){
+ lastSeg->moveto((*cubic)[0]);
+ lastSeg->curveto((*cubic)[1],(*cubic)[3],(*cubic)[3]);
+ if( tmpCurve->get_segment_count() == 1){
+ tmpCurve = lastSeg;
+ }else{
+ //we eliminate the last segment
+ tmpCurve->backspace();
+ //and we add it again with the recreation
+ tmpCurve->append_continuous(lastSeg, 0.0625);
+ }
+ tmpCurve = tmpCurve->create_reverse();
+ pc->green_curve->reset();
+ pc->green_curve = tmpCurve;
+ }
+ }else{
+ tmpCurve = pc->sa->curve->copy();
+ if(!pc->sa->start)
+ tmpCurve = tmpCurve->create_reverse();
+ Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*tmpCurve->last_segment());
+ if(cubic){
+ lastSeg->moveto((*cubic)[0]);
+ lastSeg->curveto((*cubic)[1],(*cubic)[3],(*cubic)[3]);
+ if( tmpCurve->get_segment_count() == 1){
+ tmpCurve = lastSeg;
+ }else{
+ //we eliminate the last segment
+ tmpCurve->backspace();
+ //and we add it again with the recreation
+ tmpCurve->append_continuous(lastSeg, 0.0625);
+ }
+ if (!pc->sa->start) {
+ tmpCurve = tmpCurve->create_reverse();
+ }
+ pc->sa->curve->reset();
+ pc->sa->curve = tmpCurve;
+ }
+ }
+}
+
+
+//preparates the curves for its trasformation into BSline curves.
+static void bspline_spiro_build(PenTool *const pc)
+{
+ if(!pc->spiro && !pc->bspline)
+ return;
+
+ //We create the base curve
+ SPCurve *curve = new SPCurve();
+ //If we continuate the existing curve we add it at the start
+ if(pc->sa && !pc->sa->curve->is_empty()){
+ curve = pc->sa->curve->copy();
+ if (pc->sa->start) {
+ curve = curve->create_reverse();
+ }
+ }
+
+ if (!pc->green_curve->is_empty())
+ curve->append_continuous(pc->green_curve, 0.0625);
+
+ //and the red one
+ if (!pc->red_curve->is_empty()){
+ pc->red_curve->reset();
+ pc->red_curve->moveto(pc->p[0]);
+ pc->red_curve->curveto(pc->p[1],pc->p[2],pc->p[3]);
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve);
+ curve->append_continuous(pc->red_curve, 0.0625);
+ }
+
+ if(!curve->is_empty()){
+ //cerramos la curva si estan cerca los puntos finales de la curva spiro
+ if(Geom::are_near(curve->first_path()->initialPoint(), curve->last_path()->finalPoint())){
+ curve->closepath_current();
+ }
+ //TODO: CALL TO CLONED FUNCTION SPIRO::doEffect IN lpe-spiro.cpp
+ //For example
+ //using namespace Inkscape::LivePathEffect;
+ //LivePathEffectObject *lpeobj = static_cast<LivePathEffectObject*> (curve);
+ //Effect *spr = static_cast<Effect*> ( new LPEbspline(lpeobj) );
+ //spr->doEffect(curve);
+ if(pc->bspline){
+ bspline_doEffect(curve);
+ }else{
+ spiro_doEffect(curve);
+ }
+
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->blue_bpath), curve);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(pc->blue_bpath), pc->blue_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_item_show(pc->blue_bpath);
+ curve->unref();
+ pc->blue_curve->reset();
+ //We hide the holders that doesn't contribute anything
+ if(pc->spiro){
+ sp_canvas_item_show(pc->c1);
+ SP_CTRL(pc->c1)->moveto(pc->p[0]);
+ }else
+ sp_canvas_item_hide(pc->c1);
+ sp_canvas_item_hide(pc->cl1);
+ sp_canvas_item_hide(pc->c0);
+ sp_canvas_item_hide(pc->cl0);
+ }else{
+ //if the curve is empty
+ sp_canvas_item_hide(pc->blue_bpath);
+
+ }
+}
+
+static void bspline_doEffect(SPCurve * curve)
+{
+ if(curve->get_segment_count() < 2)
+ return;
+ // Make copy of old path as it is changed during processing
+ Geom::PathVector const original_pathv = curve->get_pathvector();
+ curve->reset();
+
+ //Recorremos todos los paths a los que queremos aplicar el efecto, hasta el penúltimo
+ for(Geom::PathVector::const_iterator path_it = original_pathv.begin(); path_it != original_pathv.end(); ++path_it) {
+ //Si está vacío...
+ if (path_it->empty())
+ continue;
+ //Itreadores
+
+ Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve
+ Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve
+ Geom::Path::const_iterator curve_endit = path_it->end_default(); // this determines when the loop has to stop
+ //Creamos las lineas rectas que unen todos los puntos del trazado y donde se calcularán
+ //los puntos clave para los manejadores.
+ //Esto hace que la curva BSpline no pierda su condición aunque se trasladen
+ //dichos manejadores
+ SPCurve *nCurve = new SPCurve();
+ Geom::Point previousNode(0,0);
+ Geom::Point node(0,0);
+ Geom::Point pointAt1(0,0);
+ Geom::Point pointAt2(0,0);
+ Geom::Point nextPointAt1(0,0);
+ Geom::Point nextPointAt2(0,0);
+ Geom::Point nextPointAt3(0,0);
+ Geom::D2< Geom::SBasis > SBasisIn;
+ Geom::D2< Geom::SBasis > SBasisOut;
+ Geom::D2< Geom::SBasis > SBasisHelper;
+ Geom::CubicBezier const *cubic = NULL;
+ if (path_it->closed()) {
+ // if the path is closed, maybe we have to stop a bit earlier because the closing line segment has zerolength.
+ const Geom::Curve &closingline = path_it->back_closed(); // the closing line segment is always of type Geom::LineSegment.
+ if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
+ // closingline.isDegenerate() did not work, because it only checks for *exact* zero length, which goes wrong for relative coordinates and rounding errors...
+ // the closing line segment has zero-length. So stop before that one!
+ curve_endit = path_it->end_open();
+ }
+ }
+ //Si la curva está cerrada calculamos el punto donde
+ //deveria estar el nodo BSpline de cierre/inicio de la curva
+ //en posible caso de que se cierre con una linea recta creando un nodo BSPline
+
+ //Recorremos todos los segmentos menos el último
+ while ( curve_it2 != curve_endit )
+ {
+ //previousPointAt3 = pointAt3;
+ //Calculamos los puntos que dividirían en tres segmentos iguales el path recto de entrada y de salida
+ SPCurve * in = new SPCurve();
+ in->moveto(curve_it1->initialPoint());
+ in->lineto(curve_it1->finalPoint());
+ cubic = dynamic_cast<Geom::CubicBezier const*>(&*curve_it1);
+ if(cubic){
+ SBasisIn = in->first_segment()->toSBasis();
+ pointAt1 = SBasisIn.valueAt(Geom::nearest_point((*cubic)[1],*in->first_segment()));
+ pointAt2 = SBasisIn.valueAt(Geom::nearest_point((*cubic)[2],*in->first_segment()));
+ }else{
+ pointAt1 = in->first_segment()->initialPoint();
+ pointAt2 = in->first_segment()->finalPoint();
+ }
+ in->reset();
+ delete in;
+ //Y hacemos lo propio con el path de salida
+ //nextPointAt0 = curveOut.valueAt(0);
+ SPCurve * out = new SPCurve();
+ out->moveto(curve_it2->initialPoint());
+ out->lineto(curve_it2->finalPoint());
+ cubic = dynamic_cast<Geom::CubicBezier const*>(&*curve_it2);
+ if(cubic){
+ SBasisOut = out->first_segment()->toSBasis();
+ nextPointAt1 = SBasisOut.valueAt(Geom::nearest_point((*cubic)[1],*out->first_segment()));
+ nextPointAt2 = SBasisOut.valueAt(Geom::nearest_point((*cubic)[2],*out->first_segment()));;
+ nextPointAt3 = (*cubic)[3];
+ }else{
+ nextPointAt1 = out->first_segment()->initialPoint();
+ nextPointAt2 = out->first_segment()->finalPoint();
+ nextPointAt3 = out->first_segment()->finalPoint();
+ }
+ out->reset();
+ delete out;
+ //La curva BSpline se forma calculando el centro del segmanto de unión
+ //de el punto situado en las 2/3 partes de el segmento de entrada
+ //con el punto situado en la posición 1/3 del segmento de salida
+ //Estos dos puntos ademas estan posicionados en el lugas correspondiente de
+ //los manejadores de la curva
+ SPCurve *lineHelper = new SPCurve();
+ lineHelper->moveto(pointAt2);
+ lineHelper->lineto(nextPointAt1);
+ SBasisHelper = lineHelper->first_segment()->toSBasis();
+ lineHelper->reset();
+ delete lineHelper;
+ //almacenamos el punto del anterior bucle -o el de cierre- que nos hara de principio de curva
+ previousNode = node;
+ //Y este hará de final de curva
+ node = SBasisHelper.valueAt(0.5);
+ SPCurve *curveHelper = new SPCurve();
+ curveHelper->moveto(previousNode);
+ curveHelper->curveto(pointAt1, pointAt2, node);
+ //añadimos la curva generada a la curva pricipal
+ nCurve->append_continuous(curveHelper, 0.0625);
+ curveHelper->reset();
+ delete curveHelper;
+ //aumentamos los valores para el siguiente paso en el bucle
+ ++curve_it1;
+ ++curve_it2;
+ }
+ //Aberiguamos la ultima parte de la curva correspondiente al último segmento
+ SPCurve *curveHelper = new SPCurve();
+ curveHelper->moveto(node);
+ //Si está cerrada la curva, la cerramos sobre el valor guardado previamente
+ //Si no finalizamos en el punto final
+ Geom::Point startNode(0,0);
+ if (path_it->closed()) {
+ SPCurve * start = new SPCurve();
+ start->moveto(path_it->begin()->initialPoint());
+ start->lineto(path_it->begin()->finalPoint());
+ Geom::D2< Geom::SBasis > SBasisStart = start->first_segment()->toSBasis();
+ SPCurve *lineHelper = new SPCurve();
+ cubic = dynamic_cast<Geom::CubicBezier const*>(&*path_it->begin());
+ if(cubic){
+ lineHelper->moveto(SBasisStart.valueAt(Geom::nearest_point((*cubic)[1],*start->first_segment())));
+ }else{
+ lineHelper->moveto(start->first_segment()->initialPoint());
+ }
+ start->reset();
+ delete start;
+
+ SPCurve * end = new SPCurve();
+ end->moveto(curve_it1->initialPoint());
+ end->lineto(curve_it1->finalPoint());
+ Geom::D2< Geom::SBasis > SBasisEnd = end->first_segment()->toSBasis();
+ //Geom::BezierCurve const *bezier = dynamic_cast<Geom::BezierCurve const*>(&*curve_endit);
+ cubic = dynamic_cast<Geom::CubicBezier const*>(&*curve_it1);
+ if(cubic){
+ lineHelper->lineto(SBasisEnd.valueAt(Geom::nearest_point((*cubic)[2],*end->first_segment())));
+ }else{
+ lineHelper->lineto(end->first_segment()->finalPoint());
+ }
+ end->reset();
+ delete end;
+ SBasisHelper = lineHelper->first_segment()->toSBasis();
+ lineHelper->reset();
+ delete lineHelper;
+ //Guardamos el principio de la curva
+ startNode = SBasisHelper.valueAt(0.5);
+ curveHelper->curveto(nextPointAt1, nextPointAt2, startNode);
+ nCurve->append_continuous(curveHelper, 0.0625);
+ nCurve->move_endpoints(startNode,startNode);
+ }else{
+ SPCurve * start = new SPCurve();
+ start->moveto(path_it->begin()->initialPoint());
+ start->lineto(path_it->begin()->finalPoint());
+ startNode = start->first_segment()->initialPoint();
+ start->reset();
+ delete start;
+ curveHelper->curveto(nextPointAt1, nextPointAt2, nextPointAt3);
+ nCurve->append_continuous(curveHelper, 0.0625);
+ nCurve->move_endpoints(startNode,nextPointAt3);
+ }
+ curveHelper->reset();
+ delete curveHelper;
+ //y cerramos la curva
+ if (path_it->closed()) {
+ nCurve->closepath_current();
+ }
+ curve->append(nCurve,false);
+ nCurve->reset();
+ delete nCurve;
+ }
+}
+
+//Spiro function cloned from lpe-spiro.cpp
+static void spiro_doEffect(SPCurve * curve)
+{
+ using Geom::X;
+ using Geom::Y;
+
+ // Make copy of old path as it is changed during processing
+ Geom::PathVector const original_pathv = curve->get_pathvector();
+ guint len = curve->get_segment_count() + 2;
+
+ curve->reset();
+ Spiro::spiro_cp *path = g_new (Spiro::spiro_cp, len);
+ int ip = 0;
+
+ for(Geom::PathVector::const_iterator path_it = original_pathv.begin(); path_it != original_pathv.end(); ++path_it) {
+ if (path_it->empty())
+ continue;
+
+ // start of path
+ {
+ Geom::Point p = path_it->front().pointAt(0);
+ path[ip].x = p[X];
+ path[ip].y = p[Y];
+ path[ip].ty = '{' ; // for closed paths, this is overwritten
+ ip++;
+ }
+
+ // midpoints
+ Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve
+ Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve
+
+ Geom::Path::const_iterator curve_endit = path_it->end_default(); // this determines when the loop has to stop
+ if (path_it->closed()) {
+ // if the path is closed, maybe we have to stop a bit earlier because the closing line segment has zerolength.
+ const Geom::Curve &closingline = path_it->back_closed(); // the closing line segment is always of type Geom::LineSegment.
+ if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
+ // closingline.isDegenerate() did not work, because it only checks for *exact* zero length, which goes wrong for relative coordinates and rounding errors...
+ // the closing line segment has zero-length. So stop before that one!
+ curve_endit = path_it->end_open();
+ }
+ }
+
+ while ( curve_it2 != curve_endit )
+ {
+ /* This deals with the node between curve_it1 and curve_it2.
+ * Loop to end_default (so without last segment), loop ends when curve_it2 hits the end
+ * and then curve_it1 points to end or closing segment */
+ Geom::Point p = curve_it1->finalPoint();
+ path[ip].x = p[X];
+ path[ip].y = p[Y];
+
+ // Determine type of spiro node this is, determined by the tangents (angles) of the curves
+ // TODO: see if this can be simplified by using /helpers/geom-nodetype.cpp:get_nodetype
+ bool this_is_line = is_straight_curve(*curve_it1);
+ bool next_is_line = is_straight_curve(*curve_it2);
+
+ Geom::NodeType nodetype = Geom::get_nodetype(*curve_it1, *curve_it2);
+
+ if ( nodetype == Geom::NODE_SMOOTH || nodetype == Geom::NODE_SYMM )
+ {
+ if (this_is_line && !next_is_line) {
+ path[ip].ty = ']';
+ } else if (next_is_line && !this_is_line) {
+ path[ip].ty = '[';
+ } else {
+ path[ip].ty = 'c';
+ }
+ } else {
+ path[ip].ty = 'v';
+ }
+
+ ++curve_it1;
+ ++curve_it2;
+ ip++;
+ }
+
+ // add last point to the spiropath
+ Geom::Point p = curve_it1->finalPoint();
+ path[ip].x = p[X];
+ path[ip].y = p[Y];
+ if (path_it->closed()) {
+ // curve_it1 points to the (visually) closing segment. determine the match between first and this last segment (the closing node)
+ Geom::NodeType nodetype = Geom::get_nodetype(*curve_it1, path_it->front());
+ switch (nodetype) {
+ case Geom::NODE_NONE: // can't happen! but if it does, it means the path isn't closed :-)
+ path[ip].ty = '}';
+ ip++;
+ break;
+ case Geom::NODE_CUSP:
+ path[0].ty = path[ip].ty = 'v';
+ break;
+ case Geom::NODE_SMOOTH:
+ case Geom::NODE_SYMM:
+ path[0].ty = path[ip].ty = 'c';
+ break;
+ }
+ } else {
+ // set type to path closer
+ path[ip].ty = '}';
+ ip++;
+ }
+
+ // run subpath through spiro
+ int sp_len = ip;
+ Spiro::spiro_run(path, sp_len, *curve);
+ ip = 0;
+ }
+
+ g_free (path);
+}
+
+//BSpline end
+
+static void spdc_pen_set_subsequent_point(PenTool *const pc, Geom::Point const p, bool statusbar, guint status)
+{
+ g_assert( pc->npoints != 0 );
+ // todo: Check callers to see whether 2 <= npoints is guaranteed.
+
+ pc->p[2] = p;
+ pc->p[3] = p;
+ pc->p[4] = p;
+ pc->npoints = 5;
+ pc->red_curve->reset();
+ bool is_curve;
+ pc->red_curve->moveto(pc->p[0]);
+ if (pc->polylines_paraxial && !statusbar) {
+ // we are drawing horizontal/vertical lines and hit an anchor;
+ Geom::Point const origin = pc->p[0];
+ // if the previous point and the anchor are not aligned either horizontally or vertically...
+ if ((abs(p[Geom::X] - origin[Geom::X]) > 1e-9) && (abs(p[Geom::Y] - origin[Geom::Y]) > 1e-9)) {
+ // ...then we should draw an L-shaped path, consisting of two paraxial segments
+ Geom::Point intermed = p;
+ pen_set_to_nearest_horiz_vert(pc, intermed, status, false);
+ pc->red_curve->lineto(intermed);
+ }
+ pc->red_curve->lineto(p);
+ is_curve = false;
+ } else {
+ // one of the 'regular' modes
+ //SpiroLive
+ if (pc->p[1] != pc->p[0] || pc->spiro) {
+ //SpiroLive End
+ pc->red_curve->curveto(pc->p[1], p, p);
+ is_curve = true;
+ } else {
+ pc->red_curve->lineto(p);
+ is_curve = false;
+ }
+ }
+
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve);
+
+ if (statusbar) {
+ gchar *message = is_curve ?
+ _("<b>Curve segment</b>: angle %3.2f&#176;, distance %s; with <b>Ctrl</b> to snap angle, <b>Enter</b> to finish the path" ):
+ _("<b>Line segment</b>: angle %3.2f&#176;, distance %s; with <b>Ctrl</b> to snap angle, <b>Enter</b> to finish the path");
+ //BSpline
+ if(pc->spiro || pc->bspline){
+ message = is_curve ?
+ _("<b>Curve segment</b>: angle %3.2f&#176;, distance %s; with <b>Shift</b> to cusp node, <b>Enter</b> to finish the path" ):
+ _("<b>Line segment</b>: angle %3.2f&#176;, distance %s; with <b>Shift</b> to cusp node, <b>Enter</b> to finish the path");
+ }
+ //BSpline End
+ spdc_pen_set_angle_distance_status_message(pc, p, 0, message);
+ }
+}
+
+static void spdc_pen_set_ctrl(PenTool *const pc, Geom::Point const p, guint const state)
+{
+ sp_canvas_item_show(pc->c1);
+ sp_canvas_item_show(pc->cl1);
+
+ if ( pc->npoints == 2 ) {
+ pc->p[1] = p;
+ sp_canvas_item_hide(pc->c0);
+ sp_canvas_item_hide(pc->cl0);
+ SP_CTRL(pc->c1)->moveto(pc->p[1]);
+ pc->cl1->setCoords(pc->p[0], pc->p[1]);
+ spdc_pen_set_angle_distance_status_message(pc, p, 0, _("<b>Curve handle</b>: angle %3.2f&#176;, length %s; with <b>Ctrl</b> to snap angle"));
+ } else if ( pc->npoints == 5 ) {
+ pc->p[4] = p;
+ sp_canvas_item_show(pc->c0);
+ sp_canvas_item_show(pc->cl0);
+ bool is_symm = false;
+ if ( ( ( pc->mode == PenTool::MODE_CLICK ) && ( state & GDK_CONTROL_MASK ) ) ||
+ ( ( pc->mode == PenTool::MODE_DRAG ) && !( state & GDK_SHIFT_MASK ) ) ) {
+ Geom::Point delta = p - pc->p[3];
+ pc->p[2] = pc->p[3] - delta;
+ is_symm = true;
+ pc->red_curve->reset();
+ pc->red_curve->moveto(pc->p[0]);
+ pc->red_curve->curveto(pc->p[1], pc->p[2], pc->p[3]);
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve);
+ }
+ SP_CTRL(pc->c0)->moveto(pc->p[2]);
+ pc->cl0 ->setCoords(pc->p[3], pc->p[2]);
+ SP_CTRL(pc->c1)->moveto(pc->p[4]);
+ pc->cl1->setCoords(pc->p[3], pc->p[4]);
+ gchar *message = is_symm ?
+ _("<b>Curve handle, symmetric</b>: angle %3.2f&#176;, length %s; with <b>Ctrl</b> to snap angle, with <b>Shift</b> to move this handle only") :
+ _("<b>Curve handle</b>: angle %3.2f&#176;, length %s; with <b>Ctrl</b> to snap angle, with <b>Shift</b> to move this handle only");
+ spdc_pen_set_angle_distance_status_message(pc, p, 3, message);
+ } else {
+ g_warning("Something bad happened - npoints is %d", pc->npoints);
+ }
+}
+
+static void spdc_pen_finish_segment(PenTool *const pc, Geom::Point const p, guint const state)
+{
+ if (pc->polylines_paraxial) {
+ pen_last_paraxial_dir = pen_next_paraxial_direction(pc, p, pc->p[0], state);
+ }
+
+ ++pc->num_clicks;
+
+ if (!pc->red_curve->is_empty()) {
+ bspline_spiro(pc,(state & GDK_SHIFT_MASK));
+ pc->green_curve->append_continuous(pc->red_curve, 0.0625);
+ SPCurve *curve = pc->red_curve->copy();
+ /// \todo fixme:
+ SPCanvasItem *cshape = sp_canvas_bpath_new(sp_desktop_sketch(pc->desktop), curve);
+ curve->unref();
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+
+ pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape);
+
+ pc->p[0] = pc->p[3];
+ pc->p[1] = pc->p[4];
+ pc->npoints = 2;
+
+ pc->red_curve->reset();
+ }
+}
+
+static void spdc_pen_finish(PenTool *const pc, gboolean const closed)
+{
+ if (pc->expecting_clicks_for_LPE > 1) {
+ // don't let the path be finished before we have collected the required number of mouse clicks
+ return;
+ }
+
+ pc->num_clicks = 0;
+ pen_disable_events(pc);
+
+ SPDesktop *const desktop = pc->desktop;
+ pc->message_context->clear();
+ desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Drawing finished"));
+
+ pc->red_curve->reset();
+ spdc_concat_colors_and_flush(pc, closed);
+ pc->sa = NULL;
+ pc->ea = NULL;
+
+ pc->npoints = 0;
+ pc->state = PenTool::POINT;
+
+ sp_canvas_item_hide(pc->c0);
+ sp_canvas_item_hide(pc->c1);
+ sp_canvas_item_hide(pc->cl0);
+ sp_canvas_item_hide(pc->cl1);
+
+ if (pc->green_anchor) {
+ pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor);
+ }
+
+
+ pc->desktop->canvas->endForcedFullRedraws();
+
+ pen_enable_events(pc);
+}
+
+static void pen_disable_events(PenTool *const pc) {
+ pc->events_disabled++;
+}
+
+static void pen_enable_events(PenTool *const pc) {
+ g_return_if_fail(pc->events_disabled != 0);
+
+ pc->events_disabled--;
+}
+
+void sp_pen_context_wait_for_LPE_mouse_clicks(PenTool *pc, Inkscape::LivePathEffect::EffectType effect_type,
+ unsigned int num_clicks, bool use_polylines)
+{
+ if (effect_type == Inkscape::LivePathEffect::INVALID_LPE)
+ return;
+
+ pc->waiting_LPE_type = effect_type;
+ pc->expecting_clicks_for_LPE = num_clicks;
+ pc->polylines_only = use_polylines;
+ pc->polylines_paraxial = false; // TODO: think if this is correct for all cases
+}
+
+void sp_pen_context_cancel_waiting_for_LPE(PenTool *pc)
+{
+ pc->waiting_LPE_type = Inkscape::LivePathEffect::INVALID_LPE;
+ pc->expecting_clicks_for_LPE = 0;
+ sp_pen_context_set_polyline_mode(pc);
+}
+
+static int pen_next_paraxial_direction(const PenTool *const pc,
+ Geom::Point const &pt, Geom::Point const &origin, guint state) {
+ //
+ // after the first mouse click we determine whether the mouse pointer is closest to a
+ // horizontal or vertical segment; for all subsequent mouse clicks, we use the direction
+ // orthogonal to the last one; pressing Shift toggles the direction
+ //
+ // num_clicks is not reliable because spdc_pen_finish_segment is sometimes called too early
+ // (on first mouse release), in which case num_clicks immediately becomes 1.
+ // if (pc->num_clicks == 0) {
+
+ if (pc->green_curve->is_empty()) {
+ // first mouse click
+ double dist_h = fabs(pt[Geom::X] - origin[Geom::X]);
+ double dist_v = fabs(pt[Geom::Y] - origin[Geom::Y]);
+ int ret = (dist_h < dist_v) ? 1 : 0; // 0 = horizontal, 1 = vertical
+ pen_last_paraxial_dir = (state & GDK_SHIFT_MASK) ? 1 - ret : ret;
+ return pen_last_paraxial_dir;
+ } else {
+ // subsequent mouse click
+ return (state & GDK_SHIFT_MASK) ? pen_last_paraxial_dir : 1 - pen_last_paraxial_dir;
+ }
+}
+
+void pen_set_to_nearest_horiz_vert(const PenTool *const pc, Geom::Point &pt, guint const state, bool snap)
+{
+ Geom::Point const origin = pc->p[0];
+
+ int next_dir = pen_next_paraxial_direction(pc, pt, origin, state);
+
+ if (!snap) {
+ if (next_dir == 0) {
+ // line is forced to be horizontal
+ pt[Geom::Y] = origin[Geom::Y];
+ } else {
+ // line is forced to be vertical
+ pt[Geom::X] = origin[Geom::X];
+ }
+ } else {
+ // Create a horizontal or vertical constraint line
+ Inkscape::Snapper::SnapConstraint cl(origin, next_dir ? Geom::Point(0, 1) : Geom::Point(1, 0));
+
+ // Snap along the constraint line; if we didn't snap then still the constraint will be applied
+ SnapManager &m = pc->desktop->namedview->snap_manager;
+
+ Inkscape::Selection *selection = sp_desktop_selection (pc->desktop);
+ // selection->singleItem() is the item that is currently being drawn. This item will not be snapped to (to avoid self-snapping)
+ // TODO: Allow snapping to the stationary parts of the item, and only ignore the last segment
+
+ m.setup(pc->desktop, true, selection->singleItem());
+ m.constrainedSnapReturnByRef(pt, Inkscape::SNAPSOURCE_NODE_HANDLE, cl);
+ m.unSetup();
+ }
+}
+
+}
+}
+}
+
+/*
+ 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/tools/pen-tool.h b/src/ui/tools/pen-tool.h
new file mode 100644
index 000000000..553e4c557
--- /dev/null
+++ b/src/ui/tools/pen-tool.h
@@ -0,0 +1,110 @@
+#ifndef SEEN_PEN_CONTEXT_H
+#define SEEN_PEN_CONTEXT_H
+
+/** \file
+ * PenTool: a context for pen tool events.
+ */
+
+#include "ui/tools/freehand-base.h"
+#include "live_effects/effect.h"
+
+#define SP_PEN_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::PenTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_PEN_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::PenTool*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+struct SPCtrlLine;
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+/**
+ * PenTool: a context for pen tool events.
+ */
+class PenTool : public FreehandBase {
+public:
+ PenTool();
+ virtual ~PenTool();
+
+ enum Mode {
+ MODE_CLICK,
+ MODE_DRAG
+ };
+
+ enum State {
+ POINT,
+ CONTROL,
+ CLOSE,
+ STOP
+ };
+
+ Geom::Point p[5];
+
+ /** \invar npoints in {0, 2, 5}. */
+ // npoints somehow determines the type of the node (what does it mean, exactly? the number of Bezier handles?)
+ gint npoints;
+
+ Mode mode;
+ State state;
+
+ bool polylines_only;
+ bool polylines_paraxial;
+ //SpiroLive
+ //Propiedad que guarda si el modo Spiro está activo o no
+ bool spiro;
+ bool bspline;
+ //SpiroLIve End
+ int num_clicks;
+
+ unsigned int expecting_clicks_for_LPE; // if positive, finish the path after this many clicks
+ Inkscape::LivePathEffect::Effect *waiting_LPE; // if NULL, waiting_LPE_type in SPDrawContext is taken into account
+ SPLPEItem *waiting_item;
+
+ SPCanvasItem *c0;
+ SPCanvasItem *c1;
+
+ SPCtrlLine *cl0;
+ SPCtrlLine *cl1;
+
+ unsigned int events_disabled : 1;
+
+ static const std::string prefsPath;
+
+ virtual const std::string& getPrefsPath();
+
+protected:
+ virtual void setup();
+ virtual void finish();
+ virtual void set(const Inkscape::Preferences::Entry& val);
+ virtual bool root_handler(GdkEvent* event);
+ virtual bool item_handler(SPItem* item, GdkEvent* event);
+};
+
+inline bool sp_pen_context_has_waiting_LPE(PenTool *pc) {
+ // note: waiting_LPE_type is defined in SPDrawContext
+ return (pc->waiting_LPE != NULL ||
+ pc->waiting_LPE_type != Inkscape::LivePathEffect::INVALID_LPE);
+}
+
+void sp_pen_context_set_polyline_mode(PenTool *const pc);
+void sp_pen_context_wait_for_LPE_mouse_clicks(PenTool *pc, Inkscape::LivePathEffect::EffectType effect_type,
+ unsigned int num_clicks, bool use_polylines = true);
+void sp_pen_context_cancel_waiting_for_LPE(PenTool *pc);
+void sp_pen_context_put_into_waiting_mode(SPDesktop *desktop, Inkscape::LivePathEffect::EffectType effect_type,
+ unsigned int num_clicks, bool use_polylines = true);
+
+}
+}
+}
+
+#endif /* !SEEN_PEN_CONTEXT_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/tools/pencil-tool.cpp b/src/ui/tools/pencil-tool.cpp
new file mode 100644
index 000000000..ff14d61ec
--- /dev/null
+++ b/src/ui/tools/pencil-tool.cpp
@@ -0,0 +1,952 @@
+/** \file
+ * Pencil event context implementation.
+ */
+
+/*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2000 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2002 Lauris Kaplinski
+ * Copyright (C) 2004 Monash University
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <gdk/gdkkeysyms.h>
+
+#include "ui/tools/pencil-tool.h"
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "selection.h"
+#include "selection-chemistry.h"
+#include "draw-anchor.h"
+#include "message-stack.h"
+#include "message-context.h"
+#include "modifier-fns.h"
+#include "sp-path.h"
+#include "preferences.h"
+#include "snap.h"
+#include "pixmaps/cursor-pencil.xpm"
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/bezier-utils.h>
+#include "display/canvas-bpath.h"
+#include <glibmm/i18n.h>
+#include "context-fns.h"
+#include "sp-namedview.h"
+#include "xml/repr.h"
+#include "document.h"
+#include "desktop-style.h"
+#include "macros.h"
+#include "display/sp-canvas.h"
+#include "display/curve.h"
+#include "livarot/Path.h"
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+static gint pencil_handle_button_press(PencilTool *const pc, GdkEventButton const &bevent);
+static gint pencil_handle_motion_notify(PencilTool *const pc, GdkEventMotion const &mevent);
+static gint pencil_handle_button_release(PencilTool *const pc, GdkEventButton const &revent);
+static gint pencil_handle_key_press(PencilTool *const pc, guint const keyval, guint const state);
+static gint pencil_handle_key_release(PencilTool *const pc, guint const keyval, guint const state);
+
+static void spdc_set_startpoint(PencilTool *pc, Geom::Point const &p);
+static void spdc_set_endpoint(PencilTool *pc, Geom::Point const &p);
+static void spdc_finish_endpoint(PencilTool *pc);
+static void spdc_add_freehand_point(PencilTool *pc, Geom::Point const &p, guint state);
+static void fit_and_split(PencilTool *pc);
+static void interpolate(PencilTool *pc);
+static void sketch_interpolate(PencilTool *pc);
+
+static Geom::Point pencil_drag_origin_w(0, 0);
+static bool pencil_within_tolerance = false;
+
+static bool in_svg_plane(Geom::Point const &p) { return Geom::LInfty(p) < 1e18; }
+
+namespace {
+ ToolBase* createPencilContext() {
+ return new PencilTool();
+ }
+
+ bool pencilContextRegistered = ToolFactory::instance().registerObject("/tools/freehand/pencil", createPencilContext);
+}
+
+const std::string& PencilTool::getPrefsPath() {
+ return PencilTool::prefsPath;
+}
+
+const std::string PencilTool::prefsPath = "/tools/freehand/pencil";
+
+PencilTool::PencilTool() :
+ FreehandBase(),
+ p(),
+ npoints(0),
+ state(SP_PENCIL_CONTEXT_IDLE),
+ req_tangent(0,0),
+ is_drawing(false),
+ ps(),
+ sketch_interpolation(Geom::Piecewise<Geom::D2<Geom::SBasis> >())// since PencilTool is not properly constructed...
+{
+ this->cursor_shape = cursor_pencil_xpm;
+ this->hot_x = 4;
+ this->hot_y = 4;
+ this->sketch_n = 0;
+}
+
+void PencilTool::setup() {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/freehand/pencil/selcue")) {
+ this->enableSelectionCue();
+ }
+
+ FreehandBase::setup();
+
+ this->is_drawing = false;
+ this->anchor_statusbar = false;
+}
+
+PencilTool::~PencilTool() {
+}
+
+/** Snaps new node relative to the previous node. */
+static void
+spdc_endpoint_snap(PencilTool const *pc, Geom::Point &p, guint const state)
+{
+ if ((state & GDK_CONTROL_MASK)) { //CTRL enables constrained snapping
+ if (pc->npoints > 0) {
+ spdc_endpoint_snap_rotation(pc, p, pc->p[0], state);
+ }
+ } else {
+ if (!(state & GDK_SHIFT_MASK)) { //SHIFT disables all snapping, except the angular snapping above
+ //After all, the user explicitely asked for angular snapping by
+ //pressing CTRL
+ boost::optional<Geom::Point> origin = pc->npoints > 0 ? pc->p[0] : boost::optional<Geom::Point>();
+ spdc_endpoint_snap_free(pc, p, origin, state);
+ }
+ }
+}
+
+/**
+ * Callback for handling all pencil context events.
+ */
+bool PencilTool::root_handler(GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ ret = pencil_handle_button_press(this, event->button);
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ ret = pencil_handle_motion_notify(this, event->motion);
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ ret = pencil_handle_button_release(this, event->button);
+ break;
+
+ case GDK_KEY_PRESS:
+ ret = pencil_handle_key_press(this, get_group0_keyval (&event->key), event->key.state);
+ break;
+
+ case GDK_KEY_RELEASE:
+ ret = pencil_handle_key_release(this, get_group0_keyval (&event->key), event->key.state);
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = FreehandBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+static gint
+pencil_handle_button_press(PencilTool *const pc, GdkEventButton const &bevent)
+{
+ gint ret = FALSE;
+ ToolBase *event_context = SP_EVENT_CONTEXT(pc);
+ if ( bevent.button == 1 && !event_context->space_panning) {
+
+ FreehandBase *dc = SP_DRAW_CONTEXT (pc);
+ SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(dc);
+ Inkscape::Selection *selection = sp_desktop_selection(desktop);
+
+ if (Inkscape::have_viable_layer(desktop, dc->message_context) == false) {
+ return TRUE;
+ }
+
+ if (!pc->grab) {
+ /* Grab mouse, so release will not pass unnoticed */
+ pc->grab = SP_CANVAS_ITEM(desktop->acetate);
+ sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK ),
+ NULL, bevent.time);
+ }
+
+ Geom::Point const button_w(bevent.x, bevent.y);
+
+ /* Find desktop coordinates */
+ Geom::Point p = pc->desktop->w2d(button_w);
+
+ /* Test whether we hit any anchor. */
+ SPDrawAnchor *anchor = spdc_test_inside(pc, button_w);
+
+ pencil_drag_origin_w = Geom::Point(bevent.x,bevent.y);
+ pencil_within_tolerance = true;
+
+ switch (pc->state) {
+ case SP_PENCIL_CONTEXT_ADDLINE:
+ /* Current segment will be finished with release */
+ ret = TRUE;
+ break;
+ default:
+ /* Set first point of sequence */
+ SnapManager &m = desktop->namedview->snap_manager;
+
+ if (bevent.state & GDK_CONTROL_MASK) {
+ m.setup(desktop);
+ if (!(bevent.state & GDK_SHIFT_MASK)) {
+ m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ }
+ spdc_create_single_dot(event_context, p, "/tools/freehand/pencil", bevent.state);
+ m.unSetup();
+ ret = true;
+ break;
+ }
+ if (anchor) {
+ p = anchor->dp;
+ desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path"));
+ } else {
+ m.setup(desktop);
+ if (!(bevent.state & GDK_SHIFT_MASK)) {
+ // This is the first click of a new curve; deselect item so that
+ // this curve is not combined with it (unless it is drawn from its
+ // anchor, which is handled by the sibling branch above)
+ selection->clear();
+ desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path"));
+ m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ } else if (selection->singleItem() && SP_IS_PATH(selection->singleItem())) {
+ desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Appending to selected path"));
+ m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ }
+ m.unSetup();
+ }
+ pc->sa = anchor;
+ spdc_set_startpoint(pc, p);
+ ret = TRUE;
+ break;
+ }
+
+ pc->is_drawing = true;
+ }
+ return ret;
+}
+
+static gint
+pencil_handle_motion_notify(PencilTool *const pc, GdkEventMotion const &mevent)
+{
+ SPDesktop *const dt = pc->desktop;
+
+ if ((mevent.state & GDK_CONTROL_MASK) && (mevent.state & GDK_BUTTON1_MASK)) {
+ // mouse was accidentally moved during Ctrl+click;
+ // ignore the motion and create a single point
+ pc->is_drawing = false;
+ return TRUE;
+ }
+ gint ret = FALSE;
+
+ ToolBase *event_context = SP_EVENT_CONTEXT(pc);
+ if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) {
+ // allow scrolling
+ return FALSE;
+ }
+
+ if ( ( mevent.state & GDK_BUTTON1_MASK ) && !pc->grab && pc->is_drawing) {
+ /* Grab mouse, so release will not pass unnoticed */
+ pc->grab = SP_CANVAS_ITEM(dt->acetate);
+ sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK ),
+ NULL, mevent.time);
+ }
+
+ /* Find desktop coordinates */
+ Geom::Point p = dt->w2d(Geom::Point(mevent.x, mevent.y));
+
+ /* Test whether we hit any anchor. */
+ SPDrawAnchor *anchor = spdc_test_inside(pc, Geom::Point(mevent.x, mevent.y));
+
+ if (pencil_within_tolerance) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+ if ( Geom::LInfty( Geom::Point(mevent.x,mevent.y) - pencil_drag_origin_w ) < tolerance ) {
+ return FALSE; // Do not drag if we're within tolerance from origin.
+ }
+ }
+
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to move the object, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ pencil_within_tolerance = false;
+
+ switch (pc->state) {
+ case SP_PENCIL_CONTEXT_ADDLINE:
+ /* Set red endpoint */
+ if (anchor) {
+ p = anchor->dp;
+ } else {
+ Geom::Point ptnr(p);
+ spdc_endpoint_snap(pc, ptnr, mevent.state);
+ p = ptnr;
+ }
+ spdc_set_endpoint(pc, p);
+ ret = TRUE;
+ break;
+ default:
+ /* We may be idle or already freehand */
+ if ( mevent.state & GDK_BUTTON1_MASK && pc->is_drawing ) {
+ if (pc->state == SP_PENCIL_CONTEXT_IDLE) {
+ sp_event_context_discard_delayed_snap_event(event_context);
+ }
+ pc->state = SP_PENCIL_CONTEXT_FREEHAND;
+
+ if ( !pc->sa && !pc->green_anchor ) {
+ /* Create green anchor */
+ pc->green_anchor = sp_draw_anchor_new(pc, pc->green_curve, TRUE, pc->p[0]);
+ }
+ if (anchor) {
+ p = anchor->dp;
+ }
+
+ if ( pc->npoints != 0) { // buttonpress may have happened before we entered draw context!
+ if (pc->ps.empty()) {
+ // Only in freehand mode we have to add the first point also to pc->ps (apparently)
+ // - We cannot add this point in spdc_set_startpoint, because we only need it for freehand
+ // - We cannot do this in the button press handler because at that point we don't know yet
+ // wheter we're going into freehand mode or not
+ pc->ps.push_back(pc->p[0]);
+ }
+ spdc_add_freehand_point(pc, p, mevent.state);
+ ret = TRUE;
+ }
+
+ if (anchor && !pc->anchor_statusbar) {
+ pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Release</b> here to close and finish the path."));
+ pc->anchor_statusbar = true;
+ } else if (!anchor && pc->anchor_statusbar) {
+ pc->message_context->clear();
+ pc->anchor_statusbar = false;
+ } else if (!anchor) {
+ pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("Drawing a freehand path"));
+ }
+
+ } else {
+ if (anchor && !pc->anchor_statusbar) {
+ pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Drag</b> to continue the path from this point."));
+ pc->anchor_statusbar = true;
+ } else if (!anchor && pc->anchor_statusbar) {
+ pc->message_context->clear();
+ pc->anchor_statusbar = false;
+ }
+ }
+
+ // Show the pre-snap indicator to communicate to the user where we would snap to if he/she were to
+ // a) press the mousebutton to start a freehand drawing, or
+ // b) release the mousebutton to finish a freehand drawing
+ if (!sp_event_context_knot_mouseover(pc)) {
+ SnapManager &m = dt->namedview->snap_manager;
+ m.setup(dt);
+ m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE));
+ m.unSetup();
+ }
+ break;
+ }
+ return ret;
+}
+
+static gint
+pencil_handle_button_release(PencilTool *const pc, GdkEventButton const &revent)
+{
+ gint ret = FALSE;
+
+ ToolBase *event_context = SP_EVENT_CONTEXT(pc);
+ if ( revent.button == 1 && pc->is_drawing && !event_context->space_panning) {
+ SPDesktop *const dt = pc->desktop;
+
+ pc->is_drawing = false;
+
+ /* Find desktop coordinates */
+ Geom::Point p = dt->w2d(Geom::Point(revent.x, revent.y));
+
+ /* Test whether we hit any anchor. */
+ SPDrawAnchor *anchor = spdc_test_inside(pc, Geom::Point(revent.x,
+ revent.y));
+
+ switch (pc->state) {
+ case SP_PENCIL_CONTEXT_IDLE:
+ /* Releasing button in idle mode means single click */
+ /* We have already set up start point/anchor in button_press */
+ if (!(revent.state & GDK_CONTROL_MASK)) {
+ // Ctrl+click creates a single point so only set context in ADDLINE mode when Ctrl isn't pressed
+ pc->state = SP_PENCIL_CONTEXT_ADDLINE;
+ }
+ ret = TRUE;
+ break;
+ case SP_PENCIL_CONTEXT_ADDLINE:
+ /* Finish segment now */
+ if (anchor) {
+ p = anchor->dp;
+ } else {
+ spdc_endpoint_snap(pc, p, revent.state);
+ }
+ pc->ea = anchor;
+ spdc_set_endpoint(pc, p);
+ spdc_finish_endpoint(pc);
+ pc->state = SP_PENCIL_CONTEXT_IDLE;
+ sp_event_context_discard_delayed_snap_event(event_context);
+ ret = TRUE;
+ break;
+ case SP_PENCIL_CONTEXT_FREEHAND:
+ if (revent.state & GDK_MOD1_MASK) {
+ /* sketch mode: interpolate the sketched path and improve the current output path with the new interpolation. don't finish sketch */
+
+ sketch_interpolate(pc);
+
+ if (pc->green_anchor) {
+ pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor);
+ }
+
+ pc->state = SP_PENCIL_CONTEXT_SKETCH;
+ } else {
+ /* Finish segment now */
+ /// \todo fixme: Clean up what follows (Lauris)
+ if (anchor) {
+ p = anchor->dp;
+ } else {
+ Geom::Point p_end = p;
+ spdc_endpoint_snap(pc, p_end, revent.state);
+ if (p_end != p) {
+ // then we must have snapped!
+ spdc_add_freehand_point(pc, p_end, revent.state);
+ }
+ }
+
+ pc->ea = anchor;
+ /* Write curves to object */
+
+ dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing freehand"));
+
+ interpolate(pc);
+ spdc_concat_colors_and_flush(pc, FALSE);
+ pc->sa = NULL;
+ pc->ea = NULL;
+ if (pc->green_anchor) {
+ pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor);
+ }
+ pc->state = SP_PENCIL_CONTEXT_IDLE;
+ // reset sketch mode too
+ pc->sketch_n = 0;
+ }
+ ret = TRUE;
+ break;
+ case SP_PENCIL_CONTEXT_SKETCH:
+ default:
+ break;
+ }
+
+ if (pc->grab) {
+ /* Release grab now */
+ sp_canvas_item_ungrab(pc->grab, revent.time);
+ pc->grab = NULL;
+ }
+
+ ret = TRUE;
+ }
+ return ret;
+}
+
+static void
+pencil_cancel (PencilTool *const pc)
+{
+ if (pc->grab) {
+ /* Release grab now */
+ sp_canvas_item_ungrab(pc->grab, 0);
+ pc->grab = NULL;
+ }
+
+ pc->is_drawing = false;
+ pc->state = SP_PENCIL_CONTEXT_IDLE;
+ sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(pc));
+
+ pc->red_curve->reset();
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL);
+ while (pc->green_bpaths) {
+ sp_canvas_item_destroy(SP_CANVAS_ITEM(pc->green_bpaths->data));
+ pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data);
+ }
+ pc->green_curve->reset();
+ if (pc->green_anchor) {
+ pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor);
+ }
+
+ pc->message_context->clear();
+ pc->message_context->flash(Inkscape::NORMAL_MESSAGE, _("Drawing cancelled"));
+
+ pc->desktop->canvas->endForcedFullRedraws();
+}
+
+static gint
+pencil_handle_key_press(PencilTool *const pc, guint const keyval, guint const state)
+{
+ gint ret = FALSE;
+ switch (keyval) {
+ case GDK_KEY_Up:
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_Down:
+ // Prevent the zoom field from activation.
+ if (!mod_ctrl_only(state)) {
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Escape:
+ if (pc->npoints != 0) {
+ // if drawing, cancel, otherwise pass it up for deselecting
+ if (pc->state != SP_PENCIL_CONTEXT_IDLE) {
+ pencil_cancel (pc);
+ ret = TRUE;
+ }
+ }
+ break;
+ case GDK_KEY_z:
+ case GDK_KEY_Z:
+ if (mod_ctrl_only(state) && pc->npoints != 0) {
+ // if drawing, cancel, otherwise pass it up for undo
+ if (pc->state != SP_PENCIL_CONTEXT_IDLE) {
+ pencil_cancel (pc);
+ ret = TRUE;
+ }
+ }
+ break;
+ case GDK_KEY_g:
+ case GDK_KEY_G:
+ if (mod_shift_only(state)) {
+ sp_selection_to_guides(SP_EVENT_CONTEXT(pc)->desktop);
+ ret = true;
+ }
+ break;
+ case GDK_KEY_Alt_L:
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Meta_L:
+ case GDK_KEY_Meta_R:
+ if (pc->state == SP_PENCIL_CONTEXT_IDLE) {
+ pc->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("<b>Sketch mode</b>: holding <b>Alt</b> interpolates between sketched paths. Release <b>Alt</b> to finalize."));
+ }
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static gint
+pencil_handle_key_release(PencilTool *const pc, guint const keyval, guint const /*state*/)
+{
+ gint ret = FALSE;
+ switch (keyval) {
+ case GDK_KEY_Alt_L:
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Meta_L:
+ case GDK_KEY_Meta_R:
+ if (pc->state == SP_PENCIL_CONTEXT_SKETCH) {
+ spdc_concat_colors_and_flush(pc, FALSE);
+ pc->sketch_n = 0;
+ pc->sa = NULL;
+ pc->ea = NULL;
+ if (pc->green_anchor) {
+ pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor);
+ }
+ pc->state = SP_PENCIL_CONTEXT_IDLE;
+ sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(pc));
+ pc->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing freehand sketch"));
+ ret = TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+/**
+ * Reset points and set new starting point.
+ */
+static void
+spdc_set_startpoint(PencilTool *const pc, Geom::Point const &p)
+{
+ pc->npoints = 0;
+ pc->red_curve_is_valid = false;
+ if (in_svg_plane(p)) {
+ pc->p[pc->npoints++] = p;
+ }
+}
+
+/**
+ * Change moving endpoint position.
+ * <ul>
+ * <li>Ctrl constrains to moving to H/V direction, snapping in given direction.
+ * <li>Otherwise we snap freely to whatever attractors are available.
+ * </ul>
+ *
+ * Number of points is (re)set to 2 always, 2nd point is modified.
+ * We change RED curve.
+ */
+static void
+spdc_set_endpoint(PencilTool *const pc, Geom::Point const &p)
+{
+ if (pc->npoints == 0) {
+ return;
+ /* May occur if first point wasn't in SVG plane (e.g. weird w2d transform, perhaps from bad
+ * zoom setting).
+ */
+ }
+ g_return_if_fail( pc->npoints > 0 );
+
+ pc->red_curve->reset();
+ if ( ( p == pc->p[0] )
+ || !in_svg_plane(p) )
+ {
+ pc->npoints = 1;
+ } else {
+ pc->p[1] = p;
+ pc->npoints = 2;
+
+ pc->red_curve->moveto(pc->p[0]);
+ pc->red_curve->lineto(pc->p[1]);
+ pc->red_curve_is_valid = true;
+
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve);
+ }
+}
+
+/**
+ * Finalize addline.
+ *
+ * \todo
+ * fixme: I'd like remove red reset from concat colors (lauris).
+ * Still not sure, how it will make most sense.
+ */
+static void
+spdc_finish_endpoint(PencilTool *const pc)
+{
+ if ( ( pc->red_curve->is_empty() )
+ || ( *(pc->red_curve->first_point()) == *(pc->red_curve->second_point()) ) )
+ {
+ pc->red_curve->reset();
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL);
+ } else {
+ /* Write curves to object. */
+ spdc_concat_colors_and_flush(pc, FALSE);
+ pc->sa = NULL;
+ pc->ea = NULL;
+ }
+}
+
+
+static void
+spdc_add_freehand_point(PencilTool *pc, Geom::Point const &p, guint /*state*/)
+{
+ g_assert( pc->npoints > 0 );
+ g_return_if_fail(unsigned(pc->npoints) < G_N_ELEMENTS(pc->p));
+
+ if ( ( p != pc->p[ pc->npoints - 1 ] )
+ && in_svg_plane(p) )
+ {
+ pc->ps.push_back(p);
+ pc->p[pc->npoints++] = p;
+ fit_and_split(pc);
+ }
+}
+
+static inline double
+square(double const x)
+{
+ return x * x;
+}
+
+static void
+interpolate(PencilTool *pc)
+{
+ if ( pc->ps.size() <= 1 ) {
+ return;
+ }
+
+ //BSpline
+ using Geom::X;
+ using Geom::Y;
+ //BSpline end
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double const tol = prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 1.0, 100.0) * 0.4;
+ double const tolerance_sq = 0.02 * square( pc->desktop->w2d().descrim() *
+ tol) * exp(0.2*tol - 2);
+
+ g_assert(is_zero(pc->req_tangent)
+ || is_unit_vector(pc->req_tangent));
+ Geom::Point const tHatEnd(0, 0);
+
+ guint n_points = pc->ps.size();
+ pc->green_curve->reset();
+ pc->red_curve->reset();
+ pc->red_curve_is_valid = false;
+
+ Geom::Point * b = g_new(Geom::Point, 4*n_points);
+ Geom::Point * points = g_new(Geom::Point, 4*n_points);
+ for (unsigned int i = 0; i < pc->ps.size(); i++) {
+ points[i] = pc->ps[i];
+ }
+
+ // worst case gives us a segment per point
+ int max_segs = 4*n_points;
+
+ int const n_segs = Geom::bezier_fit_cubic_r(b, points, n_points,
+ tolerance_sq, max_segs);
+
+ if ( n_segs > 0)
+ {
+ /* Fit and draw and reset state */
+ pc->green_curve->moveto(b[0]);
+ //BSpline
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ guint mode = prefs->getInt("/tools/freehand/pencil/freehand-mode", 0);
+ //BSpline End
+ for (int c = 0; c < n_segs; c++) {
+ //BSpline
+ //Si el modo es BSpline modificamos para que el trazado cree los nodos adhoc
+ if(mode == 2){
+ Geom::Point BP = b[4*c+0] + (1./3)*(b[4*c+3] - b[4*c+0]);
+ BP = Geom::Point(BP[X] + 0.0001,BP[Y] + 0.0001);
+ Geom::Point CP = b[4*c+3] + (1./3)*(b[4*c+0] - b[4*c+3]);
+ CP = Geom::Point(CP[X] + 0.0001,CP[Y] + 0.0001);
+ pc->green_curve->curveto(BP,CP,b[4*c+3]);
+ }else{
+ pc->green_curve->curveto(b[4*c+1], b[4*c+2], b[4*c+3]);
+ }
+ //BSpline
+ }
+
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->green_curve);
+
+ /* Fit and draw and copy last point */
+ g_assert(!pc->green_curve->is_empty());
+
+ /* Set up direction of next curve. */
+ {
+ Geom::Curve const * last_seg = pc->green_curve->last_segment();
+ g_assert( last_seg ); // Relevance: validity of (*last_seg)
+ pc->p[0] = last_seg->finalPoint();
+ pc->npoints = 1;
+ Geom::Curve *last_seg_reverse = last_seg->reverse();
+ Geom::Point const req_vec( -last_seg_reverse->unitTangentAt(0) );
+ delete last_seg_reverse;
+ pc->req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) )
+ ? Geom::Point(0, 0)
+ : Geom::unit_vector(req_vec) );
+ }
+ }
+ g_free(b);
+ g_free(points);
+ pc->ps.clear();
+}
+
+
+/* interpolates the sketched curve and tweaks the current sketch interpolation*/
+static void
+sketch_interpolate(PencilTool *pc)
+{
+ if ( pc->ps.size() <= 1 ) {
+ return;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double const tol = prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 1.0, 100.0) * 0.4;
+ double const tolerance_sq = 0.02 * square( pc->desktop->w2d().descrim() *
+ tol) * exp(0.2*tol - 2);
+
+ bool average_all_sketches = prefs->getBool("/tools/freehand/pencil/average_all_sketches", true);
+
+ g_assert(is_zero(pc->req_tangent)
+ || is_unit_vector(pc->req_tangent));
+ Geom::Point const tHatEnd(0, 0);
+
+ guint n_points = pc->ps.size();
+ pc->red_curve->reset();
+ pc->red_curve_is_valid = false;
+
+ Geom::Point * b = g_new(Geom::Point, 4*n_points);
+ Geom::Point * points = g_new(Geom::Point, 4*n_points);
+ for (unsigned i = 0; i < pc->ps.size(); i++) {
+ points[i] = pc->ps[i];
+ }
+
+ // worst case gives us a segment per point
+ int max_segs = 4*n_points;
+
+ int const n_segs = Geom::bezier_fit_cubic_r(b, points, n_points,
+ tolerance_sq, max_segs);
+
+ if ( n_segs > 0)
+ {
+ Geom::Path fit(b[0]);
+ for (int c = 0; c < n_segs; c++) {
+ fit.appendNew<Geom::CubicBezier>(b[4*c+1], b[4*c+2], b[4*c+3]);
+ }
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > fit_pwd2 = fit.toPwSb();
+
+ if ( pc->sketch_n > 0 ) {
+ double t =0.;
+ if (average_all_sketches) {
+ // Average = (sum of all) / n
+ // = (sum of all + new one) / n+1
+ // = ((old average)*n + new one) / n+1
+ t = pc->sketch_n / (pc->sketch_n + 1.);
+ } else {
+ t = 0.5;
+ }
+ pc->sketch_interpolation = Geom::lerp(t, fit_pwd2, pc->sketch_interpolation);
+ // simplify path, to eliminate small segments
+ Path *path = new Path;
+ path->LoadPathVector(Geom::path_from_piecewise(pc->sketch_interpolation, 0.01));
+ path->Simplify(0.5);
+ Geom::PathVector *pathv = path->MakePathVector();
+ pc->sketch_interpolation = (*pathv)[0].toPwSb();
+ delete path;
+ delete pathv;
+ } else {
+ pc->sketch_interpolation = fit_pwd2;
+ }
+ pc->sketch_n++;
+
+ pc->green_curve->reset();
+ pc->green_curve->set_pathvector(Geom::path_from_piecewise(pc->sketch_interpolation, 0.01));
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->green_curve);
+
+ /* Fit and draw and copy last point */
+ g_assert(!pc->green_curve->is_empty());
+
+ /* Set up direction of next curve. */
+ {
+ Geom::Curve const * last_seg = pc->green_curve->last_segment();
+ g_assert( last_seg ); // Relevance: validity of (*last_seg)
+ pc->p[0] = last_seg->finalPoint();
+ pc->npoints = 1;
+ Geom::Curve *last_seg_reverse = last_seg->reverse();
+ Geom::Point const req_vec( -last_seg_reverse->unitTangentAt(0) );
+ delete last_seg_reverse;
+ pc->req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) )
+ ? Geom::Point(0, 0)
+ : Geom::unit_vector(req_vec) );
+ }
+ }
+ g_free(b);
+ g_free(points);
+ pc->ps.clear();
+}
+
+static void
+fit_and_split(PencilTool *pc)
+{
+ g_assert( pc->npoints > 1 );
+
+ double const tolerance_sq = 0;
+
+ Geom::Point b[4];
+ g_assert(is_zero(pc->req_tangent)
+ || is_unit_vector(pc->req_tangent));
+ Geom::Point const tHatEnd(0, 0);
+ int const n_segs = Geom::bezier_fit_cubic_full(b, NULL, pc->p, pc->npoints,
+ pc->req_tangent, tHatEnd,
+ tolerance_sq, 1);
+ if ( n_segs > 0
+ && unsigned(pc->npoints) < G_N_ELEMENTS(pc->p) )
+ {
+ /* Fit and draw and reset state */
+ pc->red_curve->reset();
+ pc->red_curve->moveto(b[0]);
+ //BSpline
+ using Geom::X;
+ using Geom::Y;
+ //Si el modo es BSpline modificamos para que el trazado cree los nodos adhoc
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ guint mode = prefs->getInt("/tools/freehand/pencil/freehand-mode", 0);
+ if(mode == 2){
+ Geom::Point B = b[0] + (1./3)*(b[3] - b[0]);
+ B = Geom::Point(B[X] + 0.0001,B[Y] + 0.0001);
+ Geom::Point C = b[3] + (1./3)*(b[0] - b[3]);
+ C = Geom::Point(C[X] + 0.0001,C[Y] + 0.0001);
+ pc->red_curve->curveto(B,C,b[3]);
+ }else{
+ pc->red_curve->curveto(b[1], b[2], b[3]);
+ }
+ //BSpline End
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve);
+ pc->red_curve_is_valid = true;
+ } else {
+ /* Fit and draw and copy last point */
+
+ g_assert(!pc->red_curve->is_empty());
+
+ /* Set up direction of next curve. */
+ {
+ Geom::Curve const * last_seg = pc->red_curve->last_segment();
+ g_assert( last_seg ); // Relevance: validity of (*last_seg)
+ pc->p[0] = last_seg->finalPoint();
+ pc->npoints = 1;
+ Geom::Curve *last_seg_reverse = last_seg->reverse();
+ Geom::Point const req_vec( -last_seg_reverse->unitTangentAt(0) );
+ delete last_seg_reverse;
+ pc->req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) )
+ ? Geom::Point(0, 0)
+ : Geom::unit_vector(req_vec) );
+ }
+
+
+ pc->green_curve->append_continuous(pc->red_curve, 0.0625);
+ SPCurve *curve = pc->red_curve->copy();
+
+ /// \todo fixme:
+ SPCanvasItem *cshape = sp_canvas_bpath_new(sp_desktop_sketch(pc->desktop), curve);
+ curve->unref();
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+
+ pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape);
+
+ pc->red_curve_is_valid = 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/tools/pencil-tool.h b/src/ui/tools/pencil-tool.h
new file mode 100644
index 000000000..6ced9eb56
--- /dev/null
+++ b/src/ui/tools/pencil-tool.h
@@ -0,0 +1,68 @@
+#ifndef SEEN_PENCIL_CONTEXT_H
+#define SEEN_PENCIL_CONTEXT_H
+
+/** \file
+ * PencilTool: a context for pencil tool events
+ */
+
+#include "ui/tools/freehand-base.h"
+
+#define SP_PENCIL_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::PencilTool*>((ToolBase*)obj))
+#define SP_IS_PENCIL_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::PencilTool*>((const ToolBase*)obj) != NULL)
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+enum PencilState {
+ SP_PENCIL_CONTEXT_IDLE,
+ SP_PENCIL_CONTEXT_ADDLINE,
+ SP_PENCIL_CONTEXT_FREEHAND,
+ SP_PENCIL_CONTEXT_SKETCH
+};
+
+/**
+ * PencilTool: a context for pencil tool events
+ */
+class PencilTool : public FreehandBase {
+public:
+ PencilTool();
+ virtual ~PencilTool();
+
+ Geom::Point p[16];
+ gint npoints;
+ PencilState state;
+ Geom::Point req_tangent;
+
+ bool is_drawing;
+
+ std::vector<Geom::Point> ps;
+
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > sketch_interpolation; // the current proposal from the sketched paths
+ unsigned sketch_n; // number of sketches done
+
+ static const std::string prefsPath;
+
+ virtual const std::string& getPrefsPath();
+
+protected:
+ virtual void setup();
+ virtual bool root_handler(GdkEvent* event);
+};
+
+}
+}
+}
+
+#endif /* !SEEN_PENCIL_CONTEXT_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/tools/rect-tool.cpp b/src/ui/tools/rect-tool.cpp
new file mode 100644
index 000000000..263fdea84
--- /dev/null
+++ b/src/ui/tools/rect-tool.cpp
@@ -0,0 +1,527 @@
+/*
+ * Rectangle drawing context
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl>
+ * Copyright (C) 2000-2005 authors
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "config.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <cstring>
+#include <string>
+
+#include "macros.h"
+#include "display/sp-canvas.h"
+#include "sp-rect.h"
+#include "document.h"
+#include "document-undo.h"
+#include "sp-namedview.h"
+#include "selection.h"
+#include "selection-chemistry.h"
+#include "desktop-handles.h"
+#include "snap.h"
+#include "desktop.h"
+#include "desktop-style.h"
+#include "message-context.h"
+#include "pixmaps/cursor-rect.xpm"
+#include "ui/tools/rect-tool.h"
+#include <glibmm/i18n.h>
+#include "xml/repr.h"
+#include "xml/node-event-vector.h"
+#include "preferences.h"
+#include "context-fns.h"
+#include "shape-editor.h"
+#include "verbs.h"
+#include "display/sp-canvas-item.h"
+
+using Inkscape::DocumentUndo;
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+namespace {
+ ToolBase* createRectContext() {
+ return new RectTool();
+ }
+
+ bool rectContextRegistered = ToolFactory::instance().registerObject("/tools/shapes/rect", createRectContext);
+}
+
+const std::string& RectTool::getPrefsPath() {
+ return RectTool::prefsPath;
+}
+
+const std::string RectTool::prefsPath = "/tools/shapes/rect";
+
+RectTool::RectTool() : ToolBase() {
+ this->cursor_shape = cursor_rect_xpm;
+ this->hot_x = 4;
+ this->hot_y = 4;
+ this->xp = 0;
+ this->yp = 0;
+ this->tolerance = 0;
+ this->within_tolerance = false;
+ this->item_to_select = NULL;
+
+ this->rect = NULL;
+
+ this->rx = 0.0;
+ this->ry = 0.0;
+}
+
+void RectTool::finish() {
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(this->desktop->acetate), GDK_CURRENT_TIME);
+
+ this->finishItem();
+ this->sel_changed_connection.disconnect();
+
+ ToolBase::finish();
+}
+
+RectTool::~RectTool() {
+ this->enableGrDrag(false);
+
+ this->sel_changed_connection.disconnect();
+
+ delete this->shape_editor;
+ this->shape_editor = NULL;
+
+ /* fixme: This is necessary because we do not grab */
+ if (this->rect) {
+ this->finishItem();
+ }
+}
+
+/**
+ * Callback that processes the "changed" signal on the selection;
+ * destroys old and creates new knotholder.
+ */
+void RectTool::selection_changed(Inkscape::Selection* selection) {
+ this->shape_editor->unset_item(SH_KNOTHOLDER);
+ this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER);
+}
+
+void RectTool::setup() {
+ ToolBase::setup();
+
+ this->shape_editor = new ShapeEditor(this->desktop);
+
+ SPItem *item = sp_desktop_selection(this->desktop)->singleItem();
+ if (item) {
+ this->shape_editor->set_item(item, SH_KNOTHOLDER);
+ }
+
+ this->sel_changed_connection.disconnect();
+ this->sel_changed_connection = sp_desktop_selection(this->desktop)->connectChanged(
+ sigc::mem_fun(this, &RectTool::selection_changed)
+ );
+
+ sp_event_context_read(this, "rx");
+ sp_event_context_read(this, "ry");
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/shapes/selcue")) {
+ this->enableSelectionCue();
+ }
+
+ if (prefs->getBool("/tools/shapes/gradientdrag")) {
+ this->enableGrDrag();
+ }
+}
+
+void RectTool::set(const Inkscape::Preferences::Entry& val) {
+ /* fixme: Proper error handling for non-numeric data. Use a locale-independent function like
+ * g_ascii_strtod (or a thin wrapper that does the right thing for invalid values inf/nan). */
+ Glib::ustring name = val.getEntryName();
+
+ if ( name == "rx" ) {
+ this->rx = val.getDoubleLimited(); // prevents NaN and +/-Inf from messing up
+ } else if ( name == "ry" ) {
+ this->ry = val.getDoubleLimited();
+ }
+}
+
+bool RectTool::item_handler(SPItem* item, GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if ( event->button.button == 1 && !this->space_panning) {
+ Inkscape::setup_for_drag_start(desktop, this, event);
+ ret = TRUE;
+ }
+ break;
+ // motion and release are always on root (why?)
+ default:
+ break;
+ }
+
+ ret = ToolBase::item_handler(item, event);
+
+ return ret;
+}
+
+bool RectTool::root_handler(GdkEvent* event) {
+ static bool dragging;
+
+ SPDesktop *desktop = this->desktop;
+ Inkscape::Selection *selection = sp_desktop_selection (desktop);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+ Geom::Point const button_w(event->button.x, event->button.y);
+
+ // save drag origin
+ this->xp = (gint) button_w[Geom::X];
+ this->yp = (gint) button_w[Geom::Y];
+ this->within_tolerance = true;
+
+ // remember clicked item, disregarding groups, honoring Alt
+ this->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE);
+
+ dragging = true;
+
+ /* Position center */
+ Geom::Point button_dt(desktop->w2d(button_w));
+ this->center = button_dt;
+
+ /* Snap center */
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+ m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ m.unSetup();
+ this->center = button_dt;
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ ( GDK_KEY_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_POINTER_MOTION_HINT_MASK |
+ GDK_BUTTON_PRESS_MASK ),
+ NULL, event->button.time);
+
+ ret = TRUE;
+ }
+ break;
+ case GDK_MOTION_NOTIFY:
+ if ( dragging
+ && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning)
+ {
+ if ( this->within_tolerance
+ && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance )
+ && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) {
+ break; // do not drag if we're within tolerance from origin
+ }
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to draw, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ this->within_tolerance = false;
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point motion_dt(desktop->w2d(motion_w));
+
+ this->drag(motion_dt, event->motion.state); // this will also handle the snapping
+ gobble_motion_events(GDK_BUTTON1_MASK);
+ ret = TRUE;
+ } else if (!sp_event_context_knot_mouseover(this)) {
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point motion_dt(desktop->w2d(motion_w));
+
+ m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE));
+ m.unSetup();
+ }
+ break;
+ case GDK_BUTTON_RELEASE:
+ this->xp = this->yp = 0;
+ if (event->button.button == 1 && !this->space_panning) {
+ dragging = false;
+ sp_event_context_discard_delayed_snap_event(this);
+
+ if (!this->within_tolerance) {
+ // we've been dragging, finish the rect
+ this->finishItem();
+ } else if (this->item_to_select) {
+ // no dragging, select clicked item if any
+ if (event->button.state & GDK_SHIFT_MASK) {
+ selection->toggle(this->item_to_select);
+ } else {
+ selection->set(this->item_to_select);
+ }
+ } else {
+ // click in an empty space
+ selection->clear();
+ }
+
+ this->item_to_select = NULL;
+ ret = TRUE;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
+ event->button.time);
+ }
+ break;
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Alt_L:
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine)
+ case GDK_KEY_Meta_R:
+ if (!dragging){
+ sp_event_show_modifier_tip (this->defaultMessageContext(), event,
+ _("<b>Ctrl</b>: make square or integer-ratio rect, lock a rounded corner circular"),
+ _("<b>Shift</b>: draw around the starting point"),
+ NULL);
+ }
+ break;
+ case GDK_KEY_Up:
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_Down:
+ // prevent the zoom field from activation
+ if (!MOD__CTRL_ONLY(event))
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_x:
+ case GDK_KEY_X:
+ if (MOD__ALT_ONLY(event)) {
+ desktop->setToolboxFocusTo ("altx-rect");
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_g:
+ case GDK_KEY_G:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_selection_to_guides(desktop);
+ ret = true;
+ }
+ break;
+
+ case GDK_KEY_Escape:
+ if (dragging) {
+ dragging = false;
+ sp_event_context_discard_delayed_snap_event(this);
+ // if drawing, cancel, otherwise pass it up for deselecting
+ this->cancel();
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_space:
+ if (dragging) {
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
+ event->button.time);
+ dragging = false;
+ sp_event_context_discard_delayed_snap_event(this);
+
+ if (!this->within_tolerance) {
+ // we've been dragging, finish the rect
+ this->finishItem();
+ }
+ // do not return true, so that space would work switching to selector
+ }
+ break;
+
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ case GDK_KEY_BackSpace:
+ ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event));
+ break;
+
+ default:
+ break;
+ }
+ break;
+ case GDK_KEY_RELEASE:
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Alt_L:
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt
+ case GDK_KEY_Meta_R:
+ this->defaultMessageContext()->clear();
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+void RectTool::drag(Geom::Point const pt, guint state) {
+ SPDesktop *desktop = this->desktop;
+
+ if (!this->rect) {
+ if (Inkscape::have_viable_layer(desktop, this->message_context) == false) {
+ return;
+ }
+
+ // Create object
+ Inkscape::XML::Document *xml_doc = this->desktop->doc()->getReprDoc();
+ Inkscape::XML::Node *repr = xml_doc->createElement("svg:rect");
+
+ // Set style
+ sp_desktop_apply_style_tool (desktop, repr, "/tools/shapes/rect", false);
+
+ this->rect = SP_RECT(desktop->currentLayer()->appendChildRepr(repr));
+ Inkscape::GC::release(repr);
+
+ this->rect->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
+ this->rect->updateRepr();
+
+ desktop->canvas->forceFullRedrawAfterInterruptions(5);
+ }
+
+ Geom::Rect const r = Inkscape::snap_rectangular_box(desktop, this->rect, pt, this->center, state);
+
+ this->rect->setPosition(r.min()[Geom::X], r.min()[Geom::Y], r.dimensions()[Geom::X], r.dimensions()[Geom::Y]);
+
+ if (this->rx != 0.0) {
+ this->rect->setRx(true, this->rx);
+ }
+
+ if (this->ry != 0.0) {
+ if (this->rx == 0.0)
+ this->rect->setRy(true, CLAMP(this->ry, 0, MIN(r.dimensions()[Geom::X], r.dimensions()[Geom::Y])/2));
+ else
+ this->rect->setRy(true, CLAMP(this->ry, 0, r.dimensions()[Geom::Y]));
+ }
+
+ // status text
+ double rdimx = r.dimensions()[Geom::X];
+ double rdimy = r.dimensions()[Geom::Y];
+
+ Inkscape::Util::Quantity rdimx_q = Inkscape::Util::Quantity(rdimx, "px");
+ Inkscape::Util::Quantity rdimy_q = Inkscape::Util::Quantity(rdimy, "px");
+ GString *xs = g_string_new(rdimx_q.string(desktop->namedview->doc_units).c_str());
+ GString *ys = g_string_new(rdimy_q.string(desktop->namedview->doc_units).c_str());
+
+ if (state & GDK_CONTROL_MASK) {
+ int ratio_x, ratio_y;
+ bool is_golden_ratio = false;
+
+ if (fabs (rdimx) > fabs (rdimy)) {
+ if (fabs(rdimx / rdimy - goldenratio) < 1e-6) {
+ is_golden_ratio = true;
+ }
+
+ ratio_x = (int) rint (rdimx / rdimy);
+ ratio_y = 1;
+ } else {
+ if (fabs(rdimy / rdimx - goldenratio) < 1e-6) {
+ is_golden_ratio = true;
+ }
+
+ ratio_x = 1;
+ ratio_y = (int) rint (rdimy / rdimx);
+ }
+
+ if (!is_golden_ratio) {
+ this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Rectangle</b>: %s &#215; %s (constrained to ratio %d:%d); with <b>Shift</b> to draw around the starting point"), xs->str, ys->str, ratio_x, ratio_y);
+ } else {
+ if (ratio_y == 1) {
+ this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Rectangle</b>: %s &#215; %s (constrained to golden ratio 1.618 : 1); with <b>Shift</b> to draw around the starting point"), xs->str, ys->str);
+ } else {
+ this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Rectangle</b>: %s &#215; %s (constrained to golden ratio 1 : 1.618); with <b>Shift</b> to draw around the starting point"), xs->str, ys->str);
+ }
+ }
+ } else {
+ this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Rectangle</b>: %s &#215; %s; with <b>Ctrl</b> to make square or integer-ratio rectangle; with <b>Shift</b> to draw around the starting point"), xs->str, ys->str);
+ }
+
+ g_string_free(xs, FALSE);
+ g_string_free(ys, FALSE);
+}
+
+void RectTool::finishItem() {
+ this->message_context->clear();
+
+ if (this->rect != NULL) {
+ if (this->rect->width.computed == 0 || this->rect->height.computed == 0) {
+ this->cancel(); // Don't allow the creating of zero sized rectangle, for example when the start and and point snap to the snap grid point
+ return;
+ }
+
+ this->rect->updateRepr();
+ this->rect->doWriteTransform(this->rect->getRepr(), this->rect->transform, NULL, true);
+
+ this->desktop->canvas->endForcedFullRedraws();
+
+ sp_desktop_selection(this->desktop)->set(this->rect);
+
+ DocumentUndo::done(sp_desktop_document(this->desktop), SP_VERB_CONTEXT_RECT, _("Create rectangle"));
+
+ this->rect = NULL;
+ }
+}
+
+void RectTool::cancel(){
+ sp_desktop_selection(this->desktop)->clear();
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(this->desktop->acetate), 0);
+
+ if (this->rect != NULL) {
+ this->rect->deleteObject();
+ this->rect = NULL;
+ }
+
+ this->within_tolerance = false;
+ this->xp = 0;
+ this->yp = 0;
+ this->item_to_select = NULL;
+
+ this->desktop->canvas->endForcedFullRedraws();
+
+ DocumentUndo::cancel(sp_desktop_document(this->desktop));
+}
+
+}
+}
+}
+
+/*
+ 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/tools/rect-tool.h b/src/ui/tools/rect-tool.h
new file mode 100644
index 000000000..a50fd7b24
--- /dev/null
+++ b/src/ui/tools/rect-tool.h
@@ -0,0 +1,65 @@
+#ifndef __SP_RECT_CONTEXT_H__
+#define __SP_RECT_CONTEXT_H__
+
+/*
+ * Rectangle drawing context
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2000 Lauris Kaplinski
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2002 Lauris Kaplinski
+ *
+ * Released under GNU GPL
+ */
+
+#include <stddef.h>
+#include <sigc++/sigc++.h>
+#include <2geom/point.h>
+#include "ui/tools/tool-base.h"
+
+#include "sp-rect.h"
+
+#define SP_RECT_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::RectTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_RECT_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::RectTool*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class RectTool : public ToolBase {
+public:
+ RectTool();
+ virtual ~RectTool();
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual void finish();
+ virtual void set(const Inkscape::Preferences::Entry& val);
+ virtual bool root_handler(GdkEvent* event);
+ virtual bool item_handler(SPItem* item, GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+private:
+ SPRect *rect;
+ Geom::Point center;
+
+ gdouble rx; /* roundness radius (x direction) */
+ gdouble ry; /* roundness radius (y direction) */
+
+ sigc::connection sel_changed_connection;
+
+ void drag(Geom::Point const pt, guint state);
+ void finishItem();
+ void cancel();
+ void selection_changed(Inkscape::Selection* selection);
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/ui/tools/select-tool.cpp b/src/ui/tools/select-tool.cpp
new file mode 100644
index 000000000..498882417
--- /dev/null
+++ b/src/ui/tools/select-tool.cpp
@@ -0,0 +1,1252 @@
+/*
+ * Selection and transformation context
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Abhishek Sharma
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2010 authors
+ * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl>
+ * Copyright (C) 1999-2005 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <cstring>
+#include <string>
+#include <gdk/gdkkeysyms.h>
+#include "macros.h"
+#include "rubberband.h"
+#include "document.h"
+#include "document-undo.h"
+#include "selection.h"
+#include "sp-cursor.h"
+#include "style.h"
+#include "pixmaps/cursor-select-m.xpm"
+#include "pixmaps/cursor-select-d.xpm"
+#include "pixmaps/handles.xpm"
+#include <glibmm/i18n.h>
+
+#include "ui/tools/select-tool.h"
+#include "selection-chemistry.h"
+#ifdef WITH_DBUS
+#include "extension/dbus/document-interface.h"
+#endif
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "sp-root.h"
+#include "preferences.h"
+#include "tools-switch.h"
+#include "message-stack.h"
+#include "selection-describer.h"
+#include "seltrans.h"
+#include "box3d.h"
+#include "display/sp-canvas.h"
+#include "display/sp-canvas-item.h"
+#include "display/drawing-item.h"
+#include "tool-factory.h"
+
+using Inkscape::DocumentUndo;
+
+GdkPixbuf *handles[13];
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+static GdkCursor *CursorSelectMouseover = NULL;
+static GdkCursor *CursorSelectDragging = NULL;
+
+static gint rb_escaped = 0; // if non-zero, rubberband was canceled by esc, so the next button release should not deselect
+static gint drag_escaped = 0; // if non-zero, drag was canceled by esc
+
+namespace {
+ ToolBase* createSelectContext() {
+ return new SelectTool();
+ }
+
+ bool selectContextRegistered = ToolFactory::instance().registerObject("/tools/select", createSelectContext);
+}
+
+const std::string& SelectTool::getPrefsPath() {
+ return SelectTool::prefsPath;
+}
+
+const std::string SelectTool::prefsPath = "/tools/select";
+
+
+//Creates rotated variations for handles
+static void
+sp_load_handles(int start, int count, char const **xpm) {
+ handles[start] = gdk_pixbuf_new_from_xpm_data((gchar const **)xpm);
+ for(int i = start + 1; i < start + count; i++) {
+ // We use either the original at *start or previous loop item to rotate
+ handles[i] = gdk_pixbuf_rotate_simple(handles[i-1], GDK_PIXBUF_ROTATE_CLOCKWISE);
+ }
+}
+
+SelectTool::SelectTool() : ToolBase() {
+ this->grabbed = 0;
+ this->item = 0;
+
+ this->dragging = FALSE;
+ this->moved = FALSE;
+ this->button_press_shift = false;
+ this->button_press_ctrl = false;
+ this->button_press_alt = false;
+ this->cycling_items = NULL;
+ this->cycling_items_cmp = NULL;
+ this->cycling_items_selected_before = NULL;
+ this->cycling_cur_item = NULL;
+ this->cycling_wrap = true;
+ this->_seltrans = NULL;
+ this->_describer = NULL;
+
+
+ // cursors in select context
+ CursorSelectMouseover = sp_cursor_new_from_xpm(cursor_select_m_xpm , 1, 1);
+ CursorSelectDragging = sp_cursor_new_from_xpm(cursor_select_d_xpm , 1, 1);
+
+ // selection handles
+ sp_load_handles(0, 2, handle_scale_xpm);
+ sp_load_handles(2, 2, handle_stretch_xpm);
+ sp_load_handles(4, 4, handle_rotate_xpm);
+ sp_load_handles(8, 4, handle_skew_xpm);
+ sp_load_handles(12, 1, handle_center_xpm);
+}
+
+//static gint xp = 0, yp = 0; // where drag started
+//static gint tolerance = 0;
+//static bool within_tolerance = false;
+static bool is_cycling = false;
+static bool moved_while_cycling = false;
+ToolBase *prev_event_context = NULL;
+
+
+SelectTool::~SelectTool() {
+ this->enableGrDrag(false);
+
+ if (this->grabbed) {
+ sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME);
+ this->grabbed = NULL;
+ }
+
+ delete this->_seltrans;
+ this->_seltrans = NULL;
+
+ delete this->_describer;
+ this->_describer = NULL;
+
+ if (CursorSelectDragging) {
+#if GTK_CHECK_VERSION(3,0,0)
+ g_object_unref(CursorSelectDragging);
+#else
+ gdk_cursor_unref (CursorSelectDragging);
+#endif
+ CursorSelectDragging = NULL;
+ }
+
+ if (CursorSelectMouseover) {
+#if GTK_CHECK_VERSION(3,0,0)
+ g_object_unref(CursorSelectMouseover);
+#else
+ gdk_cursor_unref (CursorSelectMouseover);
+#endif
+ CursorSelectMouseover = NULL;
+ }
+}
+
+void SelectTool::setup() {
+ ToolBase::setup();
+
+ this->_describer = new Inkscape::SelectionDescriber(
+ desktop->selection,
+ desktop->messageStack(),
+ _("Click selection to toggle scale/rotation handles"),
+ _("No objects selected. Click, Shift+click, Alt+scroll mouse on top of objects, or drag around objects to select.")
+ );
+
+ this->_seltrans = new Inkscape::SelTrans(desktop);
+
+ sp_event_context_read(this, "show");
+ sp_event_context_read(this, "transform");
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (prefs->getBool("/tools/select/gradientdrag")) {
+ this->enableGrDrag();
+ }
+}
+
+void SelectTool::set(const Inkscape::Preferences::Entry& val) {
+ Glib::ustring path = val.getEntryName();
+
+ if (path == "show") {
+ if (val.getString() == "outline") {
+ this->_seltrans->setShow(Inkscape::SelTrans::SHOW_OUTLINE);
+ } else {
+ this->_seltrans->setShow(Inkscape::SelTrans::SHOW_CONTENT);
+ }
+ }
+}
+
+bool SelectTool::sp_select_context_abort() {
+ Inkscape::SelTrans *seltrans = this->_seltrans;
+
+ if (this->dragging) {
+ if (this->moved) { // cancel dragging an object
+ seltrans->ungrab();
+ this->moved = FALSE;
+ this->dragging = FALSE;
+ sp_event_context_discard_delayed_snap_event(this);
+ drag_escaped = 1;
+
+ if (this->item) {
+ // only undo if the item is still valid
+ if (this->item->document) {
+ DocumentUndo::undo(sp_desktop_document(desktop));
+ }
+
+ sp_object_unref( this->item, NULL);
+ } else if (this->button_press_ctrl) {
+ // NOTE: This is a workaround to a bug.
+ // When the ctrl key is held, sc->item is not defined
+ // so in this case (only), we skip the object doc check
+ DocumentUndo::undo(sp_desktop_document(desktop));
+ }
+ this->item = NULL;
+
+ SP_EVENT_CONTEXT(this)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Move canceled."));
+ return true;
+ }
+ } else {
+ if (Inkscape::Rubberband::get(desktop)->is_started()) {
+ Inkscape::Rubberband::get(desktop)->stop();
+ rb_escaped = 1;
+ SP_EVENT_CONTEXT(this)->defaultMessageContext()->clear();
+ SP_EVENT_CONTEXT(this)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selection canceled."));
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool
+key_is_a_modifier (guint key) {
+ return (key == GDK_KEY_Alt_L ||
+ key == GDK_KEY_Alt_R ||
+ key == GDK_KEY_Control_L ||
+ key == GDK_KEY_Control_R ||
+ key == GDK_KEY_Shift_L ||
+ key == GDK_KEY_Shift_R ||
+ key == GDK_KEY_Meta_L || // Meta is when you press Shift+Alt (at least on my machine)
+ key == GDK_KEY_Meta_R);
+}
+
+static void
+sp_select_context_up_one_layer(SPDesktop *desktop)
+{
+ /* Click in empty place, go up one level -- but don't leave a layer to root.
+ *
+ * (Rationale: we don't usually allow users to go to the root, since that
+ * detracts from the layer metaphor: objects at the root level can in front
+ * of or behind layers. Whereas it's fine to go to the root if editing
+ * a document that has no layers (e.g. a non-Inkscape document).)
+ *
+ * Once we support editing SVG "islands" (e.g. <svg> embedded in an xhtml
+ * document), we might consider further restricting the below to disallow
+ * leaving a layer to go to a non-layer.
+ */
+ SPObject *const current_layer = desktop->currentLayer();
+ if (current_layer) {
+ SPObject *const parent = current_layer->parent;
+ if ( parent
+ && ( parent->parent
+ || !( SP_IS_GROUP(current_layer)
+ && ( SPGroup::LAYER
+ == SP_GROUP(current_layer)->layerMode() ) ) ) )
+ {
+ desktop->setCurrentLayer(parent);
+ if (SP_IS_GROUP(current_layer) && SPGroup::LAYER != SP_GROUP(current_layer)->layerMode())
+ sp_desktop_selection(desktop)->set(current_layer);
+ }
+ }
+}
+
+bool SelectTool::item_handler(SPItem* item, GdkEvent* event) {
+ gint ret = FALSE;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ // make sure we still have valid objects to move around
+ if (this->item && this->item->document == NULL) {
+ this->sp_select_context_abort();
+ }
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+ /* Left mousebutton */
+
+ // save drag origin
+ xp = (gint) event->button.x;
+ yp = (gint) event->button.y;
+ within_tolerance = true;
+
+ // remember what modifiers were on before button press
+ this->button_press_shift = (event->button.state & GDK_SHIFT_MASK) ? true : false;
+ this->button_press_ctrl = (event->button.state & GDK_CONTROL_MASK) ? true : false;
+ this->button_press_alt = (event->button.state & GDK_MOD1_MASK) ? true : false;
+
+ if (event->button.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) {
+ // if shift or ctrl was pressed, do not move objects;
+ // pass the event to root handler which will perform rubberband, shift-click, ctrl-click, ctrl-drag
+ } else {
+ GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop)));
+
+ this->dragging = TRUE;
+ this->moved = FALSE;
+
+ gdk_window_set_cursor(window, CursorSelectDragging);
+
+ desktop->canvas->forceFullRedrawAfterInterruptions(5);
+
+ // remember the clicked item in this->item:
+ if (this->item) {
+ sp_object_unref(this->item, NULL);
+ this->item = NULL;
+ }
+
+ this->item = sp_event_context_find_item (desktop,
+ Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, FALSE);
+ sp_object_ref(this->item, NULL);
+
+ rb_escaped = drag_escaped = 0;
+
+ if (this->grabbed) {
+ sp_canvas_item_ungrab(this->grabbed, event->button.time);
+ this->grabbed = NULL;
+ }
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->drawing),
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
+ NULL, event->button.time);
+
+ this->grabbed = SP_CANVAS_ITEM(desktop->drawing);
+
+ desktop->canvas->forceFullRedrawAfterInterruptions(5);
+
+ ret = TRUE;
+ }
+ } else if (event->button.button == 3) {
+ // right click; do not eat it so that right-click menu can appear, but cancel dragging & rubberband
+ this->sp_select_context_abort();
+ }
+ break;
+
+ case GDK_ENTER_NOTIFY: {
+ if (!desktop->isWaitingCursor() && !this->dragging) {
+ GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop)));
+
+ gdk_window_set_cursor(window, CursorSelectMouseover);
+ }
+ break;
+ }
+ case GDK_LEAVE_NOTIFY:
+ if (!desktop->isWaitingCursor() && !this->dragging) {
+ GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop)));
+
+ gdk_window_set_cursor(window, this->cursor);
+ }
+ break;
+
+ case GDK_KEY_PRESS:
+ if (get_group0_keyval (&event->key) == GDK_KEY_space) {
+ if (this->dragging && this->grabbed) {
+ /* stamping mode: show content mode moving */
+ _seltrans->stamp();
+ ret = TRUE;
+ }
+ } else if (get_group0_keyval (&event->key) == GDK_KEY_Tab) {
+ if (this->dragging && this->grabbed) {
+ _seltrans->getNextClosestPoint(false);
+ ret = TRUE;
+ }
+ } else if (get_group0_keyval (&event->key) == GDK_KEY_ISO_Left_Tab) {
+ if (this->dragging && this->grabbed) {
+ _seltrans->getNextClosestPoint(true);
+ ret = TRUE;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::item_handler(item, event);
+ }
+
+ return ret;
+}
+
+void SelectTool::sp_select_context_cycle_through_items(Inkscape::Selection *selection, GdkEventScroll *scroll_event, bool shift_pressed) {
+ if (!this->cycling_cur_item) {
+ return;
+ }
+
+ Inkscape::DrawingItem *arenaitem;
+ SPItem *item = SP_ITEM(this->cycling_cur_item->data);
+
+ // Deactivate current item
+ if (!g_list_find(this->cycling_items_selected_before, item) && selection->includes(item)) {
+ selection->remove(item);
+ }
+
+ arenaitem = item->get_arenaitem(desktop->dkey);
+ arenaitem->setOpacity(0.3);
+
+ // Find next item and activate it
+ GList *next;
+ if (scroll_event->direction == GDK_SCROLL_UP) {
+ next = this->cycling_cur_item->next;
+ if (next == NULL && this->cycling_wrap)
+ next = this->cycling_items;
+ } else {
+ next = this->cycling_cur_item->prev;
+ if (next == NULL && this->cycling_wrap)
+ next = g_list_last(this->cycling_items);
+ }
+
+ if (next) {
+ this->cycling_cur_item = next;
+ item = SP_ITEM(this->cycling_cur_item->data);
+ }
+
+ arenaitem = item->get_arenaitem(desktop->dkey);
+ arenaitem->setOpacity(1.0);
+
+ if (shift_pressed) {
+ selection->add(item);
+ } else {
+ selection->set(item);
+ }
+}
+
+
+static void
+sp_select_context_reset_opacities(ToolBase *event_context)
+{
+ // SPDesktop *desktop = event_context->desktop;
+ SelectTool *sc = SP_SELECT_CONTEXT(event_context);
+ Inkscape::DrawingItem *arenaitem;
+ for (GList *l = sc->cycling_items; l != NULL; l = g_list_next(l)) {
+ arenaitem = SP_ITEM(l->data)->get_arenaitem(event_context->desktop->dkey);
+ arenaitem->setOpacity(SP_SCALE24_TO_FLOAT(SP_ITEM(l->data)->style->opacity.value));
+ }
+ g_list_free(sc->cycling_items);
+ g_list_free(sc->cycling_items_selected_before);
+ g_list_free(sc->cycling_items_cmp);
+ sc->cycling_items = NULL;
+ sc->cycling_items_selected_before = NULL;
+ sc->cycling_cur_item = NULL;
+ sc->cycling_items_cmp = NULL;
+}
+
+bool SelectTool::root_handler(GdkEvent* event) {
+ SPItem *item = NULL;
+ SPItem *item_at_point = NULL, *group_at_point = NULL, *item_in_group = NULL;
+ gint ret = FALSE;
+
+ Inkscape::Selection *selection = sp_desktop_selection(desktop);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ // make sure we still have valid objects to move around
+ if (this->item && this->item->document == NULL) {
+ this->sp_select_context_abort();
+ }
+
+ switch (event->type) {
+ case GDK_2BUTTON_PRESS:
+ if (event->button.button == 1) {
+ if (!selection->isEmpty()) {
+ SPItem *clicked_item = static_cast<SPItem *>(selection->itemList()->data);
+
+ if (SP_IS_GROUP(clicked_item) && !SP_IS_BOX3D(clicked_item)) { // enter group if it's not a 3D box
+ desktop->setCurrentLayer(reinterpret_cast<SPObject *>(clicked_item));
+ sp_desktop_selection(desktop)->clear();
+ this->dragging = false;
+ sp_event_context_discard_delayed_snap_event(this);
+
+ desktop->canvas->endForcedFullRedraws();
+ } else { // switch tool
+ Geom::Point const button_pt(event->button.x, event->button.y);
+ Geom::Point const p(desktop->w2d(button_pt));
+ tools_switch_by_item (desktop, clicked_item, p);
+ }
+ } else {
+ sp_select_context_up_one_layer(desktop);
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+ // save drag origin
+ xp = (gint) event->button.x;
+ yp = (gint) event->button.y;
+ within_tolerance = true;
+
+ Geom::Point const button_pt(event->button.x, event->button.y);
+ Geom::Point const p(desktop->w2d(button_pt));
+
+ if (event->button.state & GDK_MOD1_MASK) {
+ Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH);
+ }
+
+ Inkscape::Rubberband::get(desktop)->start(desktop, p);
+
+ if (this->grabbed) {
+ sp_canvas_item_ungrab(this->grabbed, event->button.time);
+ this->grabbed = NULL;
+ }
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK,
+ NULL, event->button.time);
+
+ this->grabbed = SP_CANVAS_ITEM(desktop->acetate);
+
+ // remember what modifiers were on before button press
+ this->button_press_shift = (event->button.state & GDK_SHIFT_MASK) ? true : false;
+ this->button_press_ctrl = (event->button.state & GDK_CONTROL_MASK) ? true : false;
+ this->button_press_alt = (event->button.state & GDK_MOD1_MASK) ? true : false;
+
+ this->moved = FALSE;
+
+ rb_escaped = drag_escaped = 0;
+
+ ret = TRUE;
+ } else if (event->button.button == 3) {
+ // right click; do not eat it so that right-click menu can appear, but cancel dragging & rubberband
+ this->sp_select_context_abort();
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ {
+ if (is_cycling)
+ {
+ moved_while_cycling = true;
+ prev_event_context = this;
+ }
+
+ tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ if ((event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) {
+ Geom::Point const motion_pt(event->motion.x, event->motion.y);
+ Geom::Point const p(desktop->w2d(motion_pt));
+
+ if ( within_tolerance
+ && ( abs( (gint) event->motion.x - xp ) < tolerance )
+ && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) {
+ break; // do not drag if we're within tolerance from origin
+ }
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to move the object, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ within_tolerance = false;
+
+ if (this->button_press_ctrl || (this->button_press_alt && !this->button_press_shift && !selection->isEmpty())) {
+ // if it's not click and ctrl or alt was pressed (the latter with some selection
+ // but not with shift) we want to drag rather than rubberband
+ this->dragging = TRUE;
+
+ GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop)));
+
+ gdk_window_set_cursor(window, CursorSelectDragging);
+
+ desktop->canvas->forceFullRedrawAfterInterruptions(5);
+ }
+
+ if (this->dragging) {
+ /* User has dragged fast, so we get events on root (lauris)*/
+ // not only that; we will end up here when ctrl-dragging as well
+ // and also when we started within tolerance, but trespassed tolerance outside of item
+ Inkscape::Rubberband::get(desktop)->stop();
+ this->defaultMessageContext()->clear();
+
+ item_at_point = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), FALSE);
+
+ if (!item_at_point) { // if no item at this point, try at the click point (bug 1012200)
+ item_at_point = desktop->getItemAtPoint(Geom::Point(xp, yp), FALSE);
+ }
+
+ if (item_at_point || this->moved || this->button_press_alt) {
+ // drag only if starting from an item, or if something is already grabbed, or if alt-dragging
+ if (!this->moved) {
+ item_in_group = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE);
+ group_at_point = desktop->getGroupAtPoint(Geom::Point(event->button.x, event->button.y));
+
+ if (SP_IS_LAYER(selection->single())) {
+ group_at_point = SP_GROUP(selection->single());
+ }
+
+ // group-at-point is meant to be topmost item if it's a group,
+ // not topmost group of all items at point
+ if (group_at_point != item_in_group &&
+ !(group_at_point && item_at_point &&
+ group_at_point->isAncestorOf(item_at_point))) {
+ group_at_point = NULL;
+ }
+
+ // if neither a group nor an item (possibly in a group) at point are selected, set selection to the item at point
+ if ((!item_in_group || !selection->includes(item_in_group)) &&
+ (!group_at_point || !selection->includes(group_at_point))
+ && !this->button_press_alt) {
+ // select what is under cursor
+ if (!_seltrans->isEmpty()) {
+ _seltrans->resetState();
+ }
+
+ // when simply ctrl-dragging, we don't want to go into groups
+ if (item_at_point && !selection->includes(item_at_point)) {
+ selection->set(item_at_point);
+ }
+ } // otherwise, do not change selection so that dragging selected-within-group items, as well as alt-dragging, is possible
+
+ _seltrans->grab(p, -1, -1, FALSE, TRUE);
+ this->moved = TRUE;
+ }
+
+ if (!_seltrans->isEmpty()) {
+ _seltrans->moveTo(p, event->button.state);
+ }
+
+ desktop->scroll_to_point(p);
+ gobble_motion_events(GDK_BUTTON1_MASK);
+ ret = TRUE;
+ } else {
+ this->dragging = FALSE;
+ sp_event_context_discard_delayed_snap_event(this);
+ desktop->canvas->endForcedFullRedraws();
+ }
+ } else {
+ if (Inkscape::Rubberband::get(desktop)->is_started()) {
+ Inkscape::Rubberband::get(desktop)->move(p);
+
+ if (Inkscape::Rubberband::get(desktop)->getMode() == RUBBERBAND_MODE_TOUCHPATH) {
+ this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Draw over</b> objects to select them; release <b>Alt</b> to switch to rubberband selection"));
+ } else {
+ this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Drag around</b> objects to select them; press <b>Alt</b> to switch to touch selection"));
+ }
+
+ gobble_motion_events(GDK_BUTTON1_MASK);
+ }
+ }
+ }
+ break;
+ }
+ case GDK_BUTTON_RELEASE:
+ xp = yp = 0;
+
+ if ((event->button.button == 1) && (this->grabbed) && !this->space_panning) {
+ if (this->dragging) {
+ GdkWindow* window;
+
+ if (this->moved) {
+ // item has been moved
+ _seltrans->ungrab();
+ this->moved = FALSE;
+#ifdef WITH_DBUS
+ dbus_send_ping(desktop, this->item);
+#endif
+ } else if (this->item && !drag_escaped) {
+ // item has not been moved -> simply a click, do selecting
+ if (!selection->isEmpty()) {
+ if (event->button.state & GDK_SHIFT_MASK) {
+ // with shift, toggle selection
+ _seltrans->resetState();
+ selection->toggle(this->item);
+ } else {
+ SPObject* single = selection->single();
+ // without shift, increase state (i.e. toggle scale/rotation handles)
+ if (selection->includes(this->item)) {
+ _seltrans->increaseState();
+ } else if (SP_IS_LAYER(single) && single->isAncestorOf(this->item)) {
+ _seltrans->increaseState();
+ } else {
+ _seltrans->resetState();
+ selection->set(this->item);
+ }
+ }
+ } else { // simple or shift click, no previous selection
+ _seltrans->resetState();
+ selection->set(this->item);
+ }
+ }
+
+ this->dragging = FALSE;
+ window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop)));
+
+ gdk_window_set_cursor(window, CursorSelectMouseover);
+ sp_event_context_discard_delayed_snap_event(this);
+ desktop->canvas->endForcedFullRedraws();
+
+ if (this->item) {
+ sp_object_unref( this->item, NULL);
+ }
+
+ this->item = NULL;
+ } else {
+ Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop);
+
+ if (r->is_started() && !within_tolerance) {
+ // this was a rubberband drag
+ GSList *items = NULL;
+
+ if (r->getMode() == RUBBERBAND_MODE_RECT) {
+ Geom::OptRect const b = r->getRectangle();
+ items = sp_desktop_document(desktop)->getItemsInBox(desktop->dkey, *b);
+ } else if (r->getMode() == RUBBERBAND_MODE_TOUCHPATH) {
+ items = sp_desktop_document(desktop)->getItemsAtPoints(desktop->dkey, r->getPoints());
+ }
+
+ _seltrans->resetState();
+ r->stop();
+ this->defaultMessageContext()->clear();
+
+ if (event->button.state & GDK_SHIFT_MASK) {
+ // with shift, add to selection
+ selection->addList (items);
+ } else {
+ // without shift, simply select anew
+ selection->setList (items);
+ }
+
+ g_slist_free (items);
+ } else { // it was just a click, or a too small rubberband
+ r->stop();
+
+ if (this->button_press_shift && !rb_escaped && !drag_escaped) {
+ // this was a shift+click or alt+shift+click, select what was clicked upon
+ this->button_press_shift = false;
+
+ if (this->button_press_ctrl) {
+ // go into groups, honoring Alt
+ item = sp_event_context_find_item (desktop,
+ Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, TRUE);
+ this->button_press_ctrl = FALSE;
+ } else {
+ // don't go into groups, honoring Alt
+ item = sp_event_context_find_item (desktop,
+ Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, FALSE);
+ }
+
+ if (item) {
+ selection->toggle(item);
+ item = NULL;
+ }
+
+ } else if ((this->button_press_ctrl || this->button_press_alt) && !rb_escaped && !drag_escaped) { // ctrl+click, alt+click
+ item = sp_event_context_find_item (desktop,
+ Geom::Point(event->button.x, event->button.y), this->button_press_alt, this->button_press_ctrl);
+
+ this->button_press_ctrl = FALSE;
+ this->button_press_alt = FALSE;
+
+ if (item) {
+ if (selection->includes(item)) {
+ _seltrans->increaseState();
+ } else {
+ _seltrans->resetState();
+ selection->set(item);
+ }
+
+ item = NULL;
+ }
+ } else { // click without shift, simply deselect, unless with Alt or something was cancelled
+ if (!selection->isEmpty()) {
+ if (!(rb_escaped) && !(drag_escaped) && !(event->button.state & GDK_MOD1_MASK)) {
+ selection->clear();
+ }
+
+ rb_escaped = 0;
+ ret = TRUE;
+ }
+ }
+ }
+
+ ret = TRUE;
+ }
+
+ if (this->grabbed) {
+ sp_canvas_item_ungrab(this->grabbed, event->button.time);
+ this->grabbed = NULL;
+ }
+
+ desktop->updateNow();
+ }
+
+ if (event->button.button == 1) {
+ Inkscape::Rubberband::get(desktop)->stop(); // might have been started in another tool!
+ }
+
+ this->button_press_shift = false;
+ this->button_press_ctrl = false;
+ this->button_press_alt = false;
+ break;
+
+ case GDK_SCROLL: {
+ GdkEventScroll *scroll_event = (GdkEventScroll*) event;
+
+ if (scroll_event->state & GDK_MOD1_MASK) { // alt modified pressed
+ if (moved_while_cycling)
+ {
+ moved_while_cycling = false;
+ sp_select_context_reset_opacities(prev_event_context);
+ prev_event_context = NULL;
+ }
+
+ is_cycling = true;
+
+ bool shift_pressed = scroll_event->state & GDK_SHIFT_MASK;
+
+ /* Rebuild list of items underneath the mouse pointer */
+ Geom::Point p = desktop->d2w(desktop->point());
+ SPItem *item = desktop->getItemAtPoint(p, true, NULL);
+
+ // Save pointer to current cycle-item so that we can find it again later, in the freshly built list
+ SPItem *tmp_cur_item = this->cycling_cur_item ? SP_ITEM(this->cycling_cur_item->data) : NULL;
+ g_list_free(this->cycling_items);
+ this->cycling_items = NULL;
+ this->cycling_cur_item = NULL;
+
+ while(item != NULL) {
+ this->cycling_items = g_list_append(this->cycling_items, item);
+ item = desktop->getItemAtPoint(p, true, item);
+ }
+
+ /* Compare current item list with item list during previous scroll ... */
+ GList *l1, *l2;
+ bool item_lists_differ = false;
+
+ // Note that we can do an 'or' comparison in the loop because it is safe to call g_list_next with a NULL pointer.
+ for (l1 = this->cycling_items, l2 = this->cycling_items_cmp; l1 != NULL || l2 != NULL; l1 = g_list_next(l1), l2 = g_list_next(l2)) {
+ if ((l1 !=NULL && l2 == NULL) || (l1 == NULL && l2 != NULL) || (l1->data != l2->data)) {
+ item_lists_differ = true;
+ break;
+ }
+ }
+
+ /* If list of items under mouse pointer hasn't changed ... */
+ if (!item_lists_differ) {
+ // ... find current item in the freshly built list and continue cycling ...
+ // TODO: This wouldn't be necessary if cycling_cur_item pointed to an element of cycling_items_cmp instead
+ this->cycling_cur_item = g_list_find(this->cycling_items, tmp_cur_item);
+ g_assert(this->cycling_cur_item != NULL || this->cycling_items == NULL);
+ } else {
+ // ... otherwise reset opacities for outdated items ...
+ Inkscape::DrawingItem *arenaitem;
+
+ for(GList *l = this->cycling_items_cmp; l != NULL; l = l->next) {
+ arenaitem = SP_ITEM(l->data)->get_arenaitem(desktop->dkey);
+ arenaitem->setOpacity(1.0);
+ //if (!shift_pressed && !g_list_find(this->cycling_items_selected_before, SP_ITEM(l->data)) && selection->includes(SP_ITEM(l->data)))
+ if (!g_list_find(this->cycling_items_selected_before, SP_ITEM(l->data)) && selection->includes(SP_ITEM(l->data))) {
+ selection->remove(SP_ITEM(l->data));
+ }
+ }
+
+ // ... clear the lists ...
+ g_list_free(this->cycling_items_cmp);
+ g_list_free(this->cycling_items_selected_before);
+
+ this->cycling_items_cmp = NULL;
+ this->cycling_items_selected_before = NULL;
+ this->cycling_cur_item = NULL;
+
+ // ... and rebuild them with the new items.
+ this->cycling_items_cmp = g_list_copy(this->cycling_items);
+ SPItem *item;
+
+ for(GList *l = this->cycling_items; l != NULL; l = l->next) {
+ item = SP_ITEM(l->data);
+ arenaitem = item->get_arenaitem(desktop->dkey);
+ arenaitem->setOpacity(0.3);
+
+ if (selection->includes(item)) {
+ // already selected items are stored separately, too
+ this->cycling_items_selected_before = g_list_append(this->cycling_items_selected_before, item);
+ }
+ }
+
+ // set the current item to the bottommost one so that the cycling step below re-starts at the top
+ this->cycling_cur_item = g_list_last(this->cycling_items);
+ }
+
+ this->cycling_wrap = prefs->getBool("/options/selection/cycleWrap", true);
+
+ // Cycle through the items underneath the mouse pointer, one-by-one
+ this->sp_select_context_cycle_through_items(selection, scroll_event, shift_pressed);
+
+ ret = TRUE;
+
+ GtkWindow *w =GTK_WINDOW(gtk_widget_get_toplevel( GTK_WIDGET(desktop->canvas) ));
+ if (w)
+ {
+ gtk_window_present(w);
+ gtk_widget_grab_focus (GTK_WIDGET(desktop->canvas));
+ }
+ }
+ break;
+ }
+
+ case GDK_KEY_PRESS: // keybindings for select context
+ {
+ {
+ guint keyval = get_group0_keyval(&event->key);
+
+ bool alt = ( MOD__ALT(event)
+ || (keyval == GDK_KEY_Alt_L)
+ || (keyval == GDK_KEY_Alt_R)
+ || (keyval == GDK_KEY_Meta_L)
+ || (keyval == GDK_KEY_Meta_R));
+
+ if (!key_is_a_modifier (keyval)) {
+ this->defaultMessageContext()->clear();
+ } else if (this->grabbed || _seltrans->isGrabbed()) {
+ if (Inkscape::Rubberband::get(desktop)->is_started()) {
+ // if Alt then change cursor to moving cursor:
+ if (alt) {
+ Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH);
+ }
+ } else {
+ // do not change the statusbar text when mousekey is down to move or transform the object,
+ // because the statusbar text is already updated somewhere else.
+ break;
+ }
+ } else {
+ sp_event_show_modifier_tip (this->defaultMessageContext(), event,
+ _("<b>Ctrl</b>: click to select in groups; drag to move hor/vert"),
+ _("<b>Shift</b>: click to toggle select; drag for rubberband selection"),
+ _("<b>Alt</b>: click to select under; scroll mouse-wheel to cycle-select; drag to move selected or select by touch"));
+
+ // if Alt and nonempty selection, show moving cursor ("move selected"):
+ if (alt && !selection->isEmpty() && !desktop->isWaitingCursor()) {
+ GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop)));
+
+ gdk_window_set_cursor(window, CursorSelectDragging);
+ }
+ //*/
+ break;
+ }
+ }
+
+ gdouble const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px
+ gdouble const offset = prefs->getDoubleLimited("/options/defaultscale/value", 2, 0, 1000, "px");
+ int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
+
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Left: // move selection left
+ case GDK_KEY_KP_Left:
+ if (!MOD__CTRL(event)) { // not ctrl
+ gint mul = 1 + gobble_key_events( get_group0_keyval(&event->key), 0); // with any mask
+
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) {
+ sp_selection_move_screen(sp_desktop_selection(desktop), mul*-10, 0); // shift
+ } else {
+ sp_selection_move_screen(sp_desktop_selection(desktop), mul*-1, 0); // no shift
+ }
+ } else { // no alt
+ if (MOD__SHIFT(event)) {
+ sp_selection_move(sp_desktop_selection(desktop), mul*-10*nudge, 0); // shift
+ } else {
+ sp_selection_move(sp_desktop_selection(desktop), mul*-nudge, 0); // no shift
+ }
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Up: // move selection up
+ case GDK_KEY_KP_Up:
+ if (!MOD__CTRL(event)) { // not ctrl
+ gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask
+
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) {
+ sp_selection_move_screen(sp_desktop_selection(desktop), 0, mul*10); // shift
+ } else {
+ sp_selection_move_screen(sp_desktop_selection(desktop), 0, mul*1); // no shift
+ }
+ } else { // no alt
+ if (MOD__SHIFT(event)) {
+ sp_selection_move(sp_desktop_selection(desktop), 0, mul*10*nudge); // shift
+ } else {
+ sp_selection_move(sp_desktop_selection(desktop), 0, mul*nudge); // no shift
+ }
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Right: // move selection right
+ case GDK_KEY_KP_Right:
+ if (!MOD__CTRL(event)) { // not ctrl
+ gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask
+
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) {
+ sp_selection_move_screen(sp_desktop_selection(desktop), mul*10, 0); // shift
+ } else {
+ sp_selection_move_screen(sp_desktop_selection(desktop), mul*1, 0); // no shift
+ }
+ } else { // no alt
+ if (MOD__SHIFT(event)) {
+ sp_selection_move(sp_desktop_selection(desktop), mul*10*nudge, 0); // shift
+ } else {
+ sp_selection_move(sp_desktop_selection(desktop), mul*nudge, 0); // no shift
+ }
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Down: // move selection down
+ case GDK_KEY_KP_Down:
+ if (!MOD__CTRL(event)) { // not ctrl
+ gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask
+
+ if (MOD__ALT(event)) { // alt
+ if (MOD__SHIFT(event)) {
+ sp_selection_move_screen(sp_desktop_selection(desktop), 0, mul*-10); // shift
+ } else {
+ sp_selection_move_screen(sp_desktop_selection(desktop), 0, mul*-1); // no shift
+ }
+ } else { // no alt
+ if (MOD__SHIFT(event)) {
+ sp_selection_move(sp_desktop_selection(desktop), 0, mul*-10*nudge); // shift
+ } else {
+ sp_selection_move(sp_desktop_selection(desktop), 0, mul*-nudge); // no shift
+ }
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Escape:
+ if (!this->sp_select_context_abort()) {
+ selection->clear();
+ }
+
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_a:
+ case GDK_KEY_A:
+ if (MOD__CTRL_ONLY(event)) {
+ sp_edit_select_all(desktop);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_space:
+ /* stamping mode: show outline mode moving */
+ /* FIXME: Is next condition ok? (lauris) */
+ if (this->dragging && this->grabbed) {
+ _seltrans->stamp();
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_x:
+ case GDK_KEY_X:
+ if (MOD__ALT_ONLY(event)) {
+ desktop->setToolboxFocusTo ("altx");
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_bracketleft:
+ if (MOD__ALT(event)) {
+ gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask
+ sp_selection_rotate_screen(selection, mul*1);
+ } else if (MOD__CTRL(event)) {
+ sp_selection_rotate(selection, 90);
+ } else if (snaps) {
+ sp_selection_rotate(selection, 180.0/snaps);
+ }
+
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_bracketright:
+ if (MOD__ALT(event)) {
+ gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask
+ sp_selection_rotate_screen(selection, -1*mul);
+ } else if (MOD__CTRL(event)) {
+ sp_selection_rotate(selection, -90);
+ } else if (snaps) {
+ sp_selection_rotate(selection, -180.0/snaps);
+ }
+
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_less:
+ case GDK_KEY_comma:
+ if (MOD__ALT(event)) {
+ gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask
+ sp_selection_scale_screen(selection, -2*mul);
+ } else if (MOD__CTRL(event)) {
+ sp_selection_scale_times(selection, 0.5);
+ } else {
+ gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask
+ sp_selection_scale(selection, -offset*mul);
+ }
+
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_greater:
+ case GDK_KEY_period:
+ if (MOD__ALT(event)) {
+ gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask
+ sp_selection_scale_screen(selection, 2*mul);
+ } else if (MOD__CTRL(event)) {
+ sp_selection_scale_times(selection, 2);
+ } else {
+ gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask
+ sp_selection_scale(selection, offset*mul);
+ }
+
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_Return:
+ if (MOD__CTRL_ONLY(event)) {
+ if (selection->singleItem()) {
+ SPItem *clicked_item = selection->singleItem();
+
+ if ( SP_IS_GROUP(clicked_item) || SP_IS_BOX3D(clicked_item)) { // enter group or a 3D box
+ desktop->setCurrentLayer(reinterpret_cast<SPObject *>(clicked_item));
+ sp_desktop_selection(desktop)->clear();
+ } else {
+ this->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selected object is not a group. Cannot enter."));
+ }
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_BackSpace:
+ if (MOD__CTRL_ONLY(event)) {
+ sp_select_context_up_one_layer(desktop);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_s:
+ case GDK_KEY_S:
+ if (MOD__SHIFT_ONLY(event)) {
+ if (!selection->isEmpty()) {
+ _seltrans->increaseState();
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_g:
+ case GDK_KEY_G:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_selection_to_guides(desktop);
+ ret = true;
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+ case GDK_KEY_RELEASE: {
+ guint keyval = get_group0_keyval(&event->key);
+ if (key_is_a_modifier (keyval)) {
+ this->defaultMessageContext()->clear();
+ }
+
+ bool alt = ( MOD__ALT(event)
+ || (keyval == GDK_KEY_Alt_L)
+ || (keyval == GDK_KEY_Alt_R)
+ || (keyval == GDK_KEY_Meta_L)
+ || (keyval == GDK_KEY_Meta_R));
+
+ if (Inkscape::Rubberband::get(desktop)->is_started()) {
+ // if Alt then change cursor to moving cursor:
+ if (alt) {
+ Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_RECT);
+ }
+ } else {
+ if (alt) { // TODO: Should we have a variable like is_cycling or is it harmless to run this piece of code each time?
+ // quit cycle-selection and reset opacities
+ if (is_cycling)
+ {
+ sp_select_context_reset_opacities(this);
+ is_cycling = false;
+ }
+
+ }
+ }
+
+ }
+ // set cursor to default.
+ if (!desktop->isWaitingCursor()) {
+ // Do we need to reset the cursor here on key release ?
+ //GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop)));
+ //gdk_window_set_cursor(window, event_context->cursor);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+}
+}
+}
+
+/*
+ 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/tools/select-tool.h b/src/ui/tools/select-tool.h
new file mode 100644
index 000000000..b26fc03bc
--- /dev/null
+++ b/src/ui/tools/select-tool.h
@@ -0,0 +1,73 @@
+#ifndef __SP_SELECT_CONTEXT_H__
+#define __SP_SELECT_CONTEXT_H__
+
+/*
+ * Select tool
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 1999-2002 authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/tools/tool-base.h"
+#include <gtk/gtk.h>
+
+#define SP_SELECT_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::SelectTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_SELECT_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::SelectTool*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+struct SPCanvasItem;
+
+namespace Inkscape {
+ class MessageContext;
+ class SelTrans;
+ class SelectionDescriber;
+}
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class SelectTool : public ToolBase {
+public:
+ SelectTool();
+ virtual ~SelectTool();
+
+ guint dragging : 1;
+ guint moved : 1;
+ bool button_press_shift;
+ bool button_press_ctrl;
+ bool button_press_alt;
+
+ GList *cycling_items;
+ GList *cycling_items_cmp;
+ GList *cycling_items_selected_before;
+ GList *cycling_cur_item;
+ bool cycling_wrap;
+
+ SPItem *item;
+ SPCanvasItem *grabbed;
+ Inkscape::SelTrans *_seltrans;
+ Inkscape::SelectionDescriber *_describer;
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual void set(const Inkscape::Preferences::Entry& val);
+ virtual bool root_handler(GdkEvent* event);
+ virtual bool item_handler(SPItem* item, GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+private:
+ bool sp_select_context_abort();
+ void sp_select_context_cycle_through_items(Inkscape::Selection *selection, GdkEventScroll *scroll_event, bool shift_pressed);
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/ui/tools/spiral-tool.cpp b/src/ui/tools/spiral-tool.cpp
new file mode 100644
index 000000000..3199ee06e
--- /dev/null
+++ b/src/ui/tools/spiral-tool.cpp
@@ -0,0 +1,467 @@
+/*
+ * Spiral drawing context
+ *
+ * Authors:
+ * Mitsuru Oka
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 1999-2001 Lauris Kaplinski
+ * Copyright (C) 2001-2002 Mitsuru Oka
+ *
+ * Released under GNU GPL
+ */
+
+#include "config.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <cstring>
+#include <string>
+
+#include "macros.h"
+#include "display/sp-canvas.h"
+#include "sp-spiral.h"
+#include "document.h"
+#include "document-undo.h"
+#include "sp-namedview.h"
+#include "selection.h"
+#include "desktop-handles.h"
+#include "snap.h"
+#include "desktop.h"
+#include "desktop-style.h"
+#include "message-context.h"
+#include "pixmaps/cursor-spiral.xpm"
+#include "ui/tools/spiral-tool.h"
+#include <glibmm/i18n.h>
+#include "xml/repr.h"
+#include "xml/node-event-vector.h"
+#include "preferences.h"
+#include "context-fns.h"
+#include "shape-editor.h"
+#include "verbs.h"
+#include "display/sp-canvas-item.h"
+
+using Inkscape::DocumentUndo;
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+namespace {
+ ToolBase* createSpiralContext() {
+ return new SpiralTool();
+ }
+
+ bool spiralContextRegistered = ToolFactory::instance().registerObject("/tools/shapes/spiral", createSpiralContext);
+}
+
+const std::string& SpiralTool::getPrefsPath() {
+ return SpiralTool::prefsPath;
+}
+
+const std::string SpiralTool::prefsPath = "/tools/shapes/spiral";
+
+SpiralTool::SpiralTool() : ToolBase() {
+ this->cursor_shape = cursor_spiral_xpm;
+ this->hot_x = 4;
+ this->hot_y = 4;
+ this->xp = 0;
+ this->yp = 0;
+ this->tolerance = 0;
+ this->within_tolerance = false;
+ this->item_to_select = NULL;
+
+ this->spiral = NULL;
+
+ this->revo = 3.0;
+ this->exp = 1.0;
+ this->t0 = 0.0;
+}
+
+void SpiralTool::finish() {
+ SPDesktop *desktop = this->desktop;
+
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME);
+
+ this->finishItem();
+ this->sel_changed_connection.disconnect();
+
+ ToolBase::finish();
+}
+
+SpiralTool::~SpiralTool() {
+ this->enableGrDrag(false);
+
+ this->sel_changed_connection.disconnect();
+
+ delete this->shape_editor;
+ this->shape_editor = NULL;
+
+ /* fixme: This is necessary because we do not grab */
+ if (this->spiral) {
+ this->finishItem();
+ }
+}
+
+/**
+ * Callback that processes the "changed" signal on the selection;
+ * destroys old and creates new knotholder.
+ */
+void SpiralTool::selection_changed(Inkscape::Selection *selection) {
+ this->shape_editor->unset_item(SH_KNOTHOLDER);
+ this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER);
+}
+
+void SpiralTool::setup() {
+ ToolBase::setup();
+
+ sp_event_context_read(this, "expansion");
+ sp_event_context_read(this, "revolution");
+ sp_event_context_read(this, "t0");
+
+ this->shape_editor = new ShapeEditor(this->desktop);
+
+ SPItem *item = sp_desktop_selection(this->desktop)->singleItem();
+ if (item) {
+ this->shape_editor->set_item(item, SH_KNOTHOLDER);
+ }
+
+ Inkscape::Selection *selection = sp_desktop_selection(this->desktop);
+ this->sel_changed_connection.disconnect();
+
+ this->sel_changed_connection = selection->connectChanged(sigc::mem_fun(this, &SpiralTool::selection_changed));
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (prefs->getBool("/tools/shapes/selcue")) {
+ this->enableSelectionCue();
+ }
+
+ if (prefs->getBool("/tools/shapes/gradientdrag")) {
+ this->enableGrDrag();
+ }
+}
+
+void SpiralTool::set(const Inkscape::Preferences::Entry& val) {
+ Glib::ustring name = val.getEntryName();
+
+ if (name == "expansion") {
+ this->exp = CLAMP(val.getDouble(), 0.0, 1000.0);
+ } else if (name == "revolution") {
+ this->revo = CLAMP(val.getDouble(3.0), 0.05, 40.0);
+ } else if (name == "t0") {
+ this->t0 = CLAMP(val.getDouble(), 0.0, 0.999);
+ }
+}
+
+bool SpiralTool::root_handler(GdkEvent* event) {
+ static gboolean dragging;
+
+ SPDesktop *desktop = this->desktop;
+ Inkscape::Selection *selection = sp_desktop_selection (desktop);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+ dragging = TRUE;
+
+ this->center = Inkscape::setup_for_drag_start(desktop, this, event);
+
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+ m.freeSnapReturnByRef(this->center, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ m.unSetup();
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ ( GDK_KEY_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_POINTER_MOTION_HINT_MASK |
+ GDK_BUTTON_PRESS_MASK ),
+ NULL, event->button.time);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ if (dragging && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) {
+ if ( this->within_tolerance
+ && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance )
+ && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) {
+ break; // do not drag if we're within tolerance from origin
+ }
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to draw, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ this->within_tolerance = false;
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point motion_dt(this->desktop->w2d(motion_w));
+
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop, true, this->spiral);
+ m.freeSnapReturnByRef(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ m.unSetup();
+
+ this->drag(motion_dt, event->motion.state);
+
+ gobble_motion_events(GDK_BUTTON1_MASK);
+
+ ret = TRUE;
+ } else if (!sp_event_context_knot_mouseover(this)) {
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point motion_dt(desktop->w2d(motion_w));
+ m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE));
+ m.unSetup();
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ this->xp = this->yp = 0;
+ if (event->button.button == 1 && !this->space_panning) {
+ dragging = FALSE;
+ sp_event_context_discard_delayed_snap_event(this);
+
+ if (!this->within_tolerance) {
+ // we've been dragging, finish the spiral
+ this->finishItem();
+ } else if (this->item_to_select) {
+ // no dragging, select clicked item if any
+ if (event->button.state & GDK_SHIFT_MASK) {
+ selection->toggle(this->item_to_select);
+ } else {
+ selection->set(this->item_to_select);
+ }
+ } else {
+ // click in an empty space
+ selection->clear();
+ }
+
+ this->item_to_select = NULL;
+ ret = TRUE;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
+ }
+ break;
+
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval(&event->key)) {
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine)
+ case GDK_KEY_Meta_R:
+ sp_event_show_modifier_tip(this->defaultMessageContext(), event,
+ _("<b>Ctrl</b>: snap angle"),
+ NULL,
+ _("<b>Alt</b>: lock spiral radius"));
+ break;
+ case GDK_KEY_Up:
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_Down:
+ // prevent the zoom field from activation
+ if (!MOD__CTRL_ONLY(event))
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_x:
+ case GDK_KEY_X:
+ if (MOD__ALT_ONLY(event)) {
+ desktop->setToolboxFocusTo ("altx-spiral");
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Escape:
+ if (dragging) {
+ dragging = false;
+ sp_event_context_discard_delayed_snap_event(this);
+ // if drawing, cancel, otherwise pass it up for deselecting
+ this->cancel();
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_space:
+ if (dragging) {
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
+ event->button.time);
+ dragging = false;
+ sp_event_context_discard_delayed_snap_event(this);
+
+ if (!this->within_tolerance) {
+ // we've been dragging, finish the spiral
+ this->finish();
+ }
+ // do not return true, so that space would work switching to selector
+ }
+ break;
+
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ case GDK_KEY_BackSpace:
+ ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event));
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case GDK_KEY_RELEASE:
+ switch (get_group0_keyval(&event->key)) {
+ case GDK_KEY_Alt_L:
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt
+ case GDK_KEY_Meta_R:
+ this->defaultMessageContext()->clear();
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+void SpiralTool::drag(Geom::Point const &p, guint state) {
+ SPDesktop *desktop = SP_EVENT_CONTEXT(this)->desktop;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
+
+ if (!this->spiral) {
+ if (Inkscape::have_viable_layer(desktop, this->message_context) == false) {
+ return;
+ }
+
+ // Create object
+ Inkscape::XML::Document *xml_doc = SP_EVENT_CONTEXT_DOCUMENT(this)->getReprDoc();
+ Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
+ repr->setAttribute("sodipodi:type", "spiral");
+
+ // Set style
+ sp_desktop_apply_style_tool(desktop, repr, "/tools/shapes/spiral", false);
+
+ this->spiral = SP_SPIRAL(desktop->currentLayer()->appendChildRepr(repr));
+ Inkscape::GC::release(repr);
+ this->spiral->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
+ this->spiral->updateRepr();
+
+ desktop->canvas->forceFullRedrawAfterInterruptions(5);
+ }
+
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop, true, this->spiral);
+ Geom::Point pt2g = p;
+ m.freeSnapReturnByRef(pt2g, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ m.unSetup();
+ Geom::Point const p0 = desktop->dt2doc(this->center);
+ Geom::Point const p1 = desktop->dt2doc(pt2g);
+
+ Geom::Point const delta = p1 - p0;
+ gdouble const rad = Geom::L2(delta);
+
+ gdouble arg = Geom::atan2(delta) - 2.0*M_PI*this->spiral->revo;
+
+ if (state & GDK_CONTROL_MASK) {
+ arg = sp_round(arg, M_PI/snaps);
+ }
+
+ /* Fixme: these parameters should be got from dialog box */
+ this->spiral->setPosition(p0[Geom::X], p0[Geom::Y],
+ /*expansion*/ this->exp,
+ /*revolution*/ this->revo,
+ rad, arg,
+ /*t0*/ this->t0);
+
+ /* status text */
+ Inkscape::Util::Quantity q = Inkscape::Util::Quantity(rad, "px");
+ GString *rads = g_string_new(q.string(desktop->namedview->doc_units).c_str());
+ this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE,
+ _("<b>Spiral</b>: radius %s, angle %5g&#176;; with <b>Ctrl</b> to snap angle"),
+ rads->str, sp_round((arg + 2.0*M_PI*this->spiral->revo)*180/M_PI, 0.0001));
+ g_string_free(rads, FALSE);
+}
+
+void SpiralTool::finishItem() {
+ this->message_context->clear();
+
+ if (this->spiral != NULL) {
+ if (this->spiral->rad == 0) {
+ this->cancel(); // Don't allow the creating of zero sized spiral, for example when the start and and point snap to the snap grid point
+ return;
+ }
+
+ spiral->set_shape();
+ spiral->updateRepr(SP_OBJECT_WRITE_EXT);
+ spiral->doWriteTransform(spiral->getRepr(), spiral->transform, NULL, true);
+
+ this->desktop->canvas->endForcedFullRedraws();
+
+ sp_desktop_selection(this->desktop)->set(this->spiral);
+ DocumentUndo::done(sp_desktop_document(this->desktop), SP_VERB_CONTEXT_SPIRAL, _("Create spiral"));
+
+ this->spiral = NULL;
+ }
+}
+
+void SpiralTool::cancel() {
+ sp_desktop_selection(this->desktop)->clear();
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(this->desktop->acetate), 0);
+
+ if (this->spiral != NULL) {
+ this->spiral->deleteObject();
+ this->spiral = NULL;
+ }
+
+ this->within_tolerance = false;
+ this->xp = 0;
+ this->yp = 0;
+ this->item_to_select = NULL;
+
+ this->desktop->canvas->endForcedFullRedraws();
+
+ DocumentUndo::cancel(sp_desktop_document(this->desktop));
+}
+
+}
+}
+}
+
+/*
+ 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/tools/spiral-tool.h b/src/ui/tools/spiral-tool.h
new file mode 100644
index 000000000..416aeb7e4
--- /dev/null
+++ b/src/ui/tools/spiral-tool.h
@@ -0,0 +1,66 @@
+#ifndef __SP_SPIRAL_CONTEXT_H__
+#define __SP_SPIRAL_CONTEXT_H__
+
+/** \file
+ * Spiral drawing context
+ */
+/*
+ * Authors:
+ * Mitsuru Oka
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 1999-2001 Lauris Kaplinski
+ * Copyright (C) 2001-2002 Mitsuru Oka
+ *
+ * Released under GNU GPL
+ */
+
+#include <gtk/gtk.h>
+#include <stddef.h>
+#include <sigc++/sigc++.h>
+#include <2geom/point.h>
+#include "ui/tools/tool-base.h"
+
+#include "sp-spiral.h"
+
+#define SP_SPIRAL_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::SpiralTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_SPIRAL_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::SpiralTool*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class SpiralTool : public ToolBase {
+public:
+ SpiralTool();
+ virtual ~SpiralTool();
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual void finish();
+ virtual void set(const Inkscape::Preferences::Entry& val);
+ virtual bool root_handler(GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+private:
+ SPSpiral * spiral;
+ Geom::Point center;
+ gdouble revo;
+ gdouble exp;
+ gdouble t0;
+
+ sigc::connection sel_changed_connection;
+
+ void drag(Geom::Point const &p, guint state);
+ void finishItem();
+ void cancel();
+ void selection_changed(Inkscape::Selection *selection);
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/ui/tools/spray-tool.cpp b/src/ui/tools/spray-tool.cpp
new file mode 100644
index 000000000..0ded1e44b
--- /dev/null
+++ b/src/ui/tools/spray-tool.cpp
@@ -0,0 +1,890 @@
+/*
+ * Spray Tool
+ *
+ * Authors:
+ * Pierre-Antoine MARC
+ * Pierre CACLIN
+ * Aurel-Aimé MARMION
+ * Julien LERAY
+ * Benoît LAVORATA
+ * Vincent MONTAGNE
+ * Pierre BARBRY-BLOT
+ * Steren GIANNINI (steren.giannini@gmail.com)
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2009 authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "config.h"
+
+#include <numeric>
+
+#include "ui/dialog/dialog-manager.h"
+
+#include "svg/svg.h"
+
+#include <glib.h>
+#include "macros.h"
+#include "document.h"
+#include "document-undo.h"
+#include "selection.h"
+#include "desktop.h"
+#include "desktop-events.h"
+#include "desktop-handles.h"
+#include "message-context.h"
+#include "pixmaps/cursor-spray.xpm"
+#include <boost/optional.hpp>
+#include "xml/repr.h"
+#include "context-fns.h"
+#include "sp-item.h"
+#include "inkscape.h"
+
+#include "splivarot.h"
+#include "sp-item-group.h"
+#include "sp-shape.h"
+#include "sp-path.h"
+#include "path-chemistry.h"
+
+#include "sp-text.h"
+#include "sp-flowtext.h"
+#include "display/sp-canvas.h"
+#include "display/canvas-bpath.h"
+#include "display/canvas-arena.h"
+#include "display/curve.h"
+#include "livarot/Shape.h"
+#include <2geom/circle.h>
+#include <2geom/transforms.h>
+#include "preferences.h"
+#include "style.h"
+#include "box3d.h"
+#include "sp-item-transform.h"
+#include "filter-chemistry.h"
+
+#include "ui/tools/spray-tool.h"
+#include "helper/action.h"
+#include "verbs.h"
+
+#include <iostream>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glibmm/i18n.h>
+
+using Inkscape::DocumentUndo;
+using namespace std;
+
+#define DDC_RED_RGBA 0xff0000ff
+#define DYNA_MIN_WIDTH 1.0e-6
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+namespace {
+ ToolBase* createSprayContext() {
+ return new SprayTool();
+ }
+
+ bool sprayContextRegistered = ToolFactory::instance().registerObject("/tools/spray", createSprayContext);
+}
+
+const std::string& SprayTool::getPrefsPath() {
+ return SprayTool::prefsPath;
+}
+
+const std::string SprayTool::prefsPath = "/tools/spray";
+
+/**
+ * This function returns pseudo-random numbers from a normal distribution
+ * @param mu : mean
+ * @param sigma : standard deviation ( > 0 )
+ */
+inline double NormalDistribution(double mu, double sigma)
+{
+ // use Box Muller's algorithm
+ return mu + sigma * sqrt( -2.0 * log(g_random_double_range(0, 1)) ) * cos( 2.0*M_PI*g_random_double_range(0, 1) );
+}
+
+/* Method to rotate items */
+static void sp_spray_rotate_rel(Geom::Point c, SPDesktop */*desktop*/, SPItem *item, Geom::Rotate const &rotation)
+{
+ Geom::Translate const s(c);
+ Geom::Affine affine = Geom::Affine(s).inverse() * Geom::Affine(rotation) * Geom::Affine(s);
+ // Rotate item.
+ item->set_i2d_affine(item->i2dt_affine() * (Geom::Affine)affine);
+ // Use each item's own transform writer, consistent with sp_selection_apply_affine()
+ item->doWriteTransform(item->getRepr(), item->transform);
+ // Restore the center position (it's changed because the bbox center changed)
+ if (item->isCenterSet()) {
+ item->setCenter(c);
+ item->updateRepr();
+ }
+}
+
+/* Method to scale items */
+static void sp_spray_scale_rel(Geom::Point c, SPDesktop */*desktop*/, SPItem *item, Geom::Scale const &scale)
+{
+ Geom::Translate const s(c);
+ item->set_i2d_affine(item->i2dt_affine() * s.inverse() * scale * s);
+ item->doWriteTransform(item->getRepr(), item->transform);
+}
+
+SprayTool::SprayTool() : ToolBase() {
+ this->usetilt = 0;
+ this->dilate_area = 0;
+ this->usetext = false;
+ this->population = 0;
+ this->is_drawing = false;
+ this->mode = 0;
+ this->usepressure = 0;
+
+ this->cursor_shape = cursor_spray_xpm;
+ this->hot_x = 4;
+ this->hot_y = 4;
+
+ /* attributes */
+ this->dragging = FALSE;
+ this->distrib = 1;
+ this->width = 0.2;
+ this->force = 0.2;
+ this->ratio = 0;
+ this->tilt = 0;
+ this->mean = 0.2;
+ this->rotation_variation = 0;
+ this->standard_deviation = 0.2;
+ this->scale = 1;
+ this->scale_variation = 1;
+ this->pressure = TC_DEFAULT_PRESSURE;
+
+ this->is_dilating = false;
+ this->has_dilated = false;
+}
+
+SprayTool::~SprayTool() {
+ this->enableGrDrag(false);
+ this->style_set_connection.disconnect();
+
+ if (this->dilate_area) {
+ sp_canvas_item_destroy(this->dilate_area);
+ this->dilate_area = NULL;
+ }
+}
+
+static bool is_transform_modes(gint mode)
+{
+ return (mode == SPRAY_MODE_COPY ||
+ mode == SPRAY_MODE_CLONE ||
+ mode == SPRAY_MODE_SINGLE_PATH ||
+ mode == SPRAY_OPTION);
+}
+
+void SprayTool::update_cursor(bool /*with_shift*/) {
+ guint num = 0;
+ gchar *sel_message = NULL;
+
+ if (!desktop->selection->isEmpty()) {
+ num = g_slist_length(const_cast<GSList *>(desktop->selection->itemList()));
+ sel_message = g_strdup_printf(ngettext("<b>%i</b> object selected","<b>%i</b> objects selected",num), num);
+ } else {
+ sel_message = g_strdup_printf("%s", _("<b>Nothing</b> selected"));
+ }
+
+ switch (this->mode) {
+ case SPRAY_MODE_COPY:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray <b>copies</b> of the initial selection."), sel_message);
+ break;
+ case SPRAY_MODE_CLONE:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray <b>clones</b> of the initial selection."), sel_message);
+ break;
+ case SPRAY_MODE_SINGLE_PATH:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray in a <b>single path</b> of the initial selection."), sel_message);
+ break;
+ default:
+ break;
+ }
+
+ this->sp_event_context_update_cursor();
+ g_free(sel_message);
+}
+
+void SprayTool::setup() {
+ ToolBase::setup();
+
+ {
+ /* TODO: have a look at sp_dyna_draw_context_setup where the same is done.. generalize? at least make it an arcto! */
+ Geom::PathVector path;
+ Geom::Circle(0, 0, 1).getPath(path);
+
+ SPCurve *c = new SPCurve(path);
+
+ this->dilate_area = sp_canvas_bpath_new(sp_desktop_controls(this->desktop), c);
+ c->unref();
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->dilate_area), 0x00000000,(SPWindRule)0);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->dilate_area), 0xff9900ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_item_hide(this->dilate_area);
+ }
+
+ this->is_drawing = false;
+
+ sp_event_context_read(this, "distrib");
+ sp_event_context_read(this, "width");
+ sp_event_context_read(this, "ratio");
+ sp_event_context_read(this, "tilt");
+ sp_event_context_read(this, "rotation_variation");
+ sp_event_context_read(this, "scale_variation");
+ sp_event_context_read(this, "mode");
+ sp_event_context_read(this, "population");
+ sp_event_context_read(this, "force");
+ sp_event_context_read(this, "mean");
+ sp_event_context_read(this, "standard_deviation");
+ sp_event_context_read(this, "usepressure");
+ sp_event_context_read(this, "Scale");
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/spray/selcue")) {
+ this->enableSelectionCue();
+ }
+ if (prefs->getBool("/tools/spray/gradientdrag")) {
+ this->enableGrDrag();
+ }
+}
+
+void SprayTool::set(const Inkscape::Preferences::Entry& val) {
+ Glib::ustring path = val.getEntryName();
+
+ if (path == "mode") {
+ this->mode = val.getInt();
+ this->update_cursor(false);
+ } else if (path == "width") {
+ this->width = 0.01 * CLAMP(val.getInt(10), 1, 100);
+ } else if (path == "usepressure") {
+ this->usepressure = val.getBool();
+ } else if (path == "population") {
+ this->population = 0.01 * CLAMP(val.getInt(10), 1, 100);
+ } else if (path == "rotation_variation") {
+ this->rotation_variation = CLAMP(val.getDouble(0.0), 0, 100.0);
+ } else if (path == "scale_variation") {
+ this->scale_variation = CLAMP(val.getDouble(1.0), 0, 100.0);
+ } else if (path == "standard_deviation") {
+ this->standard_deviation = 0.01 * CLAMP(val.getInt(10), 1, 100);
+ } else if (path == "mean") {
+ this->mean = 0.01 * CLAMP(val.getInt(10), 1, 100);
+// Not implemented in the toolbar and preferences yet
+ } else if (path == "distribution") {
+ this->distrib = val.getInt(1);
+ } else if (path == "tilt") {
+ this->tilt = CLAMP(val.getDouble(0.1), 0, 1000.0);
+ } else if (path == "ratio") {
+ this->ratio = CLAMP(val.getDouble(), 0.0, 0.9);
+ } else if (path == "force") {
+ this->force = CLAMP(val.getDouble(1.0), 0, 1.0);
+ }
+}
+
+static void sp_spray_extinput(SprayTool *tc, GdkEvent *event)
+{
+ if (gdk_event_get_axis(event, GDK_AXIS_PRESSURE, &tc->pressure)) {
+ tc->pressure = CLAMP(tc->pressure, TC_MIN_PRESSURE, TC_MAX_PRESSURE);
+ } else {
+ tc->pressure = TC_DEFAULT_PRESSURE;
+ }
+}
+
+static double get_dilate_radius(SprayTool *tc)
+{
+ return 250 * tc->width/SP_EVENT_CONTEXT(tc)->desktop->current_zoom();
+}
+
+static double get_path_force(SprayTool *tc)
+{
+ double force = 8 * (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE)
+ /sqrt(SP_EVENT_CONTEXT(tc)->desktop->current_zoom());
+ if (force > 3) {
+ force += 4 * (force - 3);
+ }
+ return force * tc->force;
+}
+
+static double get_path_mean(SprayTool *tc)
+{
+ return tc->mean;
+}
+
+static double get_path_standard_deviation(SprayTool *tc)
+{
+ return tc->standard_deviation;
+}
+
+static double get_move_force(SprayTool *tc)
+{
+ double force = (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE);
+ return force * tc->force;
+}
+
+static double get_move_mean(SprayTool *tc)
+{
+ return tc->mean;
+}
+
+static double get_move_standard_deviation(SprayTool *tc)
+{
+ return tc->standard_deviation;
+}
+
+/**
+ * Method to handle the distribution of the items
+ * @param[out] radius : radius of the position of the sprayed object
+ * @param[out] angle : angle of the position of the sprayed object
+ * @param[in] a : mean
+ * @param[in] s : standard deviation
+ * @param[in] choice :
+
+ */
+static void random_position(double &radius, double &angle, double &a, double &s, int /*choice*/)
+{
+ // angle is taken from an uniform distribution
+ angle = g_random_double_range(0, M_PI*2.0);
+
+ // radius is taken from a Normal Distribution
+ double radius_temp =-1;
+ while(!((radius_temp >= 0) && (radius_temp <=1 )))
+ {
+ radius_temp = NormalDistribution(a, s);
+ }
+ // Because we are in polar coordinates, a special treatment has to be done to the radius.
+ // Otherwise, positions taken from an uniform repartition on radius and angle will not seam to
+ // be uniformily distributed on the disk (more at the center and less at the boundary).
+ // We counter this effect with a 0.5 exponent. This is empiric.
+ radius = pow(radius_temp, 0.5);
+
+}
+
+static bool sp_spray_recursive(SPDesktop *desktop,
+ Inkscape::Selection *selection,
+ SPItem *item,
+ Geom::Point p,
+ Geom::Point /*vector*/,
+ gint mode,
+ double radius,
+ double /*force*/,
+ double population,
+ double &scale,
+ double scale_variation,
+ bool /*reverse*/,
+ double mean,
+ double standard_deviation,
+ double ratio,
+ double tilt,
+ double rotation_variation,
+ gint _distrib)
+{
+ bool did = false;
+
+ if (SP_IS_BOX3D(item) ) {
+ // convert 3D boxes to ordinary groups before spraying their shapes
+ item = box3d_convert_to_group(SP_BOX3D(item));
+ selection->add(item);
+ }
+
+ double _fid = g_random_double_range(0, 1);
+ double angle = g_random_double_range( - rotation_variation / 100.0 * M_PI , rotation_variation / 100.0 * M_PI );
+ double _scale = g_random_double_range( 1.0 - scale_variation / 100.0, 1.0 + scale_variation / 100.0 );
+ double dr; double dp;
+ random_position( dr, dp, mean, standard_deviation, _distrib );
+ dr=dr*radius;
+
+ if (mode == SPRAY_MODE_COPY) {
+ Geom::OptRect a = item->documentVisualBounds();
+ if (a) {
+ SPItem *item_copied;
+ if(_fid <= population)
+ {
+ // duplicate
+ SPDocument *doc = item->document;
+ Inkscape::XML::Document* xml_doc = doc->getReprDoc();
+ Inkscape::XML::Node *old_repr = item->getRepr();
+ Inkscape::XML::Node *parent = old_repr->parent();
+ Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc);
+ parent->appendChild(copy);
+
+ SPObject *new_obj = doc->getObjectByRepr(copy);
+ item_copied = SP_ITEM(new_obj); //convertion object->item
+ Geom::Point center=item->getCenter();
+ sp_spray_scale_rel(center,desktop, item_copied, Geom::Scale(_scale,_scale));
+ sp_spray_scale_rel(center,desktop, item_copied, Geom::Scale(scale,scale));
+
+ sp_spray_rotate_rel(center,desktop,item_copied, Geom::Rotate(angle));
+ //Move the cursor p
+ Geom::Point move = (Geom::Point(cos(tilt)*cos(dp)*dr/(1-ratio)+sin(tilt)*sin(dp)*dr/(1+ratio), -sin(tilt)*cos(dp)*dr/(1-ratio)+cos(tilt)*sin(dp)*dr/(1+ratio)))+(p-a->midpoint());
+ sp_item_move_rel(item_copied, Geom::Translate(move[Geom::X], -move[Geom::Y]));
+ did = true;
+ }
+ }
+ } else if (mode == SPRAY_MODE_SINGLE_PATH) {
+
+ SPItem *father = NULL; //initial Object
+ SPItem *item_copied = NULL; //Projected Object
+ SPItem *unionResult = NULL; //previous union
+ SPItem *son = NULL; //father copy
+
+ int i=1;
+ for (GSList *items = g_slist_copy(const_cast<GSList *>(selection->itemList()));
+ items != NULL;
+ items = items->next) {
+
+ SPItem *item1 = SP_ITEM(items->data);
+ if (i == 1) {
+ father = item1;
+ }
+ if (i == 2) {
+ unionResult = item1;
+ }
+ i++;
+ }
+ SPDocument *doc = father->document;
+ Inkscape::XML::Document* xml_doc = doc->getReprDoc();
+ Inkscape::XML::Node *old_repr = father->getRepr();
+ Inkscape::XML::Node *parent = old_repr->parent();
+
+ Geom::OptRect a = father->documentVisualBounds();
+ if (a) {
+ if (i == 2) {
+ Inkscape::XML::Node *copy1 = old_repr->duplicate(xml_doc);
+ parent->appendChild(copy1);
+ SPObject *new_obj1 = doc->getObjectByRepr(copy1);
+ son = SP_ITEM(new_obj1); // conversion object->item
+ unionResult = son;
+ Inkscape::GC::release(copy1);
+ }
+
+ if (_fid <= population) { // Rules the population of objects sprayed
+ // duplicates the father
+ Inkscape::XML::Node *copy2 = old_repr->duplicate(xml_doc);
+ parent->appendChild(copy2);
+ SPObject *new_obj2 = doc->getObjectByRepr(copy2);
+ item_copied = SP_ITEM(new_obj2);
+
+ // Move around the cursor
+ Geom::Point move = (Geom::Point(cos(tilt)*cos(dp)*dr/(1-ratio)+sin(tilt)*sin(dp)*dr/(1+ratio), -sin(tilt)*cos(dp)*dr/(1-ratio)+cos(tilt)*sin(dp)*dr/(1+ratio)))+(p-a->midpoint());
+
+ Geom::Point center=father->getCenter();
+ sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(_scale, _scale));
+ sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(scale, scale));
+ sp_spray_rotate_rel(center, desktop, item_copied, Geom::Rotate(angle));
+ sp_item_move_rel(item_copied, Geom::Translate(move[Geom::X], -move[Geom::Y]));
+
+ // union and duplication
+ selection->clear();
+ selection->add(item_copied);
+ selection->add(unionResult);
+ sp_selected_path_union_skip_undo(selection, selection->desktop());
+ selection->add(father);
+ Inkscape::GC::release(copy2);
+ did = true;
+ }
+ }
+ } else if (mode == SPRAY_MODE_CLONE) {
+ Geom::OptRect a = item->documentVisualBounds();
+ if (a) {
+ if(_fid <= population) {
+ SPItem *item_copied;
+ SPDocument *doc = item->document;
+ Inkscape::XML::Document* xml_doc = doc->getReprDoc();
+ Inkscape::XML::Node *old_repr = item->getRepr();
+ Inkscape::XML::Node *parent = old_repr->parent();
+
+ // Creation of the clone
+ Inkscape::XML::Node *clone = xml_doc->createElement("svg:use");
+ // Ad the clone to the list of the father's sons
+ parent->appendChild(clone);
+ // Generates the link between father and son attributes
+ gchar *href_str = g_strdup_printf("#%s", old_repr->attribute("id"));
+ clone->setAttribute("xlink:href", href_str, false);
+ g_free(href_str);
+
+ SPObject *clone_object = doc->getObjectByRepr(clone);
+ // conversion object->item
+ item_copied = SP_ITEM(clone_object);
+ Geom::Point center = item->getCenter();
+ sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(_scale, _scale));
+ sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(scale, scale));
+ sp_spray_rotate_rel(center, desktop, item_copied, Geom::Rotate(angle));
+ Geom::Point move = (Geom::Point(cos(tilt)*cos(dp)*dr/(1-ratio)+sin(tilt)*sin(dp)*dr/(1+ratio), -sin(tilt)*cos(dp)*dr/(1-ratio)+cos(tilt)*sin(dp)*dr/(1+ratio)))+(p-a->midpoint());
+ sp_item_move_rel(item_copied, Geom::Translate(move[Geom::X], -move[Geom::Y]));
+
+ Inkscape::GC::release(clone);
+
+ did = true;
+ }
+ }
+ }
+
+ return did;
+}
+
+static bool sp_spray_dilate(SprayTool *tc, Geom::Point /*event_p*/, Geom::Point p, Geom::Point vector, bool reverse)
+{
+ Inkscape::Selection *selection = sp_desktop_selection(SP_EVENT_CONTEXT(tc)->desktop);
+ SPDesktop *desktop = SP_EVENT_CONTEXT(tc)->desktop;
+
+ if (selection->isEmpty()) {
+ return false;
+ }
+
+ bool did = false;
+ double radius = get_dilate_radius(tc);
+ double path_force = get_path_force(tc);
+ if (radius == 0 || path_force == 0) {
+ return false;
+ }
+ double path_mean = get_path_mean(tc);
+ if (radius == 0 || path_mean == 0) {
+ return false;
+ }
+ double path_standard_deviation = get_path_standard_deviation(tc);
+ if (radius == 0 || path_standard_deviation == 0) {
+ return false;
+ }
+ double move_force = get_move_force(tc);
+ double move_mean = get_move_mean(tc);
+ double move_standard_deviation = get_move_standard_deviation(tc);
+
+ for (GSList *items = g_slist_copy(const_cast<GSList *>(selection->itemList()));
+ items != NULL;
+ items = items->next) {
+
+ SPItem *item = SP_ITEM(items->data);
+
+ if (is_transform_modes(tc->mode)) {
+ if (sp_spray_recursive(desktop, selection, item, p, vector, tc->mode, radius, move_force, tc->population, tc->scale, tc->scale_variation, reverse, move_mean, move_standard_deviation, tc->ratio, tc->tilt, tc->rotation_variation, tc->distrib))
+ did = true;
+ } else {
+ if (sp_spray_recursive(desktop, selection, item, p, vector, tc->mode, radius, path_force, tc->population, tc->scale, tc->scale_variation, reverse, path_mean, path_standard_deviation, tc->ratio, tc->tilt, tc->rotation_variation, tc->distrib))
+ did = true;
+ }
+ }
+
+ return did;
+}
+
+static void sp_spray_update_area(SprayTool *tc)
+{
+ double radius = get_dilate_radius(tc);
+ Geom::Affine const sm ( Geom::Scale(radius/(1-tc->ratio), radius/(1+tc->ratio)) );
+ sp_canvas_item_affine_absolute(tc->dilate_area, (sm* Geom::Rotate(tc->tilt))* Geom::Translate(SP_EVENT_CONTEXT(tc)->desktop->point()));
+ sp_canvas_item_show(tc->dilate_area);
+}
+
+static void sp_spray_switch_mode(SprayTool *tc, gint mode, bool with_shift)
+{
+ // select the button mode
+ SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue("spray_tool_mode", mode);
+ // need to set explicitly, because the prefs may not have changed by the previous
+ tc->mode = mode;
+ tc->update_cursor(with_shift);
+}
+
+bool SprayTool::root_handler(GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_ENTER_NOTIFY:
+ sp_canvas_item_show(this->dilate_area);
+ break;
+ case GDK_LEAVE_NOTIFY:
+ sp_canvas_item_hide(this->dilate_area);
+ break;
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+ if (Inkscape::have_viable_layer(desktop, this->message_context) == false) {
+ return TRUE;
+ }
+
+ Geom::Point const motion_w(event->button.x, event->button.y);
+ Geom::Point const motion_dt(desktop->w2d(motion_w));
+ this->last_push = desktop->dt2doc(motion_dt);
+
+ sp_spray_extinput(this, event);
+
+ desktop->canvas->forceFullRedrawAfterInterruptions(3);
+ this->is_drawing = true;
+ this->is_dilating = true;
+ this->has_dilated = false;
+
+ if(this->is_dilating && event->button.button == 1 && !this->space_panning) {
+ sp_spray_dilate(this, motion_w, desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT(event));
+ }
+
+ this->has_dilated = true;
+ ret = TRUE;
+ }
+ break;
+ case GDK_MOTION_NOTIFY: {
+ Geom::Point const motion_w(event->motion.x,
+ event->motion.y);
+ Geom::Point motion_dt(desktop->w2d(motion_w));
+ Geom::Point motion_doc(desktop->dt2doc(motion_dt));
+ sp_spray_extinput(this, event);
+
+ // draw the dilating cursor
+ double radius = get_dilate_radius(this);
+ Geom::Affine const sm (Geom::Scale(radius/(1-this->ratio), radius/(1+this->ratio)) );
+ sp_canvas_item_affine_absolute(this->dilate_area, (sm*Geom::Rotate(this->tilt))*Geom::Translate(desktop->w2d(motion_w)));
+ sp_canvas_item_show(this->dilate_area);
+
+ guint num = 0;
+ if (!desktop->selection->isEmpty()) {
+ num = g_slist_length(const_cast<GSList *>(desktop->selection->itemList()));
+ }
+ if (num == 0) {
+ this->message_context->flash(Inkscape::ERROR_MESSAGE, _("<b>Nothing selected!</b> Select objects to spray."));
+ }
+
+ // dilating:
+ if (this->is_drawing && ( event->motion.state & GDK_BUTTON1_MASK )) {
+ sp_spray_dilate(this, motion_w, motion_doc, motion_doc - this->last_push, event->button.state & GDK_SHIFT_MASK? true : false);
+ //this->last_push = motion_doc;
+ this->has_dilated = true;
+
+ // it's slow, so prevent clogging up with events
+ gobble_motion_events(GDK_BUTTON1_MASK);
+ return TRUE;
+ }
+ }
+ break;
+ /*Spray with the scroll*/
+ case GDK_SCROLL: {
+ if (event->scroll.state & GDK_BUTTON1_MASK) {
+ double temp ;
+ temp = this->population;
+ this->population = 1.0;
+ desktop->setToolboxAdjustmentValue("population", this->population * 100);
+ Geom::Point const scroll_w(event->button.x, event->button.y);
+ Geom::Point const scroll_dt = desktop->point();;
+ Geom::Point motion_doc(desktop->dt2doc(scroll_dt));
+ switch (event->scroll.direction) {
+ case GDK_SCROLL_DOWN:
+ case GDK_SCROLL_UP: {
+ if (Inkscape::have_viable_layer(desktop, this->message_context) == false) {
+ return TRUE;
+ }
+ this->last_push = desktop->dt2doc(scroll_dt);
+ sp_spray_extinput(this, event);
+ desktop->canvas->forceFullRedrawAfterInterruptions(3);
+ this->is_drawing = true;
+ this->is_dilating = true;
+ this->has_dilated = false;
+ if(this->is_dilating && !this->space_panning) {
+ sp_spray_dilate(this, scroll_w, desktop->dt2doc(scroll_dt), Geom::Point(0,0), false);
+ }
+ this->has_dilated = true;
+
+ this->population = temp;
+ desktop->setToolboxAdjustmentValue("population", this->population * 100);
+
+ ret = TRUE;
+ }
+ break;
+ case GDK_SCROLL_RIGHT:
+ {} break;
+ case GDK_SCROLL_LEFT:
+ {} break;
+ }
+ }
+ break;
+ }
+
+ case GDK_BUTTON_RELEASE: {
+ Geom::Point const motion_w(event->button.x, event->button.y);
+ Geom::Point const motion_dt(desktop->w2d(motion_w));
+
+ desktop->canvas->endForcedFullRedraws();
+ this->is_drawing = false;
+
+ if (this->is_dilating && event->button.button == 1 && !this->space_panning) {
+ if (!this->has_dilated) {
+ // if we did not rub, do a light tap
+ this->pressure = 0.03;
+ sp_spray_dilate(this, motion_w, desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT(event));
+ }
+ this->is_dilating = false;
+ this->has_dilated = false;
+ switch (this->mode) {
+ case SPRAY_MODE_COPY:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_SPRAY, _("Spray with copies"));
+ break;
+ case SPRAY_MODE_CLONE:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_SPRAY, _("Spray with clones"));
+ break;
+ case SPRAY_MODE_SINGLE_PATH:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_SPRAY, _("Spray in single path"));
+ break;
+ }
+ }
+ break;
+ }
+
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_j:
+ case GDK_KEY_J:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_spray_switch_mode(this, SPRAY_MODE_COPY, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_k:
+ case GDK_KEY_K:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_spray_switch_mode(this, SPRAY_MODE_CLONE, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_l:
+ case GDK_KEY_L:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_spray_switch_mode(this, SPRAY_MODE_SINGLE_PATH, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Up:
+ case GDK_KEY_KP_Up:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->population += 0.01;
+ if (this->population > 1.0) {
+ this->population = 1.0;
+ }
+ desktop->setToolboxAdjustmentValue("spray-population", this->population * 100);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Down:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->population -= 0.01;
+ if (this->population < 0.0) {
+ this->population = 0.0;
+ }
+ desktop->setToolboxAdjustmentValue("spray-population", this->population * 100);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Right:
+ case GDK_KEY_KP_Right:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->width += 0.01;
+ if (this->width > 1.0) {
+ this->width = 1.0;
+ }
+ // the same spinbutton is for alt+x
+ desktop->setToolboxAdjustmentValue("altx-spray", this->width * 100);
+ sp_spray_update_area(this);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Left:
+ case GDK_KEY_KP_Left:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->width -= 0.01;
+ if (this->width < 0.01) {
+ this->width = 0.01;
+ }
+ desktop->setToolboxAdjustmentValue("altx-spray", this->width * 100);
+ sp_spray_update_area(this);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Home:
+ case GDK_KEY_KP_Home:
+ this->width = 0.01;
+ desktop->setToolboxAdjustmentValue("altx-spray", this->width * 100);
+ sp_spray_update_area(this);
+ ret = TRUE;
+ break;
+ case GDK_KEY_End:
+ case GDK_KEY_KP_End:
+ this->width = 1.0;
+ desktop->setToolboxAdjustmentValue("altx-spray", this->width * 100);
+ sp_spray_update_area(this);
+ ret = TRUE;
+ break;
+ case GDK_KEY_x:
+ case GDK_KEY_X:
+ if (MOD__ALT_ONLY(event)) {
+ desktop->setToolboxFocusTo("altx-spray");
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ this->update_cursor(true);
+ break;
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ break;
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ case GDK_KEY_BackSpace:
+ ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event));
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case GDK_KEY_RELEASE: {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ switch (get_group0_keyval(&event->key)) {
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ this->update_cursor(false);
+ break;
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ sp_spray_switch_mode (this, prefs->getInt("/tools/spray/mode"), MOD__SHIFT(event));
+ this->message_context->clear();
+ break;
+ default:
+ sp_spray_switch_mode (this, prefs->getInt("/tools/spray/mode"), MOD__SHIFT(event));
+ break;
+ }
+ }
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+// if ((SP_EVENT_CONTEXT_CLASS(sp_spray_context_parent_class))->root_handler) {
+// ret = (SP_EVENT_CONTEXT_CLASS(sp_spray_context_parent_class))->root_handler(event_context, event);
+// }
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+}
+}
+}
+
+/*
+ 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/tools/spray-tool.h b/src/ui/tools/spray-tool.h
new file mode 100644
index 000000000..e7362fd50
--- /dev/null
+++ b/src/ui/tools/spray-tool.h
@@ -0,0 +1,121 @@
+#ifndef __SP_SPRAY_CONTEXT_H__
+#define __SP_SPRAY_CONTEXT_H__
+
+/*
+ * Spray Tool
+ *
+ * Authors:
+ * Pierre-Antoine MARC
+ * Pierre CACLIN
+ * Aurel-Aimé MARMION
+ * Julien LERAY
+ * Benoît LAVORATA
+ * Vincent MONTAGNE
+ * Pierre BARBRY-BLOT
+ *
+ * Copyright (C) 2009 authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <2geom/point.h>
+#include "ui/tools/tool-base.h"
+
+#define SP_SPRAY_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::SprayTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_SPRAY_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::SprayTool*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+namespace Inkscape {
+ namespace UI {
+ namespace Dialog {
+ class Dialog;
+ }
+ }
+}
+
+
+#define SAMPLING_SIZE 8 /* fixme: ?? */
+
+#define TC_MIN_PRESSURE 0.0
+#define TC_MAX_PRESSURE 1.0
+#define TC_DEFAULT_PRESSURE 0.35
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+enum {
+ SPRAY_MODE_COPY,
+ SPRAY_MODE_CLONE,
+ SPRAY_MODE_SINGLE_PATH,
+ SPRAY_OPTION,
+};
+
+class SprayTool : public ToolBase {
+public:
+ SprayTool();
+ virtual ~SprayTool();
+
+ //ToolBase event_context;
+ //Inkscape::UI::Dialog::Dialog *dialog_option;//Attribut de type SprayOptionClass, localisé dans scr/ui/dialog
+ /* extended input data */
+ gdouble pressure;
+
+ /* attributes */
+ guint dragging : 1; /* mouse state: mouse is dragging */
+ guint usepressure : 1;
+ guint usetilt : 1;
+ bool usetext ;
+
+ double width;
+ double ratio;
+ double tilt;
+ double rotation_variation;
+ double force;
+ double population;
+ double scale_variation;
+ double scale;
+ double mean;
+ double standard_deviation;
+
+ gint distrib;
+
+ gint mode;
+
+ bool is_drawing;
+
+ bool is_dilating;
+ bool has_dilated;
+ Geom::Point last_push;
+ SPCanvasItem *dilate_area;
+
+ sigc::connection style_set_connection;
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual void set(const Inkscape::Preferences::Entry& val);
+ virtual bool root_handler(GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+
+ void update_cursor(bool /*with_shift*/);
+};
+
+}
+}
+}
+
+#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/tools/star-tool.cpp b/src/ui/tools/star-tool.cpp
new file mode 100644
index 000000000..b5d8c4418
--- /dev/null
+++ b/src/ui/tools/star-tool.cpp
@@ -0,0 +1,494 @@
+/*
+ * Star drawing context
+ *
+ * Authors:
+ * Mitsuru Oka <oka326@parkcity.ne.jp>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ * Copyright (C) 2001-2002 Mitsuru Oka
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <cstring>
+#include <string>
+
+#include <gdk/gdkkeysyms.h>
+
+#include "macros.h"
+#include "display/sp-canvas.h"
+#include "sp-star.h"
+#include "document.h"
+#include "document-undo.h"
+#include "sp-namedview.h"
+#include "selection.h"
+#include "desktop-handles.h"
+#include "snap.h"
+#include "desktop.h"
+#include "desktop-style.h"
+#include "message-context.h"
+#include "pixmaps/cursor-star.xpm"
+#include <glibmm/i18n.h>
+#include "preferences.h"
+#include "xml/repr.h"
+#include "xml/node-event-vector.h"
+#include "context-fns.h"
+#include "shape-editor.h"
+#include "verbs.h"
+#include "display/sp-canvas-item.h"
+
+#include "ui/tools/star-tool.h"
+
+using Inkscape::DocumentUndo;
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+namespace {
+ ToolBase* createStarContext() {
+ return new StarTool();
+ }
+
+ bool starContextRegistered = ToolFactory::instance().registerObject("/tools/shapes/star", createStarContext);
+}
+
+const std::string& StarTool::getPrefsPath() {
+ return StarTool::prefsPath;
+}
+
+const std::string StarTool::prefsPath = "/tools/shapes/star";
+
+StarTool::StarTool() : ToolBase() {
+ this->randomized = 0;
+ this->rounded = 0;
+
+ this->cursor_shape = cursor_star_xpm;
+ this->hot_x = 4;
+ this->hot_y = 4;
+ this->xp = 0;
+ this->yp = 0;
+ this->tolerance = 0;
+ this->within_tolerance = false;
+ this->item_to_select = NULL;
+ //this->tool_url = "/tools/shapes/star";
+
+ this->star = NULL;
+
+ this->magnitude = 5;
+ this->proportion = 0.5;
+ this->isflatsided = false;
+}
+
+void StarTool::finish() {
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME);
+
+ this->finishItem();
+ this->sel_changed_connection.disconnect();
+
+ ToolBase::finish();
+}
+
+StarTool::~StarTool() {
+ this->enableGrDrag(false);
+
+ this->sel_changed_connection.disconnect();
+
+ delete this->shape_editor;
+ this->shape_editor = NULL;
+
+ /* fixme: This is necessary because we do not grab */
+ if (this->star) {
+ this->finishItem();
+ }
+}
+
+/**
+ * Callback that processes the "changed" signal on the selection;
+ * destroys old and creates new knotholder.
+ *
+ * @param selection Should not be NULL.
+ */
+void StarTool::selection_changed(Inkscape::Selection* selection) {
+ g_assert (selection != NULL);
+
+ this->shape_editor->unset_item(SH_KNOTHOLDER);
+ this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER);
+}
+
+void StarTool::setup() {
+ ToolBase::setup();
+
+ sp_event_context_read(this, "magnitude");
+ sp_event_context_read(this, "proportion");
+ sp_event_context_read(this, "isflatsided");
+ sp_event_context_read(this, "rounded");
+ sp_event_context_read(this, "randomized");
+
+ this->shape_editor = new ShapeEditor(this->desktop);
+
+ SPItem *item = sp_desktop_selection(this->desktop)->singleItem();
+ if (item) {
+ this->shape_editor->set_item(item, SH_KNOTHOLDER);
+ }
+
+ Inkscape::Selection *selection = sp_desktop_selection(this->desktop);
+
+ this->sel_changed_connection.disconnect();
+
+ this->sel_changed_connection = selection->connectChanged(sigc::mem_fun(this, &StarTool::selection_changed));
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/shapes/selcue")) {
+ this->enableSelectionCue();
+ }
+
+ if (prefs->getBool("/tools/shapes/gradientdrag")) {
+ this->enableGrDrag();
+ }
+}
+
+void StarTool::set(const Inkscape::Preferences::Entry& val) {
+ Glib::ustring path = val.getEntryName();
+
+ if (path == "magnitude") {
+ this->magnitude = CLAMP(val.getInt(5), 3, 1024);
+ } else if (path == "proportion") {
+ this->proportion = CLAMP(val.getDouble(0.5), 0.01, 2.0);
+ } else if (path == "isflatsided") {
+ this->isflatsided = val.getBool();
+ } else if (path == "rounded") {
+ this->rounded = val.getDouble();
+ } else if (path == "randomized") {
+ this->randomized = val.getDouble();
+ }
+}
+
+bool StarTool::root_handler(GdkEvent* event) {
+ static bool dragging;
+
+ SPDesktop *desktop = this->desktop;
+ Inkscape::Selection *selection = sp_desktop_selection (desktop);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+ dragging = true;
+
+ this->center = Inkscape::setup_for_drag_start(desktop, this, event);
+
+ /* Snap center */
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop, true);
+ m.freeSnapReturnByRef(this->center, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ m.unSetup();
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_POINTER_MOTION_HINT_MASK |
+ GDK_BUTTON_PRESS_MASK,
+ NULL, event->button.time);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ if (dragging && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) {
+ if ( this->within_tolerance
+ && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance )
+ && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) {
+ break; // do not drag if we're within tolerance from origin
+ }
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to draw, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ this->within_tolerance = false;
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point motion_dt(desktop->w2d(motion_w));
+
+ this->drag(motion_dt, event->motion.state);
+
+ gobble_motion_events(GDK_BUTTON1_MASK);
+
+ ret = TRUE;
+ } else if (!sp_event_context_knot_mouseover(this)) {
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point motion_dt(desktop->w2d(motion_w));
+
+ m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE));
+ m.unSetup();
+ }
+ break;
+ case GDK_BUTTON_RELEASE:
+ this->xp = this->yp = 0;
+
+ if (event->button.button == 1 && !this->space_panning) {
+ dragging = false;
+
+ sp_event_context_discard_delayed_snap_event(this);
+
+ if (!this->within_tolerance) {
+ // we've been dragging, finish the star
+ this->finishItem();
+ } else if (this->item_to_select) {
+ // no dragging, select clicked item if any
+ if (event->button.state & GDK_SHIFT_MASK) {
+ selection->toggle(this->item_to_select);
+ } else {
+ selection->set(this->item_to_select);
+ }
+ } else {
+ // click in an empty space
+ selection->clear();
+ }
+
+ this->item_to_select = NULL;
+ ret = TRUE;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM (desktop->acetate), event->button.time);
+ }
+ break;
+
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval(&event->key)) {
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine)
+ case GDK_KEY_Meta_R:
+ sp_event_show_modifier_tip(this->defaultMessageContext(), event,
+ _("<b>Ctrl</b>: snap angle; keep rays radial"),
+ NULL,
+ NULL);
+ break;
+
+ case GDK_KEY_Up:
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_Down:
+ // prevent the zoom field from activation
+ if (!MOD__CTRL_ONLY(event))
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_x:
+ case GDK_KEY_X:
+ if (MOD__ALT_ONLY(event)) {
+ desktop->setToolboxFocusTo ("altx-star");
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Escape:
+ if (dragging) {
+ dragging = false;
+ sp_event_context_discard_delayed_snap_event(this);
+ // if drawing, cancel, otherwise pass it up for deselecting
+ this->cancel();
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_space:
+ if (dragging) {
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
+
+ dragging = false;
+
+ sp_event_context_discard_delayed_snap_event(this);
+
+ if (!this->within_tolerance) {
+ // we've been dragging, finish the star
+ this->finishItem();
+ }
+ // do not return true, so that space would work switching to selector
+ }
+ break;
+
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ case GDK_KEY_BackSpace:
+ ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event));
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case GDK_KEY_RELEASE:
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Alt_L:
+ case GDK_KEY_Alt_R:
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt
+ case GDK_KEY_Meta_R:
+ this->defaultMessageContext()->clear();
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+void StarTool::drag(Geom::Point p, guint state)
+{
+ SPDesktop *desktop = this->desktop;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
+
+ if (!this->star) {
+ if (Inkscape::have_viable_layer(desktop, this->message_context) == false) {
+ return;
+ }
+
+ // Create object
+ Inkscape::XML::Document *xml_doc = this->desktop->doc()->getReprDoc();
+ Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
+ repr->setAttribute("sodipodi:type", "star");
+
+ // Set style
+ sp_desktop_apply_style_tool(desktop, repr, "/tools/shapes/star", false);
+
+ this->star = SP_STAR(desktop->currentLayer()->appendChildRepr(repr));
+
+ Inkscape::GC::release(repr);
+ this->star->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
+ this->star->updateRepr();
+
+ desktop->canvas->forceFullRedrawAfterInterruptions(5);
+ }
+
+ /* Snap corner point with no constraints */
+ SnapManager &m = desktop->namedview->snap_manager;
+
+ m.setup(desktop, true, this->star);
+ Geom::Point pt2g = p;
+ m.freeSnapReturnByRef(pt2g, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ m.unSetup();
+
+ Geom::Point const p0 = desktop->dt2doc(this->center);
+ Geom::Point const p1 = desktop->dt2doc(pt2g);
+
+ double const sides = (gdouble) this->magnitude;
+ Geom::Point const d = p1 - p0;
+ Geom::Coord const r1 = Geom::L2(d);
+ double arg1 = atan2(d);
+
+ if (state & GDK_CONTROL_MASK) {
+ /* Snap angle */
+ arg1 = sp_round(arg1, M_PI / snaps);
+ }
+
+ sp_star_position_set(this->star, this->magnitude, p0, r1, r1 * this->proportion,
+ arg1, arg1 + M_PI / sides, this->isflatsided, this->rounded, this->randomized);
+
+ /* status text */
+ Inkscape::Util::Quantity q = Inkscape::Util::Quantity(r1, "px");
+ GString *rads = g_string_new(q.string(desktop->namedview->doc_units).c_str());
+ this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE,
+ ( this->isflatsided?
+ _("<b>Polygon</b>: radius %s, angle %5g&#176;; with <b>Ctrl</b> to snap angle")
+ : _("<b>Star</b>: radius %s, angle %5g&#176;; with <b>Ctrl</b> to snap angle") ),
+ rads->str, sp_round((arg1) * 180 / M_PI, 0.0001));
+
+ g_string_free(rads, FALSE);
+}
+
+void StarTool::finishItem() {
+ this->message_context->clear();
+
+ if (this->star != NULL) {
+ if (this->star->r[1] == 0) {
+ // Don't allow the creating of zero sized arc, for example
+ // when the start and and point snap to the snap grid point
+ this->cancel();
+ return;
+ }
+
+ // Set transform center, so that odd stars rotate correctly
+ // LP #462157
+ this->star->setCenter(this->center);
+ this->star->set_shape();
+ this->star->updateRepr(SP_OBJECT_WRITE_EXT);
+ this->star->doWriteTransform(this->star->getRepr(), this->star->transform, NULL, true);
+
+ desktop->canvas->endForcedFullRedraws();
+
+ sp_desktop_selection(desktop)->set(this->star);
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_STAR,
+ _("Create star"));
+
+ this->star = NULL;
+ }
+}
+
+void StarTool::cancel() {
+ sp_desktop_selection(desktop)->clear();
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), 0);
+
+ if (this->star != NULL) {
+ this->star->deleteObject();
+ this->star = NULL;
+ }
+
+ this->within_tolerance = false;
+ this->xp = 0;
+ this->yp = 0;
+ this->item_to_select = NULL;
+
+ desktop->canvas->endForcedFullRedraws();
+
+ DocumentUndo::cancel(sp_desktop_document(desktop));
+}
+
+}
+}
+}
+
+/*
+ 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/tools/star-tool.h b/src/ui/tools/star-tool.h
new file mode 100644
index 000000000..da3774e68
--- /dev/null
+++ b/src/ui/tools/star-tool.h
@@ -0,0 +1,74 @@
+#ifndef __SP_STAR_CONTEXT_H__
+#define __SP_STAR_CONTEXT_H__
+
+/*
+ * Star drawing context
+ *
+ * Authors:
+ * Mitsuru Oka <oka326@parkcity.ne.jp>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 1999-2002 Lauris Kaplinski
+ * Copyright (C) 2001-2002 Mitsuru Oka
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <stddef.h>
+#include <sigc++/sigc++.h>
+#include <2geom/point.h>
+#include "ui/tools/tool-base.h"
+
+#include "sp-star.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class StarTool : public ToolBase {
+public:
+ StarTool();
+ virtual ~StarTool();
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual void finish();
+ virtual void set(const Inkscape::Preferences::Entry& val);
+ virtual bool root_handler(GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+private:
+ SPStar* star;
+
+ Geom::Point center;
+
+ /* Number of corners */
+ gint magnitude;
+
+ /* Outer/inner radius ratio */
+ gdouble proportion;
+
+ /* flat sides or not? */
+ bool isflatsided;
+
+ /* rounded corners ratio */
+ gdouble rounded;
+
+ // randomization
+ gdouble randomized;
+
+ sigc::connection sel_changed_connection;
+
+ void drag(Geom::Point p, guint state);
+ void finishItem();
+ void cancel();
+ void selection_changed(Inkscape::Selection* selection);
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/ui/tools/text-tool.cpp b/src/ui/tools/text-tool.cpp
new file mode 100644
index 000000000..0a8b35110
--- /dev/null
+++ b/src/ui/tools/text-tool.cpp
@@ -0,0 +1,1765 @@
+/*
+ * TextTool
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 1999-2005 authors
+ * Copyright (C) 2001 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <gtkmm/clipboard.h>
+#include <display/sp-ctrlline.h>
+#include <display/sodipodi-ctrlrect.h>
+#include <display/sp-ctrlquadr.h>
+#include <gdk/gdkkeysyms.h>
+#include <glibmm/i18n.h>
+#include <sstream>
+
+#include "context-fns.h"
+#include "desktop-handles.h"
+#include "desktop-style.h"
+#include "desktop.h"
+#include "document.h"
+#include "document-undo.h"
+#include "macros.h"
+#include "message-context.h"
+#include "message-stack.h"
+#include "pixmaps/cursor-text-insert.xpm"
+#include "pixmaps/cursor-text.xpm"
+#include "preferences.h"
+#include "rubberband.h"
+#include "selection-chemistry.h"
+#include "selection.h"
+#include "shape-editor.h"
+#include "sp-flowtext.h"
+#include "sp-namedview.h"
+#include "sp-text.h"
+#include "style.h"
+#include "ui/tools/text-tool.h"
+#include "text-editing.h"
+#include "ui/control-manager.h"
+#include "verbs.h"
+#include "xml/node-event-vector.h"
+#include "xml/repr.h"
+#include <gtk/gtk.h>
+#include "tool-factory.h"
+
+using Inkscape::ControlManager;
+using Inkscape::DocumentUndo;
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+static void sp_text_context_selection_changed(Inkscape::Selection *selection, TextTool *tc);
+static void sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, TextTool *tc);
+static bool sp_text_context_style_set(SPCSSAttr const *css, TextTool *tc);
+static int sp_text_context_style_query(SPStyle *style, int property, TextTool *tc);
+
+static void sp_text_context_validate_cursor_iterators(TextTool *tc);
+static void sp_text_context_update_cursor(TextTool *tc, bool scroll_to_see = true);
+static void sp_text_context_update_text_selection(TextTool *tc);
+static gint sp_text_context_timeout(TextTool *tc);
+static void sp_text_context_forget_text(TextTool *tc);
+
+static gint sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, TextTool *tc);
+static gint sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, TextTool *tc);
+static void sptc_commit(GtkIMContext *imc, gchar *string, TextTool *tc);
+
+namespace {
+ ToolBase* createTextContext() {
+ return new TextTool();
+ }
+
+ bool textContextRegistered = ToolFactory::instance().registerObject("/tools/text", createTextContext);
+}
+
+const std::string& TextTool::getPrefsPath() {
+ return TextTool::prefsPath;
+}
+
+const std::string TextTool::prefsPath = "/tools/text";
+
+
+TextTool::TextTool() : ToolBase() {
+ this->preedit_string = 0;
+ this->unipos = 0;
+
+ this->cursor_shape = cursor_text_xpm;
+ this->hot_x = 7;
+ this->hot_y = 7;
+
+ this->xp = 0;
+ this->yp = 0;
+ this->tolerance = 0;
+ this->within_tolerance = false;
+
+ this->imc = NULL;
+
+ this->text = NULL;
+ this->pdoc = Geom::Point(0, 0);
+
+ this->unimode = false;
+
+ this->cursor = NULL;
+ this->indicator = NULL;
+ this->frame = NULL;
+ this->grabbed = NULL;
+ this->timeout = 0;
+ this->show = FALSE;
+ this->phase = 0;
+ this->nascent_object = 0;
+ this->over_text = 0;
+ this->dragging = 0;
+ this->creating = 0;
+}
+
+TextTool::~TextTool() {
+ delete this->shape_editor;
+ this->shape_editor = NULL;
+
+ if (this->grabbed) {
+ sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME);
+ this->grabbed = NULL;
+ }
+
+ Inkscape::Rubberband::get(this->desktop)->stop();
+}
+
+void TextTool::setup() {
+ GtkSettings* settings = gtk_settings_get_default();
+ gint timeout = 0;
+ g_object_get( settings, "gtk-cursor-blink-time", &timeout, NULL );
+
+ if (timeout < 0) {
+ timeout = 200;
+ } else {
+ timeout /= 2;
+ }
+
+ this->cursor = ControlManager::getManager().createControlLine(sp_desktop_controls(desktop), Geom::Point(100, 0), Geom::Point(100, 100));
+ this->cursor->setRgba32(0x000000ff);
+ sp_canvas_item_hide(this->cursor);
+
+ this->indicator = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
+ SP_CTRLRECT(this->indicator)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100)));
+ SP_CTRLRECT(this->indicator)->setColor(0x0000ff7f, false, 0);
+ sp_canvas_item_hide(this->indicator);
+
+ this->frame = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL);
+ SP_CTRLRECT(this->frame)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100)));
+ SP_CTRLRECT(this->frame)->setColor(0x0000ff7f, false, 0);
+ sp_canvas_item_hide(this->frame);
+
+ this->timeout = g_timeout_add(timeout, (GSourceFunc) sp_text_context_timeout, this);
+
+ this->imc = gtk_im_multicontext_new();
+ if (this->imc) {
+ GtkWidget *canvas = GTK_WIDGET(sp_desktop_canvas(desktop));
+
+ /* im preedit handling is very broken in inkscape for
+ * multi-byte characters. See bug 1086769.
+ * We need to let the IM handle the preediting, and
+ * just take in the characters when they're finished being
+ * entered.
+ */
+ gtk_im_context_set_use_preedit(this->imc, FALSE);
+ gtk_im_context_set_client_window(this->imc,
+ gtk_widget_get_window (canvas));
+
+ g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), this);
+ g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), this);
+ g_signal_connect(G_OBJECT(this->imc), "commit", G_CALLBACK(sptc_commit), this);
+
+ if (gtk_widget_has_focus(canvas)) {
+ sptc_focus_in(canvas, NULL, this);
+ }
+ }
+
+ ToolBase::setup();
+
+ this->shape_editor = new ShapeEditor(this->desktop);
+
+ SPItem *item = sp_desktop_selection(this->desktop)->singleItem();
+ if (item && SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) {
+ this->shape_editor->set_item(item, SH_KNOTHOLDER);
+ }
+
+ this->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged(
+ sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), this)
+ );
+ this->sel_modified_connection = sp_desktop_selection(desktop)->connectModified(
+ sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), this)
+ );
+ this->style_set_connection = desktop->connectSetStyle(
+ sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), this)
+ );
+ this->style_query_connection = desktop->connectQueryStyle(
+ sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), this)
+ );
+
+ sp_text_context_selection_changed(sp_desktop_selection(desktop), this);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/text/selcue")) {
+ this->enableSelectionCue();
+ }
+ if (prefs->getBool("/tools/text/gradientdrag")) {
+ this->enableGrDrag();
+ }
+}
+
+void TextTool::finish() {
+ if (this->desktop) {
+ sp_signal_disconnect_by_data(sp_desktop_canvas(this->desktop), this);
+ }
+
+ this->enableGrDrag(false);
+
+ this->style_set_connection.disconnect();
+ this->style_query_connection.disconnect();
+ this->sel_changed_connection.disconnect();
+ this->sel_modified_connection.disconnect();
+
+ sp_text_context_forget_text(SP_TEXT_CONTEXT(this));
+
+ if (this->imc) {
+ g_object_unref(G_OBJECT(this->imc));
+ this->imc = NULL;
+ }
+
+ if (this->timeout) {
+ g_source_remove(this->timeout);
+ this->timeout = 0;
+ }
+
+ if (this->cursor) {
+ sp_canvas_item_destroy(this->cursor);
+ this->cursor = NULL;
+ }
+
+ if (this->indicator) {
+ sp_canvas_item_destroy(this->indicator);
+ this->indicator = NULL;
+ }
+
+ if (this->frame) {
+ sp_canvas_item_destroy(this->frame);
+ this->frame = NULL;
+ }
+
+ for (std::vector<SPCanvasItem*>::iterator it = this->text_selection_quads.begin() ;
+ it != this->text_selection_quads.end() ; ++it) {
+ sp_canvas_item_hide(*it);
+ sp_canvas_item_destroy(*it);
+ }
+
+ this->text_selection_quads.clear();
+}
+
+bool TextTool::item_handler(SPItem* item, GdkEvent* event) {
+ SPItem *item_ungrouped;
+
+ gint ret = FALSE;
+
+ sp_text_context_validate_cursor_iterators(this);
+ Inkscape::Text::Layout::iterator old_start = this->text_sel_start;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+ // find out clicked item, disregarding groups
+ item_ungrouped = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE);
+ if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
+ sp_desktop_selection(desktop)->set(item_ungrouped);
+ if (this->text) {
+ // find out click point in document coordinates
+ Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
+ // set the cursor closest to that point
+ if (event->button.state & GDK_SHIFT_MASK) {
+ this->text_sel_start = old_start;
+ this->text_sel_end = sp_te_get_position_by_coords(this->text, p);
+ } else {
+ this->text_sel_start = this->text_sel_end = sp_te_get_position_by_coords(this->text, p);
+ }
+ // update display
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ this->dragging = 1;
+ }
+ ret = TRUE;
+ }
+ }
+ break;
+ case GDK_2BUTTON_PRESS:
+ if (event->button.button == 1 && this->text) {
+ Inkscape::Text::Layout const *layout = te_get_layout(this->text);
+ if (layout) {
+ if (!layout->isStartOfWord(this->text_sel_start))
+ this->text_sel_start.prevStartOfWord();
+ if (!layout->isEndOfWord(this->text_sel_end))
+ this->text_sel_end.nextEndOfWord();
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ this->dragging = 2;
+ ret = TRUE;
+ }
+ }
+ break;
+ case GDK_3BUTTON_PRESS:
+ if (event->button.button == 1 && this->text) {
+ this->text_sel_start.thisStartOfLine();
+ this->text_sel_end.thisEndOfLine();
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ this->dragging = 3;
+ ret = TRUE;
+ }
+ break;
+ case GDK_BUTTON_RELEASE:
+ if (event->button.button == 1 && this->dragging && !this->space_panning) {
+ this->dragging = 0;
+ sp_event_context_discard_delayed_snap_event(this);
+ ret = TRUE;
+ }
+ break;
+ case GDK_MOTION_NOTIFY:
+ if ((event->motion.state & GDK_BUTTON1_MASK) && this->dragging && !this->space_panning) {
+ Inkscape::Text::Layout const *layout = te_get_layout(this->text);
+ if (!layout) break;
+ // find out click point in document coordinates
+ Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
+ // set the cursor closest to that point
+ Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(this->text, p);
+ if (this->dragging == 2) {
+ // double-click dragging: go by word
+ if (new_end < this->text_sel_start) {
+ if (!layout->isStartOfWord(new_end))
+ new_end.prevStartOfWord();
+ } else
+ if (!layout->isEndOfWord(new_end))
+ new_end.nextEndOfWord();
+ } else if (this->dragging == 3) {
+ // triple-click dragging: go by line
+ if (new_end < this->text_sel_start)
+ new_end.thisStartOfLine();
+ else
+ new_end.thisEndOfLine();
+ }
+ // update display
+ if (this->text_sel_end != new_end) {
+ this->text_sel_end = new_end;
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ }
+ gobble_motion_events(GDK_BUTTON1_MASK);
+ ret = TRUE;
+ break;
+ }
+ // find out item under mouse, disregarding groups
+ item_ungrouped = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE);
+ if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) {
+
+ Inkscape::Text::Layout const *layout = te_get_layout(item_ungrouped);
+ if (layout->inputTruncated()) {
+ SP_CTRLRECT(this->indicator)->setColor(0xff0000ff, false, 0);
+ } else {
+ SP_CTRLRECT(this->indicator)->setColor(0x0000ff7f, false, 0);
+ }
+ Geom::OptRect ibbox = item_ungrouped->desktopVisualBounds();
+ if (ibbox) {
+ SP_CTRLRECT(this->indicator)->setRectangle(*ibbox);
+ }
+ sp_canvas_item_show(this->indicator);
+
+ this->cursor_shape = cursor_text_insert_xpm;
+ this->hot_x = 7;
+ this->hot_y = 10;
+ this->sp_event_context_update_cursor();
+ sp_text_context_update_text_selection(this);
+
+ if (SP_IS_TEXT (item_ungrouped)) {
+ desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the text, <b>drag</b> to select part of the text."));
+ } else {
+ desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to edit the flowed text, <b>drag</b> to select part of the text."));
+ }
+
+ this->over_text = true;
+
+ ret = TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::item_handler(item, event);
+ }
+
+ return ret;
+}
+
+static void sp_text_context_setup_text(TextTool *tc)
+{
+ ToolBase *ec = SP_EVENT_CONTEXT(tc);
+
+ /* Create <text> */
+ Inkscape::XML::Document *xml_doc = SP_EVENT_CONTEXT_DESKTOP(ec)->doc()->getReprDoc();
+ Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
+ rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
+
+ /* Set style */
+ sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "/tools/text", true);
+
+ sp_repr_set_svg_double(rtext, "x", tc->pdoc[Geom::X]);
+ sp_repr_set_svg_double(rtext, "y", tc->pdoc[Geom::Y]);
+
+ /* Create <tspan> */
+ Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
+ rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
+ rtext->addChild(rtspan, NULL);
+ Inkscape::GC::release(rtspan);
+
+ /* Create TEXT */
+ Inkscape::XML::Node *rstring = xml_doc->createTextNode("");
+ rtspan->addChild(rstring, NULL);
+ Inkscape::GC::release(rstring);
+ SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext));
+ /* fixme: Is selection::changed really immediate? */
+ /* yes, it's immediate .. why does it matter? */
+ sp_desktop_selection(ec->desktop)->set(text_item);
+ Inkscape::GC::release(rtext);
+ text_item->transform = SP_ITEM(ec->desktop->currentLayer())->i2doc_affine().inverse();
+
+ text_item->updateRepr();
+ text_item->doWriteTransform(text_item->getRepr(), text_item->transform, NULL, true);
+ DocumentUndo::done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
+ _("Create text"));
+}
+
+/**
+ * Insert the character indicated by tc.uni to replace the current selection,
+ * and reset tc.uni/tc.unipos to empty string.
+ *
+ * \pre tc.uni/tc.unipos non-empty.
+ */
+static void insert_uni_char(TextTool *const tc)
+{
+ g_return_if_fail(tc->unipos
+ && tc->unipos < sizeof(tc->uni)
+ && tc->uni[tc->unipos] == '\0');
+ unsigned int uv;
+ std::stringstream ss;
+ ss << std::hex << tc->uni;
+ ss >> uv;
+ tc->unipos = 0;
+ tc->uni[tc->unipos] = '\0';
+
+ if ( !g_unichar_isprint(static_cast<gunichar>(uv))
+ && !(g_unichar_validate(static_cast<gunichar>(uv)) && (g_unichar_type(static_cast<gunichar>(uv)) == G_UNICODE_PRIVATE_USE) ) ) {
+ // This may be due to bad input, so it goes to statusbar.
+ tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
+ _("Non-printable character"));
+ } else {
+ if (!tc->text) { // printable key; create text if none (i.e. if nascent_object)
+ sp_text_context_setup_text(tc);
+ tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
+ }
+
+ gchar u[10];
+ guint const len = g_unichar_to_utf8(uv, u);
+ u[len] = '\0';
+
+ tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u);
+ sp_text_context_update_cursor(tc);
+ sp_text_context_update_text_selection(tc);
+ DocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM,
+ _("Insert Unicode character"));
+ }
+}
+
+static void hex_to_printable_utf8_buf(char const *const ehex, char *utf8)
+{
+ unsigned int uv;
+ std::stringstream ss;
+ ss << std::hex << ehex;
+ ss >> uv;
+ if (!g_unichar_isprint((gunichar) uv)) {
+ uv = 0xfffd;
+ }
+ guint const len = g_unichar_to_utf8(uv, utf8);
+ utf8[len] = '\0';
+}
+
+static void show_curr_uni_char(TextTool *const tc)
+{
+ g_return_if_fail(tc->unipos < sizeof(tc->uni)
+ && tc->uni[tc->unipos] == '\0');
+ if (tc->unipos) {
+ char utf8[10];
+ hex_to_printable_utf8_buf(tc->uni, utf8);
+
+ /* Status bar messages are in pango markup, so we need xml escaping. */
+ if (utf8[1] == '\0') {
+ switch(utf8[0]) {
+ case '<': strcpy(utf8, "&lt;"); break;
+ case '>': strcpy(utf8, "&gt;"); break;
+ case '&': strcpy(utf8, "&amp;"); break;
+ default: break;
+ }
+ }
+ tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
+ _("Unicode (<b>Enter</b> to finish): %s: %s"), tc->uni, utf8);
+ } else {
+ tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
+ }
+}
+
+bool TextTool::root_handler(GdkEvent* event) {
+ sp_canvas_item_hide(this->indicator);
+
+ sp_text_context_validate_cursor_iterators(this);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+
+ if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) {
+ return TRUE;
+ }
+
+ // save drag origin
+ this->xp = (gint) event->button.x;
+ this->yp = (gint) event->button.y;
+ this->within_tolerance = true;
+
+ Geom::Point const button_pt(event->button.x, event->button.y);
+ Geom::Point button_dt(desktop->w2d(button_pt));
+
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+ m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ m.unSetup();
+
+ this->p0 = button_dt;
+ Inkscape::Rubberband::get(desktop)->start(desktop, this->p0);
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
+ NULL, event->button.time);
+ this->grabbed = SP_CANVAS_ITEM(desktop->acetate);
+ this->creating = 1;
+
+ /* Processed */
+ return TRUE;
+ }
+ break;
+ case GDK_MOTION_NOTIFY:
+ if (this->over_text) {
+ this->over_text = 0;
+ // update cursor and statusbar: we are not over a text object now
+ this->cursor_shape = cursor_text_xpm;
+ this->hot_x = 7;
+ this->hot_y = 7;
+ this->sp_event_context_update_cursor();
+ desktop->event_context->defaultMessageContext()->clear();
+ }
+
+ if (this->creating && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) {
+ if ( this->within_tolerance
+ && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance )
+ && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) {
+ break; // do not drag if we're within tolerance from origin
+ }
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to draw, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ this->within_tolerance = false;
+
+ Geom::Point const motion_pt(event->motion.x, event->motion.y);
+ Geom::Point p = desktop->w2d(motion_pt);
+
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+ m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ m.unSetup();
+
+ Inkscape::Rubberband::get(desktop)->move(p);
+ gobble_motion_events(GDK_BUTTON1_MASK);
+
+ // status text
+ Inkscape::Util::Quantity x_q = Inkscape::Util::Quantity(fabs((p - this->p0)[Geom::X]), "px");
+ Inkscape::Util::Quantity y_q = Inkscape::Util::Quantity(fabs((p - this->p0)[Geom::Y]), "px");
+ GString *xs = g_string_new(x_q.string(desktop->namedview->doc_units).c_str());
+ GString *ys = g_string_new(y_q.string(desktop->namedview->doc_units).c_str());
+ this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("<b>Flowed text frame</b>: %s &#215; %s"), xs->str, ys->str);
+
+ g_string_free(xs, FALSE);
+ g_string_free(ys, FALSE);
+
+ } else if (!sp_event_context_knot_mouseover(this)) {
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point motion_dt(desktop->w2d(motion_w));
+ m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE));
+ m.unSetup();
+ }
+ break;
+ case GDK_BUTTON_RELEASE:
+ if (event->button.button == 1 && !this->space_panning) {
+ sp_event_context_discard_delayed_snap_event(this);
+
+ Geom::Point p1 = desktop->w2d(Geom::Point(event->button.x, event->button.y));
+
+ SnapManager &m = desktop->namedview->snap_manager;
+ m.setup(desktop);
+ m.freeSnapReturnByRef(p1, Inkscape::SNAPSOURCE_NODE_HANDLE);
+ m.unSetup();
+
+ if (this->grabbed) {
+ sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME);
+ this->grabbed = NULL;
+ }
+
+ Inkscape::Rubberband::get(desktop)->stop();
+
+ if (this->creating && this->within_tolerance) {
+ /* Button 1, set X & Y & new item */
+ sp_desktop_selection(desktop)->clear();
+ this->pdoc = desktop->dt2doc(p1);
+ this->show = TRUE;
+ this->phase = 1;
+ this->nascent_object = 1; // new object was just created
+
+ /* Cursor */
+ sp_canvas_item_show(this->cursor);
+ // Cursor height is defined by the new text object's font size; it needs to be set
+ // artificially here, for the text object does not exist yet:
+ double cursor_height = sp_desktop_get_font_size_tool(desktop);
+ this->cursor->setCoords(p1, p1 + Geom::Point(0, cursor_height));
+ if (this->imc) {
+ GdkRectangle im_cursor;
+ Geom::Point const top_left = SP_EVENT_CONTEXT(this)->desktop->get_display_area().corner(3);
+ Geom::Point const cursor_size(0, cursor_height);
+ Geom::Point const im_position = SP_EVENT_CONTEXT(this)->desktop->d2w(p1 + cursor_size - top_left);
+ im_cursor.x = (int) floor(im_position[Geom::X]);
+ im_cursor.y = (int) floor(im_position[Geom::Y]);
+ im_cursor.width = 0;
+ im_cursor.height = (int) -floor(SP_EVENT_CONTEXT(this)->desktop->d2w(cursor_size)[Geom::Y]);
+ gtk_im_context_set_cursor_location(this->imc, &im_cursor);
+ }
+ this->message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; <b>Enter</b> to start new line.")); // FIXME:: this is a copy of a string from _update_cursor below, do not desync
+
+ this->within_tolerance = false;
+ } else if (this->creating) {
+ double cursor_height = sp_desktop_get_font_size_tool(desktop);
+ if (fabs(p1[Geom::Y] - this->p0[Geom::Y]) > cursor_height) {
+ // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance)
+ SPItem *ft = create_flowtext_with_internal_frame (desktop, this->p0, p1);
+ /* Set style */
+ sp_desktop_apply_style_tool(desktop, ft->getRepr(), "/tools/text", true);
+ sp_desktop_selection(desktop)->set(ft);
+ desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created."));
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
+ _("Create flowed text"));
+ } else {
+ desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is <b>too small</b> for the current font size. Flowed text not created."));
+ }
+ }
+ this->creating = false;
+ return TRUE;
+ }
+ break;
+ case GDK_KEY_PRESS: {
+ guint const group0_keyval = get_group0_keyval(&event->key);
+
+ if (group0_keyval == GDK_KEY_KP_Add ||
+ group0_keyval == GDK_KEY_KP_Subtract) {
+ if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys
+ break; // otherwise pass on keypad +/- so they can zoom
+ }
+
+ if ((this->text) || (this->nascent_object)) {
+ // there is an active text object in this context, or a new object was just created
+
+ if (this->unimode || !this->imc
+ || (MOD__CTRL(event) && MOD__SHIFT(event)) // input methods tend to steal this for unimode,
+ // but we have our own so make sure they don't swallow it
+ || !gtk_im_context_filter_keypress(this->imc, (GdkEventKey*) event)) {
+ //IM did not consume the key, or we're in unimode
+
+ if (!MOD__CTRL_ONLY(event) && this->unimode) {
+ /* TODO: ISO 14755 (section 3 Definitions) says that we should also
+ accept the first 6 characters of alphabets other than the latin
+ alphabet "if the Latin alphabet is not used". The below is also
+ reasonable (viz. hope that the user's keyboard includes latin
+ characters and force latin interpretation -- just as we do for our
+ keyboard shortcuts), but differs from the ISO 14755
+ recommendation. */
+ switch (group0_keyval) {
+ case GDK_KEY_space:
+ case GDK_KEY_KP_Space: {
+ if (this->unipos) {
+ insert_uni_char(this);
+ }
+ /* Stay in unimode. */
+ show_curr_uni_char(this);
+ return TRUE;
+ }
+
+ case GDK_KEY_BackSpace: {
+ g_return_val_if_fail(this->unipos < sizeof(this->uni), TRUE);
+ if (this->unipos) {
+ this->uni[--this->unipos] = '\0';
+ }
+ show_curr_uni_char(this);
+ return TRUE;
+ }
+
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter: {
+ if (this->unipos) {
+ insert_uni_char(this);
+ }
+ /* Exit unimode. */
+ this->unimode = false;
+ this->defaultMessageContext()->clear();
+ return TRUE;
+ }
+
+ case GDK_KEY_Escape: {
+ // Cancel unimode.
+ this->unimode = false;
+ gtk_im_context_reset(this->imc);
+ this->defaultMessageContext()->clear();
+ return TRUE;
+ }
+
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ break;
+
+ default: {
+ if (g_ascii_isxdigit(group0_keyval)) {
+ g_return_val_if_fail(this->unipos < sizeof(this->uni) - 1, TRUE);
+ this->uni[this->unipos++] = group0_keyval;
+ this->uni[this->unipos] = '\0';
+ if (this->unipos == 8) {
+ /* This behaviour is partly to allow us to continue to
+ use a fixed-length buffer for tc->uni. Reason for
+ choosing the number 8 is that it's the length of
+ ``canonical form'' mentioned in the ISO 14755 spec.
+ An advantage over choosing 6 is that it allows using
+ backspace for typos & misremembering when entering a
+ 6-digit number. */
+ insert_uni_char(this);
+ }
+ show_curr_uni_char(this);
+ return TRUE;
+ } else {
+ /* The intent is to ignore but consume characters that could be
+ typos for hex digits. Gtk seems to ignore & consume all
+ non-hex-digits, and we do similar here. Though note that some
+ shortcuts (like keypad +/- for zoom) get processed before
+ reaching this code. */
+ return TRUE;
+ }
+ }
+ }
+ }
+
+ Inkscape::Text::Layout::iterator old_start = this->text_sel_start;
+ Inkscape::Text::Layout::iterator old_end = this->text_sel_end;
+ bool cursor_moved = false;
+ int screenlines = 1;
+ if (this->text) {
+ double spacing = sp_te_get_average_linespacing(this->text);
+ Geom::Rect const d = desktop->get_display_area();
+ screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1;
+ if (screenlines <= 0)
+ screenlines = 1;
+ }
+
+ /* Neither unimode nor IM consumed key; process text tool shortcuts */
+ switch (group0_keyval) {
+ case GDK_KEY_x:
+ case GDK_KEY_X:
+ if (MOD__ALT_ONLY(event)) {
+ desktop->setToolboxFocusTo ("altx-text");
+ return TRUE;
+ }
+ break;
+ case GDK_KEY_space:
+ if (MOD__CTRL_ONLY(event)) {
+ /* No-break space */
+ if (!this->text) { // printable key; create text if none (i.e. if nascent_object)
+ sp_text_context_setup_text(this);
+ this->nascent_object = 0; // we don't need it anymore, having created a real <text>
+ }
+ this->text_sel_start = this->text_sel_end = sp_te_replace(this->text, this->text_sel_start, this->text_sel_end, "\302\240");
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space"));
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
+ _("Insert no-break space"));
+ return TRUE;
+ }
+ break;
+ case GDK_KEY_U:
+ case GDK_KEY_u:
+ if (MOD__CTRL_ONLY(event) || (MOD__CTRL(event) && MOD__SHIFT(event))) {
+ if (this->unimode) {
+ this->unimode = false;
+ this->defaultMessageContext()->clear();
+ } else {
+ this->unimode = true;
+ this->unipos = 0;
+ this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (<b>Enter</b> to finish): "));
+ }
+ if (this->imc) {
+ gtk_im_context_reset(this->imc);
+ }
+ return TRUE;
+ }
+ break;
+ case GDK_KEY_B:
+ case GDK_KEY_b:
+ if (MOD__CTRL_ONLY(event) && this->text) {
+ SPStyle const *style = sp_te_style_at_position(this->text, std::min(this->text_sel_start, this->text_sel_end));
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL
+ || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100
+ || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200
+ || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300
+ || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400)
+ sp_repr_css_set_property(css, "font-weight", "bold");
+ else
+ sp_repr_css_set_property(css, "font-weight", "normal");
+ sp_te_apply_style(this->text, this->text_sel_start, this->text_sel_end, css);
+ sp_repr_css_attr_unref(css);
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
+ _("Make bold"));
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ return TRUE;
+ }
+ break;
+ case GDK_KEY_I:
+ case GDK_KEY_i:
+ if (MOD__CTRL_ONLY(event) && this->text) {
+ SPStyle const *style = sp_te_style_at_position(this->text, std::min(this->text_sel_start, this->text_sel_end));
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ if (style->font_style.computed != SP_CSS_FONT_STYLE_NORMAL)
+ sp_repr_css_set_property(css, "font-style", "normal");
+ else
+ sp_repr_css_set_property(css, "font-style", "italic");
+ sp_te_apply_style(this->text, this->text_sel_start, this->text_sel_end, css);
+ sp_repr_css_attr_unref(css);
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
+ _("Make italic"));
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ return TRUE;
+ }
+ break;
+
+ case GDK_KEY_A:
+ case GDK_KEY_a:
+ if (MOD__CTRL_ONLY(event) && this->text) {
+ Inkscape::Text::Layout const *layout = te_get_layout(this->text);
+ if (layout) {
+ this->text_sel_start = layout->begin();
+ this->text_sel_end = layout->end();
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ return TRUE;
+ }
+ }
+ break;
+
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ {
+ if (!this->text) { // printable key; create text if none (i.e. if nascent_object)
+ sp_text_context_setup_text(this);
+ this->nascent_object = 0; // we don't need it anymore, having created a real <text>
+ }
+
+ iterator_pair enter_pair;
+ bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, enter_pair);
+ (void)success; // TODO cleanup
+ this->text_sel_start = this->text_sel_end = enter_pair.first;
+
+ this->text_sel_start = this->text_sel_end = sp_te_insert_line(this->text, this->text_sel_start);
+
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
+ _("New line"));
+ return TRUE;
+ }
+ case GDK_KEY_BackSpace:
+ if (this->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys
+
+ bool noSelection = false;
+
+ if (MOD__CTRL(event)) {
+ this->text_sel_start = this->text_sel_end;
+ }
+
+ if (this->text_sel_start == this->text_sel_end) {
+ if (MOD__CTRL(event)) {
+ this->text_sel_start.prevStartOfWord();
+ } else {
+ this->text_sel_start.prevCursorPosition();
+ }
+ noSelection = true;
+ }
+
+ iterator_pair bspace_pair;
+ bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, bspace_pair);
+
+ if (noSelection) {
+ if (success) {
+ this->text_sel_start = this->text_sel_end = bspace_pair.first;
+ } else { // nothing deleted
+ this->text_sel_start = this->text_sel_end = bspace_pair.second;
+ }
+ } else {
+ if (success) {
+ this->text_sel_start = this->text_sel_end = bspace_pair.first;
+ } else { // nothing deleted
+ this->text_sel_start = bspace_pair.first;
+ this->text_sel_end = bspace_pair.second;
+ }
+ }
+
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
+ _("Backspace"));
+ }
+ return TRUE;
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ if (this->text) {
+ bool noSelection = false;
+
+ if (MOD__CTRL(event)) {
+ this->text_sel_start = this->text_sel_end;
+ }
+
+ if (this->text_sel_start == this->text_sel_end) {
+ if (MOD__CTRL(event)) {
+ this->text_sel_end.nextEndOfWord();
+ } else {
+ this->text_sel_end.nextCursorPosition();
+ }
+ noSelection = true;
+ }
+
+ iterator_pair del_pair;
+ bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, del_pair);
+
+ if (noSelection) {
+ this->text_sel_start = this->text_sel_end = del_pair.first;
+ } else {
+ if (success) {
+ this->text_sel_start = this->text_sel_end = del_pair.first;
+ } else { // nothing deleted
+ this->text_sel_start = del_pair.first;
+ this->text_sel_end = del_pair.second;
+ }
+ }
+
+
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT,
+ _("Delete"));
+ }
+ return TRUE;
+ case GDK_KEY_Left:
+ case GDK_KEY_KP_Left:
+ case GDK_KEY_KP_4:
+ if (this->text) {
+ if (MOD__ALT(event)) {
+ gint mul = 1 + gobble_key_events(
+ get_group0_keyval(&event->key), 0); // with any mask
+ if (MOD__SHIFT(event))
+ sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*-10, 0));
+ else
+ sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*-1, 0));
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT,
+ _("Kern to the left"));
+ } else {
+ if (MOD__CTRL(event))
+ this->text_sel_end.cursorLeftWithControl();
+ else
+ this->text_sel_end.cursorLeft();
+ cursor_moved = true;
+ break;
+ }
+ }
+ return TRUE;
+ case GDK_KEY_Right:
+ case GDK_KEY_KP_Right:
+ case GDK_KEY_KP_6:
+ if (this->text) {
+ if (MOD__ALT(event)) {
+ gint mul = 1 + gobble_key_events(
+ get_group0_keyval(&event->key), 0); // with any mask
+ if (MOD__SHIFT(event))
+ sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*10, 0));
+ else
+ sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*1, 0));
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT,
+ _("Kern to the right"));
+ } else {
+ if (MOD__CTRL(event))
+ this->text_sel_end.cursorRightWithControl();
+ else
+ this->text_sel_end.cursorRight();
+ cursor_moved = true;
+ break;
+ }
+ }
+ return TRUE;
+ case GDK_KEY_Up:
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_8:
+ if (this->text) {
+ if (MOD__ALT(event)) {
+ gint mul = 1 + gobble_key_events(
+ get_group0_keyval(&event->key), 0); // with any mask
+ if (MOD__SHIFT(event))
+ sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*-10));
+ else
+ sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*-1));
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT,
+ _("Kern up"));
+ } else {
+ if (MOD__CTRL(event))
+ this->text_sel_end.cursorUpWithControl();
+ else
+ this->text_sel_end.cursorUp();
+ cursor_moved = true;
+ break;
+ }
+ }
+ return TRUE;
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Down:
+ case GDK_KEY_KP_2:
+ if (this->text) {
+ if (MOD__ALT(event)) {
+ gint mul = 1 + gobble_key_events(
+ get_group0_keyval(&event->key), 0); // with any mask
+ if (MOD__SHIFT(event))
+ sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*10));
+ else
+ sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*1));
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT,
+ _("Kern down"));
+ } else {
+ if (MOD__CTRL(event))
+ this->text_sel_end.cursorDownWithControl();
+ else
+ this->text_sel_end.cursorDown();
+ cursor_moved = true;
+ break;
+ }
+ }
+ return TRUE;
+ case GDK_KEY_Home:
+ case GDK_KEY_KP_Home:
+ if (this->text) {
+ if (MOD__CTRL(event))
+ this->text_sel_end.thisStartOfShape();
+ else
+ this->text_sel_end.thisStartOfLine();
+ cursor_moved = true;
+ break;
+ }
+ return TRUE;
+ case GDK_KEY_End:
+ case GDK_KEY_KP_End:
+ if (this->text) {
+ if (MOD__CTRL(event))
+ this->text_sel_end.nextStartOfShape();
+ else
+ this->text_sel_end.thisEndOfLine();
+ cursor_moved = true;
+ break;
+ }
+ return TRUE;
+ case GDK_KEY_Page_Down:
+ case GDK_KEY_KP_Page_Down:
+ if (this->text) {
+ this->text_sel_end.cursorDown(screenlines);
+ cursor_moved = true;
+ break;
+ }
+ return TRUE;
+ case GDK_KEY_Page_Up:
+ case GDK_KEY_KP_Page_Up:
+ if (this->text) {
+ this->text_sel_end.cursorUp(screenlines);
+ cursor_moved = true;
+ break;
+ }
+ return TRUE;
+ case GDK_KEY_Escape:
+ if (this->creating) {
+ this->creating = 0;
+ if (this->grabbed) {
+ sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME);
+ this->grabbed = NULL;
+ }
+ Inkscape::Rubberband::get(desktop)->stop();
+ } else {
+ sp_desktop_selection(desktop)->clear();
+ }
+ this->nascent_object = FALSE;
+ return TRUE;
+ case GDK_KEY_bracketleft:
+ if (this->text) {
+ if (MOD__ALT(event) || MOD__CTRL(event)) {
+ if (MOD__ALT(event)) {
+ if (MOD__SHIFT(event)) {
+ // FIXME: alt+shift+[] does not work, don't know why
+ sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10);
+ } else {
+ sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1);
+ }
+ } else {
+ sp_te_adjust_rotation(this->text, this->text_sel_start, this->text_sel_end, desktop, -90);
+ }
+ DocumentUndo::maybeDone(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT,
+ _("Rotate counterclockwise"));
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ return TRUE;
+ }
+ }
+ break;
+ case GDK_KEY_bracketright:
+ if (this->text) {
+ if (MOD__ALT(event) || MOD__CTRL(event)) {
+ if (MOD__ALT(event)) {
+ if (MOD__SHIFT(event)) {
+ // FIXME: alt+shift+[] does not work, don't know why
+ sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10);
+ } else {
+ sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1);
+ }
+ } else {
+ sp_te_adjust_rotation(this->text, this->text_sel_start, this->text_sel_end, desktop, 90);
+ }
+ DocumentUndo::maybeDone(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT,
+ _("Rotate clockwise"));
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ return TRUE;
+ }
+ }
+ break;
+ case GDK_KEY_less:
+ case GDK_KEY_comma:
+ if (this->text) {
+ if (MOD__ALT(event)) {
+ if (MOD__CTRL(event)) {
+ if (MOD__SHIFT(event))
+ sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10);
+ else
+ sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1);
+ DocumentUndo::maybeDone(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT,
+ _("Contract line spacing"));
+ } else {
+ if (MOD__SHIFT(event))
+ sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10);
+ else
+ sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1);
+ DocumentUndo::maybeDone(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT,
+ _("Contract letter spacing"));
+ }
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ return TRUE;
+ }
+ }
+ break;
+ case GDK_KEY_greater:
+ case GDK_KEY_period:
+ if (this->text) {
+ if (MOD__ALT(event)) {
+ if (MOD__CTRL(event)) {
+ if (MOD__SHIFT(event))
+ sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10);
+ else
+ sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1);
+ DocumentUndo::maybeDone(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT,
+ _("Expand line spacing"));
+ } else {
+ if (MOD__SHIFT(event))
+ sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10);
+ else
+ sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1);
+ DocumentUndo::maybeDone(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT,
+ _("Expand letter spacing"));\
+ }
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ return TRUE;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (cursor_moved) {
+ if (!MOD__SHIFT(event))
+ this->text_sel_start = this->text_sel_end;
+ if (old_start != this->text_sel_start || old_end != this->text_sel_end) {
+ sp_text_context_update_cursor(this);
+ sp_text_context_update_text_selection(this);
+ }
+ return TRUE;
+ }
+
+ } else return TRUE; // return the "I took care of it" value if it was consumed by the IM
+ } else { // do nothing if there's no object to type in - the key will be sent to parent context,
+ // except up/down that are swallowed to prevent the zoom field from activation
+ if ((group0_keyval == GDK_KEY_Up ||
+ group0_keyval == GDK_KEY_Down ||
+ group0_keyval == GDK_KEY_KP_Up ||
+ group0_keyval == GDK_KEY_KP_Down )
+ && !MOD__CTRL_ONLY(event)) {
+ return TRUE;
+ } else if (group0_keyval == GDK_KEY_Escape) { // cancel rubberband
+ if (this->creating) {
+ this->creating = 0;
+ if (this->grabbed) {
+ sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME);
+ this->grabbed = NULL;
+ }
+ Inkscape::Rubberband::get(desktop)->stop();
+ }
+ } else if ((group0_keyval == GDK_KEY_x || group0_keyval == GDK_KEY_X) && MOD__ALT_ONLY(event)) {
+ desktop->setToolboxFocusTo ("altx-text");
+ return TRUE;
+ }
+ }
+ break;
+ }
+
+ case GDK_KEY_RELEASE:
+ if (!this->unimode && this->imc && gtk_im_context_filter_keypress(this->imc, (GdkEventKey*) event)) {
+ return TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+
+ // if nobody consumed it so far
+// if ((SP_EVENT_CONTEXT_CLASS(sp_text_context_parent_class))->root_handler) { // and there's a handler in parent context,
+// return (SP_EVENT_CONTEXT_CLASS(sp_text_context_parent_class))->root_handler(event_context, event); // send event to parent
+// } else {
+// return FALSE; // return "I did nothing" value so that global shortcuts can be activated
+// }
+ return ToolBase::root_handler(event);
+
+}
+
+/**
+ Attempts to paste system clipboard into the currently edited text, returns true on success
+ */
+bool sp_text_paste_inline(ToolBase *ec)
+{
+ if (!SP_IS_TEXT_CONTEXT(ec))
+ return false;
+
+ TextTool *tc = SP_TEXT_CONTEXT(ec);
+
+ if ((tc->text) || (tc->nascent_object)) {
+ // there is an active text object in this context, or a new object was just created
+
+ Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
+ Glib::ustring const clip_text = refClipboard->wait_for_text();
+
+ if (!clip_text.empty()) {
+ // Fix for 244940
+ // The XML standard defines the following as valid characters
+ // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2)
+ // char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
+ // Since what comes in off the paste buffer will go right into XML, clean
+ // the text here.
+ Glib::ustring text(clip_text);
+ Glib::ustring::iterator itr = text.begin();
+ gunichar paste_string_uchar;
+
+ while(itr != text.end())
+ {
+ paste_string_uchar = *itr;
+
+ // Make sure we don't have a control character. We should really check
+ // for the whole range above... Add the rest of the invalid cases from
+ // above if we find additional issues
+ if(paste_string_uchar >= 0x00000020 ||
+ paste_string_uchar == 0x00000009 ||
+ paste_string_uchar == 0x0000000A ||
+ paste_string_uchar == 0x0000000D) {
+ itr++;
+ } else {
+ itr = text.erase(itr);
+ }
+ }
+
+ if (!tc->text) { // create text if none (i.e. if nascent_object)
+ sp_text_context_setup_text(tc);
+ tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
+ }
+
+ // using indices is slow in ustrings. Whatever.
+ Glib::ustring::size_type begin = 0;
+ for ( ; ; ) {
+ Glib::ustring::size_type end = text.find('\n', begin);
+ if (end == Glib::ustring::npos) {
+ if (begin != text.length())
+ tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, text.substr(begin).c_str());
+ break;
+ }
+ tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, text.substr(begin, end - begin).c_str());
+ tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start);
+ begin = end + 1;
+ }
+ DocumentUndo::done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT,
+ _("Paste text"));
+
+ return true;
+ }
+ } // FIXME: else create and select a new object under cursor!
+
+ return false;
+}
+
+/**
+ Gets the raw characters that comprise the currently selected text, converting line
+ breaks into lf characters.
+*/
+Glib::ustring sp_text_get_selected_text(ToolBase const *ec)
+{
+ if (!SP_IS_TEXT_CONTEXT(ec))
+ return "";
+ TextTool const *tc = SP_TEXT_CONTEXT(ec);
+ if (tc->text == NULL)
+ return "";
+
+ return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end);
+}
+
+SPCSSAttr *sp_text_get_style_at_cursor(ToolBase const *ec)
+{
+ if (!SP_IS_TEXT_CONTEXT(ec))
+ return NULL;
+ TextTool const *tc = SP_TEXT_CONTEXT(ec);
+ if (tc->text == NULL)
+ return NULL;
+
+ SPObject const *obj = sp_te_object_at_position(tc->text, tc->text_sel_end);
+
+ if (obj) {
+ return take_style_from_item(const_cast<SPObject*>(obj));
+ }
+
+ return NULL;
+}
+
+/**
+ Deletes the currently selected characters. Returns false if there is no
+ text selection currently.
+*/
+bool sp_text_delete_selection(ToolBase *ec)
+{
+ if (!SP_IS_TEXT_CONTEXT(ec))
+ return false;
+ TextTool *tc = SP_TEXT_CONTEXT(ec);
+ if (tc->text == NULL)
+ return false;
+
+ if (tc->text_sel_start == tc->text_sel_end)
+ return false;
+
+ iterator_pair pair;
+ bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair);
+
+
+ if (success) {
+ tc->text_sel_start = tc->text_sel_end = pair.first;
+ } else { // nothing deleted
+ tc->text_sel_start = pair.first;
+ tc->text_sel_end = pair.second;
+ }
+
+ sp_text_context_update_cursor(tc);
+ sp_text_context_update_text_selection(tc);
+
+ return true;
+}
+
+/**
+ * \param selection Should not be NULL.
+ */
+static void
+sp_text_context_selection_changed(Inkscape::Selection *selection, TextTool *tc)
+{
+ g_assert(selection != NULL);
+
+ ToolBase *ec = SP_EVENT_CONTEXT(tc);
+
+ ec->shape_editor->unset_item(SH_KNOTHOLDER);
+ SPItem *item = selection->singleItem();
+ if (item && SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) {
+ ec->shape_editor->set_item(item, SH_KNOTHOLDER);
+ }
+
+ if (tc->text && (item != tc->text)) {
+ sp_text_context_forget_text(tc);
+ }
+ tc->text = NULL;
+
+ if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
+ tc->text = item;
+ Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
+ if (layout)
+ tc->text_sel_start = tc->text_sel_end = layout->end();
+ } else {
+ tc->text = NULL;
+ }
+
+ // we update cursor without scrolling, because this position may not be final;
+ // item_handler moves cusros to the point of click immediately
+ sp_text_context_update_cursor(tc, false);
+ sp_text_context_update_text_selection(tc);
+}
+
+static void
+sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, TextTool *tc)
+{
+ sp_text_context_update_cursor(tc);
+ sp_text_context_update_text_selection(tc);
+}
+
+static bool sp_text_context_style_set(SPCSSAttr const *css, TextTool *tc)
+{
+ if (tc->text == NULL)
+ return false;
+ if (tc->text_sel_start == tc->text_sel_end)
+ return false; // will get picked up by the parent and applied to the whole text object
+
+ sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css);
+ DocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
+ _("Set text style"));
+ sp_text_context_update_cursor(tc);
+ sp_text_context_update_text_selection(tc);
+
+ return true;
+}
+
+static int
+sp_text_context_style_query(SPStyle *style, int property, TextTool *tc)
+{
+ if (tc->text == NULL) {
+ return QUERY_STYLE_NOTHING;
+ }
+ const Inkscape::Text::Layout *layout = te_get_layout(tc->text);
+ if (layout == NULL) {
+ return QUERY_STYLE_NOTHING;
+ }
+ sp_text_context_validate_cursor_iterators(tc);
+
+ GSList *styles_list = NULL;
+
+ Inkscape::Text::Layout::iterator begin_it, end_it;
+ if (tc->text_sel_start < tc->text_sel_end) {
+ begin_it = tc->text_sel_start;
+ end_it = tc->text_sel_end;
+ } else {
+ begin_it = tc->text_sel_end;
+ end_it = tc->text_sel_start;
+ }
+ if (begin_it == end_it) {
+ if (!begin_it.prevCharacter()) {
+ end_it.nextCharacter();
+ }
+ }
+ for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) {
+ SPObject const *pos_obj = 0;
+ void *rawptr = 0;
+ layout->getSourceOfCharacter(it, &rawptr);
+ if (!rawptr || !SP_IS_OBJECT(rawptr)) {
+ continue;
+ }
+ pos_obj = SP_OBJECT(rawptr);
+ while (SP_IS_STRING(pos_obj) && pos_obj->parent) {
+ pos_obj = pos_obj->parent; // SPStrings don't have style
+ }
+ styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj);
+ }
+
+ int result = sp_desktop_query_style_from_list (styles_list, style, property);
+
+ g_slist_free(styles_list);
+ return result;
+}
+
+static void sp_text_context_validate_cursor_iterators(TextTool *tc)
+{
+ if (tc->text == NULL)
+ return;
+ Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
+ if (layout) { // undo can change the text length without us knowing it
+ layout->validateIterator(&tc->text_sel_start);
+ layout->validateIterator(&tc->text_sel_end);
+ }
+}
+
+static void sp_text_context_update_cursor(TextTool *tc, bool scroll_to_see)
+{
+ // due to interruptible display, tc may already be destroyed during a display update before
+ // the cursor update (can't do both atomically, alas)
+ if (!tc->desktop) return;
+
+ if (tc->text) {
+ Geom::Point p0, p1;
+ sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1);
+ Geom::Point const d0 = p0 * tc->text->i2dt_affine();
+ Geom::Point const d1 = p1 * tc->text->i2dt_affine();
+
+ // scroll to show cursor
+ if (scroll_to_see) {
+ Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint();
+ if (Geom::L2(d0 - center) > Geom::L2(d1 - center))
+ // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed
+ SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0);
+ else
+ SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0);
+ }
+
+ sp_canvas_item_show(tc->cursor);
+ tc->cursor->setCoords(d0, d1);
+
+ /* fixme: ... need another transformation to get canvas widget coordinate space? */
+ if (tc->imc) {
+ GdkRectangle im_cursor = { 0, 0, 1, 1 };
+ Geom::Point const top_left = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().corner(3);
+ Geom::Point const im_d0 = SP_EVENT_CONTEXT(tc)->desktop->d2w(d0 - top_left);
+ Geom::Point const im_d1 = SP_EVENT_CONTEXT(tc)->desktop->d2w(d1 - top_left);
+ im_cursor.x = (int) floor(im_d0[Geom::X]);
+ im_cursor.y = (int) floor(im_d1[Geom::Y]);
+ im_cursor.width = (int) floor(im_d1[Geom::X]) - im_cursor.x;
+ im_cursor.height = (int) floor(im_d0[Geom::Y]) - im_cursor.y;
+ gtk_im_context_set_cursor_location(tc->imc, &im_cursor);
+ }
+
+ tc->show = TRUE;
+ tc->phase = 1;
+
+ Inkscape::Text::Layout const *layout = te_get_layout(tc->text);
+ int const nChars = layout->iteratorToCharIndex(layout->end());
+ char const *trunc = "";
+ bool truncated = false;
+ if (layout->inputTruncated()) {
+ truncated = true;
+ trunc = _(" [truncated]");
+ }
+ if (SP_IS_FLOWTEXT(tc->text)) {
+ SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only
+ if (frame) {
+ if (truncated) {
+ SP_CTRLRECT(tc->frame)->setColor(0xff0000ff, false, 0);
+ } else {
+ SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0);
+ }
+ sp_canvas_item_show(tc->frame);
+ Geom::OptRect frame_bbox = frame->desktopVisualBounds();
+ if (frame_bbox) {
+ SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox);
+ }
+ }
+
+ SP_EVENT_CONTEXT(tc)->message_context->setF(Inkscape::NORMAL_MESSAGE, ngettext("Type or edit flowed text (%d character%s); <b>Enter</b> to start new paragraph.", "Type or edit flowed text (%d characters%s); <b>Enter</b> to start new paragraph.", nChars), nChars, trunc);
+ } else {
+ SP_EVENT_CONTEXT(tc)->message_context->setF(Inkscape::NORMAL_MESSAGE, ngettext("Type or edit text (%d character%s); <b>Enter</b> to start new line.", "Type or edit text (%d characters%s); <b>Enter</b> to start new line.", nChars), nChars, trunc);
+ }
+
+ } else {
+ sp_canvas_item_hide(tc->cursor);
+ sp_canvas_item_hide(tc->frame);
+ tc->show = FALSE;
+ if (!tc->nascent_object) {
+ SP_EVENT_CONTEXT(tc)->message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> to select or create text, <b>drag</b> to create flowed text; then type.")); // FIXME: this is a copy of string from tools-switch, do not desync
+ }
+ }
+
+ SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc);
+}
+
+static void sp_text_context_update_text_selection(TextTool *tc)
+{
+ // due to interruptible display, tc may already be destroyed during a display update before
+ // the selection update (can't do both atomically, alas)
+ if (!tc->desktop) return;
+
+ for (std::vector<SPCanvasItem*>::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) {
+ sp_canvas_item_hide(*it);
+ sp_canvas_item_destroy(*it);
+ }
+ tc->text_selection_quads.clear();
+
+ std::vector<Geom::Point> quads;
+ if (tc->text != NULL)
+ quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, (tc->text)->i2dt_affine());
+ for (unsigned i = 0 ; i < quads.size() ; i += 4) {
+ SPCanvasItem *quad_canvasitem;
+ quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL);
+ // FIXME: make the color settable in prefs
+ // for now, use semitrasparent blue, as cairo cannot do inversion :(
+ sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777);
+ sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]);
+ sp_canvas_item_show(quad_canvasitem);
+ tc->text_selection_quads.push_back(quad_canvasitem);
+ }
+}
+
+static gint sp_text_context_timeout(TextTool *tc)
+{
+ if (tc->show) {
+ sp_canvas_item_show(tc->cursor);
+ if (tc->phase) {
+ tc->phase = 0;
+ tc->cursor->setRgba32(0x000000ff);
+ } else {
+ tc->phase = 1;
+ tc->cursor->setRgba32(0xffffffff);
+ }
+ }
+
+ return TRUE;
+}
+
+static void sp_text_context_forget_text(TextTool *tc)
+{
+ if (! tc->text) return;
+ SPItem *ti = tc->text;
+ (void)ti;
+ /* We have to set it to zero,
+ * or selection changed signal messes everything up */
+ tc->text = NULL;
+
+/* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext.
+ So don't create an empty flowtext in the first place? Create it when first character is typed.
+ */
+/*
+ if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) {
+ Inkscape::XML::Node *text_repr = ti->getRepr();
+ // the repr may already have been unparented
+ // if we were called e.g. as the result of
+ // an undo or the element being removed from
+ // the XML editor
+ if ( text_repr && text_repr->parent() ) {
+ sp_repr_unparent(text_repr);
+ SPDocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT,
+ _("Remove empty text"));
+ }
+ }
+*/
+}
+
+gint sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, TextTool *tc)
+{
+ gtk_im_context_focus_in(tc->imc);
+ return FALSE;
+}
+
+gint sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, TextTool *tc)
+{
+ gtk_im_context_focus_out(tc->imc);
+ return FALSE;
+}
+
+static void sptc_commit(GtkIMContext */*imc*/, gchar *string, TextTool *tc)
+{
+ if (!tc->text) {
+ sp_text_context_setup_text(tc);
+ tc->nascent_object = 0; // we don't need it anymore, having created a real <text>
+ }
+
+ tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string);
+ sp_text_context_update_cursor(tc);
+ sp_text_context_update_text_selection(tc);
+
+ DocumentUndo::done(tc->text->document, SP_VERB_CONTEXT_TEXT,
+ _("Type text"));
+}
+
+void sp_text_context_place_cursor (TextTool *tc, SPObject *text, Inkscape::Text::Layout::iterator where)
+{
+ SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text);
+ tc->text_sel_start = tc->text_sel_end = where;
+ sp_text_context_update_cursor(tc);
+ sp_text_context_update_text_selection(tc);
+}
+
+void sp_text_context_place_cursor_at (TextTool *tc, SPObject *text, Geom::Point const p)
+{
+ SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text);
+ sp_text_context_place_cursor (tc, text, sp_te_get_position_by_coords(tc->text, p));
+}
+
+Inkscape::Text::Layout::iterator *sp_text_context_get_cursor_position(TextTool *tc, SPObject *text)
+{
+ if (text != tc->text)
+ return NULL;
+ return &(tc->text_sel_end);
+}
+
+}
+}
+}
+
+/*
+ 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/tools/text-tool.h b/src/ui/tools/text-tool.h
new file mode 100644
index 000000000..ef8a67984
--- /dev/null
+++ b/src/ui/tools/text-tool.h
@@ -0,0 +1,101 @@
+#ifndef __SP_TEXT_CONTEXT_H__
+#define __SP_TEXT_CONTEXT_H__
+
+/*
+ * TextTool
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ * Copyright (C) 1999-2005 authors
+ * Copyright (C) 2001 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+/* #include <gdk/gdkic.h> */
+#include <stddef.h>
+#include <sigc++/sigc++.h>
+#include <gtk/gtk.h>
+
+#include "ui/tools/tool-base.h"
+#include <2geom/point.h>
+#include "libnrtype/Layout-TNG.h"
+
+#define SP_TEXT_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::TextTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_TEXT_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::TextTool*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+struct SPCtrlLine;
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class TextTool : public ToolBase {
+public:
+ TextTool();
+ virtual ~TextTool();
+
+ sigc::connection sel_changed_connection;
+ sigc::connection sel_modified_connection;
+ sigc::connection style_set_connection;
+ sigc::connection style_query_connection;
+
+ GtkIMContext *imc;
+
+ SPItem *text; // the text we're editing, or NULL if none selected
+
+ /* Text item position in root coordinates */
+ Geom::Point pdoc;
+ /* Insertion point position */
+ Inkscape::Text::Layout::iterator text_sel_start;
+ Inkscape::Text::Layout::iterator text_sel_end;
+
+ gchar uni[9];
+ bool unimode;
+ guint unipos;
+
+ SPCtrlLine *cursor;
+ SPCanvasItem *indicator;
+ SPCanvasItem *frame; // hiliting the first frame of flowtext; FIXME: make this a list to accommodate arbitrarily many chained shapes
+ std::vector<SPCanvasItem*> text_selection_quads;
+ gint timeout;
+ guint show : 1;
+ guint phase : 1;
+ guint nascent_object : 1; // true if we're clicked on canvas to put cursor, but no text typed yet so ->text is still NULL
+
+ guint over_text : 1; // true if cursor is over a text object
+
+ guint dragging : 2; // dragging selection over text
+
+ guint creating : 1; // dragging rubberband to create flowtext
+ SPCanvasItem *grabbed; // we grab while we are creating, to get events even if the mouse goes out of the window
+ Geom::Point p0; // initial point if the flowtext rect
+
+ /* Preedit String */
+ gchar* preedit_string;
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual void finish();
+ virtual bool root_handler(GdkEvent* event);
+ virtual bool item_handler(SPItem* item, GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+};
+
+bool sp_text_paste_inline(ToolBase *ec);
+Glib::ustring sp_text_get_selected_text(ToolBase const *ec);
+SPCSSAttr *sp_text_get_style_at_cursor(ToolBase const *ec);
+bool sp_text_delete_selection(ToolBase *ec);
+void sp_text_context_place_cursor (TextTool *tc, SPObject *text, Inkscape::Text::Layout::iterator where);
+void sp_text_context_place_cursor_at (TextTool *tc, SPObject *text, Geom::Point const p);
+Inkscape::Text::Layout::iterator *sp_text_context_get_cursor_position(TextTool *tc, SPObject *text);
+
+}
+}
+}
+
+#endif
diff --git a/src/ui/tools/tool-base.cpp b/src/ui/tools/tool-base.cpp
new file mode 100644
index 000000000..c123aec27
--- /dev/null
+++ b/src/ui/tools/tool-base.cpp
@@ -0,0 +1,1541 @@
+/*
+ * Main event handling, and related helper functions.
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Frank Felfe <innerspace@iname.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 1999-2012 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#if GLIBMM_DISABLE_DEPRECATED && HAVE_GLIBMM_THREADS_H
+#include <glibmm/threads.h>
+#endif
+
+#include "shortcuts.h"
+#include "file.h"
+#include "ui/tools/tool-base.h"
+
+#include <string.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <glibmm/i18n.h>
+#include <cstring>
+#include <string>
+
+#include "display/sp-canvas.h"
+#include "xml/node-event-vector.h"
+#include "sp-cursor.h"
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "desktop-events.h"
+#include "desktop-style.h"
+#include "widgets/desktop-widget.h"
+#include "sp-namedview.h"
+#include "selection.h"
+#include "interface.h"
+#include "macros.h"
+#include "tools-switch.h"
+#include "preferences.h"
+#include "message-context.h"
+#include "gradient-drag.h"
+#include "attributes.h"
+#include "rubberband.h"
+#include "selcue.h"
+#include "ui/tools/lpe-tool.h"
+#include "ui/tool/control-point.h"
+#include "shape-editor.h"
+#include "sp-guide.h"
+#include "color.h"
+
+// globals for temporary switching to selector by space
+static bool selector_toggled = FALSE;
+static int switch_selector_to = 0;
+
+// globals for temporary switching to dropper by 'D'
+static bool dropper_toggled = FALSE;
+static int switch_dropper_to = 0;
+
+//static gint xp = 0, yp = 0; // where drag started
+//static gint tolerance = 0;
+//static bool within_tolerance = false;
+
+// globals for keeping track of keyboard scroll events in order to accelerate
+static guint32 scroll_event_time = 0;
+static gdouble scroll_multiply = 1;
+static guint scroll_keyval = 0;
+
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+static void set_event_location(SPDesktop * desktop, GdkEvent * event);
+
+
+void ToolBase::set(const Inkscape::Preferences::Entry& /*val*/) {
+}
+
+void ToolBase::activate() {
+}
+
+void ToolBase::deactivate() {
+}
+
+void ToolBase::finish() {
+ this->enableSelectionCue(false);
+}
+
+ToolBase::ToolBase() {
+ this->hot_y = 0;
+ this->xp = 0;
+ this->cursor_shape = 0;
+ this->pref_observer = 0;
+ this->hot_x = 0;
+ this->yp = 0;
+ this->within_tolerance = false;
+ this->tolerance = 0;
+ //this->key = 0;
+ this->item_to_select = 0;
+
+ this->desktop = NULL;
+ this->cursor = NULL;
+ this->message_context = NULL;
+ this->_selcue = NULL;
+ this->_grdrag = NULL;
+ this->space_panning = false;
+ this->shape_editor = NULL;
+ this->_delayed_snap_event = NULL;
+ this->_dse_callback_in_process = false;
+ //this->tool_url = NULL;
+}
+
+ToolBase::~ToolBase() {
+ if (this->message_context) {
+ delete this->message_context;
+ }
+
+ if (this->cursor != NULL) {
+#if GTK_CHECK_VERSION(3,0,0)
+ g_object_unref(this->cursor);
+#else
+ gdk_cursor_unref(this->cursor);
+#endif
+ this->cursor = NULL;
+ }
+
+ if (this->desktop) {
+ this->desktop = NULL;
+ }
+
+ if (this->pref_observer) {
+ delete this->pref_observer;
+ }
+
+ if (this->_delayed_snap_event) {
+ delete this->_delayed_snap_event;
+ }
+}
+
+
+/**
+ * Set the cursor to a standard GDK cursor
+ */
+static void sp_event_context_set_cursor(ToolBase *event_context, GdkCursorType cursor_type) {
+
+ GtkWidget *w = GTK_WIDGET(sp_desktop_canvas(event_context->desktop));
+ GdkDisplay *display = gdk_display_get_default();
+ GdkCursor *cursor = gdk_cursor_new_for_display(display, cursor_type);
+
+#if WITH_GTKMM_3_0
+ if (cursor) {
+ gdk_window_set_cursor (gtk_widget_get_window (w), cursor);
+ g_object_unref (cursor);
+ }
+#else
+ gdk_window_set_cursor (gtk_widget_get_window (w), cursor);
+ gdk_cursor_unref (cursor);
+#endif
+
+}
+
+/**
+ * Recreates and draws cursor on desktop related to ToolBase.
+ */
+void ToolBase::sp_event_context_update_cursor() {
+ GtkWidget *w = GTK_WIDGET(sp_desktop_canvas(this->desktop));
+ if (gtk_widget_get_window (w)) {
+
+ GtkStyle *style = gtk_widget_get_style(w);
+
+ /* fixme: */
+ if (this->cursor_shape) {
+ GdkDisplay *display = gdk_display_get_default();
+ if (gdk_display_supports_cursor_alpha(display) && gdk_display_supports_cursor_color(display)) {
+ bool fillHasColor=false, strokeHasColor=false;
+ guint32 fillColor = sp_desktop_get_color_tool(this->desktop, this->getPrefsPath(), true, &fillHasColor);
+ guint32 strokeColor = sp_desktop_get_color_tool(this->desktop, this->getPrefsPath(), false, &strokeHasColor);
+ double fillOpacity = fillHasColor ? sp_desktop_get_opacity_tool(this->desktop, this->getPrefsPath(), true) : 0;
+ double strokeOpacity = strokeHasColor ? sp_desktop_get_opacity_tool(this->desktop, this->getPrefsPath(), false) : 0;
+
+ GdkPixbuf *pixbuf = sp_cursor_pixbuf_from_xpm(
+ this->cursor_shape,
+ style->black, style->white,
+ SP_RGBA32_U_COMPOSE(SP_RGBA32_R_U(fillColor),SP_RGBA32_G_U(fillColor),SP_RGBA32_B_U(fillColor),SP_COLOR_F_TO_U(fillOpacity)),
+ SP_RGBA32_U_COMPOSE(SP_RGBA32_R_U(strokeColor),SP_RGBA32_G_U(strokeColor),SP_RGBA32_B_U(strokeColor),SP_COLOR_F_TO_U(strokeOpacity))
+ );
+ if (pixbuf != NULL) {
+ if (this->cursor) {
+#if GTK_CHECK_VERSION(3,0,0)
+ g_object_unref(this->cursor);
+#else
+ gdk_cursor_unref(this->cursor);
+#endif
+ }
+ this->cursor = gdk_cursor_new_from_pixbuf(display, pixbuf, this->hot_x, this->hot_y);
+ g_object_unref(pixbuf);
+ }
+ } else {
+ GdkPixbuf *pixbuf = gdk_pixbuf_new_from_xpm_data((const gchar **)this->cursor_shape);
+
+ if (pixbuf) {
+ if (this->cursor) {
+#if GTK_CHECK_VERSION(3,0,0)
+ g_object_unref(this->cursor);
+#else
+ gdk_cursor_unref(this->cursor);
+#endif
+ }
+ this->cursor = gdk_cursor_new_from_pixbuf(display,
+ pixbuf, this->hot_x, this->hot_y);
+ g_object_unref(pixbuf);
+ }
+ }
+ }
+ gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor);
+ gdk_flush();
+ }
+ this->desktop->waiting_cursor = false;
+}
+
+/**
+ * Callback that gets called on initialization of ToolBase object.
+ * Redraws mouse cursor, at the moment.
+ */
+
+/**
+ * When you override it, call this method first.
+ */
+void ToolBase::setup() {
+ this->pref_observer = new ToolPrefObserver(this->getPrefsPath(), this);
+ Inkscape::Preferences::get()->addObserver(*(this->pref_observer));
+
+ this->sp_event_context_update_cursor();
+}
+
+/**
+ * Gobbles next key events on the queue with the same keyval and mask. Returns the number of events consumed.
+ */
+gint gobble_key_events(guint keyval, gint mask) {
+ GdkEvent *event_next;
+ gint i = 0;
+
+ event_next = gdk_event_get();
+ // while the next event is also a key notify with the same keyval and mask,
+ while (event_next && (event_next->type == GDK_KEY_PRESS || event_next->type
+ == GDK_KEY_RELEASE) && event_next->key.keyval == keyval && (!mask
+ || (event_next->key.state & mask))) {
+ if (event_next->type == GDK_KEY_PRESS)
+ i++;
+ // kill it
+ gdk_event_free(event_next);
+ // get next
+ event_next = gdk_event_get();
+ }
+ // otherwise, put it back onto the queue
+ if (event_next)
+ gdk_event_put(event_next);
+
+ return i;
+}
+
+/**
+ * Gobbles next motion notify events on the queue with the same mask. Returns the number of events consumed.
+ */
+gint gobble_motion_events(gint mask) {
+ GdkEvent *event_next;
+ gint i = 0;
+
+ event_next = gdk_event_get();
+ // while the next event is also a key notify with the same keyval and mask,
+ while (event_next && event_next->type == GDK_MOTION_NOTIFY
+ && (event_next->motion.state & mask)) {
+ // kill it
+ gdk_event_free(event_next);
+ // get next
+ event_next = gdk_event_get();
+ i++;
+ }
+ // otherwise, put it back onto the queue
+ if (event_next)
+ gdk_event_put(event_next);
+
+ return i;
+}
+
+/**
+ * Toggles current tool between active tool and selector tool.
+ * Subroutine of sp_event_context_private_root_handler().
+ */
+static void sp_toggle_selector(SPDesktop *dt) {
+ if (!dt->event_context)
+ return;
+
+ if (tools_isactive(dt, TOOLS_SELECT)) {
+ if (selector_toggled) {
+ if (switch_selector_to)
+ tools_switch(dt, switch_selector_to);
+ selector_toggled = FALSE;
+ } else
+ return;
+ } else {
+ selector_toggled = TRUE;
+ switch_selector_to = tools_active(dt);
+ tools_switch(dt, TOOLS_SELECT);
+ }
+}
+
+/**
+ * Toggles current tool between active tool and dropper tool.
+ * Subroutine of sp_event_context_private_root_handler().
+ */
+void sp_toggle_dropper(SPDesktop *dt) {
+ if (!dt->event_context)
+ return;
+
+ if (tools_isactive(dt, TOOLS_DROPPER)) {
+ if (dropper_toggled) {
+ if (switch_dropper_to)
+ tools_switch(dt, switch_dropper_to);
+ dropper_toggled = FALSE;
+ } else
+ return;
+ } else {
+ dropper_toggled = TRUE;
+ switch_dropper_to = tools_active(dt);
+ tools_switch(dt, TOOLS_DROPPER);
+ }
+}
+
+/**
+ * Calculates and keeps track of scroll acceleration.
+ * Subroutine of sp_event_context_private_root_handler().
+ */
+static gdouble accelerate_scroll(GdkEvent *event, gdouble acceleration,
+ SPCanvas */*canvas*/) {
+ guint32 time_diff = ((GdkEventKey *) event)->time - scroll_event_time;
+
+ /* key pressed within 500ms ? (1/2 second) */
+ if (time_diff > 500 || event->key.keyval != scroll_keyval) {
+ scroll_multiply = 1; // abort acceleration
+ } else {
+ scroll_multiply += acceleration; // continue acceleration
+ }
+
+ scroll_event_time = ((GdkEventKey *) event)->time;
+ scroll_keyval = event->key.keyval;
+
+ return scroll_multiply;
+}
+
+/**
+ * Main event dispatch, gets called from Gdk.
+ */
+//static gint sp_event_context_private_root_handler(
+// ToolBase *event_context, GdkEvent *event) {
+//
+// return event_context->ceventcontext->root_handler(event);
+//}
+
+bool ToolBase::root_handler(GdkEvent* event) {
+ static Geom::Point button_w;
+ static unsigned int panning = 0;
+ static unsigned int panning_cursor = 0;
+ static unsigned int zoom_rb = 0;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ /// @todo REmove redundant /value in preference keys
+ tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_2BUTTON_PRESS:
+ if (panning) {
+ panning = 0;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
+ ret = TRUE;
+ } else {
+ /* sp_desktop_dialog(); */
+ }
+ break;
+
+ case GDK_BUTTON_PRESS:
+ // save drag origin
+ xp = (gint) event->button.x;
+ yp = (gint) event->button.y;
+ within_tolerance = true;
+
+ button_w = Geom::Point(event->button.x, event->button.y);
+
+ switch (event->button.button) {
+ case 1:
+ if (this->space_panning) {
+ // When starting panning, make sure there are no snap events pending because these might disable the panning again
+ sp_event_context_discard_delayed_snap_event(this);
+ panning = 1;
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK
+ | GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK, NULL,
+ event->button.time - 1);
+
+ ret = TRUE;
+ }
+ break;
+
+ case 2:
+ if (event->button.state & GDK_SHIFT_MASK) {
+ zoom_rb = 2;
+ } else {
+ // When starting panning, make sure there are no snap events pending because these might disable the panning again
+ sp_event_context_discard_delayed_snap_event(this);
+ panning = 2;
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK, NULL,
+ event->button.time - 1);
+
+ }
+
+ ret = TRUE;
+ break;
+
+ case 3:
+ if ((event->button.state & GDK_SHIFT_MASK) || (event->button.state & GDK_CONTROL_MASK)) {
+ // When starting panning, make sure there are no snap events pending because these might disable the panning again
+ sp_event_context_discard_delayed_snap_event(this);
+ panning = 3;
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK, NULL,
+ event->button.time);
+
+ ret = TRUE;
+ } else {
+ sp_event_root_menu_popup(desktop, NULL, event);
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ if (panning) {
+ if (panning == 4 && !xp && !yp ) {
+ // <Space> + mouse panning started, save location and grab canvas
+ xp = event->motion.x;
+ yp = event->motion.y;
+ button_w = Geom::Point(event->motion.x, event->motion.y);
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK
+ | GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK, NULL,
+ event->motion.time - 1);
+ }
+
+ if ((panning == 2 && !(event->motion.state & GDK_BUTTON2_MASK))
+ || (panning == 1 && !(event->motion.state & GDK_BUTTON1_MASK))
+ || (panning == 3 && !(event->motion.state & GDK_BUTTON3_MASK))) {
+ /* Gdk seems to lose button release for us sometimes :-( */
+ panning = 0;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
+ ret = TRUE;
+ } else {
+ if (within_tolerance && (abs((gint) event->motion.x - xp)
+ < tolerance) && (abs((gint) event->motion.y - yp)
+ < tolerance)) {
+ // do not drag if we're within tolerance from origin
+ break;
+ }
+
+ // Once the user has moved farther than tolerance from
+ // the original location (indicating they intend to move
+ // the object, not click), then always process the motion
+ // notify coordinates as given (no snapping back to origin)
+ within_tolerance = false;
+
+ // gobble subsequent motion events to prevent "sticking"
+ // when scrolling is slow
+ gobble_motion_events(panning == 2 ? GDK_BUTTON2_MASK : (panning
+ == 1 ? GDK_BUTTON1_MASK : GDK_BUTTON3_MASK));
+
+ if (panning_cursor == 0) {
+ panning_cursor = 1;
+ sp_event_context_set_cursor(this, GDK_FLEUR);
+ }
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point const moved_w(motion_w - button_w);
+ this->desktop->scroll_world(moved_w, true); // we're still scrolling, do not redraw
+ ret = TRUE;
+ }
+ } else if (zoom_rb) {
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point const motion_dt(desktop->w2d(motion_w));
+
+ if (within_tolerance && (abs((gint) event->motion.x - xp)
+ < tolerance) && (abs((gint) event->motion.y - yp)
+ < tolerance)) {
+ break; // do not drag if we're within tolerance from origin
+ }
+
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to move the object, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ within_tolerance = false;
+
+ if (Inkscape::Rubberband::get(desktop)->is_started()) {
+ Inkscape::Rubberband::get(desktop)->move(motion_dt);
+ } else {
+ Inkscape::Rubberband::get(desktop)->start(desktop, motion_dt);
+ }
+
+ if (zoom_rb == 2) {
+ gobble_motion_events(GDK_BUTTON2_MASK);
+ }
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ xp = yp = 0;
+
+ if (panning_cursor == 1) {
+ panning_cursor = 0;
+ GtkWidget *w = GTK_WIDGET(sp_desktop_canvas(this->desktop));
+ gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor);
+ }
+
+ if (within_tolerance && (panning || zoom_rb)) {
+ zoom_rb = 0;
+
+ if (panning) {
+ panning = 0;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
+ event->button.time);
+ }
+
+ Geom::Point const event_w(event->button.x, event->button.y);
+ Geom::Point const event_dt(desktop->w2d(event_w));
+
+ double const zoom_inc = prefs->getDoubleLimited(
+ "/options/zoomincrement/value", M_SQRT2, 1.01, 10);
+
+ desktop->zoom_relative_keep_point(event_dt, (event->button.state
+ & GDK_SHIFT_MASK) ? 1 / zoom_inc : zoom_inc);
+
+ desktop->updateNow();
+ ret = TRUE;
+ } else if (panning == event->button.button) {
+ panning = 0;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
+ event->button.time);
+
+ // in slow complex drawings, some of the motion events are lost;
+ // to make up for this, we scroll it once again to the button-up event coordinates
+ // (i.e. canvas will always get scrolled all the way to the mouse release point,
+ // even if few intermediate steps were visible)
+ Geom::Point const motion_w(event->button.x, event->button.y);
+ Geom::Point const moved_w(motion_w - button_w);
+
+ this->desktop->scroll_world(moved_w);
+ desktop->updateNow();
+ ret = TRUE;
+ } else if (zoom_rb == event->button.button) {
+ zoom_rb = 0;
+
+ Geom::OptRect const b = Inkscape::Rubberband::get(desktop)->getRectangle();
+ Inkscape::Rubberband::get(desktop)->stop();
+
+ if (b && !within_tolerance) {
+ desktop->set_display_area(*b, 10);
+ }
+
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_PRESS: {
+ double const acceleration = prefs->getDoubleLimited(
+ "/options/scrollingacceleration/value", 0, 0, 6);
+
+ int const key_scroll = prefs->getIntLimited("/options/keyscroll/value",
+ 10, 0, 1000);
+
+ switch (get_group0_keyval(&event->key)) {
+ // GDK insists on stealing these keys (F1 for no idea what, tab for cycling widgets
+ // in the editing window). So we resteal them back and run our regular shortcut
+ // invoker on them.
+ unsigned int shortcut;
+ case GDK_KEY_Tab:
+ case GDK_KEY_ISO_Left_Tab:
+ case GDK_KEY_F1:
+ shortcut = get_group0_keyval(&event->key);
+
+ if (event->key.state & GDK_SHIFT_MASK) {
+ shortcut |= SP_SHORTCUT_SHIFT_MASK;
+ }
+
+ if (event->key.state & GDK_CONTROL_MASK) {
+ shortcut |= SP_SHORTCUT_CONTROL_MASK;
+ }
+
+ if (event->key.state & GDK_MOD1_MASK) {
+ shortcut |= SP_SHORTCUT_ALT_MASK;
+ }
+
+ ret = sp_shortcut_invoke(shortcut, desktop);
+ break;
+
+ case GDK_KEY_Q:
+ case GDK_KEY_q:
+ if (desktop->quick_zoomed()) {
+ ret = TRUE;
+ }
+ if (!MOD__SHIFT(event) && !MOD__CTRL(event) && !MOD__ALT(event)) {
+ desktop->zoom_quick(true);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_W:
+ case GDK_KEY_w:
+ case GDK_KEY_F4:
+ /* Close view */
+ if (MOD__CTRL_ONLY(event)) {
+ sp_ui_close_view(NULL);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Left: // Ctrl Left
+ case GDK_KEY_KP_Left:
+ case GDK_KEY_KP_4:
+ if (MOD__CTRL_ONLY(event)) {
+ int i = (int) floor(key_scroll * accelerate_scroll(event,
+ acceleration, sp_desktop_canvas(desktop)));
+
+ gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
+ this->desktop->scroll_world(i, 0);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Up: // Ctrl Up
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_8:
+ if (MOD__CTRL_ONLY(event)) {
+ int i = (int) floor(key_scroll * accelerate_scroll(event,
+ acceleration, sp_desktop_canvas(desktop)));
+
+ gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
+ this->desktop->scroll_world(0, i);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Right: // Ctrl Right
+ case GDK_KEY_KP_Right:
+ case GDK_KEY_KP_6:
+ if (MOD__CTRL_ONLY(event)) {
+ int i = (int) floor(key_scroll * accelerate_scroll(event,
+ acceleration, sp_desktop_canvas(desktop)));
+
+ gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
+ this->desktop->scroll_world(-i, 0);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Down: // Ctrl Down
+ case GDK_KEY_KP_Down:
+ case GDK_KEY_KP_2:
+ if (MOD__CTRL_ONLY(event)) {
+ int i = (int) floor(key_scroll * accelerate_scroll(event,
+ acceleration, sp_desktop_canvas(desktop)));
+
+ gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
+ this->desktop->scroll_world(0, -i);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_F10:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_event_root_menu_popup(desktop, NULL, event);
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_space:
+ xp = yp = 0;
+ within_tolerance = true;
+ panning = 4;
+
+ this->space_panning = true;
+ this->message_context->set(Inkscape::INFORMATION_MESSAGE,
+ _("<b>Space+mouse move</b> to pan canvas"));
+
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_z:
+ case GDK_KEY_Z:
+ if (MOD__ALT_ONLY(event)) {
+ desktop->zoom_grab_focus();
+ ret = TRUE;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ break;
+
+ case GDK_KEY_RELEASE:
+ // Stop panning on any key release
+ if (this->space_panning) {
+ this->space_panning = false;
+ this->message_context->clear();
+ }
+
+ if (panning) {
+ panning = 0;
+ xp = yp = 0;
+
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
+ event->key.time);
+
+ desktop->updateNow();
+ }
+
+ if (panning_cursor == 1) {
+ panning_cursor = 0;
+ GtkWidget *w = GTK_WIDGET(sp_desktop_canvas(this->desktop));
+ gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor);
+ }
+
+ switch (get_group0_keyval(&event->key)) {
+ case GDK_KEY_space:
+ if (within_tolerance == true) {
+ // Space was pressed, but not panned
+ sp_toggle_selector(desktop);
+ ret = TRUE;
+ }
+
+ within_tolerance = false;
+ break;
+
+ case GDK_KEY_Q:
+ case GDK_KEY_q:
+ if (desktop->quick_zoomed()) {
+ desktop->zoom_quick(false);
+ ret = TRUE;
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case GDK_SCROLL: {
+ bool ctrl = (event->scroll.state & GDK_CONTROL_MASK);
+ bool wheelzooms = prefs->getBool("/options/wheelzooms/value");
+
+ int const wheel_scroll = prefs->getIntLimited(
+ "/options/wheelscroll/value", 40, 0, 1000);
+
+#if GTK_CHECK_VERSION(3,0,0)
+ // Size of smooth-scrolls (only used in GTK+ 3)
+ gdouble delta_x = 0;
+ gdouble delta_y = 0;
+#endif
+
+ /* shift + wheel, pan left--right */
+ if (event->scroll.state & GDK_SHIFT_MASK) {
+ switch (event->scroll.direction) {
+ case GDK_SCROLL_UP:
+ desktop->scroll_world(wheel_scroll, 0);
+ break;
+
+ case GDK_SCROLL_DOWN:
+ desktop->scroll_world(-wheel_scroll, 0);
+ break;
+
+ default:
+ break;
+ }
+
+ /* ctrl + wheel, zoom in--out */
+ } else if ((ctrl && !wheelzooms) || (!ctrl && wheelzooms)) {
+ double rel_zoom;
+ double const zoom_inc = prefs->getDoubleLimited(
+ "/options/zoomincrement/value", M_SQRT2, 1.01, 10);
+
+ switch (event->scroll.direction) {
+ case GDK_SCROLL_UP:
+ rel_zoom = zoom_inc;
+ break;
+
+ case GDK_SCROLL_DOWN:
+ rel_zoom = 1 / zoom_inc;
+ break;
+
+ default:
+ rel_zoom = 0.0;
+ break;
+ }
+
+ if (rel_zoom != 0.0) {
+ Geom::Point const scroll_dt = desktop->point();
+ desktop->zoom_relative_keep_point(scroll_dt, rel_zoom);
+ }
+
+ /* no modifier, pan up--down (left--right on multiwheel mice?) */
+ } else {
+ switch (event->scroll.direction) {
+ case GDK_SCROLL_UP:
+ desktop->scroll_world(0, wheel_scroll);
+ break;
+
+ case GDK_SCROLL_DOWN:
+ desktop->scroll_world(0, -wheel_scroll);
+ break;
+
+ case GDK_SCROLL_LEFT:
+ desktop->scroll_world(wheel_scroll, 0);
+ break;
+
+ case GDK_SCROLL_RIGHT:
+ desktop->scroll_world(-wheel_scroll, 0);
+ break;
+
+#if GTK_CHECK_VERSION(3,0,0)
+ case GDK_SCROLL_SMOOTH:
+ gdk_event_get_scroll_deltas(event, &delta_x, &delta_y);
+ desktop->scroll_world(delta_x, delta_y);
+ break;
+#endif
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * Handles item specific events. Gets called from Gdk.
+ *
+ * Only reacts to right mouse button at the moment.
+ * \todo Fixme: do context sensitive popup menu on items.
+ */
+//gint sp_event_context_private_item_handler(ToolBase *ec, SPItem *item,
+// GdkEvent *event) {
+//
+// return ec->ceventcontext->item_handler(item, event);
+//}
+
+bool ToolBase::item_handler(SPItem* item, GdkEvent* event) {
+ int ret = FALSE;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ if ((event->button.button == 3) && !((event->button.state & GDK_SHIFT_MASK) || (event->button.state & GDK_CONTROL_MASK))) {
+ sp_event_root_menu_popup(this->desktop, item, event);
+ ret = TRUE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * Returns true if we're hovering above a knot (needed because we don't want to pre-snap in that case).
+ */
+bool sp_event_context_knot_mouseover(ToolBase *ec)
+{
+ if (ec->shape_editor) {
+ return ec->shape_editor->knot_mouseover();
+ }
+
+ return false;
+}
+
+/**
+ * Creates new ToolBase object and calls its virtual setup() function.
+ * @todo This is bogus. pref_path should be a private property of the inheriting objects.
+ */
+//ToolBase *
+//sp_event_context_new(GType type, SPDesktop *desktop, gchar const *pref_path,
+// unsigned int key) {
+// g_return_val_if_fail(g_type_is_a(type, SP_TYPE_EVENT_CONTEXT), NULL);
+// g_return_val_if_fail(desktop != NULL, NULL);
+//
+// ToolBase * const ec = static_cast<ToolBase*>(g_object_new(type, NULL));
+//
+// ec->desktop = desktop;
+// ec->_message_context
+// = new Inkscape::MessageContext(desktop->messageStack());
+// ec->key = key;
+// ec->pref_observer = NULL;
+//
+// if (pref_path) {
+// ec->pref_observer = new ToolPrefObserver(pref_path, ec);
+//
+// Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+// prefs->addObserver(*(ec->pref_observer));
+// }
+//
+//// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->setup)
+//// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->setup(ec);
+// ec->ceventcontext->setup();
+//
+// return ec;
+//}
+
+/**
+ * Finishes ToolBase.
+ */
+//void sp_event_context_finish(ToolBase *ec) {
+// g_return_if_fail(ec != NULL);
+// g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
+//
+// ec->enableSelectionCue(false);
+//
+//// if (ec->next) {
+//// g_warning("Finishing event context with active link\n");
+//// }
+//
+//// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->finish)
+//// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->finish(ec);
+// ec->finish();
+//}
+
+//-------------------------------member functions
+
+/**
+ * Enables/disables the ToolBase's SelCue.
+ */
+void ToolBase::enableSelectionCue(bool enable) {
+ if (enable) {
+ if (!_selcue) {
+ _selcue = new Inkscape::SelCue(desktop);
+ }
+ } else {
+ delete _selcue;
+ _selcue = NULL;
+ }
+}
+
+/**
+ * Enables/disables the ToolBase's GrDrag.
+ */
+void ToolBase::enableGrDrag(bool enable) {
+ if (enable) {
+ if (!_grdrag) {
+ _grdrag = new GrDrag(desktop);
+ }
+ } else {
+ if (_grdrag) {
+ delete _grdrag;
+ _grdrag = NULL;
+ }
+ }
+}
+
+/**
+ * Delete a selected GrDrag point
+ */
+bool ToolBase::deleteSelectedDrag(bool just_one) {
+
+ if (_grdrag && _grdrag->selected) {
+ _grdrag->deleteSelected(just_one);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Calls virtual set() function of ToolBase.
+ */
+void sp_event_context_read(ToolBase *ec, gchar const *key) {
+ g_return_if_fail(ec != NULL);
+ g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
+ g_return_if_fail(key != NULL);
+
+// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->set) {
+// Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+// Inkscape::Preferences::Entry val = prefs->getEntry(
+// ec->pref_observer->observed_path + '/' + key);
+// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->set(ec, &val);
+// }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ Inkscape::Preferences::Entry val = prefs->getEntry(ec->pref_observer->observed_path + '/' + key);
+ ec->set(val);
+}
+
+/**
+ * Calls virtual activate() function of ToolBase.
+ */
+void sp_event_context_activate(ToolBase *ec) {
+ g_return_if_fail(ec != NULL);
+ g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
+
+ // Make sure no delayed snapping events are carried over after switching contexts
+ // (this is only an additional safety measure against sloppy coding, because each
+ // context should take care of this by itself.
+ sp_event_context_discard_delayed_snap_event(ec);
+
+// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->activate)
+// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->activate(ec);
+ ec->activate();
+}
+
+/**
+ * Calls virtual deactivate() function of ToolBase.
+ */
+//void sp_event_context_deactivate(ToolBase *ec) {
+// g_return_if_fail(ec != NULL);
+// g_return_if_fail(SP_IS_EVENT_CONTEXT(ec));
+//
+//// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->deactivate)
+//// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->deactivate(ec);
+// ec->deactivate();
+//}
+
+/**
+ * Calls virtual root_handler(), the main event handling function.
+ */
+gint sp_event_context_root_handler(ToolBase * event_context,
+ GdkEvent * event)
+{
+ switch (event->type) {
+ case GDK_MOTION_NOTIFY:
+ sp_event_context_snap_delay_handler(event_context, NULL, NULL,
+ (GdkEventMotion *) event,
+ DelayedSnapEvent::EVENTCONTEXT_ROOT_HANDLER);
+ break;
+ case GDK_BUTTON_RELEASE:
+ if (event_context && event_context->_delayed_snap_event) {
+ // If we have any pending snapping action, then invoke it now
+ sp_event_context_snap_watchdog_callback(
+ event_context->_delayed_snap_event);
+ }
+ break;
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ // Snapping will be on hold if we're moving the mouse at high speeds. When starting
+ // drawing a new shape we really should snap though.
+ event_context->desktop->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(
+ false);
+ break;
+ default:
+ break;
+ }
+
+ return sp_event_context_virtual_root_handler(event_context, event);
+}
+
+gint sp_event_context_virtual_root_handler(ToolBase * event_context, GdkEvent * event) {
+ gint ret = false;
+ if (event_context) { // If no event-context is available then do nothing, otherwise Inkscape would crash
+ // (see the comment in SPDesktop::set_event_context, and bug LP #622350)
+ //ret = (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(event_context)))->root_handler(event_context, event);
+ ret = event_context->root_handler(event);
+
+ set_event_location(event_context->desktop, event);
+ }
+ return ret;
+}
+
+/**
+ * Calls virtual item_handler(), the item event handling function.
+ */
+gint sp_event_context_item_handler(ToolBase * event_context,
+ SPItem * item, GdkEvent * event) {
+ switch (event->type) {
+ case GDK_MOTION_NOTIFY:
+ sp_event_context_snap_delay_handler(event_context, (gpointer) item, NULL, (GdkEventMotion *) event, DelayedSnapEvent::EVENTCONTEXT_ITEM_HANDLER);
+ break;
+ case GDK_BUTTON_RELEASE:
+ if (event_context && event_context->_delayed_snap_event) {
+ // If we have any pending snapping action, then invoke it now
+ sp_event_context_snap_watchdog_callback(event_context->_delayed_snap_event);
+ }
+ break;
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ // Snapping will be on hold if we're moving the mouse at high speeds. When starting
+ // drawing a new shape we really should snap though.
+ event_context->desktop->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(false);
+ break;
+ default:
+ break;
+ }
+
+ return sp_event_context_virtual_item_handler(event_context, item, event);
+}
+
+gint sp_event_context_virtual_item_handler(ToolBase * event_context, SPItem * item, GdkEvent * event) {
+ gint ret = false;
+ if (event_context) { // If no event-context is available then do nothing, otherwise Inkscape would crash
+ // (see the comment in SPDesktop::set_event_context, and bug LP #622350)
+ //ret = (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(event_context)))->item_handler(event_context, item, event);
+ ret = event_context->item_handler(item, event);
+
+ if (!ret) {
+ ret = sp_event_context_virtual_root_handler(event_context, event);
+ } else {
+ set_event_location(event_context->desktop, event);
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * Shows coordinates on status bar.
+ */
+static void set_event_location(SPDesktop *desktop, GdkEvent *event) {
+ if (event->type != GDK_MOTION_NOTIFY) {
+ return;
+ }
+
+ Geom::Point const button_w(event->button.x, event->button.y);
+ Geom::Point const button_dt(desktop->w2d(button_w));
+ desktop->set_coordinate_status(button_dt);
+}
+
+//-------------------------------------------------------------------
+/**
+ * Create popup menu and tell Gtk to show it.
+ */
+void sp_event_root_menu_popup(SPDesktop *desktop, SPItem *item, GdkEvent *event) {
+
+ // It seems the param item is the SPItem at the bottom of the z-order
+ // Using the same function call used on left click in sp_select_context_item_handler() to get top of z-order
+ // fixme: sp_canvas_arena should set the top z-order object as arena->active
+ item = sp_event_context_find_item (desktop,
+ Geom::Point(event->button.x, event->button.y), FALSE, FALSE);
+
+ /* fixme: This is not what I want but works for now (Lauris) */
+ if (event->type == GDK_KEY_PRESS) {
+ item = sp_desktop_selection(desktop)->singleItem();
+ }
+
+ ContextMenu* CM = new ContextMenu(desktop, item);
+ CM->show();
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ CM->popup(event->button.button, event->button.time);
+ break;
+ case GDK_KEY_PRESS:
+ CM->popup(0, event->key.time);
+ break;
+ default:
+ break;
+ }
+}
+
+/**
+ * Show tool context specific modifier tip.
+ */
+void sp_event_show_modifier_tip(Inkscape::MessageContext *message_context,
+ GdkEvent *event, gchar const *ctrl_tip, gchar const *shift_tip,
+ gchar const *alt_tip) {
+ guint keyval = get_group0_keyval(&event->key);
+
+ bool ctrl = ctrl_tip && (MOD__CTRL(event) || (keyval == GDK_KEY_Control_L) || (keyval
+ == GDK_KEY_Control_R));
+ bool shift = shift_tip && (MOD__SHIFT(event) || (keyval == GDK_KEY_Shift_L) || (keyval
+ == GDK_KEY_Shift_R));
+ bool alt = alt_tip && (MOD__ALT(event) || (keyval == GDK_KEY_Alt_L) || (keyval
+ == GDK_KEY_Alt_R) || (keyval == GDK_KEY_Meta_L) || (keyval == GDK_KEY_Meta_R));
+
+ gchar *tip = g_strdup_printf("%s%s%s%s%s", (ctrl ? ctrl_tip : ""), (ctrl
+ && (shift || alt) ? "; " : ""), (shift ? shift_tip : ""), ((ctrl
+ || shift) && alt ? "; " : ""), (alt ? alt_tip : ""));
+
+ if (strlen(tip) > 0) {
+ message_context->flash(Inkscape::INFORMATION_MESSAGE, tip);
+ }
+
+ g_free(tip);
+}
+
+/**
+ * Return the keyval corresponding to the key event in group 0, i.e.,
+ * in the main (English) layout.
+ *
+ * Use this instead of simply event->keyval, so that your keyboard shortcuts
+ * work regardless of layouts (e.g., in Cyrillic).
+ */
+guint get_group0_keyval(GdkEventKey *event) {
+ guint keyval = 0;
+
+ gdk_keymap_translate_keyboard_state(gdk_keymap_get_for_display(
+ gdk_display_get_default()), event->hardware_keycode,
+ (GdkModifierType) event->state, 0 /*event->key.group*/, &keyval,
+ NULL, NULL, NULL);
+
+ return keyval;
+}
+
+/**
+ * Returns item at point p in desktop.
+ *
+ * If state includes alt key mask, cyclically selects under; honors
+ * into_groups.
+ */
+SPItem *sp_event_context_find_item(SPDesktop *desktop, Geom::Point const &p,
+ bool select_under, bool into_groups)
+{
+ SPItem *item = 0;
+
+ if (select_under) {
+ SPItem *selected_at_point = desktop->getItemFromListAtPointBottom(
+ desktop->selection->itemList(), p);
+ item = desktop->getItemAtPoint(p, into_groups, selected_at_point);
+ if (item == NULL) { // we may have reached bottom, flip over to the top
+ item = desktop->getItemAtPoint(p, into_groups, NULL);
+ }
+ } else {
+ item = desktop->getItemAtPoint(p, into_groups, NULL);
+ }
+
+ return item;
+}
+
+/**
+ * Returns item if it is under point p in desktop, at any depth; otherwise returns NULL.
+ *
+ * Honors into_groups.
+ */
+SPItem *
+sp_event_context_over_item(SPDesktop *desktop, SPItem *item,
+ Geom::Point const &p) {
+ GSList *temp = NULL;
+ temp = g_slist_prepend(temp, item);
+ SPItem *item_at_point = desktop->getItemFromListAtPointBottom(temp, p);
+ g_slist_free(temp);
+
+ return item_at_point;
+}
+
+ShapeEditor *
+sp_event_context_get_shape_editor(ToolBase *ec) {
+ return ec->shape_editor;
+}
+
+void event_context_print_event_info(GdkEvent *event, bool print_return) {
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ g_print("GDK_BUTTON_PRESS");
+ break;
+ case GDK_2BUTTON_PRESS:
+ g_print("GDK_2BUTTON_PRESS");
+ break;
+ case GDK_3BUTTON_PRESS:
+ g_print("GDK_3BUTTON_PRESS");
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ g_print("GDK_MOTION_NOTIFY");
+ break;
+ case GDK_ENTER_NOTIFY:
+ g_print("GDK_ENTER_NOTIFY");
+ break;
+
+ case GDK_LEAVE_NOTIFY:
+ g_print("GDK_LEAVE_NOTIFY");
+ break;
+ case GDK_BUTTON_RELEASE:
+ g_print("GDK_BUTTON_RELEASE");
+ break;
+
+ case GDK_KEY_PRESS:
+ g_print("GDK_KEY_PRESS: %d", get_group0_keyval(&event->key));
+ break;
+ case GDK_KEY_RELEASE:
+ g_print("GDK_KEY_RELEASE: %d", get_group0_keyval(&event->key));
+ break;
+ default:
+ //g_print ("even type not recognized");
+ break;
+ }
+
+ if (print_return) {
+ g_print("\n");
+ }
+}
+
+/**
+ * Analyses the current event, calculates the mouse speed, turns snapping off (temporarily) if the
+ * mouse speed is above a threshold, and stores the current event such that it can be re-triggered when needed
+ * (re-triggering is controlled by a watchdog timer).
+ *
+ * @param ec Pointer to the event context.
+ * @param dse_item Pointer that store a reference to a canvas or to an item.
+ * @param dse_item2 Another pointer, storing a reference to a knot or controlpoint.
+ * @param event Pointer to the motion event.
+ * @param origin Identifier (enum) specifying where the delay (and the call to this method) were initiated.
+ */
+void sp_event_context_snap_delay_handler(ToolBase *ec,
+ gpointer const dse_item, gpointer const dse_item2, GdkEventMotion *event,
+ DelayedSnapEvent::DelayedSnapEventOrigin origin)
+{
+ static guint32 prev_time;
+ static boost::optional<Geom::Point> prev_pos;
+
+ if (ec->_dse_callback_in_process) {
+ return;
+ }
+
+ // Snapping occurs when dragging with the left mouse button down, or when hovering e.g. in the pen tool with left mouse button up
+ bool const c1 = event->state & GDK_BUTTON2_MASK; // We shouldn't hold back any events when other mouse buttons have been
+ bool const c2 = event->state & GDK_BUTTON3_MASK; // pressed, e.g. when scrolling with the middle mouse button; if we do then
+ // Inkscape will get stuck in an unresponsive state
+ bool const c3 = tools_isactive(ec->desktop, TOOLS_CALLIGRAPHIC);
+ // The snap delay will repeat the last motion event, which will lead to
+ // erroneous points in the calligraphy context. And because we don't snap
+ // in this context, we might just as well disable the snap delay all together
+
+ if (c1 || c2 || c3) {
+ // Make sure that we don't send any pending snap events to a context if we know in advance
+ // that we're not going to snap any way (e.g. while scrolling with middle mouse button)
+ // Any motion event might affect the state of the context, leading to unexpected behavior
+ sp_event_context_discard_delayed_snap_event(ec);
+ } else if (ec->desktop
+ && ec->desktop->namedview->snap_manager.snapprefs.getSnapEnabledGlobally()) {
+ // Snap when speed drops below e.g. 0.02 px/msec, or when no motion events have occurred for some period.
+ // i.e. snap when we're at stand still. A speed threshold enforces snapping for tablets, which might never
+ // be fully at stand still and might keep spitting out motion events.
+ ec->desktop->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(true); // put snapping on hold
+
+ Geom::Point event_pos(event->x, event->y);
+ guint32 event_t = gdk_event_get_time((GdkEvent *) event);
+
+ if (prev_pos) {
+ Geom::Coord dist = Geom::L2(event_pos - *prev_pos);
+ guint32 delta_t = event_t - prev_time;
+ gdouble speed = delta_t > 0 ? dist / delta_t : 1000;
+ //std::cout << "Mouse speed = " << speed << " px/msec " << std::endl;
+ if (speed > 0.02) { // Jitter threshold, might be needed for tablets
+ // We're moving fast, so postpone any snapping until the next GDK_MOTION_NOTIFY event. We
+ // will keep on postponing the snapping as long as the speed is high.
+ // We must snap at some point in time though, so set a watchdog timer at some time from
+ // now, just in case there's no future motion event that drops under the speed limit (when
+ // stopping abruptly)
+ delete ec->_delayed_snap_event;
+ ec->_delayed_snap_event = new DelayedSnapEvent(ec, dse_item, dse_item2,
+ event, origin); // watchdog is reset, i.e. pushed forward in time
+ // If the watchdog expires before a new motion event is received, we will snap (as explained
+ // above). This means however that when the timer is too short, we will always snap and that the
+ // speed threshold is ineffective. In the extreme case the delay is set to zero, and snapping will
+ // be immediate, as it used to be in the old days ;-).
+ } else { // Speed is very low, so we're virtually at stand still
+ // But if we're really standing still, then we should snap now. We could use some low-pass filtering,
+ // otherwise snapping occurs for each jitter movement. For this filtering we'll leave the watchdog to expire,
+ // snap, and set a new watchdog again.
+ if (ec->_delayed_snap_event == NULL) { // no watchdog has been set
+ // it might have already expired, so we'll set a new one; the snapping frequency will be limited this way
+ ec->_delayed_snap_event = new DelayedSnapEvent(ec, dse_item,
+ dse_item2, event, origin);
+ } // else: watchdog has been set before and we'll wait for it to expire
+ }
+ } else {
+ // This is the first GDK_MOTION_NOTIFY event, so postpone snapping and set the watchdog
+ g_assert(ec->_delayed_snap_event == NULL);
+ ec->_delayed_snap_event = new DelayedSnapEvent(ec, dse_item, dse_item2,
+ event, origin);
+ }
+
+ prev_pos = event_pos;
+ prev_time = event_t;
+ }
+}
+
+/**
+ * When the snap delay watchdog timer barks, this method will be called and will re-inject the last motion
+ * event in an appropriate place, with snapping being turned on again.
+ */
+gboolean sp_event_context_snap_watchdog_callback(gpointer data) {
+ // Snap NOW! For this the "postponed" flag will be reset and the last motion event will be repeated
+ DelayedSnapEvent *dse = reinterpret_cast<DelayedSnapEvent*> (data);
+
+ if (dse == NULL) {
+ // This might occur when this method is called directly, i.e. not through the timer
+ // E.g. on GDK_BUTTON_RELEASE in sp_event_context_root_handler()
+ return FALSE;
+ }
+
+ ToolBase *ec = dse->getEventContext();
+ if (ec == NULL) {
+ delete dse;
+ return false;
+ }
+ if (ec->desktop == NULL) {
+ ec->_delayed_snap_event = NULL;
+ delete dse;
+ return false;
+ }
+
+ ec->_dse_callback_in_process = true;
+
+ SPDesktop *dt = ec->desktop;
+ dt->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(false);
+
+ // Depending on where the delayed snap event originated from, we will inject it back at it's origin
+ // The switch below takes care of that and prepares the relevant parameters
+ switch (dse->getOrigin()) {
+ case DelayedSnapEvent::EVENTCONTEXT_ROOT_HANDLER:
+ sp_event_context_virtual_root_handler(ec, dse->getEvent());
+ break;
+ case DelayedSnapEvent::EVENTCONTEXT_ITEM_HANDLER: {
+ gpointer item = dse->getItem();
+ if (item && SP_IS_ITEM(item)) {
+ sp_event_context_virtual_item_handler(ec, SP_ITEM(item), dse->getEvent());
+ }
+ }
+ break;
+ case DelayedSnapEvent::KNOT_HANDLER: {
+ gpointer knot = dse->getItem2();
+ if (knot && SP_IS_KNOT(knot)) {
+ sp_knot_handler_request_position(dse->getEvent(), SP_KNOT(knot));
+ }
+ }
+ break;
+ case DelayedSnapEvent::CONTROL_POINT_HANDLER: {
+ using Inkscape::UI::ControlPoint;
+ gpointer pitem2 = dse->getItem2();
+ if (!pitem2)
+ {
+ ec->_delayed_snap_event = NULL;
+ delete dse;
+ return false;
+ }
+ ControlPoint *point = reinterpret_cast<ControlPoint*> (pitem2);
+ if (point) {
+ if (point->position().isFinite() && (dt == point->_desktop)) {
+ point->_eventHandler(ec, dse->getEvent());
+ }
+ else {
+ //workaround:
+ //[Bug 781893] Crash after moving a Bezier node after Knot path effect?
+ // --> at some time, some point with X = 0 and Y = nan (not a number) is created ...
+ // even so, the desktop pointer is invalid and equal to 0xff
+ g_warning ("encountered non finite point when evaluating snapping callback");
+ }
+ }
+ }
+ break;
+ case DelayedSnapEvent::GUIDE_HANDLER: {
+ gpointer item = dse->getItem();
+ gpointer item2 = dse->getItem2();
+ if (item && item2) {
+ g_assert(SP_IS_CANVAS_ITEM(item));
+ g_assert(SP_IS_GUIDE(item2));
+ sp_dt_guide_event(SP_CANVAS_ITEM(item), dse->getEvent(), item2);
+ }
+ }
+ break;
+ case DelayedSnapEvent::GUIDE_HRULER:
+ case DelayedSnapEvent::GUIDE_VRULER: {
+ gpointer item = dse->getItem();
+ gpointer item2 = dse->getItem2();
+ if (item && item2) {
+ g_assert(GTK_IS_WIDGET(item));
+ g_assert(SP_IS_DESKTOP_WIDGET(item2));
+ if (dse->getOrigin() == DelayedSnapEvent::GUIDE_HRULER) {
+ sp_dt_hruler_event(GTK_WIDGET(item), dse->getEvent(), SP_DESKTOP_WIDGET(item2));
+ } else {
+ sp_dt_vruler_event(GTK_WIDGET(item), dse->getEvent(), SP_DESKTOP_WIDGET(item2));
+ }
+ }
+ }
+ break;
+ default:
+ g_warning("Origin of snap-delay event has not been defined!;");
+ break;
+ }
+
+ ec->_delayed_snap_event = NULL;
+ delete dse;
+
+ ec->_dse_callback_in_process = false;
+
+ return FALSE; //Kills the timer and stops it from executing this callback over and over again.
+}
+
+void sp_event_context_discard_delayed_snap_event(ToolBase *ec) {
+ delete ec->_delayed_snap_event;
+ ec->_delayed_snap_event = NULL;
+ ec->desktop->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(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/tools/tool-base.h b/src/ui/tools/tool-base.h
new file mode 100644
index 000000000..5185f89b1
--- /dev/null
+++ b/src/ui/tools/tool-base.h
@@ -0,0 +1,244 @@
+#ifndef SEEN_SP_EVENT_CONTEXT_H
+#define SEEN_SP_EVENT_CONTEXT_H
+
+/*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Frank Felfe <innerspace@iname.com>
+ *
+ * Copyright (C) 1999-2002 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glib-object.h>
+#include <gdk/gdk.h>
+#include "knot.h"
+
+#include "2geom/forward.h"
+#include "preferences.h"
+
+class GrDrag;
+class SPDesktop;
+class SPItem;
+class ShapeEditor;
+
+namespace Inkscape {
+ class MessageContext;
+ class SelCue;
+ namespace XML {
+ class Node;
+ }
+}
+
+#define SP_EVENT_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::ToolBase*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_EVENT_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::ToolBase*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class ToolBase;
+
+gboolean sp_event_context_snap_watchdog_callback(gpointer data);
+void sp_event_context_discard_delayed_snap_event(ToolBase *ec);
+
+class DelayedSnapEvent
+{
+public:
+ enum DelayedSnapEventOrigin {
+ UNDEFINED_HANDLER = 0,
+ EVENTCONTEXT_ROOT_HANDLER,
+ EVENTCONTEXT_ITEM_HANDLER,
+ KNOT_HANDLER,
+ CONTROL_POINT_HANDLER,
+ GUIDE_HANDLER,
+ GUIDE_HRULER,
+ GUIDE_VRULER
+ };
+
+ DelayedSnapEvent(ToolBase *event_context, gpointer const dse_item, gpointer dse_item2, GdkEventMotion const *event, DelayedSnapEvent::DelayedSnapEventOrigin const origin)
+ : _timer_id(0), _event(NULL), _item(dse_item), _item2(dse_item2), _origin(origin), _event_context(event_context)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double value = prefs->getDoubleLimited("/options/snapdelay/value", 0, 0, 1000);
+ _timer_id = g_timeout_add(value, &sp_event_context_snap_watchdog_callback, this);
+ _event = gdk_event_copy((GdkEvent*) event);
+ ((GdkEventMotion *)_event)->time = GDK_CURRENT_TIME;
+ }
+
+ ~DelayedSnapEvent() {
+ if (_timer_id > 0) g_source_remove(_timer_id); // Kill the watchdog
+ if (_event != NULL) gdk_event_free(_event); // Remove the copy of the original event
+ }
+
+ ToolBase* getEventContext() {return _event_context;}
+ DelayedSnapEventOrigin getOrigin() {return _origin;}
+ GdkEvent* getEvent() {return _event;}
+ gpointer getItem() {return _item;}
+ gpointer getItem2() {return _item2;}
+
+private:
+ guint _timer_id;
+ GdkEvent* _event;
+ gpointer _item;
+ gpointer _item2;
+ DelayedSnapEventOrigin _origin;
+ ToolBase* _event_context;
+};
+
+void sp_event_context_snap_delay_handler(ToolBase *ec, gpointer const dse_item, gpointer const dse_item2, GdkEventMotion *event, DelayedSnapEvent::DelayedSnapEventOrigin origin);
+
+
+/**
+ * Base class for Event processors.
+ *
+ * This is per desktop object, which (its derivatives) implements
+ * different actions bound to mouse events.
+ *
+ * ToolBase is an abstract base class of all tools. As the name
+ * indicates, event context implementations process UI events (mouse
+ * movements and keypresses) and take actions (like creating or modifying
+ * objects). There is one event context implementation for each tool,
+ * plus few abstract base classes. Writing a new tool involves
+ * subclassing ToolBase.
+ */
+class ToolBase {
+public:
+ void enableSelectionCue (bool enable=true);
+ void enableGrDrag (bool enable=true);
+ bool deleteSelectedDrag(bool just_one);
+
+ ToolBase();
+ virtual ~ToolBase();
+
+ SPDesktop *desktop;
+ Inkscape::Preferences::Observer *pref_observer;
+ gchar const *const *cursor_shape;
+ gint hot_x, hot_y; ///< indicates the cursor's hot spot
+ GdkCursor *cursor;
+
+ gint xp, yp; ///< where drag started
+ gint tolerance;
+ bool within_tolerance; ///< are we still within tolerance of origin
+
+ SPItem *item_to_select; ///< the item where mouse_press occurred, to
+ ///< be selected if this is a click not drag
+
+ Inkscape::MessageContext *defaultMessageContext() {
+ return message_context;
+ }
+
+ Inkscape::MessageContext *message_context;
+
+ Inkscape::SelCue *_selcue;
+
+ GrDrag *_grdrag;
+ GrDrag *get_drag () {return _grdrag;}
+
+ ShapeEditor* shape_editor;
+
+ bool space_panning;
+
+ DelayedSnapEvent *_delayed_snap_event;
+ bool _dse_callback_in_process;
+
+ virtual void setup();
+ virtual void finish();
+
+ // Is called by our pref_observer if a preference has been changed.
+ virtual void set(const Inkscape::Preferences::Entry& val);
+
+ virtual void activate();
+ virtual void deactivate();
+
+ virtual bool root_handler(GdkEvent* event);
+ virtual bool item_handler(SPItem* item, GdkEvent* event);
+
+ virtual const std::string& getPrefsPath() = 0;
+
+ /**
+ * An observer that relays pref changes to the derived classes.
+ */
+ class ToolPrefObserver: public Inkscape::Preferences::Observer {
+ public:
+ ToolPrefObserver(Glib::ustring const &path, ToolBase *ec) :
+ Inkscape::Preferences::Observer(path), ec(ec) {
+ }
+
+ virtual void notify(Inkscape::Preferences::Entry const &val) {
+ ec->set(val);
+ }
+
+ private:
+ ToolBase * const ec;
+ };
+
+//protected:
+ void sp_event_context_update_cursor();
+
+private:
+ ToolBase(const ToolBase&);
+ ToolBase& operator=(const ToolBase&);
+};
+
+#define SP_EVENT_CONTEXT_DESKTOP(e) (SP_EVENT_CONTEXT(e)->desktop)
+#define SP_EVENT_CONTEXT_DOCUMENT(e) ((SP_EVENT_CONTEXT_DESKTOP(e))->doc())
+
+#define SP_EVENT_CONTEXT_STATIC 0
+
+//ToolBase *sp_event_context_new(GType type, SPDesktop *desktop, gchar const *pref_path, unsigned key);
+//void sp_event_context_finish(ToolBase *ec);
+void sp_event_context_read(ToolBase *ec, gchar const *key);
+void sp_event_context_activate(ToolBase *ec);
+//void sp_event_context_deactivate(ToolBase *ec);
+
+gint sp_event_context_root_handler(ToolBase *ec, GdkEvent *event);
+gint sp_event_context_virtual_root_handler(ToolBase *ec, GdkEvent *event);
+gint sp_event_context_item_handler(ToolBase *ec, SPItem *item, GdkEvent *event);
+gint sp_event_context_virtual_item_handler(ToolBase *ec, SPItem *item, GdkEvent *event);
+
+void sp_event_root_menu_popup(SPDesktop *desktop, SPItem *item, GdkEvent *event);
+
+gint gobble_key_events(guint keyval, gint mask);
+gint gobble_motion_events(gint mask);
+
+//void sp_event_context_update_cursor(ToolBase *ec);
+
+void sp_event_show_modifier_tip(Inkscape::MessageContext *message_context, GdkEvent *event,
+ gchar const *ctrl_tip, gchar const *shift_tip, gchar const *alt_tip);
+
+guint get_group0_keyval(GdkEventKey *event);
+
+SPItem *sp_event_context_find_item (SPDesktop *desktop, Geom::Point const &p, bool select_under, bool into_groups);
+SPItem *sp_event_context_over_item (SPDesktop *desktop, SPItem *item, Geom::Point const &p);
+
+void sp_toggle_dropper(SPDesktop *dt);
+
+//ShapeEditor *sp_event_context_get_shape_editor (ToolBase *ec);
+bool sp_event_context_knot_mouseover(ToolBase *ec);
+
+//void ec_shape_event_attr_changed(Inkscape::XML::Node *shape_repr,
+// gchar const *name, gchar const *old_value, gchar const *new_value,
+// bool const is_interactive, gpointer const data);
+//
+//void event_context_print_event_info(GdkEvent *event, bool print_return = true);
+
+}
+}
+}
+
+#endif // SEEN_SP_EVENT_CONTEXT_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/tools/tweak-tool.cpp b/src/ui/tools/tweak-tool.cpp
new file mode 100644
index 000000000..0791eff5a
--- /dev/null
+++ b/src/ui/tools/tweak-tool.cpp
@@ -0,0 +1,1524 @@
+/*
+ * tweaking paths without node editing
+ *
+ * Authors:
+ * bulia byak
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glibmm/i18n.h>
+
+#include <numeric>
+
+#include "svg/svg.h"
+
+#include <glib.h>
+#include "macros.h"
+#include "document.h"
+#include "document-undo.h"
+#include "selection.h"
+#include "desktop.h"
+#include "desktop-events.h"
+#include "desktop-handles.h"
+#include "desktop-style.h"
+#include "message-context.h"
+#include "pixmaps/cursor-tweak-move.xpm"
+#include "pixmaps/cursor-tweak-move-in.xpm"
+#include "pixmaps/cursor-tweak-move-out.xpm"
+#include "pixmaps/cursor-tweak-move-jitter.xpm"
+#include "pixmaps/cursor-tweak-scale-up.xpm"
+#include "pixmaps/cursor-tweak-scale-down.xpm"
+#include "pixmaps/cursor-tweak-rotate-clockwise.xpm"
+#include "pixmaps/cursor-tweak-rotate-counterclockwise.xpm"
+#include "pixmaps/cursor-tweak-more.xpm"
+#include "pixmaps/cursor-tweak-less.xpm"
+#include "pixmaps/cursor-thin.xpm"
+#include "pixmaps/cursor-thicken.xpm"
+#include "pixmaps/cursor-attract.xpm"
+#include "pixmaps/cursor-repel.xpm"
+#include "pixmaps/cursor-push.xpm"
+#include "pixmaps/cursor-roughen.xpm"
+#include "pixmaps/cursor-color.xpm"
+#include <boost/optional.hpp>
+#include "xml/repr.h"
+#include "context-fns.h"
+#include "sp-item.h"
+#include "inkscape.h"
+#include "color.h"
+#include "svg/svg-color.h"
+#include "splivarot.h"
+#include "sp-item-group.h"
+#include "sp-shape.h"
+#include "sp-path.h"
+#include "path-chemistry.h"
+#include "sp-gradient.h"
+#include "sp-stop.h"
+#include "sp-gradient-reference.h"
+#include "sp-linear-gradient.h"
+#include "sp-radial-gradient.h"
+#include "gradient-chemistry.h"
+#include "sp-text.h"
+#include "sp-flowtext.h"
+#include "display/sp-canvas.h"
+#include "display/canvas-bpath.h"
+#include "display/canvas-arena.h"
+#include "display/curve.h"
+#include "livarot/Shape.h"
+#include <2geom/transforms.h>
+#include <2geom/circle.h>
+#include "preferences.h"
+#include "style.h"
+#include "box3d.h"
+#include "sp-item-transform.h"
+#include "filter-chemistry.h"
+#include "filters/gaussian-blur.h"
+#include "verbs.h"
+
+#include "ui/tools/tweak-tool.h"
+
+using Inkscape::DocumentUndo;
+
+#define DDC_RED_RGBA 0xff0000ff
+
+#define DYNA_MIN_WIDTH 1.0e-6
+
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+namespace {
+ ToolBase* createTweakContext() {
+ return new TweakTool();
+ }
+
+ bool tweakContextRegistered = ToolFactory::instance().registerObject("/tools/tweak", createTweakContext);
+}
+
+const std::string& TweakTool::getPrefsPath() {
+ return TweakTool::prefsPath;
+}
+
+const std::string TweakTool::prefsPath = "/tools/tweak";
+
+TweakTool::TweakTool() : ToolBase() {
+ this->mode = 0;
+ this->dilate_area = 0;
+ this->usetilt = 0;
+ this->usepressure = 0;
+ this->is_drawing = false;
+ this->fidelity = 0;
+
+ this->cursor_shape = cursor_push_xpm;
+ this->hot_x = 4;
+ this->hot_y = 4;
+
+ /* attributes */
+ this->dragging = FALSE;
+
+ this->width = 0.2;
+ this->force = 0.2;
+ this->pressure = TC_DEFAULT_PRESSURE;
+
+ this->is_dilating = false;
+ this->has_dilated = false;
+
+ this->do_h = true;
+ this->do_s = true;
+ this->do_l = true;
+ this->do_o = false;
+}
+
+TweakTool::~TweakTool() {
+ this->enableGrDrag(false);
+
+ this->style_set_connection.disconnect();
+
+ if (this->dilate_area) {
+ sp_canvas_item_destroy(this->dilate_area);
+ this->dilate_area = NULL;
+ }
+}
+
+static bool is_transform_mode (gint mode)
+{
+ return (mode == TWEAK_MODE_MOVE ||
+ mode == TWEAK_MODE_MOVE_IN_OUT ||
+ mode == TWEAK_MODE_MOVE_JITTER ||
+ mode == TWEAK_MODE_SCALE ||
+ mode == TWEAK_MODE_ROTATE ||
+ mode == TWEAK_MODE_MORELESS);
+}
+
+static bool is_color_mode (gint mode)
+{
+ return (mode == TWEAK_MODE_COLORPAINT || mode == TWEAK_MODE_COLORJITTER || mode == TWEAK_MODE_BLUR);
+}
+
+void TweakTool::update_cursor (bool with_shift) {
+ guint num = 0;
+ gchar *sel_message = NULL;
+
+ if (!desktop->selection->isEmpty()) {
+ num = g_slist_length(const_cast<GSList *>(desktop->selection->itemList()));
+ sel_message = g_strdup_printf(ngettext("<b>%i</b> object selected","<b>%i</b> objects selected",num), num);
+ } else {
+ sel_message = g_strdup_printf("%s", _("<b>Nothing</b> selected"));
+ }
+
+ switch (this->mode) {
+ case TWEAK_MODE_MOVE:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag to <b>move</b>."), sel_message);
+ this->cursor_shape = cursor_tweak_move_xpm;
+ break;
+ case TWEAK_MODE_MOVE_IN_OUT:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>move in</b>; with Shift to <b>move out</b>."), sel_message);
+ if (with_shift) {
+ this->cursor_shape = cursor_tweak_move_out_xpm;
+ } else {
+ this->cursor_shape = cursor_tweak_move_in_xpm;
+ }
+ break;
+ case TWEAK_MODE_MOVE_JITTER:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>move randomly</b>."), sel_message);
+ this->cursor_shape = cursor_tweak_move_jitter_xpm;
+ break;
+ case TWEAK_MODE_SCALE:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>scale down</b>; with Shift to <b>scale up</b>."), sel_message);
+ if (with_shift) {
+ this->cursor_shape = cursor_tweak_scale_up_xpm;
+ } else {
+ this->cursor_shape = cursor_tweak_scale_down_xpm;
+ }
+ break;
+ case TWEAK_MODE_ROTATE:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>rotate clockwise</b>; with Shift, <b>counterclockwise</b>."), sel_message);
+ if (with_shift) {
+ this->cursor_shape = cursor_tweak_rotate_counterclockwise_xpm;
+ } else {
+ this->cursor_shape = cursor_tweak_rotate_clockwise_xpm;
+ }
+ break;
+ case TWEAK_MODE_MORELESS:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>duplicate</b>; with Shift, <b>delete</b>."), sel_message);
+ if (with_shift) {
+ this->cursor_shape = cursor_tweak_less_xpm;
+ } else {
+ this->cursor_shape = cursor_tweak_more_xpm;
+ }
+ break;
+ case TWEAK_MODE_PUSH:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag to <b>push paths</b>."), sel_message);
+ this->cursor_shape = cursor_push_xpm;
+ break;
+ case TWEAK_MODE_SHRINK_GROW:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>inset paths</b>; with Shift to <b>outset</b>."), sel_message);
+ if (with_shift) {
+ this->cursor_shape = cursor_thicken_xpm;
+ } else {
+ this->cursor_shape = cursor_thin_xpm;
+ }
+ break;
+ case TWEAK_MODE_ATTRACT_REPEL:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>attract paths</b>; with Shift to <b>repel</b>."), sel_message);
+ if (with_shift) {
+ this->cursor_shape = cursor_repel_xpm;
+ } else {
+ this->cursor_shape = cursor_attract_xpm;
+ }
+ break;
+ case TWEAK_MODE_ROUGHEN:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>roughen paths</b>."), sel_message);
+ this->cursor_shape = cursor_roughen_xpm;
+ break;
+ case TWEAK_MODE_COLORPAINT:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>paint objects</b> with color."), sel_message);
+ this->cursor_shape = cursor_color_xpm;
+ break;
+ case TWEAK_MODE_COLORJITTER:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>randomize colors</b>."), sel_message);
+ this->cursor_shape = cursor_color_xpm;
+ break;
+ case TWEAK_MODE_BLUR:
+ this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to <b>increase blur</b>; with Shift to <b>decrease</b>."), sel_message);
+ this->cursor_shape = cursor_color_xpm;
+ break;
+ }
+
+ this->sp_event_context_update_cursor();
+ g_free(sel_message);
+}
+
+bool TweakTool::set_style(const SPCSSAttr* css) {
+ if (this->mode == TWEAK_MODE_COLORPAINT) { // intercept color setting only in this mode
+ // we cannot store properties with uris
+ css = sp_css_attr_unset_uris(const_cast<SPCSSAttr *>(css));
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setStyle("/tools/tweak/style", const_cast<SPCSSAttr *>(css));
+ return true;
+ }
+
+ return false;
+}
+
+void TweakTool::setup() {
+ ToolBase::setup();
+
+ {
+ /* TODO: have a look at sp_dyna_draw_context_setup where the same is done.. generalize? at least make it an arcto! */
+ Geom::PathVector path;
+ Geom::Circle(0, 0, 1).getPath(path);
+
+ SPCurve *c = new SPCurve(path);
+
+ this->dilate_area = sp_canvas_bpath_new(sp_desktop_controls(this->desktop), c);
+ c->unref();
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->dilate_area), 0x00000000,(SPWindRule)0);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->dilate_area), 0xff9900ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_item_hide(this->dilate_area);
+ }
+
+ this->is_drawing = false;
+
+ sp_event_context_read(this, "width");
+ sp_event_context_read(this, "mode");
+ sp_event_context_read(this, "fidelity");
+ sp_event_context_read(this, "force");
+ sp_event_context_read(this, "usepressure");
+ sp_event_context_read(this, "doh");
+ sp_event_context_read(this, "dol");
+ sp_event_context_read(this, "dos");
+ sp_event_context_read(this, "doo");
+
+ this->style_set_connection = this->desktop->connectSetStyle( // catch style-setting signal in this tool
+ //sigc::bind(sigc::ptr_fun(&sp_tweak_context_style_set), this)
+ sigc::mem_fun(this, &TweakTool::set_style)
+ );
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/tweak/selcue")) {
+ this->enableSelectionCue();
+ }
+ if (prefs->getBool("/tools/tweak/gradientdrag")) {
+ this->enableGrDrag();
+ }
+}
+
+void TweakTool::set(const Inkscape::Preferences::Entry& val) {
+ Glib::ustring path = val.getEntryName();
+
+ if (path == "width") {
+ this->width = CLAMP(val.getDouble(0.1), -1000.0, 1000.0);
+ } else if (path == "mode") {
+ this->mode = val.getInt();
+ this->update_cursor(false);
+ } else if (path == "fidelity") {
+ this->fidelity = CLAMP(val.getDouble(), 0.0, 1.0);
+ } else if (path == "force") {
+ this->force = CLAMP(val.getDouble(1.0), 0, 1.0);
+ } else if (path == "usepressure") {
+ this->usepressure = val.getBool();
+ } else if (path == "doh") {
+ this->do_h = val.getBool();
+ } else if (path == "dos") {
+ this->do_s = val.getBool();
+ } else if (path == "dol") {
+ this->do_l = val.getBool();
+ } else if (path == "doo") {
+ this->do_o = val.getBool();
+ }
+}
+
+static void
+sp_tweak_extinput(TweakTool *tc, GdkEvent *event)
+{
+ if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &tc->pressure)) {
+ tc->pressure = CLAMP (tc->pressure, TC_MIN_PRESSURE, TC_MAX_PRESSURE);
+ } else {
+ tc->pressure = TC_DEFAULT_PRESSURE;
+ }
+}
+
+static double
+get_dilate_radius (TweakTool *tc)
+{
+ // 10 times the pen width:
+ return 500 * tc->width/SP_EVENT_CONTEXT(tc)->desktop->current_zoom();
+}
+
+static double
+get_path_force (TweakTool *tc)
+{
+ double force = 8 * (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE)
+ /sqrt(SP_EVENT_CONTEXT(tc)->desktop->current_zoom());
+ if (force > 3) {
+ force += 4 * (force - 3);
+ }
+ return force * tc->force;
+}
+
+static double
+get_move_force (TweakTool *tc)
+{
+ double force = (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE);
+ return force * tc->force;
+}
+
+static bool
+sp_tweak_dilate_recursive (Inkscape::Selection *selection, SPItem *item, Geom::Point p, Geom::Point vector, gint mode, double radius, double force, double fidelity, bool reverse)
+{
+ bool did = false;
+
+ if (SP_IS_BOX3D(item) && !is_transform_mode(mode) && !is_color_mode(mode)) {
+ // convert 3D boxes to ordinary groups before tweaking their shapes
+ item = box3d_convert_to_group(SP_BOX3D(item));
+ selection->add(item);
+ }
+
+ if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
+ GSList *items = g_slist_prepend (NULL, item);
+ GSList *selected = NULL;
+ GSList *to_select = NULL;
+ SPDocument *doc = item->document;
+ sp_item_list_to_curves (items, &selected, &to_select);
+ g_slist_free (items);
+ SPObject* newObj = doc->getObjectByRepr(static_cast<Inkscape::XML::Node *>(to_select->data));
+ g_slist_free (to_select);
+ item = SP_ITEM(newObj);
+ selection->add(item);
+ }
+
+ if (SP_IS_GROUP(item) && !SP_IS_BOX3D(item)) {
+ GSList *children = NULL;
+ for (SPObject *child = item->firstChild() ; child; child = child->getNext() ) {
+ if (SP_IS_ITEM(child)) {
+ children = g_slist_prepend(children, child);
+ }
+ }
+
+ for (GSList *i = children; i; i = i->next) {
+ SPItem *child = SP_ITEM(i->data);
+ if (sp_tweak_dilate_recursive (selection, SP_ITEM(child), p, vector, mode, radius, force, fidelity, reverse))
+ did = true;
+ }
+
+ g_slist_free(children);
+
+ } else {
+ if (mode == TWEAK_MODE_MOVE) {
+
+ Geom::OptRect a = item->documentVisualBounds();
+ if (a) {
+ double x = Geom::L2(a->midpoint() - p)/radius;
+ if (a->contains(p)) x = 0;
+ if (x < 1) {
+ Geom::Point move = force * 0.5 * (cos(M_PI * x) + 1) * vector;
+ sp_item_move_rel(item, Geom::Translate(move[Geom::X], -move[Geom::Y]));
+ did = true;
+ }
+ }
+
+ } else if (mode == TWEAK_MODE_MOVE_IN_OUT) {
+
+ Geom::OptRect a = item->documentVisualBounds();
+ if (a) {
+ double x = Geom::L2(a->midpoint() - p)/radius;
+ if (a->contains(p)) x = 0;
+ if (x < 1) {
+ Geom::Point move = force * 0.5 * (cos(M_PI * x) + 1) *
+ (reverse? (a->midpoint() - p) : (p - a->midpoint()));
+ sp_item_move_rel(item, Geom::Translate(move[Geom::X], -move[Geom::Y]));
+ did = true;
+ }
+ }
+
+ } else if (mode == TWEAK_MODE_MOVE_JITTER) {
+
+ Geom::OptRect a = item->documentVisualBounds();
+ if (a) {
+ double dp = g_random_double_range(0, M_PI*2);
+ double dr = g_random_double_range(0, radius);
+ double x = Geom::L2(a->midpoint() - p)/radius;
+ if (a->contains(p)) x = 0;
+ if (x < 1) {
+ Geom::Point move = force * 0.5 * (cos(M_PI * x) + 1) * Geom::Point(cos(dp)*dr, sin(dp)*dr);
+ sp_item_move_rel(item, Geom::Translate(move[Geom::X], -move[Geom::Y]));
+ did = true;
+ }
+ }
+
+ } else if (mode == TWEAK_MODE_SCALE) {
+
+ Geom::OptRect a = item->documentVisualBounds();
+ if (a) {
+ double x = Geom::L2(a->midpoint() - p)/radius;
+ if (a->contains(p)) x = 0;
+ if (x < 1) {
+ double scale = 1 + (reverse? force : -force) * 0.05 * (cos(M_PI * x) + 1);
+ sp_item_scale_rel(item, Geom::Scale(scale, scale));
+ did = true;
+ }
+ }
+
+ } else if (mode == TWEAK_MODE_ROTATE) {
+
+ Geom::OptRect a = item->documentVisualBounds();
+ if (a) {
+ double x = Geom::L2(a->midpoint() - p)/radius;
+ if (a->contains(p)) x = 0;
+ if (x < 1) {
+ double angle = (reverse? force : -force) * 0.05 * (cos(M_PI * x) + 1) * M_PI;
+ sp_item_rotate_rel(item, Geom::Rotate(angle));
+ did = true;
+ }
+ }
+
+ } else if (mode == TWEAK_MODE_MORELESS) {
+
+ Geom::OptRect a = item->documentVisualBounds();
+ if (a) {
+ double x = Geom::L2(a->midpoint() - p)/radius;
+ if (a->contains(p)) x = 0;
+ if (x < 1) {
+ double prob = force * 0.5 * (cos(M_PI * x) + 1);
+ double chance = g_random_double_range(0, 1);
+ if (chance <= prob) {
+ if (reverse) { // delete
+ sp_object_ref(item, NULL);
+ item->deleteObject(true, true);
+ sp_object_unref(item, NULL);
+ } else { // duplicate
+ SPDocument *doc = item->document;
+ Inkscape::XML::Document* xml_doc = doc->getReprDoc();
+ Inkscape::XML::Node *old_repr = item->getRepr();
+ SPObject *old_obj = doc->getObjectByRepr(old_repr);
+ Inkscape::XML::Node *parent = old_repr->parent();
+ Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc);
+ parent->appendChild(copy);
+ SPObject *new_obj = doc->getObjectByRepr(copy);
+ if (selection->includes(old_obj)) {
+ selection->add(new_obj);
+ }
+ Inkscape::GC::release(copy);
+ }
+ did = true;
+ }
+ }
+ }
+
+ } else if (SP_IS_PATH(item) || SP_IS_SHAPE(item)) {
+
+ Inkscape::XML::Node *newrepr = NULL;
+ gint pos = 0;
+ Inkscape::XML::Node *parent = NULL;
+ char const *id = NULL;
+ if (!SP_IS_PATH(item)) {
+ newrepr = sp_selected_item_to_curved_repr(item, 0);
+ if (!newrepr) {
+ return false;
+ }
+
+ // remember the position of the item
+ pos = item->getRepr()->position();
+ // remember parent
+ parent = item->getRepr()->parent();
+ // remember id
+ id = item->getRepr()->attribute("id");
+ }
+
+ // skip those paths whose bboxes are entirely out of reach with our radius
+ Geom::OptRect bbox = item->documentVisualBounds();
+ if (bbox) {
+ bbox->expandBy(radius);
+ if (!bbox->contains(p)) {
+ return false;
+ }
+ }
+
+ Path *orig = Path_for_item(item, false);
+ if (orig == NULL) {
+ return false;
+ }
+
+ Path *res = new Path;
+ res->SetBackData(false);
+
+ Shape *theShape = new Shape;
+ Shape *theRes = new Shape;
+ Geom::Affine i2doc(item->i2doc_affine());
+
+ orig->ConvertWithBackData((0.08 - (0.07 * fidelity)) / i2doc.descrim()); // default 0.059
+ orig->Fill(theShape, 0);
+
+ SPCSSAttr *css = sp_repr_css_attr(item->getRepr(), "style");
+ gchar const *val = sp_repr_css_property(css, "fill-rule", NULL);
+ if (val && strcmp(val, "nonzero") == 0) {
+ theRes->ConvertToShape(theShape, fill_nonZero);
+ } else if (val && strcmp(val, "evenodd") == 0) {
+ theRes->ConvertToShape(theShape, fill_oddEven);
+ } else {
+ theRes->ConvertToShape(theShape, fill_nonZero);
+ }
+
+ if (Geom::L2(vector) != 0) {
+ vector = 1/Geom::L2(vector) * vector;
+ }
+
+ bool did_this = false;
+ if (mode == TWEAK_MODE_SHRINK_GROW) {
+ if (theShape->MakeTweak(tweak_mode_grow, theRes,
+ reverse? force : -force,
+ join_straight, 4.0,
+ true, p, Geom::Point(0,0), radius, &i2doc) == 0) // 0 means the shape was actually changed
+ did_this = true;
+ } else if (mode == TWEAK_MODE_ATTRACT_REPEL) {
+ if (theShape->MakeTweak(tweak_mode_repel, theRes,
+ reverse? force : -force,
+ join_straight, 4.0,
+ true, p, Geom::Point(0,0), radius, &i2doc) == 0)
+ did_this = true;
+ } else if (mode == TWEAK_MODE_PUSH) {
+ if (theShape->MakeTweak(tweak_mode_push, theRes,
+ 1.0,
+ join_straight, 4.0,
+ true, p, force*2*vector, radius, &i2doc) == 0)
+ did_this = true;
+ } else if (mode == TWEAK_MODE_ROUGHEN) {
+ if (theShape->MakeTweak(tweak_mode_roughen, theRes,
+ force,
+ join_straight, 4.0,
+ true, p, Geom::Point(0,0), radius, &i2doc) == 0)
+ did_this = true;
+ }
+
+ // the rest only makes sense if we actually changed the path
+ if (did_this) {
+ theRes->ConvertToShape(theShape, fill_positive);
+
+ res->Reset();
+ theRes->ConvertToForme(res);
+
+ double th_max = (0.6 - 0.59*sqrt(fidelity)) / i2doc.descrim();
+ double threshold = MAX(th_max, th_max*force);
+ res->ConvertEvenLines(threshold);
+ res->Simplify(threshold / (selection->desktop()->current_zoom()));
+
+ if (newrepr) { // converting to path, need to replace the repr
+ bool is_selected = selection->includes(item);
+ if (is_selected) {
+ selection->remove(item);
+ }
+
+ // It's going to resurrect, so we delete without notifying listeners.
+ item->deleteObject(false);
+
+ // restore id
+ newrepr->setAttribute("id", id);
+ // add the new repr to the parent
+ parent->appendChild(newrepr);
+ // move to the saved position
+ newrepr->setPosition(pos > 0 ? pos : 0);
+
+ if (is_selected)
+ selection->add(newrepr);
+ }
+
+ if (res->descr_cmd.size() > 1) {
+ gchar *str = res->svg_dump_path();
+ if (newrepr) {
+ newrepr->setAttribute("d", str);
+ } else {
+ if (SP_IS_LPE_ITEM(item) && SP_LPE_ITEM(item)->hasPathEffectRecursive()) {
+ item->getRepr()->setAttribute("inkscape:original-d", str);
+ } else {
+ item->getRepr()->setAttribute("d", str);
+ }
+ }
+ g_free(str);
+ } else {
+ // TODO: if there's 0 or 1 node left, delete this path altogether
+ }
+
+ if (newrepr) {
+ Inkscape::GC::release(newrepr);
+ newrepr = NULL;
+ }
+ }
+
+ delete theShape;
+ delete theRes;
+ delete orig;
+ delete res;
+
+ if (did_this) {
+ did = true;
+ }
+ }
+
+ }
+
+ return did;
+}
+
+static void
+tweak_colorpaint (float *color, guint32 goal, double force, bool do_h, bool do_s, bool do_l)
+{
+ float rgb_g[3];
+
+ if (!do_h || !do_s || !do_l) {
+ float hsl_g[3];
+ sp_color_rgb_to_hsl_floatv (hsl_g, SP_RGBA32_R_F(goal), SP_RGBA32_G_F(goal), SP_RGBA32_B_F(goal));
+ float hsl_c[3];
+ sp_color_rgb_to_hsl_floatv (hsl_c, color[0], color[1], color[2]);
+ if (!do_h) {
+ hsl_g[0] = hsl_c[0];
+ }
+ if (!do_s) {
+ hsl_g[1] = hsl_c[1];
+ }
+ if (!do_l) {
+ hsl_g[2] = hsl_c[2];
+ }
+ sp_color_hsl_to_rgb_floatv (rgb_g, hsl_g[0], hsl_g[1], hsl_g[2]);
+ } else {
+ rgb_g[0] = SP_RGBA32_R_F(goal);
+ rgb_g[1] = SP_RGBA32_G_F(goal);
+ rgb_g[2] = SP_RGBA32_B_F(goal);
+ }
+
+ for (int i = 0; i < 3; i++) {
+ double d = rgb_g[i] - color[i];
+ color[i] += d * force;
+ }
+}
+
+static void
+tweak_colorjitter (float *color, double force, bool do_h, bool do_s, bool do_l)
+{
+ float hsl_c[3];
+ sp_color_rgb_to_hsl_floatv (hsl_c, color[0], color[1], color[2]);
+
+ if (do_h) {
+ hsl_c[0] += g_random_double_range(-0.5, 0.5) * force;
+ if (hsl_c[0] > 1) {
+ hsl_c[0] -= 1;
+ }
+ if (hsl_c[0] < 0) {
+ hsl_c[0] += 1;
+ }
+ }
+ if (do_s) {
+ hsl_c[1] += g_random_double_range(-hsl_c[1], 1 - hsl_c[1]) * force;
+ }
+ if (do_l) {
+ hsl_c[2] += g_random_double_range(-hsl_c[2], 1 - hsl_c[2]) * force;
+ }
+
+ sp_color_hsl_to_rgb_floatv (color, hsl_c[0], hsl_c[1], hsl_c[2]);
+}
+
+static void
+tweak_color (guint mode, float *color, guint32 goal, double force, bool do_h, bool do_s, bool do_l)
+{
+ if (mode == TWEAK_MODE_COLORPAINT) {
+ tweak_colorpaint (color, goal, force, do_h, do_s, do_l);
+ } else if (mode == TWEAK_MODE_COLORJITTER) {
+ tweak_colorjitter (color, force, do_h, do_s, do_l);
+ }
+}
+
+static void
+tweak_opacity (guint mode, SPIScale24 *style_opacity, double opacity_goal, double force)
+{
+ double opacity = SP_SCALE24_TO_FLOAT (style_opacity->value);
+
+ if (mode == TWEAK_MODE_COLORPAINT) {
+ double d = opacity_goal - opacity;
+ opacity += d * force;
+ } else if (mode == TWEAK_MODE_COLORJITTER) {
+ opacity += g_random_double_range(-opacity, 1 - opacity) * force;
+ }
+
+ style_opacity->value = SP_SCALE24_FROM_FLOAT(opacity);
+}
+
+
+static double
+tweak_profile (double dist, double radius)
+{
+ if (radius == 0) {
+ return 0;
+ }
+ double x = dist / radius;
+ double alpha = 1;
+ if (x >= 1) {
+ return 0;
+ } else if (x <= 0) {
+ return 1;
+ } else {
+ return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
+ }
+}
+
+static void tweak_colors_in_gradient(SPItem *item, Inkscape::PaintTarget fill_or_stroke,
+ guint32 const rgb_goal, Geom::Point p_w, double radius, double force, guint mode,
+ bool do_h, bool do_s, bool do_l, bool /*do_o*/)
+{
+ SPGradient *gradient = getGradient(item, fill_or_stroke);
+
+ if (!gradient || !SP_IS_GRADIENT(gradient)) {
+ return;
+ }
+
+ Geom::Affine i2d (item->i2doc_affine ());
+ Geom::Point p = p_w * i2d.inverse();
+ p *= (gradient->gradientTransform).inverse();
+ // now p is in gradient's original coordinates
+
+ double pos = 0;
+ double r = 0;
+ if (SP_IS_LINEARGRADIENT(gradient)) {
+ SPLinearGradient *lg = SP_LINEARGRADIENT(gradient);
+
+ Geom::Point p1(lg->x1.computed, lg->y1.computed);
+ Geom::Point p2(lg->x2.computed, lg->y2.computed);
+ Geom::Point pdiff(p2 - p1);
+ double vl = Geom::L2(pdiff);
+
+ // This is the matrix which moves and rotates the gradient line
+ // so it's oriented along the X axis:
+ Geom::Affine norm = Geom::Affine(Geom::Translate(-p1)) * Geom::Affine(Geom::Rotate(-atan2(pdiff[Geom::Y], pdiff[Geom::X])));
+
+ // Transform the mouse point by it to find out its projection onto the gradient line:
+ Geom::Point pnorm = p * norm;
+
+ // Scale its X coordinate to match the length of the gradient line:
+ pos = pnorm[Geom::X] / vl;
+ // Calculate radius in lenfth-of-gradient-line units
+ r = radius / vl;
+
+ } else if (SP_IS_RADIALGRADIENT(gradient)) {
+ SPRadialGradient *rg = SP_RADIALGRADIENT(gradient);
+ Geom::Point c (rg->cx.computed, rg->cy.computed);
+ pos = Geom::L2(p - c) / rg->r.computed;
+ r = radius / rg->r.computed;
+ }
+
+ // Normalize pos to 0..1, taking into accound gradient spread:
+ double pos_e = pos;
+ if (gradient->getSpread() == SP_GRADIENT_SPREAD_PAD) {
+ if (pos > 1) {
+ pos_e = 1;
+ }
+ if (pos < 0) {
+ pos_e = 0;
+ }
+ } else if (gradient->getSpread() == SP_GRADIENT_SPREAD_REPEAT) {
+ if (pos > 1 || pos < 0) {
+ pos_e = pos - floor(pos);
+ }
+ } else if (gradient->getSpread() == SP_GRADIENT_SPREAD_REFLECT) {
+ if (pos > 1 || pos < 0) {
+ bool odd = ((int)(floor(pos)) % 2 == 1);
+ pos_e = pos - floor(pos);
+ if (odd) {
+ pos_e = 1 - pos_e;
+ }
+ }
+ }
+
+ SPGradient *vector = sp_gradient_get_forked_vector_if_necessary(gradient, false);
+
+ double offset_l = 0;
+ double offset_h = 0;
+ SPObject *child_prev = NULL;
+ for (SPObject *child = vector->firstChild(); child; child = child->getNext()) {
+ if (!SP_IS_STOP(child)) {
+ continue;
+ }
+ SPStop *stop = SP_STOP (child);
+
+ offset_h = stop->offset;
+
+ if (child_prev) {
+
+ if (offset_h - offset_l > r && pos_e >= offset_l && pos_e <= offset_h) {
+ // the summit falls in this interstop, and the radius is small,
+ // so it only affects the ends of this interstop;
+ // distribute the force between the two endstops so that they
+ // get all the painting even if they are not touched by the brush
+ tweak_color (mode, stop->specified_color.v.c, rgb_goal,
+ force * (pos_e - offset_l) / (offset_h - offset_l),
+ do_h, do_s, do_l);
+ tweak_color (mode, SP_STOP(child_prev)->specified_color.v.c, rgb_goal,
+ force * (offset_h - pos_e) / (offset_h - offset_l),
+ do_h, do_s, do_l);
+ stop->updateRepr();
+ child_prev->updateRepr();
+ break;
+ } else {
+ // wide brush, may affect more than 2 stops,
+ // paint each stop by the force from the profile curve
+ if (offset_l <= pos_e && offset_l > pos_e - r) {
+ tweak_color (mode, SP_STOP(child_prev)->specified_color.v.c, rgb_goal,
+ force * tweak_profile (fabs (pos_e - offset_l), r),
+ do_h, do_s, do_l);
+ child_prev->updateRepr();
+ }
+
+ if (offset_h >= pos_e && offset_h < pos_e + r) {
+ tweak_color (mode, stop->specified_color.v.c, rgb_goal,
+ force * tweak_profile (fabs (pos_e - offset_h), r),
+ do_h, do_s, do_l);
+ stop->updateRepr();
+ }
+ }
+ }
+
+ offset_l = offset_h;
+ child_prev = child;
+ }
+}
+
+static bool
+sp_tweak_color_recursive (guint mode, SPItem *item, SPItem *item_at_point,
+ guint32 fill_goal, bool do_fill,
+ guint32 stroke_goal, bool do_stroke,
+ float opacity_goal, bool do_opacity,
+ bool do_blur, bool reverse,
+ Geom::Point p, double radius, double force,
+ bool do_h, bool do_s, bool do_l, bool do_o)
+{
+ bool did = false;
+
+ if (SP_IS_GROUP(item)) {
+ for (SPObject *child = item->firstChild() ; child; child = child->getNext() ) {
+ if (SP_IS_ITEM(child)) {
+ if (sp_tweak_color_recursive (mode, SP_ITEM(child), item_at_point,
+ fill_goal, do_fill,
+ stroke_goal, do_stroke,
+ opacity_goal, do_opacity,
+ do_blur, reverse,
+ p, radius, force, do_h, do_s, do_l, do_o)) {
+ did = true;
+ }
+ }
+ }
+
+ } else {
+ SPStyle *style = item->style;
+ if (!style) {
+ return false;
+ }
+ Geom::OptRect bbox = item->documentGeometricBounds();
+ if (!bbox) {
+ return false;
+ }
+
+ Geom::Rect brush(p - Geom::Point(radius, radius), p + Geom::Point(radius, radius));
+
+ Geom::Point center = bbox->midpoint();
+ double this_force;
+
+// if item == item_at_point, use max force
+ if (item == item_at_point) {
+ this_force = force;
+// else if no overlap of bbox and brush box, skip:
+ } else if (!bbox->intersects(brush)) {
+ return false;
+//TODO:
+// else if object > 1.5 brush: test 4/8/16 points in the brush on hitting the object, choose max
+ //} else if (bbox->maxExtent() > 3 * radius) {
+ //}
+// else if object > 0.5 brush: test 4 corners of bbox and center on being in the brush, choose max
+// else if still smaller, then check only the object center:
+ } else {
+ this_force = force * tweak_profile (Geom::L2 (p - center), radius);
+ }
+
+ if (this_force > 0.002) {
+
+ if (do_blur) {
+ Geom::OptRect bbox = item->documentGeometricBounds();
+ if (!bbox) {
+ return did;
+ }
+
+ double blur_now = 0;
+ Geom::Affine i2dt = item->i2dt_affine ();
+ if (style->filter.set && style->getFilter()) {
+ //cycle through filter primitives
+ SPObject *primitive_obj = style->getFilter()->children;
+ while (primitive_obj) {
+ if (SP_IS_FILTER_PRIMITIVE(primitive_obj)) {
+ SPFilterPrimitive *primitive = SP_FILTER_PRIMITIVE(primitive_obj);
+ //if primitive is gaussianblur
+ if(SP_IS_GAUSSIANBLUR(primitive)) {
+ SPGaussianBlur * spblur = SP_GAUSSIANBLUR(primitive);
+ float num = spblur->stdDeviation.getNumber();
+ blur_now += num * i2dt.descrim(); // sum all blurs in the filter
+ }
+ }
+ primitive_obj = primitive_obj->next;
+ }
+ }
+ double perimeter = bbox->dimensions()[Geom::X] + bbox->dimensions()[Geom::Y];
+ blur_now = blur_now / perimeter;
+
+ double blur_new;
+ if (reverse) {
+ blur_new = blur_now - 0.06 * force;
+ } else {
+ blur_new = blur_now + 0.06 * force;
+ }
+ if (blur_new < 0.0005 && blur_new < blur_now) {
+ blur_new = 0;
+ }
+ if (blur_new == 0) {
+ remove_filter(item, false);
+ } else {
+ double radius = blur_new * perimeter;
+ SPFilter *filter = modify_filter_gaussian_blur_from_item(item->document, item, radius);
+ sp_style_set_property_url(item, "filter", filter, false);
+ }
+ return true; // do not do colors, blur is a separate mode
+ }
+
+ if (do_fill) {
+ if (style->fill.isPaintserver()) {
+ tweak_colors_in_gradient(item, Inkscape::FOR_FILL, fill_goal, p, radius, this_force, mode, do_h, do_s, do_l, do_o);
+ did = true;
+ } else if (style->fill.isColor()) {
+ tweak_color (mode, style->fill.value.color.v.c, fill_goal, this_force, do_h, do_s, do_l);
+ item->updateRepr();
+ did = true;
+ }
+ }
+ if (do_stroke) {
+ if (style->stroke.isPaintserver()) {
+ tweak_colors_in_gradient(item, Inkscape::FOR_STROKE, stroke_goal, p, radius, this_force, mode, do_h, do_s, do_l, do_o);
+ did = true;
+ } else if (style->stroke.isColor()) {
+ tweak_color (mode, style->stroke.value.color.v.c, stroke_goal, this_force, do_h, do_s, do_l);
+ item->updateRepr();
+ did = true;
+ }
+ }
+ if (do_opacity && do_o) {
+ tweak_opacity (mode, &style->opacity, opacity_goal, this_force);
+ }
+ }
+ }
+
+ return did;
+}
+
+
+static bool
+sp_tweak_dilate (TweakTool *tc, Geom::Point event_p, Geom::Point p, Geom::Point vector, bool reverse)
+{
+ Inkscape::Selection *selection = sp_desktop_selection(SP_EVENT_CONTEXT(tc)->desktop);
+ SPDesktop *desktop = SP_EVENT_CONTEXT(tc)->desktop;
+
+ if (selection->isEmpty()) {
+ return false;
+ }
+
+ bool did = false;
+ double radius = get_dilate_radius(tc);
+
+ SPItem *item_at_point = SP_EVENT_CONTEXT(tc)->desktop->getItemAtPoint(event_p, TRUE);
+
+ bool do_fill = false, do_stroke = false, do_opacity = false;
+ guint32 fill_goal = sp_desktop_get_color_tool(desktop, "/tools/tweak", true, &do_fill);
+ guint32 stroke_goal = sp_desktop_get_color_tool(desktop, "/tools/tweak", false, &do_stroke);
+ double opacity_goal = sp_desktop_get_master_opacity_tool(desktop, "/tools/tweak", &do_opacity);
+ if (reverse) {
+#if 0
+ // HSL inversion
+ float hsv[3];
+ float rgb[3];
+ sp_color_rgb_to_hsv_floatv (hsv,
+ SP_RGBA32_R_F(fill_goal),
+ SP_RGBA32_G_F(fill_goal),
+ SP_RGBA32_B_F(fill_goal));
+ sp_color_hsv_to_rgb_floatv (rgb, hsv[0]<.5? hsv[0]+.5 : hsv[0]-.5, 1 - hsv[1], 1 - hsv[2]);
+ fill_goal = SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 1);
+ sp_color_rgb_to_hsv_floatv (hsv,
+ SP_RGBA32_R_F(stroke_goal),
+ SP_RGBA32_G_F(stroke_goal),
+ SP_RGBA32_B_F(stroke_goal));
+ sp_color_hsv_to_rgb_floatv (rgb, hsv[0]<.5? hsv[0]+.5 : hsv[0]-.5, 1 - hsv[1], 1 - hsv[2]);
+ stroke_goal = SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 1);
+#else
+ // RGB inversion
+ fill_goal = SP_RGBA32_U_COMPOSE(
+ (255 - SP_RGBA32_R_U(fill_goal)),
+ (255 - SP_RGBA32_G_U(fill_goal)),
+ (255 - SP_RGBA32_B_U(fill_goal)),
+ (255 - SP_RGBA32_A_U(fill_goal)));
+ stroke_goal = SP_RGBA32_U_COMPOSE(
+ (255 - SP_RGBA32_R_U(stroke_goal)),
+ (255 - SP_RGBA32_G_U(stroke_goal)),
+ (255 - SP_RGBA32_B_U(stroke_goal)),
+ (255 - SP_RGBA32_A_U(stroke_goal)));
+#endif
+ opacity_goal = 1 - opacity_goal;
+ }
+
+ double path_force = get_path_force(tc);
+ if (radius == 0 || path_force == 0) {
+ return false;
+ }
+ double move_force = get_move_force(tc);
+ double color_force = MIN(sqrt(path_force)/20.0, 1);
+
+ for (GSList *items = g_slist_copy(const_cast<GSList *>(selection->itemList()));
+ items != NULL;
+ items = items->next) {
+
+ SPItem *item = SP_ITEM(items->data);
+
+ if (is_color_mode (tc->mode)) {
+ if (do_fill || do_stroke || do_opacity) {
+ if (sp_tweak_color_recursive (tc->mode, item, item_at_point,
+ fill_goal, do_fill,
+ stroke_goal, do_stroke,
+ opacity_goal, do_opacity,
+ tc->mode == TWEAK_MODE_BLUR, reverse,
+ p, radius, color_force, tc->do_h, tc->do_s, tc->do_l, tc->do_o)) {
+ did = true;
+ }
+ }
+ } else if (is_transform_mode(tc->mode)) {
+ if (sp_tweak_dilate_recursive (selection, item, p, vector, tc->mode, radius, move_force, tc->fidelity, reverse)) {
+ did = true;
+ }
+ } else {
+ if (sp_tweak_dilate_recursive (selection, item, p, vector, tc->mode, radius, path_force, tc->fidelity, reverse)) {
+ did = true;
+ }
+ }
+ }
+
+ return did;
+}
+
+static void
+sp_tweak_update_area (TweakTool *tc)
+{
+ double radius = get_dilate_radius(tc);
+ Geom::Affine const sm (Geom::Scale(radius, radius) * Geom::Translate(SP_EVENT_CONTEXT(tc)->desktop->point()));
+ sp_canvas_item_affine_absolute(tc->dilate_area, sm);
+ sp_canvas_item_show(tc->dilate_area);
+}
+
+static void
+sp_tweak_switch_mode (TweakTool *tc, gint mode, bool with_shift)
+{
+ SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue ("tweak_tool_mode", mode);
+ // need to set explicitly, because the prefs may not have changed by the previous
+ tc->mode = mode;
+ tc->update_cursor(with_shift);
+}
+
+static void
+sp_tweak_switch_mode_temporarily (TweakTool *tc, gint mode, bool with_shift)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ // Juggling about so that prefs have the old value but tc->mode and the button show new mode:
+ gint now_mode = prefs->getInt("/tools/tweak/mode", 0);
+ SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue ("tweak_tool_mode", mode);
+ // button has changed prefs, restore
+ prefs->setInt("/tools/tweak/mode", now_mode);
+ // changing prefs changed tc->mode, restore back :)
+ tc->mode = mode;
+ tc->update_cursor(with_shift);
+}
+
+bool TweakTool::root_handler(GdkEvent* event) {
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_ENTER_NOTIFY:
+ sp_canvas_item_show(this->dilate_area);
+ break;
+ case GDK_LEAVE_NOTIFY:
+ sp_canvas_item_hide(this->dilate_area);
+ break;
+ case GDK_BUTTON_PRESS:
+ if (event->button.button == 1 && !this->space_panning) {
+
+ if (Inkscape::have_viable_layer(desktop, this->message_context) == false) {
+ return TRUE;
+ }
+
+ Geom::Point const button_w(event->button.x,
+ event->button.y);
+ Geom::Point const button_dt(desktop->w2d(button_w));
+ this->last_push = desktop->dt2doc(button_dt);
+
+ sp_tweak_extinput(this, event);
+
+ desktop->canvas->forceFullRedrawAfterInterruptions(3);
+ this->is_drawing = true;
+ this->is_dilating = true;
+ this->has_dilated = false;
+
+ ret = TRUE;
+ }
+ break;
+ case GDK_MOTION_NOTIFY:
+ {
+ Geom::Point const motion_w(event->motion.x,
+ event->motion.y);
+ Geom::Point motion_dt(desktop->w2d(motion_w));
+ Geom::Point motion_doc(desktop->dt2doc(motion_dt));
+ sp_tweak_extinput(this, event);
+
+ // draw the dilating cursor
+ double radius = get_dilate_radius(this);
+ Geom::Affine const sm (Geom::Scale(radius, radius) * Geom::Translate(desktop->w2d(motion_w)));
+ sp_canvas_item_affine_absolute(this->dilate_area, sm);
+ sp_canvas_item_show(this->dilate_area);
+
+ guint num = 0;
+ if (!desktop->selection->isEmpty()) {
+ num = g_slist_length(const_cast<GSList *>(desktop->selection->itemList()));
+ }
+ if (num == 0) {
+ this->message_context->flash(Inkscape::ERROR_MESSAGE, _("<b>Nothing selected!</b> Select objects to tweak."));
+ }
+
+ // dilating:
+ if (this->is_drawing && ( event->motion.state & GDK_BUTTON1_MASK )) {
+ sp_tweak_dilate (this, motion_w, motion_doc, motion_doc - this->last_push, event->button.state & GDK_SHIFT_MASK? true : false);
+ //this->last_push = motion_doc;
+ this->has_dilated = true;
+ // it's slow, so prevent clogging up with events
+ gobble_motion_events(GDK_BUTTON1_MASK);
+ return TRUE;
+ }
+
+ }
+ break;
+ case GDK_BUTTON_RELEASE:
+ {
+ Geom::Point const motion_w(event->button.x, event->button.y);
+ Geom::Point const motion_dt(desktop->w2d(motion_w));
+
+ desktop->canvas->endForcedFullRedraws();
+ this->is_drawing = false;
+
+ if (this->is_dilating && event->button.button == 1 && !this->space_panning) {
+ if (!this->has_dilated) {
+ // if we did not rub, do a light tap
+ this->pressure = 0.03;
+ sp_tweak_dilate (this, motion_w, desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT(event));
+ }
+ this->is_dilating = false;
+ this->has_dilated = false;
+ switch (this->mode) {
+ case TWEAK_MODE_MOVE:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_TWEAK, _("Move tweak"));
+ break;
+ case TWEAK_MODE_MOVE_IN_OUT:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_TWEAK, _("Move in/out tweak"));
+ break;
+ case TWEAK_MODE_MOVE_JITTER:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_TWEAK, _("Move jitter tweak"));
+ break;
+ case TWEAK_MODE_SCALE:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_TWEAK, _("Scale tweak"));
+ break;
+ case TWEAK_MODE_ROTATE:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_TWEAK, _("Rotate tweak"));
+ break;
+ case TWEAK_MODE_MORELESS:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_TWEAK, _("Duplicate/delete tweak"));
+ break;
+ case TWEAK_MODE_PUSH:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_TWEAK, _("Push path tweak"));
+ break;
+ case TWEAK_MODE_SHRINK_GROW:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_TWEAK, _("Shrink/grow path tweak"));
+ break;
+ case TWEAK_MODE_ATTRACT_REPEL:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_TWEAK, _("Attract/repel path tweak"));
+ break;
+ case TWEAK_MODE_ROUGHEN:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_TWEAK, _("Roughen path tweak"));
+ break;
+ case TWEAK_MODE_COLORPAINT:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_TWEAK, _("Color paint tweak"));
+ break;
+ case TWEAK_MODE_COLORJITTER:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_TWEAK, _("Color jitter tweak"));
+ break;
+ case TWEAK_MODE_BLUR:
+ DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop),
+ SP_VERB_CONTEXT_TWEAK, _("Blur tweak"));
+ break;
+ }
+ }
+ break;
+ }
+ case GDK_KEY_PRESS:
+ {
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_m:
+ case GDK_KEY_M:
+ case GDK_KEY_0:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_tweak_switch_mode(this, TWEAK_MODE_MOVE, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_i:
+ case GDK_KEY_I:
+ case GDK_KEY_1:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_tweak_switch_mode(this, TWEAK_MODE_MOVE_IN_OUT, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_z:
+ case GDK_KEY_Z:
+ case GDK_KEY_2:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_tweak_switch_mode(this, TWEAK_MODE_MOVE_JITTER, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_less:
+ case GDK_KEY_comma:
+ case GDK_KEY_greater:
+ case GDK_KEY_period:
+ case GDK_KEY_3:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_tweak_switch_mode(this, TWEAK_MODE_SCALE, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_bracketright:
+ case GDK_KEY_bracketleft:
+ case GDK_KEY_4:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_tweak_switch_mode(this, TWEAK_MODE_ROTATE, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_d:
+ case GDK_KEY_D:
+ case GDK_KEY_5:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_tweak_switch_mode(this, TWEAK_MODE_MORELESS, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_p:
+ case GDK_KEY_P:
+ case GDK_KEY_6:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_tweak_switch_mode(this, TWEAK_MODE_PUSH, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_s:
+ case GDK_KEY_S:
+ case GDK_KEY_7:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_tweak_switch_mode(this, TWEAK_MODE_SHRINK_GROW, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_a:
+ case GDK_KEY_A:
+ case GDK_KEY_8:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_tweak_switch_mode(this, TWEAK_MODE_ATTRACT_REPEL, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_r:
+ case GDK_KEY_R:
+ case GDK_KEY_9:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_tweak_switch_mode(this, TWEAK_MODE_ROUGHEN, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_c:
+ case GDK_KEY_C:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_tweak_switch_mode(this, TWEAK_MODE_COLORPAINT, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_j:
+ case GDK_KEY_J:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_tweak_switch_mode(this, TWEAK_MODE_COLORJITTER, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_b:
+ case GDK_KEY_B:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_tweak_switch_mode(this, TWEAK_MODE_BLUR, MOD__SHIFT(event));
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Up:
+ case GDK_KEY_KP_Up:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->force += 0.05;
+ if (this->force > 1.0) {
+ this->force = 1.0;
+ }
+ desktop->setToolboxAdjustmentValue ("tweak-force", this->force * 100);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Down:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->force -= 0.05;
+ if (this->force < 0.0) {
+ this->force = 0.0;
+ }
+ desktop->setToolboxAdjustmentValue ("tweak-force", this->force * 100);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Right:
+ case GDK_KEY_KP_Right:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->width += 0.01;
+ if (this->width > 1.0) {
+ this->width = 1.0;
+ }
+ desktop->setToolboxAdjustmentValue ("altx-tweak", this->width * 100); // the same spinbutton is for alt+x
+ sp_tweak_update_area(this);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Left:
+ case GDK_KEY_KP_Left:
+ if (!MOD__CTRL_ONLY(event)) {
+ this->width -= 0.01;
+ if (this->width < 0.01) {
+ this->width = 0.01;
+ }
+ desktop->setToolboxAdjustmentValue ("altx-tweak", this->width * 100);
+ sp_tweak_update_area(this);
+ ret = TRUE;
+ }
+ break;
+ case GDK_KEY_Home:
+ case GDK_KEY_KP_Home:
+ this->width = 0.01;
+ desktop->setToolboxAdjustmentValue ("altx-tweak", this->width * 100);
+ sp_tweak_update_area(this);
+ ret = TRUE;
+ break;
+ case GDK_KEY_End:
+ case GDK_KEY_KP_End:
+ this->width = 1.0;
+ desktop->setToolboxAdjustmentValue ("altx-tweak", this->width * 100);
+ sp_tweak_update_area(this);
+ ret = TRUE;
+ break;
+ case GDK_KEY_x:
+ case GDK_KEY_X:
+ if (MOD__ALT_ONLY(event)) {
+ desktop->setToolboxFocusTo ("altx-tweak");
+ ret = TRUE;
+ }
+ break;
+
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ this->update_cursor(true);
+ break;
+
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ sp_tweak_switch_mode_temporarily(this, TWEAK_MODE_SHRINK_GROW, MOD__SHIFT(event));
+ break;
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ case GDK_KEY_BackSpace:
+ ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event));
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+ case GDK_KEY_RELEASE: {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ switch (get_group0_keyval(&event->key)) {
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ this->update_cursor(false);
+ break;
+ case GDK_KEY_Control_L:
+ case GDK_KEY_Control_R:
+ sp_tweak_switch_mode (this, prefs->getInt("/tools/tweak/mode"), MOD__SHIFT(event));
+ this->message_context->clear();
+ break;
+ default:
+ sp_tweak_switch_mode (this, prefs->getInt("/tools/tweak/mode"), MOD__SHIFT(event));
+ break;
+ }
+ }
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+}
+}
+}
+
+/*
+ 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/tools/tweak-tool.h b/src/ui/tools/tweak-tool.h
new file mode 100644
index 000000000..6cbb9aded
--- /dev/null
+++ b/src/ui/tools/tweak-tool.h
@@ -0,0 +1,106 @@
+#ifndef __SP_TWEAK_CONTEXT_H__
+#define __SP_TWEAK_CONTEXT_H__
+
+/*
+ * tweaking paths without node editing
+ *
+ * Authors:
+ * bulia byak
+ *
+ * Copyright (C) 2007 authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/tools/tool-base.h"
+#include <2geom/point.h>
+
+#define SAMPLING_SIZE 8 /* fixme: ?? */
+
+#define TC_MIN_PRESSURE 0.0
+#define TC_MAX_PRESSURE 1.0
+#define TC_DEFAULT_PRESSURE 0.35
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+enum {
+ TWEAK_MODE_MOVE,
+ TWEAK_MODE_MOVE_IN_OUT,
+ TWEAK_MODE_MOVE_JITTER,
+ TWEAK_MODE_SCALE,
+ TWEAK_MODE_ROTATE,
+ TWEAK_MODE_MORELESS,
+ TWEAK_MODE_PUSH,
+ TWEAK_MODE_SHRINK_GROW,
+ TWEAK_MODE_ATTRACT_REPEL,
+ TWEAK_MODE_ROUGHEN,
+ TWEAK_MODE_COLORPAINT,
+ TWEAK_MODE_COLORJITTER,
+ TWEAK_MODE_BLUR
+};
+
+class TweakTool : public ToolBase {
+public:
+ TweakTool();
+ virtual ~TweakTool();
+
+ /* extended input data */
+ gdouble pressure;
+
+ /* attributes */
+ guint dragging : 1; /* mouse state: mouse is dragging */
+ guint usepressure : 1;
+ guint usetilt : 1;
+
+ double width;
+ double force;
+ double fidelity;
+
+ gint mode;
+
+ bool is_drawing;
+
+ bool is_dilating;
+ bool has_dilated;
+ Geom::Point last_push;
+ SPCanvasItem *dilate_area;
+
+ bool do_h;
+ bool do_s;
+ bool do_l;
+ bool do_o;
+
+ sigc::connection style_set_connection;
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual void set(const Inkscape::Preferences::Entry& val);
+ virtual bool root_handler(GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+ void update_cursor(bool with_shift);
+
+private:
+ bool set_style(const SPCSSAttr* css);
+};
+
+}
+}
+}
+
+#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/tools/zoom-tool.cpp b/src/ui/tools/zoom-tool.cpp
new file mode 100644
index 000000000..d4ede1053
--- /dev/null
+++ b/src/ui/tools/zoom-tool.cpp
@@ -0,0 +1,250 @@
+/*
+ * Handy zooming tool
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Frank Felfe <innerspace@iname.com>
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ * Copyright (C) 1999-2002 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#include <gdk/gdkkeysyms.h>
+
+#include "macros.h"
+#include "rubberband.h"
+#include "display/sp-canvas-item.h"
+#include "display/sp-canvas-util.h"
+#include "desktop.h"
+#include "pixmaps/cursor-zoom.xpm"
+#include "pixmaps/cursor-zoom-out.xpm"
+#include "preferences.h"
+#include "selection-chemistry.h"
+
+#include "ui/tools/zoom-tool.h"
+#include "tool-factory.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+namespace {
+ ToolBase* createZoomContext() {
+ return new ZoomTool();
+ }
+
+ bool zoomContextRegistered = ToolFactory::instance().registerObject("/tools/zoom", createZoomContext);
+}
+
+const std::string& ZoomTool::getPrefsPath() {
+ return ZoomTool::prefsPath;
+}
+
+const std::string ZoomTool::prefsPath = "/tools/zoom";
+
+ZoomTool::ZoomTool() : ToolBase() {
+ this->grabbed = 0;
+ this->cursor_shape = cursor_zoom_xpm;
+ this->hot_x = 6;
+ this->hot_y = 6;
+ this->escaped = false;
+}
+
+ZoomTool::~ZoomTool() {
+}
+
+void ZoomTool::finish() {
+ this->enableGrDrag(false);
+
+ if (this->grabbed) {
+ sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME);
+ this->grabbed = NULL;
+ }
+}
+
+void ZoomTool::setup() {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (prefs->getBool("/tools/zoom/selcue")) {
+ this->enableSelectionCue();
+ }
+
+ if (prefs->getBool("/tools/zoom/gradientdrag")) {
+ this->enableGrDrag();
+ }
+
+ ToolBase::setup();
+}
+
+bool ZoomTool::root_handler(GdkEvent* event) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+ double const zoom_inc = prefs->getDoubleLimited("/options/zoomincrement/value", M_SQRT2, 1.01, 10);
+
+ bool ret = false;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ {
+ Geom::Point const button_w(event->button.x, event->button.y);
+ Geom::Point const button_dt(desktop->w2d(button_w));
+
+ if (event->button.button == 1 && !this->space_panning) {
+ // save drag origin
+ xp = (gint) event->button.x;
+ yp = (gint) event->button.y;
+ within_tolerance = true;
+
+ Inkscape::Rubberband::get(desktop)->start(desktop, button_dt);
+
+ escaped = false;
+
+ ret = true;
+ } else if (event->button.button == 3) {
+ double const zoom_rel( (event->button.state & GDK_SHIFT_MASK)
+ ? zoom_inc
+ : 1 / zoom_inc );
+
+ desktop->zoom_relative_keep_point(button_dt, zoom_rel);
+ ret = true;
+ }
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK,
+ NULL, event->button.time);
+
+ this->grabbed = SP_CANVAS_ITEM(desktop->acetate);
+ break;
+ }
+
+ case GDK_MOTION_NOTIFY:
+ if ((event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) {
+ ret = true;
+
+ if ( within_tolerance
+ && ( abs( (gint) event->motion.x - xp ) < tolerance )
+ && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) {
+ break; // do not drag if we're within tolerance from origin
+ }
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to move the object, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ within_tolerance = false;
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point const motion_dt(desktop->w2d(motion_w));
+ Inkscape::Rubberband::get(desktop)->move(motion_dt);
+ gobble_motion_events(GDK_BUTTON1_MASK);
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ {
+ Geom::Point const button_w(event->button.x, event->button.y);
+ Geom::Point const button_dt(desktop->w2d(button_w));
+
+ if ( event->button.button == 1 && !this->space_panning) {
+ Geom::OptRect const b = Inkscape::Rubberband::get(desktop)->getRectangle();
+
+ if (b && !within_tolerance) {
+ desktop->set_display_area(*b, 10);
+ } else if (!escaped) {
+ double const zoom_rel( (event->button.state & GDK_SHIFT_MASK)
+ ? 1 / zoom_inc
+ : zoom_inc );
+
+ desktop->zoom_relative_keep_point(button_dt, zoom_rel);
+ }
+
+ ret = true;
+ }
+
+ Inkscape::Rubberband::get(desktop)->stop();
+
+ if (this->grabbed) {
+ sp_canvas_item_ungrab(this->grabbed, event->button.time);
+ this->grabbed = NULL;
+ }
+
+ xp = yp = 0;
+ escaped = false;
+ break;
+ }
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Escape:
+ if (!Inkscape::Rubberband::get(desktop)->is_started()) {
+ Inkscape::SelectionHelper::selectNone(desktop);
+ }
+
+ Inkscape::Rubberband::get(desktop)->stop();
+ xp = yp = 0;
+ escaped = true;
+ ret = true;
+ break;
+
+ case GDK_KEY_Up:
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_Down:
+ // prevent the zoom field from activation
+ if (!MOD__CTRL_ONLY(event))
+ ret = true;
+ break;
+
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ this->cursor_shape = cursor_zoom_out_xpm;
+ this->sp_event_context_update_cursor();
+ break;
+
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ case GDK_KEY_BackSpace:
+ ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event));
+ break;
+
+ default:
+ break;
+ }
+ break;
+ case GDK_KEY_RELEASE:
+ switch (get_group0_keyval (&event->key)) {
+ case GDK_KEY_Shift_L:
+ case GDK_KEY_Shift_R:
+ this->cursor_shape = cursor_zoom_xpm;
+ this->sp_event_context_update_cursor();
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!ret) {
+ ret = ToolBase::root_handler(event);
+ }
+
+ return ret;
+}
+
+}
+}
+}
+
+/*
+ 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/tools/zoom-tool.h b/src/ui/tools/zoom-tool.h
new file mode 100644
index 000000000..6e5cca7aa
--- /dev/null
+++ b/src/ui/tools/zoom-tool.h
@@ -0,0 +1,47 @@
+#ifndef __SP_ZOOM_CONTEXT_H__
+#define __SP_ZOOM_CONTEXT_H__
+
+/*
+ * Handy zooming tool
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Frank Felfe <innerspace@iname.com>
+ *
+ * Copyright (C) 1999-2002 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/tools/tool-base.h"
+
+#define SP_ZOOM_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::ZoomTool*>((Inkscape::UI::Tools::ToolBase*)obj))
+#define SP_IS_ZOOM_CONTEXT(obj) (dynamic_cast<const Inkscape::UI::Tools::ZoomTool*>((const Inkscape::UI::Tools::ToolBase*)obj) != NULL)
+
+namespace Inkscape {
+namespace UI {
+namespace Tools {
+
+class ZoomTool : public ToolBase {
+public:
+ ZoomTool();
+ virtual ~ZoomTool();
+
+ static const std::string prefsPath;
+
+ virtual void setup();
+ virtual void finish();
+ virtual bool root_handler(GdkEvent* event);
+
+ virtual const std::string& getPrefsPath();
+
+private:
+ SPCanvasItem *grabbed;
+ bool escaped;
+};
+
+}
+}
+}
+
+#endif