summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorKrzysztof Kosi??ski <tweenk.pl@gmail.com>2010-01-14 15:54:06 +0000
committerKrzysztof Kosiński <tweenk.pl@gmail.com>2010-01-14 15:54:06 +0000
commit4ffa8666045001bd3822db293ebb0b728b249492 (patch)
treec4595e3c98a260bab48d37a342b3fdc4002fd6b6 /src
parentDo not append a segment when finishing an open path with right click (diff)
parentRe-enable snapping on release, for now. (diff)
downloadinkscape-4ffa8666045001bd3822db293ebb0b728b249492.tar.gz
inkscape-4ffa8666045001bd3822db293ebb0b728b249492.zip
Merge GSoC 2009 node tool rewrite
(bzr r8976)
Diffstat (limited to 'src')
-rw-r--r--src/2geom/chebyshev.cpp2
-rw-r--r--src/2geom/path.cpp5
-rw-r--r--src/2geom/svg-path-parser.cpp1
-rw-r--r--src/Makefile.am1
-rw-r--r--src/Makefile_insert3
-rw-r--r--src/desktop.cpp92
-rw-r--r--src/desktop.h5
-rw-r--r--src/display/sp-canvas-util.cpp7
-rw-r--r--src/display/sp-canvas.cpp28
-rw-r--r--src/display/sp-canvas.h1
-rw-r--r--src/dom/io/httpclient.cpp4
-rw-r--r--src/dom/odf/SvgOdg.cpp1551
-rw-r--r--src/doxygen-main.cpp2
-rw-r--r--src/event-context.cpp9
-rw-r--r--src/event-context.h3
-rw-r--r--src/extension/dxf2svg/entities2elements.cpp10
-rw-r--r--src/extension/dxf2svg/read_dxf.cpp10
-rw-r--r--src/extension/dxf2svg/tables2svg_info.cpp8
-rw-r--r--src/extension/internal/cairo-renderer.cpp1
-rw-r--r--src/gc-allocator.h106
-rw-r--r--src/libnrtype/FontFactory.cpp6
-rw-r--r--src/libnrtype/FontFactory.h2
-rw-r--r--src/libnrtype/FontInstance.cpp8
-rw-r--r--src/live_effects/effect.cpp8
-rw-r--r--src/live_effects/effect.h2
-rw-r--r--src/live_effects/lpe-constructgrid.cpp9
-rw-r--r--src/live_effects/lpe-constructgrid.h2
-rw-r--r--src/live_effects/lpe-gears.cpp9
-rw-r--r--src/live_effects/lpe-gears.h2
-rw-r--r--src/live_effects/lpe-lattice.cpp1
-rw-r--r--src/live_effects/lpe-spiro.cpp9
-rw-r--r--src/live_effects/lpe-spiro.h1
-rw-r--r--src/live_effects/lpe-vonkoch.cpp7
-rw-r--r--src/live_effects/parameter/path.cpp33
-rw-r--r--src/lpe-tool-context.cpp30
-rw-r--r--src/node-context.cpp868
-rw-r--r--src/node-context.h87
-rw-r--r--src/nodepath.cpp5146
-rw-r--r--src/nodepath.h345
-rw-r--r--src/preferences-skeleton.h2
-rw-r--r--src/preferences.cpp22
-rw-r--r--src/preferences.h24
-rw-r--r--src/selection-chemistry.cpp89
-rw-r--r--src/shape-editor.cpp397
-rw-r--r--src/shape-editor.h99
-rw-r--r--src/snap.cpp77
-rw-r--r--src/snap.h63
-rw-r--r--src/snapper.h1
-rw-r--r--src/sp-lpe-item.cpp17
-rw-r--r--src/tools-switch.cpp5
-rw-r--r--src/ui/dialog/aboutbox.cpp4
-rw-r--r--src/ui/dialog/align-and-distribute.cpp11
-rw-r--r--src/ui/dialog/inkscape-preferences.cpp17
-rw-r--r--src/ui/dialog/inkscape-preferences.h6
-rw-r--r--src/ui/tool/Makefile_insert29
-rw-r--r--src/ui/tool/commit-events.h51
-rw-r--r--src/ui/tool/control-point-selection.cpp583
-rw-r--r--src/ui/tool/control-point-selection.h151
-rw-r--r--src/ui/tool/control-point.cpp630
-rw-r--r--src/ui/tool/control-point.h169
-rw-r--r--src/ui/tool/curve-drag-point.cpp186
-rw-r--r--src/ui/tool/curve-drag-point.h60
-rw-r--r--src/ui/tool/event-utils.cpp113
-rw-r--r--src/ui/tool/event-utils.h129
-rw-r--r--src/ui/tool/manipulator.cpp89
-rw-r--r--src/ui/tool/manipulator.h165
-rw-r--r--src/ui/tool/multi-path-manipulator.cpp598
-rw-r--r--src/ui/tool/multi-path-manipulator.h132
-rw-r--r--src/ui/tool/node-tool.cpp616
-rw-r--r--src/ui/tool/node-tool.h85
-rw-r--r--src/ui/tool/node-types.h48
-rw-r--r--src/ui/tool/node.cpp1204
-rw-r--r--src/ui/tool/node.h398
-rw-r--r--src/ui/tool/path-manipulator.cpp1318
-rw-r--r--src/ui/tool/path-manipulator.h154
-rw-r--r--src/ui/tool/selectable-control-point.cpp137
-rw-r--r--src/ui/tool/selectable-control-point.h71
-rw-r--r--src/ui/tool/selector.cpp133
-rw-r--r--src/ui/tool/selector.h59
-rw-r--r--src/ui/tool/shape-record.h57
-rw-r--r--src/ui/tool/transform-handle-set.cpp644
-rw-r--r--src/ui/tool/transform-handle-set.h96
-rw-r--r--src/util/accumulators.h113
-rw-r--r--src/util/hash.h41
-rw-r--r--src/verbs.cpp66
-rw-r--r--src/widgets/toolbox.cpp234
86 files changed, 8701 insertions, 9116 deletions
diff --git a/src/2geom/chebyshev.cpp b/src/2geom/chebyshev.cpp
index 447c5183f..73baf7b6b 100644
--- a/src/2geom/chebyshev.cpp
+++ b/src/2geom/chebyshev.cpp
@@ -93,7 +93,7 @@ SBasis chebyshev_approximant_interpolating (double (*f)(double,void*),
wr.fa = fa;
wr.fb = fb;
wr.in = in;
- printf("%f %f\n", fa, fb);
+ //printf("%f %f\n", fa, fb);
wr.f = f;
wr.pp = p;
return compose(Linear(in[0], in[1]), Linear(fa, fb)) + chebyshev_approximant(f_interp, order, in, &wr) + Linear(fa, fb);
diff --git a/src/2geom/path.cpp b/src/2geom/path.cpp
index 981c9f044..88c7a99b9 100644
--- a/src/2geom/path.cpp
+++ b/src/2geom/path.cpp
@@ -203,11 +203,10 @@ Path::nearestPointPerCurve(Point const& _point) const
{
//return a single nearest point for each curve in this path
std::vector<double> np;
- const Path& _path = *this;
- for (Sequence::const_iterator it = _path.get_curves().begin() ; it != _path.get_curves().end()-1 ; ++it)
+ for (const_iterator it = begin() ; it != end_default(); ++it)
//for (std::vector<Path>::const_iterator it = _path.begin(); it != _path.end(), ++it){
{
- np.push_back((*it)->nearestPoint(_point));
+ np.push_back(it->nearestPoint(_point));
}
return np;
}
diff --git a/src/2geom/svg-path-parser.cpp b/src/2geom/svg-path-parser.cpp
index 071b171b3..691ddf022 100644
--- a/src/2geom/svg-path-parser.cpp
+++ b/src/2geom/svg-path-parser.cpp
@@ -1,4 +1,3 @@
-#line 1 "/home/njh/svn/lib2geom/src/2geom/svg-path-parser.rl"
/**
* \file
* \brief parse SVG path specifications
diff --git a/src/Makefile.am b/src/Makefile.am
index 15c7cb1b1..92e520e5b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -134,6 +134,7 @@ include algorithms/Makefile_insert
include ui/Makefile_insert
include ui/cache/Makefile_insert
include ui/dialog/Makefile_insert
+include ui/tool/Makefile_insert
include ui/view/Makefile_insert
include ui/widget/Makefile_insert
include util/Makefile_insert
diff --git a/src/Makefile_insert b/src/Makefile_insert
index b32889f65..574dfe084 100644
--- a/src/Makefile_insert
+++ b/src/Makefile_insert
@@ -57,7 +57,6 @@ ink_common_sources += \
fixes.cpp \
flood-context.cpp flood-context.h \
forward.h \
- gc-allocator.h \
gc-alloc.h \
gc-anchored.h gc-anchored.cpp \
gc-core.h \
@@ -102,8 +101,6 @@ ink_common_sources += \
message-stack.cpp message-stack.h \
mod360.cpp mod360.h \
modifier-fns.h \
- node-context.cpp node-context.h \
- nodepath.cpp nodepath.h \
object-edit.cpp object-edit.h \
object-hierarchy.cpp object-hierarchy.h \
object-snapper.cpp object-snapper.h \
diff --git a/src/desktop.cpp b/src/desktop.cpp
index 70f7c4b15..69aec65e0 100644
--- a/src/desktop.cpp
+++ b/src/desktop.cpp
@@ -91,6 +91,10 @@
#include "widgets/desktop-widget.h"
#include "box3d-context.h"
+// TODO those includes are only for node tool quick zoom. Remove them after fixing it.
+#include "ui/tool/node-tool.h"
+#include "ui/tool/control-point-selection.h"
+
#include "display/sp-canvas.h"
namespace Inkscape { namespace XML { class Node; }}
@@ -624,9 +628,12 @@ SPDesktop::set_event_context (GtkType type, const gchar *config)
while (event_context) {
ec = event_context;
sp_event_context_deactivate (ec);
- event_context = ec->next;
+ // we have to keep event_context valid during destruction - otherwise writing
+ // destructors is next to impossible
+ SPEventContext *next = ec->next;
sp_event_context_finish (ec);
g_object_unref (G_OBJECT (ec));
+ event_context = next;
}
ec = sp_event_context_new (type, this, config, SP_EVENT_CONTEXT_STATIC);
@@ -785,11 +792,12 @@ SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double
int clear = FALSE;
if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) {
- /* Set zoom factors */
+ // zoom changed - set new zoom factors
_d2w = Geom::Scale(newscale, -newscale);
_w2d = Geom::Scale(1/newscale, 1/-newscale);
sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w);
clear = TRUE;
+ signal_zoom_changed.emit(_d2w.descrim());
}
/* Calculate top left corner (in document pixels) */
@@ -875,11 +883,6 @@ SPDesktop::next_zoom()
zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data));
}
-#include "tools-switch.h"
-#include "node-context.h"
-#include "shape-editor.h"
-#include "nodepath.h"
-
/** \brief Performs a quick zoom into what the user is working on
\param enable Whether we're going in or out of quick zoom
@@ -895,67 +898,26 @@ SPDesktop::zoom_quick (bool enable)
_quick_zoom_stored_area = get_display_area();
bool zoomed = false;
- if (!zoomed) {
- SPItem * singleItem = selection->singleItem();
- if (singleItem != NULL && tools_isactive(this, TOOLS_NODES)) {
-
- Inkscape::NodePath::Path * nodepath = event_context->shape_editor->get_nodepath();
- // printf("I've got a nodepath, crazy\n");
-
- if (nodepath) {
- Geom::Rect nodes;
- bool firstnode = true;
-
- if (nodepath->selected) {
- for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
- for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
- Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
- if (node->selected) {
- // printf("\tSelected node\n");
- if (firstnode) {
- nodes = Geom::Rect(node->pos, node->pos);
- firstnode = false;
- } else {
- nodes.expandTo(node->pos);
- }
-
- if (node->p.other != NULL) {
- /* Include previous node pos */
- nodes.expandTo(node->p.other->pos);
-
- /* Include previous handle */
- if (!sp_node_side_is_line(node, &node->p)) {
- nodes.expandTo(node->p.pos);
- }
- }
-
- if (node->n.other != NULL) {
- /* Include previous node pos */
- nodes.expandTo(node->n.other->pos);
-
- /* Include previous handle */
- if (!sp_node_side_is_line(node, &node->n)) {
- nodes.expandTo(node->n.pos);
- }
- }
- }
- }
- }
-
- if (!firstnode && nodes.area() * 2.0 < _quick_zoom_stored_area.area()) {
- set_display_area(nodes, 10);
- zoomed = true;
- }
- }
- }
+ // TODO This needs to migrate into the node tool, but currently the design
+ // of this method is sufficiently wrong to prevent this.
+ if (!zoomed && INK_IS_NODE_TOOL(event_context)) {
+ InkNodeTool *nt = static_cast<InkNodeTool*>(event_context);
+ if (!nt->_selected_nodes->empty()) {
+ Geom::Rect nodes = *nt->_selected_nodes->bounds();
+ double area = nodes.area();
+ // do not zoom if a single cusp node is selected aand the bounds
+ // have zero area.
+ if (!Geom::are_near(area, 0) && area * 2.0 < _quick_zoom_stored_area.area()) {
+ set_display_area(nodes, true);
+ zoomed = true;
+ }
}
}
if (!zoomed) {
Geom::OptRect const d = selection->bounds();
if (d && d->area() * 2.0 < _quick_zoom_stored_area.area()) {
- set_display_area(*d, 10);
+ set_display_area(*d, true);
zoomed = true;
}
}
@@ -965,7 +927,7 @@ SPDesktop::zoom_quick (bool enable)
zoomed = true;
}
} else {
- set_display_area(_quick_zoom_stored_area, 0);
+ set_display_area(_quick_zoom_stored_area, false);
}
_quick_zoom_enabled = enable;
@@ -1806,7 +1768,7 @@ Geom::Point SPDesktop::dt2doc(Geom::Point const &p) const
}
-/**
+/*
* Pop event context from desktop's context stack. Never used.
*/
// void
@@ -1848,4 +1810,4 @@ Geom::Point SPDesktop::dt2doc(Geom::Point const &p) const
fill-column:99
End:
*/
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/desktop.h b/src/desktop.h
index a661d9900..00f6cfdd5 100644
--- a/src/desktop.h
+++ b/src/desktop.h
@@ -140,6 +140,11 @@ struct SPDesktop : public Inkscape::UI::View::View
sigc::signal<void, SPObject *> _layer_changed_signal;
sigc::signal<bool, const SPCSSAttr *>::accumulated<StopOnTrue> _set_style_signal;
sigc::signal<int, SPStyle *, int>::accumulated<StopOnNonZero> _query_style_signal;
+
+ /// Emitted when the zoom factor changes (not emitted when scrolling).
+ /// The parameter is the new zoom factor
+ sigc::signal<void, double> signal_zoom_changed;
+
sigc::connection connectDocumentReplaced (const sigc::slot<void,SPDesktop*,SPDocument*> & slot)
{
return _document_replaced_signal.connect (slot);
diff --git a/src/display/sp-canvas-util.cpp b/src/display/sp-canvas-util.cpp
index 30cd0dfa0..a23b157df 100644
--- a/src/display/sp-canvas-util.cpp
+++ b/src/display/sp-canvas-util.cpp
@@ -112,10 +112,11 @@ void sp_canvas_item_move_to_z (SPCanvasItem * item, gint z)
if (z == current_z)
return;
- if (z > current_z)
+ if (z > current_z) {
sp_canvas_item_raise (item, z - current_z);
-
- sp_canvas_item_lower (item, current_z - z);
+ } else {
+ sp_canvas_item_lower (item, current_z - z);
+ }
}
gint
diff --git a/src/display/sp-canvas.cpp b/src/display/sp-canvas.cpp
index aee53838f..6d996fbe2 100644
--- a/src/display/sp-canvas.cpp
+++ b/src/display/sp-canvas.cpp
@@ -103,6 +103,7 @@ static void group_remove (SPCanvasGroup *group, SPCanvasItem *item);
/* SPCanvasItem */
enum {ITEM_EVENT, ITEM_LAST_SIGNAL};
+enum {PROP_0, PROP_VISIBLE};
static void sp_canvas_request_update (SPCanvas *canvas);
@@ -113,12 +114,11 @@ static void sp_canvas_item_init (SPCanvasItem *item);
static void sp_canvas_item_dispose (GObject *object);
static void sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, gchar const *first_arg_name, va_list args);
+
static int emit_event (SPCanvas *canvas, GdkEvent *event);
static guint item_signals[ITEM_LAST_SIGNAL] = { 0 };
-static GtkObjectClass *item_parent_class;
-
/**
* Registers the SPCanvasItem class with Glib and returns its type number.
*/
@@ -151,9 +151,6 @@ sp_canvas_item_class_init (SPCanvasItemClass *klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
- /* fixme: Derive from GObject */
- item_parent_class = (GtkObjectClass*)gtk_type_class (GTK_TYPE_OBJECT);
-
item_signals[ITEM_EVENT] = g_signal_new ("event",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
@@ -172,6 +169,9 @@ sp_canvas_item_class_init (SPCanvasItemClass *klass)
static void
sp_canvas_item_init (SPCanvasItem *item)
{
+ // TODO items should not be visible on creation - this causes kludges with items
+ // that should be initially invisible; examples of such items: node handles, the CtrlRect
+ // used for rubberbanding, path outline, etc.
item->flags |= SP_CANVAS_ITEM_VISIBLE;
item->xform = Geom::Matrix(Geom::identity());
}
@@ -277,7 +277,7 @@ sp_canvas_item_dispose (GObject *object)
group_remove (SP_CANVAS_GROUP (item->parent), item);
}
- G_OBJECT_CLASS (item_parent_class)->dispose (object);
+ G_OBJECT_CLASS (g_type_class_peek(g_type_parent(sp_canvas_item_get_type())))->dispose (object);
}
/**
@@ -477,6 +477,13 @@ sp_canvas_item_lower (SPCanvasItem *item, int positions)
item->canvas->need_repick = TRUE;
}
+bool
+sp_canvas_item_is_visible (SPCanvasItem *item)
+{
+ return item->flags & SP_CANVAS_ITEM_VISIBLE;
+}
+
+
/**
* Sets visible flag on item and requests a redraw.
*/
@@ -542,8 +549,13 @@ sp_canvas_item_grab (SPCanvasItem *item, guint event_mask, GdkCursor *cursor, gu
if (item->canvas->grabbed_item)
return -1;
- if (!(item->flags & SP_CANVAS_ITEM_VISIBLE))
- return -1;
+ // This test disallows grabbing events by an invisible item, which may be useful
+ // sometimes. An example is the hidden control point used for the selector component,
+ // where it is used for object selection and rubberbanding. There seems to be nothing
+ // preventing this except this test, so I removed it.
+ // -- Krzysztof Kosiński, 2009.08.12
+ //if (!(item->flags & SP_CANVAS_ITEM_VISIBLE))
+ // return -1;
if (HAS_BROKEN_MOTION_HINTS) {
event_mask &= ~GDK_POINTER_MOTION_HINT_MASK;
diff --git a/src/display/sp-canvas.h b/src/display/sp-canvas.h
index 35e3fb5de..a2af080ef 100644
--- a/src/display/sp-canvas.h
+++ b/src/display/sp-canvas.h
@@ -101,6 +101,7 @@ void sp_canvas_item_affine_absolute(SPCanvasItem *item, Geom::Matrix const &aff)
void sp_canvas_item_raise(SPCanvasItem *item, int positions);
void sp_canvas_item_lower(SPCanvasItem *item, int positions);
+bool sp_canvas_item_is_visible(SPCanvasItem *item);
void sp_canvas_item_show(SPCanvasItem *item);
void sp_canvas_item_hide(SPCanvasItem *item);
int sp_canvas_item_grab(SPCanvasItem *item, unsigned int event_mask, GdkCursor *cursor, guint32 etime);
diff --git a/src/dom/io/httpclient.cpp b/src/dom/io/httpclient.cpp
index 4245d71f2..97c8575cf 100644
--- a/src/dom/io/httpclient.cpp
+++ b/src/dom/io/httpclient.cpp
@@ -73,7 +73,7 @@ bool HttpClient::openGet(const URI &uri)
socket.enableSSL(true);
else
{
- printf("Bad proto scheme:%d\n", uri.getScheme());
+ //printf("Bad proto scheme:%d\n", uri.getScheme());
return false;
}
@@ -106,7 +106,7 @@ bool HttpClient::openGet(const URI &uri)
{
if (!socket.readLine(msg))
return false;
- printf("header:'%s'\n", msg.c_str());
+ //printf("header:'%s'\n", msg.c_str());
if (msg.size() < 1)
break;
}
diff --git a/src/dom/odf/SvgOdg.cpp b/src/dom/odf/SvgOdg.cpp
index 2b1161310..e69de29bb 100644
--- a/src/dom/odf/SvgOdg.cpp
+++ b/src/dom/odf/SvgOdg.cpp
@@ -1,1551 +0,0 @@
-/**
- *
- * This is a small experimental class for converting between
- * SVG and OpenDocument .odg files. This code is not intended
- * to be a permanent solution for SVG-to-ODG conversion. Rather,
- * it is a quick-and-easy test bed for ideas which will be later
- * recoded into C++.
- *
- * ---------------------------------------------------------------------
- *
- * SvgOdg - A program to experiment with conversions between SVG and ODG
- * Copyright (C) 2006 Bob Jamison
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * For more information, please write to rwjj@earthlink.net
- *
- */
-
-
-/**
- *
- */
-public class SvgOdg
-{
-
-
-
-/**
- * Namespace declarations
- */
-public static final String SVG_NS =
- "http://www.w3.org/2000/svg";
-public static final String XLINK_NS =
- "http://www.w3.org/1999/xlink";
-public static final String ODF_NS =
- "urn:oasis:names:tc:opendocument:xmlns:office:1.0";
-public static final String ODG_NS =
- "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0";
-public static final String ODSVG_NS =
- "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0";
-
-
-DecimalFormat nrfmt;
-//static final double pxToCm = 0.0339;
-static final double pxToCm = 0.0275;
-static final double piToRad = 0.0174532925;
-BufferedWriter out;
-BufferedReader in;
-int imageNr;
-int styleNr;
-
-//########################################################################
-//# M E S S A G E S
-//########################################################################
-
-/**
- *
- */
-void err(String msg)
-{
- System.out.println("SvgOdg ERROR:" + msg);
-}
-
-/**
- *
- */
-void trace(String msg)
-{
- System.out.println("SvgOdg:" + msg);
-}
-
-
-
-
-//########################################################################
-//# I N P U T / O U T P U T
-//########################################################################
-
-boolean po(String s)
-{
- try
- {
- out.write(s);
- }
- catch(IOException e)
- {
- return false;
- }
- return true;
-}
-
-//########################################################################
-//# U T I L I T Y
-//########################################################################
-
-public void dumpDocument(Document doc)
-{
- String s = "";
- try
- {
- TransformerFactory factory = TransformerFactory.newInstance();
- Transformer trans = factory.newTransformer();
- DOMSource source = new DOMSource(doc);
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- StreamResult result = new StreamResult(bos);
- trans.transform(source, result);
- byte buf[] = bos.toByteArray();
- s = new String(buf);
- }
- catch (javax.xml.transform.TransformerException e)
- {
- }
- trace("doc:" + s);
-}
-
-
-//########################################################################
-//# I N N E R C L A S S ImageInfo
-//########################################################################
-public class ImageInfo
-{
-String name;
-String newName;
-byte buf[];
-
-public String getName()
-{
- return name;
-}
-
-public String getNewName()
-{
- return newName;
-}
-
-
-public byte[] getBuf()
-{
- return buf;
-}
-
-public ImageInfo(String name, String newName, byte buf[])
-{
- this.name = name;
- this.name = newName;
- this.buf = buf;
-}
-
-}
-//########################################################################
-//# I N N E R C L A S S StyleInfo
-//########################################################################
-public class StyleInfo
-{
-
-String name;
-public String getName()
-{
- return name;
-}
-
-String cssStyle;
-public String getCssStyle()
-{
- return cssStyle;
-}
-
-String stroke;
-public String getStroke()
-{
- return stroke;
-}
-
-String strokeColor;
-public String getStrokeColor()
-{
- return strokeColor;
-}
-
-String strokeWidth;
-public String getStrokeWidth()
-{
- return strokeWidth;
-}
-
-String fill;
-public String getFill()
-{
- return fill;
-}
-
-String fillColor;
-public String getFillColor()
-{
- return fillColor;
-}
-
-public StyleInfo(String name, String cssStyle)
-{
- this.name = name;
- this.cssStyle = cssStyle;
- fill = "none";
- stroke = "none";
-}
-
-}
-//########################################################################
-//# E N D I N N E R C L A S S E S
-//########################################################################
-
-
-
-
-//########################################################################
-//# V A R I A B L E S
-//########################################################################
-
-/**
- * ODF content.xml file
- */
-Document content;
-public Document getContent()
-{
- return content;
-}
-
-/**
- * ODF meta.xml file
- */
-Document meta;
-public Document getMeta()
-{
- return meta;
-}
-
-/**
- * SVG file
- */
-Document svg;
-public Document getSvg()
-{
- return svg;
-}
-
-/**
- * Loaded ODF or SVG images
- */
-ArrayList<ImageInfo> images;
-public ArrayList<ImageInfo> getImages()
-{
- return images;
-}
-
-/**
- * CSS styles
- */
-HashMap<String, StyleInfo> styles;
-public HashMap<String, StyleInfo> getStyles()
-{
- return styles;
-}
-
-
-
-
-
-//########################################################################
-//# S V G T O O D F
-//########################################################################
-
-class PathData
-{
-String cmd;
-double nr[];
-PathData(String s, double buf[])
-{
- cmd=s; nr = buf;
-}
-}
-
-double getPathNum(StringTokenizer st)
-{
- if (!st.hasMoreTokens())
- return 0.0;
- String s = st.nextToken();
- double nr = Double.parseDouble(s);
- return nr;
-}
-
-String parsePathData(String pathData, double bounds[])
-{
- double minx = Double.MAX_VALUE;
- double maxx = Double.MIN_VALUE;
- double miny = Double.MAX_VALUE;
- double maxy = Double.MIN_VALUE;
- //trace("#### pathData:" + pathData);
- ArrayList<PathData> data = new ArrayList<PathData>();
- StringTokenizer st = new StringTokenizer(pathData, " ,");
- while (true)
- {
- String s = st.nextToken();
- if ( s.equals("z") || s.equals("Z") )
- {
- PathData pd = new PathData(s, new double[0]);
- data.add(pd);
- break;
- }
- else if ( s.equals("h") || s.equals("H") )
- {
- double d[] = new double[1];
- d[0] = getPathNum(st) * pxToCm;
- if (d[0] < minx) minx = d[0];
- else if (d[0] > maxx) maxx = d[0];
- PathData pd = new PathData(s, d);
- data.add(pd);
- }
- else if ( s.equals("v") || s.equals("V") )
- {
- double d[] = new double[1];
- d[0] = getPathNum(st) * pxToCm;
- if (d[0] < miny) miny = d[0];
- else if (d[0] > maxy) maxy = d[0];
- PathData pd = new PathData(s, d);
- data.add(pd);
- }
- else if ( s.equals("m") || s.equals("M") ||
- s.equals("l") || s.equals("L") ||
- s.equals("t") || s.equals("T") )
- {
- double d[] = new double[2];
- d[0] = getPathNum(st) * pxToCm;
- d[1] = getPathNum(st) * pxToCm;
- if (d[0] < minx) minx = d[0];
- else if (d[0] > maxx) maxx = d[0];
- if (d[1] < miny) miny = d[1];
- else if (d[1] > maxy) maxy = d[1];
- PathData pd = new PathData(s, d);
- data.add(pd);
- }
- else if ( s.equals("q") || s.equals("Q") ||
- s.equals("s") || s.equals("S") )
- {
- double d[] = new double[4];
- d[0] = getPathNum(st) * pxToCm;
- d[1] = getPathNum(st) * pxToCm;
- if (d[0] < minx) minx = d[0];
- else if (d[0] > maxx) maxx = d[0];
- if (d[1] < miny) miny = d[1];
- else if (d[1] > maxy) maxy = d[1];
- d[2] = getPathNum(st) * pxToCm;
- d[3] = getPathNum(st) * pxToCm;
- if (d[2] < minx) minx = d[2];
- else if (d[2] > maxx) maxx = d[2];
- if (d[3] < miny) miny = d[3];
- else if (d[3] > maxy) maxy = d[3];
- PathData pd = new PathData(s, d);
- data.add(pd);
- }
- else if ( s.equals("c") || s.equals("C") )
- {
- double d[] = new double[6];
- d[0] = getPathNum(st) * pxToCm;
- d[1] = getPathNum(st) * pxToCm;
- if (d[0] < minx) minx = d[0];
- else if (d[0] > maxx) maxx = d[0];
- if (d[1] < miny) miny = d[1];
- else if (d[1] > maxy) maxy = d[1];
- d[2] = getPathNum(st) * pxToCm;
- d[3] = getPathNum(st) * pxToCm;
- if (d[2] < minx) minx = d[2];
- else if (d[2] > maxx) maxx = d[2];
- if (d[3] < miny) miny = d[3];
- else if (d[3] > maxy) maxy = d[3];
- d[4] = getPathNum(st) * pxToCm;
- d[5] = getPathNum(st) * pxToCm;
- if (d[4] < minx) minx = d[4];
- else if (d[4] > maxx) maxx = d[4];
- if (d[5] < miny) miny = d[5];
- else if (d[5] > maxy) maxy = d[5];
- PathData pd = new PathData(s, d);
- data.add(pd);
- }
- else if ( s.equals("a") || s.equals("A") )
- {
- double d[] = new double[6];
- d[0] = getPathNum(st) * pxToCm;
- d[1] = getPathNum(st) * pxToCm;
- if (d[0] < minx) minx = d[0];
- else if (d[0] > maxx) maxx = d[0];
- if (d[1] < miny) miny = d[1];
- else if (d[1] > maxy) maxy = d[1];
- d[2] = getPathNum(st) * piToRad;//angle
- d[3] = getPathNum(st) * piToRad;//angle
- d[4] = getPathNum(st) * pxToCm;
- d[5] = getPathNum(st) * pxToCm;
- if (d[4] < minx) minx = d[4];
- else if (d[4] > maxx) maxx = d[4];
- if (d[5] < miny) miny = d[5];
- else if (d[5] > maxy) maxy = d[5];
- PathData pd = new PathData(s, d);
- data.add(pd);
- }
- //trace("x:" + x + " y:" + y);
- }
-
- trace("minx:" + minx + " maxx:" + maxx +
- " miny:" + miny + " maxy:" + maxy);
-
- StringBuffer buf = new StringBuffer();
- for (PathData pd : data)
- {
- buf.append(pd.cmd);
- buf.append(" ");
- for (double d:pd.nr)
- {
- buf.append(nrfmt.format(d * 1000.0));
- buf.append(" ");
- }
- }
-
- bounds[0] = minx;
- bounds[1] = miny;
- bounds[2] = maxx;
- bounds[3] = maxy;
-
- return buf.toString();
-}
-
-
-
-boolean parseTransform(String transStr, AffineTransform trans)
-{
- trace("== transform:"+ transStr);
- StringTokenizer st = new StringTokenizer(transStr, ")");
- while (st.hasMoreTokens())
- {
- String chunk = st.nextToken();
- StringTokenizer st2 = new StringTokenizer(chunk, " ,(");
- if (!st2.hasMoreTokens())
- continue;
- String name = st2.nextToken();
- trace(" ++name:"+ name);
- if (name.equals("matrix"))
- {
- double v[] = new double[6];
- for (int i=0 ; i<6 ; i++)
- {
- if (!st2.hasMoreTokens())
- break;
- v[i] = Double.parseDouble(st2.nextToken()) * pxToCm;
- }
- AffineTransform mat = new AffineTransform(v);
- trans.concatenate(mat);
- }
- else if (name.equals("translate"))
- {
- double dx = 0.0;
- double dy = 0.0;
- if (!st2.hasMoreTokens())
- continue;
- dx = Double.parseDouble(st2.nextToken()) * pxToCm;
- if (st2.hasMoreTokens())
- dy = Double.parseDouble(st2.nextToken()) * pxToCm;
- trans.translate(dx, dy);
- }
- else if (name.equals("scale"))
- {
- double sx = 1.0;
- double sy = 1.0;
- if (!st2.hasMoreTokens())
- continue;
- sx = sy = Double.parseDouble(st2.nextToken());
- if (st2.hasMoreTokens())
- sy = Double.parseDouble(st2.nextToken());
- trans.scale(sx, sy);
- }
- else if (name.equals("rotate"))
- {
- double r = 0.0;
- double cx = 0.0;
- double cy = 0.0;
- if (!st2.hasMoreTokens())
- continue;
- r = Double.parseDouble(st2.nextToken()) * piToRad;
- if (st2.hasMoreTokens())
- {
- cx = Double.parseDouble(st2.nextToken()) * pxToCm;
- if (!st2.hasMoreTokens())
- continue;
- cy = Double.parseDouble(st2.nextToken()) * pxToCm;
- trans.rotate(r, cx, cy);
- }
- else
- {
- trans.rotate(r);
- }
- }
- else if (name.equals("skewX"))
- {
- double angle = 0.0;
- if (!st2.hasMoreTokens())
- continue;
- angle = Double.parseDouble(st2.nextToken());
- trans.shear(angle, 0.0);
- }
- else if (name.equals("skewY"))
- {
- double angle = 0.0;
- if (!st2.hasMoreTokens())
- continue;
- angle = Double.parseDouble(st2.nextToken());
- trans.shear(0.0, angle);
- }
- }
- return true;
-}
-
-
-
-String coordToOdg(String sval)
-{
- double nr = Double.parseDouble(sval);
- nr = nr * pxToCm;
- String s = nrfmt.format(nr) + "cm";
- return s;
-}
-
-
-boolean writeSvgAttributes(Element elem, AffineTransform trans)
-{
- NamedNodeMap attrs = elem.getAttributes();
- String ename = elem.getLocalName();
- for (int i=0 ; i<attrs.getLength() ; i++)
- {
- Attr attr = (Attr)attrs.item(i);
- String aname = attr.getName();
- String aval = attr.getValue();
- if (aname.startsWith("xmlns"))
- continue;
- else if (aname.equals("d"))//already handled
- continue;
- else if (aname.startsWith("transform"))
- {
- parseTransform(aval, trans);
- continue;
- }
- else if (aname.equals("style"))
- {
- StyleInfo style = styles.get(aval);
- if (style != null)
- {
- po(" draw:style-name=\"");
- po(style.getName());
- po("\"");
- }
- continue;
- }
- if (aname.equals("x") || aname.equals("y") ||
- aname.equals("width") || aname.equals("height"))
- {
- aval = coordToOdg(aval);
- }
- if ("id".equals(aname))
- po(" ");
- else if ("transform".equals(aname))
- po(" draw:");
- else
- po(" svg:");
- po(aname);
- po("=\"");
- po(aval);
- po("\"");
- }
-
- //Output the current transform
- if (!trans.isIdentity() &&
- !(
- ename.equals("g") ||
- ename.equals("defs") ||
- ename.equals("metadata")
- )
- )
- {
- double v[] = new double[6];
- trans.getMatrix(v);
- po(" draw:transform=\"matrix(" +
- nrfmt.format(v[0]) + "," +
- nrfmt.format(v[1]) + "," +
- nrfmt.format(v[2]) + "," +
- nrfmt.format(v[3]) + "," +
- nrfmt.format(v[4]) + "," +
- nrfmt.format(v[5]) + ")\"");
- }
- return true;
-}
-
-public boolean writeOdfContent(Element elem, AffineTransform trans)
-{
- String ns = elem.getNamespaceURI();
- String tagName = elem.getLocalName();
- //trace("ns:" + ns + " tagName:" + tagName);
- if (!ns.equals(SVG_NS))
- return true;
- if (tagName.equals("svg"))
- {
- NodeList children = elem.getChildNodes();
- for (int i=0 ; i<children.getLength() ; i++)
- {
- Node n = children.item(i);
- if (n.getNodeType() == Node.ELEMENT_NODE)
- if (!writeOdfContent((Element)n,
- (AffineTransform)trans.clone()))
- return false;
- }
- }
- else if (tagName.equals("g"))
- {
- //String transform = elem.getAttribute("transform");
- po("<draw:g");
- writeSvgAttributes(elem, trans); po(">\n");
- NodeList children = elem.getChildNodes();
- for (int i=0 ; i<children.getLength() ; i++)
- {
- Node n = children.item(i);
- if (n.getNodeType() == Node.ELEMENT_NODE)
- if (!writeOdfContent((Element)n,
- (AffineTransform)trans.clone()))
- return false;
- }
- po("</draw:g>\n");
- }
- else if (tagName.equals("text"))
- {
- String x = coordToOdg(elem.getAttribute("x"));
- String y = coordToOdg(elem.getAttribute("y"));
- String width = "5cm";
- String height = "2cm";
- String txt = elem.getTextContent();
- po("<draw:frame draw:style-name=\"grx1\" draw:layer=\"layout\" ");
- po("svg:x=\"" + x + "\" svg:y=\"" + y + "\" ");
- po("svg:width=\"" + width + "\" svg:height=\"" + height + "\"");
- po(">\n");
- po(" <draw:text-box draw:auto-grow-height=\"true\" draw:auto-grow-width=\"true\">\n");
- po(" <text:p text:style-name=\"P1\"> " + txt + "</text:p>\n");
- po(" </draw:text-box>\n");
- po("</draw:frame>\n");
- return true;
- }
- else if (tagName.equals("image"))
- {
- String x = coordToOdg(elem.getAttribute("x"));
- String y = coordToOdg(elem.getAttribute("y"));
- String width = coordToOdg(elem.getAttribute("width"));
- String height = coordToOdg(elem.getAttribute("height"));
- String imageName = elem.getAttributeNS(XLINK_NS, "href");
- po("<draw:frame draw:style-name=\"grx1\" draw:layer=\"layout\" ");
- po("svg:x=\"" + x + "\" svg:y=\"" + y + "\" ");
- po("svg:width=\"" + width + "\" svg:height=\"" + height + "\">\n");
- po(" <draw:image xlink:href=\"Pictures/" + imageName + "\" xlink:type=\"simple\" xlink:show=\"embed\" xlink:actuate=\"onLoad\"><text:p/></draw:image>\n");
- po("</draw:frame>\n");
- return true;
- }
- else if (tagName.equals("path"))
- {
- double bounds[] = new double[4];
- String d = elem.getAttribute("d");
- String newd = parsePathData(d, bounds);
- double x = bounds[0];
- double y = bounds[1];
- double width = (bounds[2]-bounds[0]);
- double height = (bounds[3]-bounds[1]);
- po("<draw:path draw:layer=\"layout\" \n");
- po(" svg:x=\"" + nrfmt.format(x) + "cm\" ");
- po("svg:y=\"" + nrfmt.format(y) + "cm\" ");
- po("svg:width=\"" + nrfmt.format(width) + "cm\" ");
- po("svg:height=\"" + nrfmt.format(height) + "cm\" ");
- po("svg:viewBox=\"0.0 0.0 " +
- nrfmt.format(bounds[2] * 1000.0) + " " +
- nrfmt.format(bounds[3] * 1000.0) + "\"\n");
- po(" svg:d=\"" + newd + "\"\n ");
-
- writeSvgAttributes(elem, trans); po("/>\n");
- //po(" svg:d=\"" + d + "\"/>\n");
- return true;
- }
-
- else
- {
- //Verbatim tab mapping
- po("<draw:"); po(tagName);
- writeSvgAttributes(elem, trans); po(">\n");
- po("</draw:"); po(tagName); po(">\n");
- }
-
- return true;
-}
-
-
-boolean writeOdfContent(ZipOutputStream outs)
-{
- try
- {
- ZipEntry ze = new ZipEntry("content.xml");
- outs.putNextEntry(ze);
- out = new BufferedWriter(new OutputStreamWriter(outs));
- }
- catch (IOException e)
- {
- return false;
- }
-
- NodeList res = svg.getElementsByTagNameNS(SVG_NS, "svg");
- if (res.getLength() < 1)
- {
- err("saveOdf: no <svg> root in .svg file");
- return false;
- }
- Element root = (Element)res.item(0);
-
-
- po("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
- po("<office:document-content\n");
- po(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n");
- po(" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\"\n");
- po(" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"\n");
- po(" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"\n");
- po(" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\"\n");
- po(" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\"\n");
- po(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
- po(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n");
- po(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n");
- po(" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\"\n");
- po(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n");
- po(" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\"\n");
- po(" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\"\n");
- po(" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\"\n");
- po(" xmlns:math=\"http://www.w3.org/1998/Math/MathML\"\n");
- po(" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\"\n");
- po(" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\"\n");
- po(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n");
- po(" xmlns:ooow=\"http://openoffice.org/2004/writer\"\n");
- po(" xmlns:oooc=\"http://openoffice.org/2004/calc\"\n");
- po(" xmlns:dom=\"http://www.w3.org/2001/xml-events\"\n");
- po(" xmlns:xforms=\"http://www.w3.org/2002/xforms\"\n");
- po(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n");
- po(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
- po(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n");
- po(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n");
- po(" office:version=\"1.0\">\n");
- po("\n");
- po("\n");
- po("<office:scripts/>\n");
- po("<office:automatic-styles>\n");
- po("<style:style style:name=\"dp1\" style:family=\"drawing-page\"/>\n");
- po("<style:style style:name=\"grx1\" style:family=\"graphic\" style:parent-style-name=\"standard\">\n");
- po(" <style:graphic-properties draw:stroke=\"none\" draw:fill=\"solid\" draw:textarea-horizontal-align=\"center\" draw:textarea-vertical-align=\"middle\" draw:color-mode=\"standard\" draw:luminance=\"0%\" draw:contrast=\"0%\" draw:gamma=\"100%\" draw:red=\"0%\" draw:green=\"0%\" draw:blue=\"0%\" fo:clip=\"rect(0cm 0cm 0cm 0cm)\" draw:image-opacity=\"100%\" style:mirror=\"none\"/>\n");
- po("</style:style>\n");
- po("<style:style style:name=\"P1\" style:family=\"paragraph\">\n");
- po(" <style:paragraph-properties fo:text-align=\"center\"/>\n");
- po("</style:style>\n");
-
- //## Dump our style table
- for (StyleInfo s : styles.values())
- {
- po("<style:style style:name=\"" + s.getName() + "\" style:family=\"graphic\" style:parent-style-name=\"standard\">\n");
- po(" <style:graphic-properties");
- po(" draw:fill=\"" + s.getFill() + "\"");
- if (!s.getFill().equals("none"))
- po(" draw:fill-color=\"" + s.getFillColor() + "\"");
- po(" draw:stroke=\"" + s.getStroke() + "\"");
- if (!s.getStroke().equals("none"))
- {
- po(" svg:stroke-width=\"" + s.getStrokeWidth() + "\"");
- po(" svg:stroke-color=\"" + s.getStrokeColor() + "\"");
- }
- po("/>\n");
- po("</style:style>\n");
- }
- po("</office:automatic-styles>\n");
- po("\n");
- po("\n");
- po("<office:body>\n");
- po("<office:drawing>\n");
- po("<draw:page draw:name=\"page1\" draw:style-name=\"dp1\" draw:master-page-name=\"Default\">\n");
- po("\n\n\n");
- AffineTransform trans = new AffineTransform();
- //trans.scale(12.0, 12.0);
- po("<!-- ######### CONVERSION FROM SVG STARTS ######## -->\n");
- writeOdfContent(root, trans);
- po("<!-- ######### CONVERSION FROM SVG ENDS ######## -->\n");
- po("\n\n\n");
-
- po("</draw:page>\n");
- po("</office:drawing>\n");
- po("</office:body>\n");
- po("</office:document-content>\n");
-
-
- try
- {
- out.flush();
- outs.closeEntry();
- }
- catch (IOException e)
- {
- err("writeOdfContent:" + e);
- return false;
- }
- return true;
-}
-
-boolean writeOdfMeta(ZipOutputStream outs)
-{
- try
- {
- ZipEntry ze = new ZipEntry("meta.xml");
- outs.putNextEntry(ze);
- out = new BufferedWriter(new OutputStreamWriter(outs));
- }
- catch (IOException e)
- {
- return false;
- }
-
- po("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
- po("<office:document-meta\n");
- po(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n");
- po(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
- po(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n");
- po(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n");
- po(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n");
- po(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n");
- po(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n");
- po(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n");
- po(" office:version=\"1.0\">\n");
- po("<office:meta>\n");
- po(" <meta:generator>Inkscape-0.43</meta:generator>\n");
- po(" <meta:initial-creator>clark kent</meta:initial-creator>\n");
- po(" <meta:creation-date>2005-12-10T10:55:13</meta:creation-date>\n");
- po(" <dc:creator>clark kent</dc:creator>\n");
- po(" <dc:date>2005-12-10T10:56:20</dc:date>\n");
- po(" <dc:language>en-US</dc:language>\n");
- po(" <meta:editing-cycles>2</meta:editing-cycles>\n");
- po(" <meta:editing-duration>PT1M13S</meta:editing-duration>\n");
- po(" <meta:user-defined meta:name=\"Info 1\"/>\n");
- po(" <meta:user-defined meta:name=\"Info 2\"/>\n");
- po(" <meta:user-defined meta:name=\"Info 3\"/>\n");
- po(" <meta:user-defined meta:name=\"Info 4\"/>\n");
- po(" <meta:document-statistic meta:object-count=\"2\"/>\n");
- po("</office:meta>\n");
- po("</office:document-meta>\n");
-
-
- try
- {
- out.flush();
- outs.closeEntry();
- }
- catch (IOException e)
- {
- err("writeOdfContent:" + e);
- return false;
- }
- return true;
-}
-
-
-boolean writeOdfManifest(ZipOutputStream outs)
-{
- try
- {
- ZipEntry ze = new ZipEntry("META-INF/manifest.xml");
- outs.putNextEntry(ze);
- out = new BufferedWriter(new OutputStreamWriter(outs));
- }
- catch (IOException e)
- {
- return false;
- }
-
- po("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
- po("<!DOCTYPE manifest:manifest PUBLIC \"-//OpenOffice.org//DTD Manifest 1.0//EN\" \"Manifest.dtd\">\n");
- po("<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\">\n");
- po(" <manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.graphics\" manifest:full-path=\"/\"/>\n");
- po(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/>\n");
- po(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"meta.xml\"/>\n");
- po(" <!--List our images here-->\n");
- for (int i=0 ; i<images.size() ; i++)
- {
- ImageInfo ie = images.get(i);
- String fname = ie.getName();
- if (fname.length() < 5)
- {
- err("image file name too short:" + fname);
- return false;
- }
- String ext = fname.substring(fname.length() - 4);
- po(" <manifest:file-entry manifest:media-type=\"");
- if (ext.equals(".gif"))
- po("image/gif");
- else if (ext.equals(".png"))
- po("image/png");
- else if (ext.equals(".jpg") || ext.equals(".jpeg"))
- po("image/jpeg");
- po("\" manifest:full-path=\"");
- po(fname);
- po("\"/>\n");
- }
- po("</manifest:manifest>\n");
-
- try
- {
- out.flush();
- outs.closeEntry();
- }
- catch (IOException e)
- {
- err("writeOdfContent:" + e);
- return false;
- }
- return true;
-}
-
-boolean writeOdfImages(ZipOutputStream outs)
-{
- for (int i=0 ; i<images.size() ; i++)
- {
- ImageInfo ie = images.get(i);
- try
- {
- String iname = "Pictures/" + ie.getName();
- ZipEntry ze = new ZipEntry(iname);
- outs.putNextEntry(ze);
- outs.write(ie.getBuf());
- outs.closeEntry();
- }
- catch (IOException e)
- {
- err("writing images:" + e);
- return false;
- }
- }
- return true;
-}
-
-/**
- *
- */
-public boolean writeOdf(OutputStream outs)
-{
- try
- {
- ZipOutputStream zos = new ZipOutputStream(outs);
- if (!writeOdfContent(zos))
- return false;
- if (!writeOdfManifest(zos))
- return false;
- if (!writeOdfMeta(zos))
- return false;
- if (!writeOdfImages(zos))
- return false;
- //if (!writeOdfStyles(zos))
- // return false;
- zos.close();
- }
- catch (IOException e)
- {
- err("closing ODF zip output stream:" + e);
- return false;
- }
- return true;
-}
-
-
-
-/**
- *
- */
-public boolean saveOdf(String fileName)
-{
- boolean ret = true;
- try
- {
- FileOutputStream fos = new FileOutputStream(fileName);
- ret = writeOdf(fos);
- fos.close();
- }
- catch (IOException e)
- {
- err("writing odf " + fileName + " : " + e);
- return false;
- }
- return ret;
-}
-
-
-
-boolean parseCss(String css)
-{
- trace("##### STYLE ### :" + css);
- String name = "gr" + styleNr;
- styleNr++;
- StyleInfo si = new StyleInfo(name, css);
- StringTokenizer st = new StringTokenizer(css, ";");
- while (st.hasMoreTokens())
- {
- String style = st.nextToken();
- //trace(" " + style);
- int pos = style.indexOf(':');
- if (pos < 1 || pos > style.length()-2)
- continue;
- String attrName = style.substring(0, pos);
- String attrVal = style.substring(pos+1);
- trace(" =" + attrName + ':' + attrVal);
- if ("stroke".equals(attrName))
- {
- si.stroke = "solid";
- si.strokeColor = attrVal;
- }
- else if ("stroke-width".equals(attrName))
- {
- si.strokeWidth = attrVal;
- }
- else if ("fill".equals(attrName))
- {
- si.fill = "solid";
- si.fillColor = attrVal;
- }
- }
- styles.put(css, si);
- return true;
-}
-
-boolean readSvg(InputStream ins)
-{
- //### LOAD XML
- try
- {
- DocumentBuilderFactory factory =
- DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(true);
- DocumentBuilder builder =
- factory.newDocumentBuilder();
- builder.setEntityResolver(new EntityResolver()
- {
- public InputSource resolveEntity(String publicId, String systemId)
- {
- return new InputSource(new ByteArrayInputStream(new byte[0]));
- }
- });
- Document doc = builder.parse(ins);
- svg = doc;
- }
- catch (javax.xml.parsers.ParserConfigurationException e)
- {
- err("making DOM parser:"+e);
- return false;
- }
- catch (org.xml.sax.SAXException e)
- {
- err("parsing svg document:"+e);
- return false;
- }
- catch (IOException e)
- {
- err("parsing svg document:"+e);
- return false;
- }
- //dumpDocument(svg);
-
- //### LOAD IMAGES
- imageNr = 0;
- images = new ArrayList<ImageInfo>();
- NodeList res = svg.getElementsByTagNameNS(SVG_NS, "image");
- for (int i=0 ; i<res.getLength() ; i++)
- {
- Element elem = (Element) res.item(i);
- String fileName = elem.getAttributeNS(XLINK_NS, "href");
- if (fileName == null)
- {
- err("No xlink:href pointer to image data for image");
- return false;
- }
- File f = new File(fileName);
- if (!f.exists())
- {
- err("image '" + fileName + "' does not exist");
- return false;
- }
- int bufSize = (int)f.length();
- byte buf[] = new byte[bufSize];
- int pos = 0;
- try
- {
- FileInputStream fis = new FileInputStream(fileName);
- while (pos < bufSize)
- {
- int len = fis.read(buf, pos, bufSize - pos);
- if (len < 0)
- break;
- pos += len;
- }
- fis.close();
- }
- catch (IOException e)
- {
- err("reading image '" + fileName + "' :" + e);
- return false;
- }
- if (pos != bufSize)
- {
- err("reading image entry. expected " +
- bufSize + ", found " + pos + " bytes.");
- return false;
- }
- ImageInfo ie = new ImageInfo(fileName, fileName, buf);
- images.add(ie);
- }
-
- //### Parse styles
- styleNr = 0;
- styles = new HashMap<String, StyleInfo>();
- res = svg.getElementsByTagName("*");
- for (int i=0 ; i<res.getLength() ; i++)
- {
- Element elem = (Element) res.item(i);
- trace("elem:"+ elem.getNodeName());
- String style = elem.getAttribute("style");
- if (style != null && style.length() > 0)
- parseCss(style);
- }
-
- return true;
-}
-
-boolean readSvg(String fileName)
-{
- try
- {
- FileInputStream fis = new FileInputStream(fileName);
- if (!readSvg(fis))
- return false;
- fis.close();
- }
- catch (IOException e)
- {
- err("opening svg file:"+e);
- return false;
- }
- return true;
-}
-
-
-//########################################################################
-//# O D F T O S V G
-//########################################################################
-
-/**
- *
- */
-public boolean readOdfEntry(ZipInputStream zis, ZipEntry ent)
-{
- String fileName = ent.getName();
- trace("fileName:" + fileName);
- if (fileName.length() < 4)
- return true;
- String ext = fileName.substring(fileName.length() - 4);
- trace("ext:" + ext);
- ArrayList<Byte> arr = new ArrayList<Byte>();
- try
- {
- while (true)
- {
- int inb = zis.read();
- if (inb < 0)
- break;
- arr.add((byte)inb);
- }
- }
- catch (IOException e)
- {
- return false;
- }
- byte buf[] = new byte[arr.size()];
- for (int i=0 ; i<buf.length ; i++)
- buf[i] = arr.get(i);
- trace("bufsize:" + buf.length);
-
- if (ext.equals(".xml"))
- {
- try
- {
- DocumentBuilderFactory factory =
- DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(true);
- DocumentBuilder builder =
- factory.newDocumentBuilder();
- builder.setEntityResolver(new EntityResolver()
- {
- public InputSource resolveEntity(String publicId, String systemId)
- {
- return new InputSource(new ByteArrayInputStream(new byte[0]));
- }
- });
- //trace("doc:"+new String(buf));
- Document doc = builder.parse(new ByteArrayInputStream(buf));
- if (fileName.equals("content.xml"))
- content = doc;
- else if (fileName.equals("meta.xml"))
- meta = doc;
- else if (fileName.equals("styles.xml"))
- {
- //styles = doc;
- }
- }
- catch (javax.xml.parsers.ParserConfigurationException e)
- {
- err("making DOM parser:"+e);
- return false;
- }
- catch (org.xml.sax.SAXException e)
- {
- err("parsing document:"+e);
- return false;
- }
- catch (IOException e)
- {
- err("parsing document:"+e);
- return false;
- }
- }
- else if (ext.equals(".png") ||
- ext.equals(".gif") ||
- ext.equals(".jpg") ||
- ext.equals(".jpeg") )
- {
- String imageName = "image" + imageNr + ext;
- imageNr++;
- ImageInfo ie = new ImageInfo(fileName, imageName, buf);
- trace("added image '" + imageName + "'. " + buf.length +" bytes.");
- images.add(ie);
-
- }
- dumpDocument(content);
- return true;
-}
-
-
-/**
- *
- */
-public boolean readOdf(InputStream ins)
-{
- imageNr = 0;
- images = new ArrayList<ImageInfo>();
- styleNr = 0;
- styles = new HashMap<String, StyleInfo>();
- ZipInputStream zis = new ZipInputStream(ins);
- while (true)
- {
- try
- {
- ZipEntry ent = zis.getNextEntry();
- if (ent == null)
- break;
- if (!readOdfEntry(zis, ent))
- return false;
- zis.closeEntry();
- }
- catch (IOException e)
- {
- err("reading zip entry");
- return false;
- }
- }
-
- return true;
-}
-
-
-/**
- *
- */
-public boolean readOdf(String fileName)
-{
- boolean ret = true;
- try
- {
- FileInputStream fis = new FileInputStream(fileName);
- ret = readOdf(fis);
- fis.close();
- }
- catch (IOException e)
- {
- err("reading " + fileName + " : " + e);
- ret = false;
- }
- return true;
-}
-
-
-
-
-public boolean writeSvgElement(Element elem)
-{
- String ns = elem.getNamespaceURI();
- String tagName = elem.getLocalName();
- trace("tag:" + tagName + " : " + ns);
- if (ns.equals(ODSVG_NS))
- {
- po("<"); po(tagName);
- NamedNodeMap attrs = elem.getAttributes();
- for (int i=0 ; i<attrs.getLength() ; i++)
- {
- Attr attr = (Attr)attrs.item(i);
- String aname = attr.getName();
- String aval = attr.getValue();
- //Replace image name
- if ("xlink:href".equals(aname) && "image".equals(tagName))
- {
- for (int j=0 ; j<images.size() ; j++)
- {
- ImageInfo ie = images.get(i);
- if (aval.equals(ie.getName()))
- aval = ie.getNewName();
- }
- }
- po(" ");
- po(aname);
- po("=\"");
- po(aval);
- po("\"");
- }
- po(">\n");
- }
- NodeList children = elem.getChildNodes();
- for (int i=0 ; i<children.getLength() ; i++)
- {
- Node n = children.item(i);
- if (n.getNodeType() == Node.ELEMENT_NODE)
- if (!writeSvgElement((Element)n))
- return false;
- }
- if (ns.equals(ODSVG_NS))
- {
- po("</"); po(tagName); po(">\n");
- }
- return true;
-}
-
-
-public boolean saveSvg(String svgFileName)
-{
- trace("====== Saving images ===========");
- try
- {
- for (int i=0 ; i<images.size() ; i++)
- {
- ImageInfo entry = images.get(i);
- trace("saving:" + entry.name);
- FileOutputStream fos = new FileOutputStream(entry.name);
- fos.write(entry.buf);
- fos.close();
- }
- }
- catch (IOException e)
- {
- err("saveAsSVG:" + e);
- return false;
- }
-
- try
- {
- out = new BufferedWriter(new FileWriter(svgFileName));
- }
- catch (IOException e)
- {
- err("save:" + e);
- return false;
- }
-
- if (content == null)
- {
- err("no content in odf");
- return false;
- }
-
- NodeList res = content.getElementsByTagNameNS(ODF_NS, "drawing");
- if (res.getLength() < 1)
- {
- err("save: no drawing in document");
- return false;
- }
- Element root = (Element)res.item(0);
- trace("NS:"+root.getNamespaceURI());
-
- res = root.getElementsByTagNameNS(ODG_NS, "page");
- if (res.getLength() < 1)
- {
- err("save: no page in drawing");
- return false;
- }
- Element page = (Element)res.item(0);
-
- po("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
- po("<svg\n");
- po(" xmlns:svg=\"http://www.w3.org/2000/svg\"\n");
- po(" xmlns=\"http://www.w3.org/2000/svg\"\n");
- po(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
- po(" version=\"1.0\"\n");
- po(" >\n");
-
- writeSvgElement(page);
-
- po("</svg>\n");
-
- try
- {
- out.close();
- }
- catch (IOException e)
- {
- err("save:close:" + e);
- return false;
- }
- return true;
-}
-
-
-
-//########################################################################
-//# C O N S T R U C T O R
-//########################################################################
-
-SvgOdg()
-{
- //init, if necessary
- nrfmt = new DecimalFormat("#.####");
-}
-
-
-//########################################################################
-//# C O M M A N D L I N E
-//########################################################################
-
-public boolean odfToSvg(String odfName, String svgName)
-{
- if (!readOdf(odfName))
- return false;
- if (!saveSvg(svgName))
- return false;
- return true;
-}
-
-public boolean svgToOdf(String svgName, String odfName)
-{
- if (!readSvg(svgName))
- return false;
- System.out.println("ok");
- if (!saveOdf(odfName))
- return false;
- return true;
-}
-
-void usage()
-{
- System.out.println("usage: SvgOdf input_file.odg output_file.svg");
- System.out.println(" SvgOdf input_file.svg output_file.odg");
-}
-
-
-boolean parseArguments(String argv[])
-{
- if (argv.length != 2)
- {
- usage();
- return false;
- }
-
- String fileName1 = argv[0];
- String fileName2 = argv[1];
-
- if (fileName1.length()<5 || fileName2.length()<5)
- {
- System.out.println("one or more file names is too short");
- usage();
- return false;
- }
-
- String ext1 = fileName1.substring(fileName1.length()-4);
- String ext2 = fileName2.substring(fileName2.length()-4);
- //System.out.println("ext1:"+ext1+" ext2:"+ext2);
-
- //##### ODG -> SVG #####
- if ((ext1.equals(".odg") || ext1.equals(".odf") || ext1.equals(".zip") ) &&
- ext2.equals(".svg"))
- {
- if (!odfToSvg(argv[0], argv[1]))
- {
- System.out.println("Conversion from ODG to SVG failed");
- return false;
- }
- }
-
- //##### SVG -> ODG #####
- else if (ext1.equals(".svg") &&
- ( ext2.equals(".odg") || ext2.equals(".odf") || ext2.equals(".zip") ) )
- {
- if (!svgToOdf(fileName1, fileName2))
- {
- System.out.println("Conversion from SVG to ODG failed");
- return false;
- }
- }
-
- //##### none of the above #####
- else
- {
- usage();
- return false;
- }
- return true;
-}
-
-
-public static void main(String argv[])
-{
- SvgOdg svgodg = new SvgOdg();
- svgodg.parseArguments(argv);
-}
-
-
-}
-
-//########################################################################
-//# E N D O F F I L E
-//########################################################################
-
diff --git a/src/doxygen-main.cpp b/src/doxygen-main.cpp
index fd8f4bb1a..c6a6bb3ab 100644
--- a/src/doxygen-main.cpp
+++ b/src/doxygen-main.cpp
@@ -224,7 +224,7 @@ namespace XML {}
* - SPEllipse
* - SPLine [\ref sp-line.cpp, \ref sp-line.h]
* - SPOffset [\ref sp-offset.cpp, \ref sp-offset.h]
- * - SPPath [\ref sp-path.cpp, \ref sp-path.h, \ref path-chemistry.cpp, \ref nodepath.cpp, \ref nodepath.h, \ref splivarot.cpp]
+ * - SPPath [\ref sp-path.cpp, \ref sp-path.h, \ref path-chemistry.cpp, \ref splivarot.cpp]
* - SPPolygon [\ref sp-polygon.cpp, \ref sp-polygon.h]
* - SPStar [\ref sp-star.cpp, \ref sp-star.h]
* - SPPolyLine [\ref sp-polyline.cpp, \ref sp-polyline.h]
diff --git a/src/event-context.cpp b/src/event-context.cpp
index 918e3d419..363f9fe71 100644
--- a/src/event-context.cpp
+++ b/src/event-context.cpp
@@ -56,8 +56,8 @@
#include "attributes.h"
#include "rubberband.h"
#include "selcue.h"
-#include "node-context.h"
#include "lpe-tool-context.h"
+#include "ui/tool/control-point.h"
static void sp_event_context_class_init(SPEventContextClass *klass);
static void sp_event_context_init(SPEventContext *event_context);
@@ -1277,6 +1277,13 @@ gboolean sp_event_context_snap_watchdog_callback(gpointer data)
}
}
break;
+ case DelayedSnapEvent::CONTROL_POINT_HANDLER:
+ {
+ using Inkscape::UI::ControlPoint;
+ ControlPoint *point = reinterpret_cast<ControlPoint*>(dse->getKnot());
+ point->_eventHandler(dse->getEvent());
+ }
+ break;
default:
g_warning("Origin of snap-delay event has not been defined!;");
break;
diff --git a/src/event-context.h b/src/event-context.h
index 5285bdb87..5be2e19fb 100644
--- a/src/event-context.h
+++ b/src/event-context.h
@@ -49,7 +49,8 @@ public:
UNDEFINED_HANDLER = 0,
EVENTCONTEXT_ROOT_HANDLER,
EVENTCONTEXT_ITEM_HANDLER,
- KNOT_HANDLER
+ KNOT_HANDLER,
+ CONTROL_POINT_HANDLER
};
DelayedSnapEvent(SPEventContext *event_context, SPItem* const item, SPKnot* knot, GdkEventMotion const *event, DelayedSnapEvent::DelayedSnapEventOrigin const origin)
diff --git a/src/extension/dxf2svg/entities2elements.cpp b/src/extension/dxf2svg/entities2elements.cpp
index ab160e265..f280bb2a8 100644
--- a/src/extension/dxf2svg/entities2elements.cpp
+++ b/src/extension/dxf2svg/entities2elements.cpp
@@ -20,10 +20,12 @@ SoC 2005
*/
-#include"entities2elements.h"
-#include"tables2svg_info.h"
-#include<iostream>
-#include<math.h>
+#include "entities2elements.h"
+#include "tables2svg_info.h"
+#include <iostream>
+#include <math.h>
+#include <string.h>
+#include <stdlib.h>
// The names indicate the DXF entitiy first and the SVG element last
// Common elements
diff --git a/src/extension/dxf2svg/read_dxf.cpp b/src/extension/dxf2svg/read_dxf.cpp
index 8a6a6d6ac..ecda343c6 100644
--- a/src/extension/dxf2svg/read_dxf.cpp
+++ b/src/extension/dxf2svg/read_dxf.cpp
@@ -11,11 +11,13 @@
-#include<fstream>
-#include<string>
-#include"read_dxf.h"
+#include <fstream>
+#include <string>
+#include "read_dxf.h"
-#include<iostream>
+#include <iostream>
+#include <string.h>
+#include <stdlib.h>
using namespace std;
diff --git a/src/extension/dxf2svg/tables2svg_info.cpp b/src/extension/dxf2svg/tables2svg_info.cpp
index 17bc47beb..3b27a9c38 100644
--- a/src/extension/dxf2svg/tables2svg_info.cpp
+++ b/src/extension/dxf2svg/tables2svg_info.cpp
@@ -10,9 +10,11 @@
*/
-#include"tables2svg_info.h"
-#include<math.h>
-#include<iostream>
+#include "tables2svg_info.h"
+#include <math.h>
+#include <iostream>
+#include <stdlib.h>
+#include <string.h>
char* pattern2dasharray(ltype info, int precision, double scaling, char* out){
std::vector< double > pattern = info.ret_pattern();
diff --git a/src/extension/internal/cairo-renderer.cpp b/src/extension/internal/cairo-renderer.cpp
index 8cc386135..0e68ae130 100644
--- a/src/extension/internal/cairo-renderer.cpp
+++ b/src/extension/internal/cairo-renderer.cpp
@@ -29,6 +29,7 @@
#include <errno.h>
#include "libnr/nr-rect.h"
+#include "libnrtype/Layout-TNG.h"
#include <2geom/transforms.h>
#include <2geom/pathvector.h>
diff --git a/src/gc-allocator.h b/src/gc-allocator.h
index 4d809cfe4..e69de29bb 100644
--- a/src/gc-allocator.h
+++ b/src/gc-allocator.h
@@ -1,106 +0,0 @@
-/** @file
- * @brief Garbage-collected STL allocator for standard containers
- */
-/* Authors:
- * Krzysztof Kosiński <tweenk.pl@gmail.com>
- *
- * Copyright 2008 Authors
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * See the file COPYING for details.
- */
-
-#ifndef SEEN_INKSCAPE_GC_ALLOCATOR_H
-#define SEEN_INKSCAPE_GC_ALLOCATOR_H
-
-#include <cstddef>
-#include <limits>
-#include "gc-core.h"
-
-namespace Inkscape {
-namespace GC {
-
-/**
- * @brief Garbage-collected allocator for the standard containers
- *
- * STL containers with default parameters cannot be used as members in garbage-collected
- * objects, because by default the destructors are not called, causing a memory leak
- * (the memory allocated by the container is not freed). To address this, STL containers
- * can be told to use this garbage-collected allocator. It usually is the last template
- * parameter. For example, to define a GC-managed map of ints to Unicode strings:
- *
- * @code typedef std::map<int, Glib::ustring, less<int>, Inkscape::GC::Allocator> gcmap; @endcode
- *
- * Afterwards, you can place gcmap as a member in a non-finalized GC-managed object, because
- * all memory used by gcmap will also be reclaimable by the garbage collector, therefore
- * avoiding memory leaks.
- */
-template <typename T>
-class Allocator {
- // required typedefs
- typedef T value_type;
- typedef size_t size_type;
- typedef ptrdiff_t difference_type;
- typedef T * pointer;
- typedef T const * const_pointer;
- typedef T & reference;
- typedef T const & const_reference;
-
- // required structure that allows accessing the same allocator for a different type
- template <typename U>
- struct rebind {
- typedef Allocator<U> other;
- };
-
- // constructors - no-ops since the allocator doesn't have any state
- Allocator() throw() {}
- Allocator(Allocator const &) throw() {}
- template <typename U> Allocator(Allocator<U> const &) throw() {}
- ~Allocator() throw() {}
-
- // trivial required methods
- pointer address(reference ref) { return &ref; }
- const_pointer address(const_reference ref) { return &ref; }
- void construct(pointer p, T const &value) { new (static_cast<void*>(p)) T(value); }
- void destroy(pointer p) { p->~T(); }
-
- // maximum meaningful memory amount that can be requested from the allocator
- size_type max_size() {
- return numeric_limits<size_type>::max() / sizeof(T);
- }
-
- // allocate memory for num elements without initializing them
- pointer allocate(size_type num, Allocator<void>::const_pointer) {
- return static_cast<pointer>( Inkscape::GC::Core::malloc(num * sizeof(T)) );
- }
-
- // deallocate memory at p
- void deallocate(pointer p, size_type) {
- Inkscape::GC::Core::free(p);
- }
-};
-
-// required comparison operators
-template <typename T1, typename T2>
-bool operator==(Allocator<T1> const &, Allocator<T2> const &) { return true; }
-template <typename T1, typename T2>
-bool operator!=(Allocator<T1> const &, Allocator<T2> const &) { return false; }
-
-} // namespace GC
-} // namespace Inkscape
-
-#endif // !SEEN_INKSCAPE_GC_ALLOCATOR_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:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/FontFactory.cpp b/src/libnrtype/FontFactory.cpp
index 1f85ee5ca..a63f70d75 100644
--- a/src/libnrtype/FontFactory.cpp
+++ b/src/libnrtype/FontFactory.cpp
@@ -26,10 +26,10 @@
/* Freetype2 */
# include <pango/pangoft2.h>
-#include <ext/hash_map>
+#include <tr1/unordered_map>
-typedef __gnu_cxx::hash_map<PangoFontDescription*, font_instance*, font_descr_hash, font_descr_equal> FaceMapType;
+typedef std::tr1::unordered_map<PangoFontDescription*, font_instance*, font_descr_hash, font_descr_equal> FaceMapType;
// need to avoid using the size field
size_t font_descr_hash::operator()( PangoFontDescription *const &x) const {
@@ -47,7 +47,7 @@ size_t font_descr_hash::operator()( PangoFontDescription *const &x) const {
h += (int)pango_font_description_get_stretch(x);
return h;
}
-bool font_descr_equal::operator()( PangoFontDescription *const&a, PangoFontDescription *const &b) {
+bool font_descr_equal::operator()( PangoFontDescription *const&a, PangoFontDescription *const &b) const {
//if ( pango_font_description_equal(a,b) ) return true;
char const *fa = pango_font_description_get_family(a);
char const *fb = pango_font_description_get_family(b);
diff --git a/src/libnrtype/FontFactory.h b/src/libnrtype/FontFactory.h
index 8d85bcf3e..0118c862d 100644
--- a/src/libnrtype/FontFactory.h
+++ b/src/libnrtype/FontFactory.h
@@ -45,7 +45,7 @@ struct font_descr_hash : public std::unary_function<PangoFontDescription*,size_t
size_t operator()(PangoFontDescription *const &x) const;
};
struct font_descr_equal : public std::binary_function<PangoFontDescription*, PangoFontDescription*, bool> {
- bool operator()(PangoFontDescription *const &a, PangoFontDescription *const &b);
+ bool operator()(PangoFontDescription *const &a, PangoFontDescription *const &b) const;
};
// Comparison functions for style names
diff --git a/src/libnrtype/FontInstance.cpp b/src/libnrtype/FontInstance.cpp
index f34a230c1..6b0725b34 100644
--- a/src/libnrtype/FontInstance.cpp
+++ b/src/libnrtype/FontInstance.cpp
@@ -29,7 +29,7 @@
# include FT_TRUETYPE_TABLES_H
# include <pango/pangoft2.h>
-#include <ext/hash_map>
+#include <tr1/unordered_map>
// the various raster_font in use at a given time are held in a hash_map whose indices are the
@@ -39,11 +39,11 @@ struct font_style_hash : public std::unary_function<font_style, size_t> {
};
struct font_style_equal : public std::binary_function<font_style, font_style, bool> {
- bool operator()(font_style const &a, font_style const &b);
+ bool operator()(font_style const &a, font_style const &b) const;
};
-typedef __gnu_cxx::hash_map<font_style, raster_font*, font_style_hash, font_style_equal> StyleMap;
+typedef std::tr1::unordered_map<font_style, raster_font*, font_style_hash, font_style_equal> StyleMap;
@@ -76,7 +76,7 @@ size_t font_style_hash::operator()(const font_style &x) const {
return h;
}
-bool font_style_equal::operator()(const font_style &a,const font_style &b) {
+bool font_style_equal::operator()(const font_style &a,const font_style &b) const {
for (int i=0;i<6;i++) {
if ( (int)(100*a.transform[i]) != (int)(100*b.transform[i]) ) return false;
}
diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp
index 9232792f6..d53048c76 100644
--- a/src/live_effects/effect.cpp
+++ b/src/live_effects/effect.cpp
@@ -28,7 +28,6 @@
#include "tools-switch.h"
#include "message-stack.h"
#include "desktop.h"
-#include "nodepath.h"
#include "knotholder.h"
#include "live_effects/lpeobject.h"
@@ -665,13 +664,6 @@ Effect::resetDefaults(SPItem * /*item*/)
}
void
-Effect::setup_nodepath(Inkscape::NodePath::Path *np)
-{
- np->helperpath_rgba = 0xff0000ff;
- np->helperpath_width = 1.0;
-}
-
-void
Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
{
// cycle through all parameters. Most parameters will not need transformation, but path and point params do.
diff --git a/src/live_effects/effect.h b/src/live_effects/effect.h
index 5d67ed016..a8d34a233 100644
--- a/src/live_effects/effect.h
+++ b/src/live_effects/effect.h
@@ -92,8 +92,6 @@ public:
*/
virtual void resetDefaults(SPItem * item);
- virtual void setup_nodepath(Inkscape::NodePath::Path *np);
-
/// /todo: is this method really necessary? it causes UI inconsistensies... (johan)
virtual void transform_multiply(Geom::Matrix const& postmul, bool set);
diff --git a/src/live_effects/lpe-constructgrid.cpp b/src/live_effects/lpe-constructgrid.cpp
index 144f4720d..4725573d7 100644
--- a/src/live_effects/lpe-constructgrid.cpp
+++ b/src/live_effects/lpe-constructgrid.cpp
@@ -16,8 +16,6 @@
#include <2geom/path.h>
#include <2geom/transforms.h>
-#include "nodepath.h"
-
namespace Inkscape {
namespace LivePathEffect {
@@ -81,13 +79,6 @@ LPEConstructGrid::doEffect_path (std::vector<Geom::Path> const & path_in)
}
}
-void
-LPEConstructGrid::setup_nodepath(Inkscape::NodePath::Path *np)
-{
- Effect::setup_nodepath(np);
- sp_nodepath_make_straight_path(np);
-}
-
} //namespace LivePathEffect
} /* namespace Inkscape */
diff --git a/src/live_effects/lpe-constructgrid.h b/src/live_effects/lpe-constructgrid.h
index 716960d32..c7e695794 100644
--- a/src/live_effects/lpe-constructgrid.h
+++ b/src/live_effects/lpe-constructgrid.h
@@ -27,8 +27,6 @@ public:
virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in);
- virtual void setup_nodepath(Inkscape::NodePath::Path *np);
-
private:
ScalarParam nr_x;
ScalarParam nr_y;
diff --git a/src/live_effects/lpe-gears.cpp b/src/live_effects/lpe-gears.cpp
index e211483c6..16eb19f44 100644
--- a/src/live_effects/lpe-gears.cpp
+++ b/src/live_effects/lpe-gears.cpp
@@ -14,8 +14,6 @@
#include <2geom/bezier-to-sbasis.h>
#include <2geom/path.h>
-#include "nodepath.h"
-
using std::vector;
using namespace Geom;
@@ -261,13 +259,6 @@ LPEGears::doEffect_path (std::vector<Geom::Path> const & path_in)
return path_out;
}
-void
-LPEGears::setup_nodepath(Inkscape::NodePath::Path *np)
-{
- Effect::setup_nodepath(np);
- sp_nodepath_make_straight_path(np);
-}
-
} // namespace LivePathEffect
} /* namespace Inkscape */
diff --git a/src/live_effects/lpe-gears.h b/src/live_effects/lpe-gears.h
index 4c3a9938b..bd5e4c4f9 100644
--- a/src/live_effects/lpe-gears.h
+++ b/src/live_effects/lpe-gears.h
@@ -24,8 +24,6 @@ public:
virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in);
- virtual void setup_nodepath(Inkscape::NodePath::Path *np);
-
private:
ScalarParam teeth;
ScalarParam phi;
diff --git a/src/live_effects/lpe-lattice.cpp b/src/live_effects/lpe-lattice.cpp
index 0beedb537..50ecdf04b 100644
--- a/src/live_effects/lpe-lattice.cpp
+++ b/src/live_effects/lpe-lattice.cpp
@@ -22,7 +22,6 @@
#include "sp-path.h"
#include "display/curve.h"
#include "svg/svg.h"
-#include "nodepath.h"
#include <2geom/sbasis.h>
#include <2geom/sbasis-2d.h>
diff --git a/src/live_effects/lpe-spiro.cpp b/src/live_effects/lpe-spiro.cpp
index 794fd980e..7c8262af6 100644
--- a/src/live_effects/lpe-spiro.cpp
+++ b/src/live_effects/lpe-spiro.cpp
@@ -7,7 +7,6 @@
#include "live_effects/lpe-spiro.h"
#include "display/curve.h"
-#include "nodepath.h"
#include <typeinfo>
#include <2geom/pathvector.h>
#include <2geom/matrix.h>
@@ -116,14 +115,6 @@ LPESpiro::~LPESpiro()
}
void
-LPESpiro::setup_nodepath(Inkscape::NodePath::Path *np)
-{
- Effect::setup_nodepath(np);
- sp_nodepath_show_handles(np, false);
-// sp_nodepath_show_helperpath(np, false);
-}
-
-void
LPESpiro::doEffect(SPCurve * curve)
{
using Geom::X;
diff --git a/src/live_effects/lpe-spiro.h b/src/live_effects/lpe-spiro.h
index 7256665a2..4fcd9eaaa 100644
--- a/src/live_effects/lpe-spiro.h
+++ b/src/live_effects/lpe-spiro.h
@@ -24,7 +24,6 @@ public:
virtual LPEPathFlashType pathFlashType() { return SUPPRESS_FLASH; }
- virtual void setup_nodepath(Inkscape::NodePath::Path *np);
virtual void doEffect(SPCurve * curve);
private:
diff --git a/src/live_effects/lpe-vonkoch.cpp b/src/live_effects/lpe-vonkoch.cpp
index 7fd0ac0b4..85f8cde0c 100644
--- a/src/live_effects/lpe-vonkoch.cpp
+++ b/src/live_effects/lpe-vonkoch.cpp
@@ -8,7 +8,6 @@
#include <cstdio>
#include "live_effects/lpe-vonkoch.h"
-#include "nodepath.h"
#include <2geom/transforms.h>
//using std::vector;
@@ -19,7 +18,7 @@ void
VonKochPathParam::param_setup_nodepath(Inkscape::NodePath::Path *np)
{
PathParam::param_setup_nodepath(np);
- sp_nodepath_make_straight_path(np);
+ //sp_nodepath_make_straight_path(np);
}
//FIXME: a path is used here instead of 2 points to work around path/point param incompatibility bug.
@@ -27,12 +26,12 @@ void
VonKochRefPathParam::param_setup_nodepath(Inkscape::NodePath::Path *np)
{
PathParam::param_setup_nodepath(np);
- sp_nodepath_make_straight_path(np);
+ //sp_nodepath_make_straight_path(np);
}
bool
VonKochRefPathParam::param_readSVGValue(const gchar * strvalue)
{
- std::vector<Geom::Path> old = _pathvector;
+ Geom::PathVector old = _pathvector;
bool res = PathParam::param_readSVGValue(strvalue);
if (res && _pathvector.size()==1 && _pathvector.front().size()==1){
return true;
diff --git a/src/live_effects/parameter/path.cpp b/src/live_effects/parameter/path.cpp
index 33e50155c..43f4b5725 100644
--- a/src/live_effects/parameter/path.cpp
+++ b/src/live_effects/parameter/path.cpp
@@ -28,10 +28,8 @@
// needed for on-canvas editting:
#include "tools-switch.h"
#include "shape-editor.h"
-#include "node-context.h"
#include "desktop-handles.h"
#include "selection.h"
-#include "nodepath.h"
// clipboard support
#include "ui/clipboard.h"
// required for linking to other paths
@@ -40,6 +38,10 @@
#include "sp-text.h"
#include "display/curve.h"
+#include "ui/tool/node-tool.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tool/shape-record.h"
+
namespace Inkscape {
@@ -195,26 +197,33 @@ PathParam::param_newWidget(Gtk::Tooltips * tooltips)
void
PathParam::param_editOncanvas(SPItem * item, SPDesktop * dt)
{
- // If not already in nodecontext, goto it!
+ using namespace Inkscape::UI;
+
+ // TODO remove the tools_switch atrocity.
if (!tools_isactive(dt, TOOLS_NODES)) {
tools_switch(dt, TOOLS_NODES);
}
- ShapeEditor * shape_editor = dt->event_context->shape_editor;
+ InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context);
+ std::set<ShapeRecord> shapes;
+ ShapeRecord r;
+
+ r.role = SHAPE_ROLE_LPE_PARAM;
+ r.edit_transform = Geom::identity(); // TODO this is almost certainly wrong
if (!href) {
- shape_editor->set_item_lpe_path_parameter(item, param_effect->getLPEObj(), param_key.c_str());
+ r.item = reinterpret_cast<SPItem*>(param_effect->getLPEObj());
+ r.lpe_key = param_key;
} else {
- // set referred item for editing
- shape_editor->set_item(ref.getObject(), SH_NODEPATH);
+ r.item = ref.getObject();
}
+ shapes.insert(r);
+ nt->_multipath->setItems(shapes);
}
void
-PathParam::param_setup_nodepath(Inkscape::NodePath::Path *np)
-{
- np->show_helperpath = true;
- np->helperpath_rgba = 0x009000ff;
- np->helperpath_width = 1.0;
+PathParam::param_setup_nodepath(Inkscape::NodePath::Path *)
+{
+ // TODO this method should not exist at all!
}
void
diff --git a/src/lpe-tool-context.cpp b/src/lpe-tool-context.cpp
index be465e324..7072f47b3 100644
--- a/src/lpe-tool-context.cpp
+++ b/src/lpe-tool-context.cpp
@@ -179,9 +179,6 @@ sp_lpetool_context_setup(SPEventContext *ec)
}
lc->_lpetool_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
-
-
- lc->shape_editor->update_statusbar();
}
/**
@@ -193,13 +190,9 @@ sp_lpetool_context_selection_changed(Inkscape::Selection *selection, gpointer da
{
SPLPEToolContext *lc = SP_LPETOOL_CONTEXT(data);
- // TODO: update ShapeEditorsCollective instead
- lc->shape_editor->unset_item(SH_NODEPATH);
lc->shape_editor->unset_item(SH_KNOTHOLDER);
SPItem *item = selection->singleItem();
- lc->shape_editor->set_item(item, SH_NODEPATH);
lc->shape_editor->set_item(item, SH_KNOTHOLDER);
- lc->shape_editor->update_statusbar();
}
static void
@@ -280,7 +273,6 @@ sp_lpetool_context_root_handler(SPEventContext *event_context, GdkEvent *event)
event_context->xp = (gint) event->button.x;
event_context->yp = (gint) event->button.y;
event_context->within_tolerance = true;
- lc->shape_editor->cancel_hit();
using namespace Inkscape::LivePathEffect;
@@ -296,28 +288,6 @@ sp_lpetool_context_root_handler(SPEventContext *event_context, GdkEvent *event)
ret = ((SPEventContextClass *) lpetool_parent_class)->root_handler(event_context, event);
}
break;
- case GDK_MOTION_NOTIFY:
- {
- if (!lc->shape_editor->has_nodepath() || selection->singleItem() == NULL) {
- break;
- }
-
- bool over_stroke = false;
- over_stroke = lc->shape_editor->is_over_stroke(Geom::Point(event->motion.x, event->motion.y), false);
-
- if (over_stroke) {
- event_context->cursor_shape = cursor_node_xpm;
- event_context->hot_x = 1;
- event_context->hot_y = 1;
- sp_event_context_update_cursor(event_context);
- } else {
- lc->cursor_shape = cursor_crosshairs_xpm;
- lc->hot_x = 7;
- lc->hot_y = 7;
- sp_event_context_update_cursor(event_context);
- }
- }
- break;
case GDK_BUTTON_RELEASE:
diff --git a/src/node-context.cpp b/src/node-context.cpp
deleted file mode 100644
index 7efa57290..000000000
--- a/src/node-context.cpp
+++ /dev/null
@@ -1,868 +0,0 @@
-#define __SP_NODE_CONTEXT_C__
-
-/*
- * Node editing context
- *
- * Authors:
- * Lauris Kaplinski <lauris@kaplinski.com>
- * bulia byak <buliabyak@users.sf.net>
- *
- * This code is in public domain
- */
-
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
-#include <cstring>
-#include <string>
-#include <gdk/gdkkeysyms.h>
-#include "macros.h"
-#include <glibmm/i18n.h>
-#include "display/sp-canvas-util.h"
-#include "object-edit.h"
-#include "sp-path.h"
-#include "path-chemistry.h"
-#include "rubberband.h"
-#include "desktop.h"
-#include "desktop-handles.h"
-#include "selection.h"
-#include "pixmaps/cursor-node.xpm"
-#include "message-context.h"
-#include "node-context.h"
-#include "pixmaps/cursor-node-d.xpm"
-#include "preferences.h"
-#include "xml/node-event-vector.h"
-#include "style.h"
-#include "splivarot.h"
-#include "shape-editor.h"
-#include "live_effects/effect.h"
-
-#include "sp-lpe-item.h"
-
-// needed for flash nodepath upon mouseover:
-#include "display/canvas-bpath.h"
-#include "display/curve.h"
-
-static void sp_node_context_class_init(SPNodeContextClass *klass);
-static void sp_node_context_init(SPNodeContext *node_context);
-static void sp_node_context_dispose(GObject *object);
-
-static void sp_node_context_setup(SPEventContext *ec);
-static gint sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event);
-static gint sp_node_context_item_handler(SPEventContext *event_context,
- SPItem *item, GdkEvent *event);
-
-static SPEventContextClass *parent_class;
-
-GType
-sp_node_context_get_type()
-{
- static GType type = 0;
- if (!type) {
- GTypeInfo info = {
- sizeof(SPNodeContextClass),
- NULL, NULL,
- (GClassInitFunc) sp_node_context_class_init,
- NULL, NULL,
- sizeof(SPNodeContext),
- 4,
- (GInstanceInitFunc) sp_node_context_init,
- NULL, /* value_table */
- };
- type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPNodeContext", &info, (GTypeFlags)0);
- }
- return type;
-}
-
-static void
-sp_node_context_class_init(SPNodeContextClass *klass)
-{
- GObjectClass *object_class = (GObjectClass *) klass;
- SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
-
- parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
-
- object_class->dispose = sp_node_context_dispose;
-
- event_context_class->setup = sp_node_context_setup;
- event_context_class->root_handler = sp_node_context_root_handler;
- event_context_class->item_handler = sp_node_context_item_handler;
-}
-
-static void
-sp_node_context_init(SPNodeContext *node_context)
-{
- SPEventContext *event_context = SP_EVENT_CONTEXT(node_context);
-
- event_context->cursor_shape = cursor_node_xpm;
- event_context->hot_x = 1;
- event_context->hot_y = 1;
-
- node_context->leftalt = FALSE;
- node_context->rightalt = FALSE;
- node_context->leftctrl = FALSE;
- node_context->rightctrl = FALSE;
-
- new (&node_context->sel_changed_connection) sigc::connection();
-
- node_context->flash_tempitem = NULL;
- node_context->flashed_item = NULL;
- node_context->remove_flash_counter = 0;
-}
-
-static void
-sp_node_context_dispose(GObject *object)
-{
- SPNodeContext *nc = SP_NODE_CONTEXT(object);
- SPEventContext *ec = SP_EVENT_CONTEXT(object);
-
- ec->enableGrDrag(false);
-
- if (nc->grabbed) {
- sp_canvas_item_ungrab(nc->grabbed, GDK_CURRENT_TIME);
- nc->grabbed = NULL;
- }
-
- nc->sel_changed_connection.disconnect();
- nc->sel_changed_connection.~connection();
-
- delete ec->shape_editor;
-
- if (nc->_node_message_context) {
- delete nc->_node_message_context;
- }
-
- G_OBJECT_CLASS(parent_class)->dispose(object);
-}
-
-static void
-sp_node_context_setup(SPEventContext *ec)
-{
- SPNodeContext *nc = SP_NODE_CONTEXT(ec);
-
- if (((SPEventContextClass *) parent_class)->setup)
- ((SPEventContextClass *) parent_class)->setup(ec);
-
- Inkscape::Selection *selection = sp_desktop_selection (ec->desktop);
- nc->sel_changed_connection.disconnect();
- nc->sel_changed_connection =
- selection->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
-
- SPItem *item = selection->singleItem();
-
- ec->shape_editor = new ShapeEditor(ec->desktop);
-
- nc->rb_escaped = false;
-
- nc->cursor_drag = false;
-
- nc->added_node = false;
-
- nc->current_state = SP_NODE_CONTEXT_INACTIVE;
-
- if (item) {
- ec->shape_editor->set_item(item, SH_NODEPATH);
- ec->shape_editor->set_item(item, SH_KNOTHOLDER);
- }
-
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- if (prefs->getBool("/tools/nodes/selcue")) {
- ec->enableSelectionCue();
- }
- if (prefs->getBool("/tools/nodes/gradientdrag")) {
- ec->enableGrDrag();
- }
-
- ec->desktop->emitToolSubselectionChanged(NULL); // sets the coord entry fields to inactive
-
- nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
-
- ec->shape_editor->update_statusbar();
-}
-
-static void
-sp_node_context_flash_path(SPEventContext *event_context, SPItem *item, guint timeout) {
- SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
-
- nc->remove_flash_counter = 3; // for some reason root_handler is called twice after each item_handler...
- if (nc->flashed_item != item) {
- // we entered a new item
- nc->flashed_item = item;
- SPDesktop *desktop = event_context->desktop;
- if (nc->flash_tempitem) {
- desktop->remove_temporary_canvasitem(nc->flash_tempitem);
- nc->flash_tempitem = NULL;
- }
-
- SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, item);
-
- if (canvasitem) {
- nc->flash_tempitem = desktop->add_temporary_canvasitem (canvasitem, timeout);
- }
- }
-}
-
-/**
-\brief 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_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
-{
- SPEventContext *ec = SP_EVENT_CONTEXT(data);
-
- // TODO: update ShapeEditorsCollective instead
- ec->shape_editor->unset_item(SH_NODEPATH);
- ec->shape_editor->unset_item(SH_KNOTHOLDER);
- SPItem *item = selection->singleItem();
- ec->shape_editor->set_item(item, SH_NODEPATH);
- ec->shape_editor->set_item(item, SH_KNOTHOLDER);
- ec->shape_editor->update_statusbar();
-}
-
-void
-sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
-{
- sp_event_show_modifier_tip
- (event_context->defaultMessageContext(), event,
- _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
- _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
- _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
-}
-
-static gint
-sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
-{
- gint ret = FALSE;
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- SPDesktop *desktop = event_context->desktop;
-
- switch (event->type) {
- case GDK_MOTION_NOTIFY:
- {
- // find out actual item we're over, disregarding groups
- SPItem *actual_item = sp_event_context_find_item (desktop,
- Geom::Point(event->button.x, event->button.y), FALSE, TRUE);
- if (!actual_item)
- break;
-
-
- if (prefs->getBool("/tools/nodes/pathflash_enabled")) {
- if (prefs->getBool("/tools/nodes/pathflash_unselected")) {
- // do not flash if we have some path selected and a single item in selection (i.e. it
- // is the same path that we're editing)
- SPDesktop *desktop = event_context->desktop;
- ShapeEditor* se = event_context->shape_editor;
- Inkscape::Selection *selection = sp_desktop_selection (desktop);
- if (se->has_nodepath() && selection->singleItem()) {
- break;
- }
- }
- if (SP_IS_LPE_ITEM(actual_item)) {
- Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(actual_item));
- if (lpe && (lpe->providesOwnFlashPaths() ||
- lpe->pathFlashType() == Inkscape::LivePathEffect::SUPPRESS_FLASH)) {
- // path should be suppressed or permanent; this is handled in
- // sp_node_context_selection_changed()
- break;
- }
- }
- guint timeout = prefs->getInt("/tools/nodes/pathflash_timeout", 500);
- sp_node_context_flash_path(event_context, actual_item, timeout);
- }
- }
- break;
-
- default:
- break;
- }
-
- if (((SPEventContextClass *) parent_class)->item_handler)
- ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
-
- return ret;
-}
-
-static gint
-sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
-{
- SPDesktop *desktop = event_context->desktop;
- ShapeEditor* se = event_context->shape_editor;
- Inkscape::Selection *selection = sp_desktop_selection (desktop);
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
-
- SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
- double const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000); // in px
- event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); // read every time, to make prefs changes really live
- int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
- double const offset = prefs->getDoubleLimited("/options/defaultscale/value", 2, 0, 1000);
-
- if ( (nc->flash_tempitem) && (nc->remove_flash_counter <= 0) ) {
- desktop->remove_temporary_canvasitem(nc->flash_tempitem);
- nc->flash_tempitem = NULL;
- nc->flashed_item = NULL; // also reset this one, so the next time the same object is hovered over it shows again the highlight
- } else {
- nc->remove_flash_counter--;
- }
-
- gint ret = FALSE;
- switch (event->type) {
- case GDK_BUTTON_PRESS:
- if (event->button.button == 1 && !event_context->space_panning) {
- // save drag origin
- event_context->xp = (gint) event->button.x;
- event_context->yp = (gint) event->button.y;
- event_context->within_tolerance = true;
- se->cancel_hit();
-
- if (!(event->button.state & GDK_SHIFT_MASK)) {
- if (!nc->drag) {
- if (se->has_nodepath() && selection->single() /* && item_over */) {
- // save drag origin
- bool over_stroke = se->is_over_stroke(Geom::Point(event->button.x, event->button.y), true);
- //only dragging curves
- if (over_stroke) {
- ret = TRUE;
- break;
- }
- }
- }
- }
- Geom::Point const button_w(event->button.x,
- event->button.y);
- Geom::Point const button_dt(desktop->w2d(button_w));
- Inkscape::Rubberband::get(desktop)->start(desktop, button_dt);
-
- if (nc->grabbed) {
- sp_canvas_item_ungrab(nc->grabbed, event->button.time);
- nc->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);
- nc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
-
- nc->current_state = SP_NODE_CONTEXT_INACTIVE;
- desktop->updateNow();
- ret = TRUE;
- }
- break;
- case GDK_MOTION_NOTIFY:
- if (event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
-
- if ( event_context->within_tolerance
- && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
- && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
- break; // do not drag if we're within tolerance from origin
- }
-
- // The path went away while dragging; throw away any further motion
- // events until the mouse pointer is released.
-
- if (se->hits_curve() && !se->has_nodepath()) {
- 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)
- event_context->within_tolerance = false;
-
- // Once we determine what the user is doing (dragging either a node or the
- // selection rubberband), make sure we continue to perform that operation
- // until the mouse pointer is lifted.
- if (nc->current_state == SP_NODE_CONTEXT_INACTIVE) {
- if (se->hits_curve() && se->has_nodepath()) {
- nc->current_state = SP_NODE_CONTEXT_NODE_DRAGGING;
- } else {
- nc->current_state = SP_NODE_CONTEXT_RUBBERBAND_DRAGGING;
- }
- }
-
- switch (nc->current_state) {
- case SP_NODE_CONTEXT_NODE_DRAGGING:
- {
- se->curve_drag (event->motion.x, event->motion.y);
-
- gobble_motion_events(GDK_BUTTON1_MASK);
- break;
- }
- case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING:
- if (Inkscape::Rubberband::get(desktop)->is_started()) {
- 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);
- }
- break;
- }
-
- nc->drag = TRUE;
- ret = TRUE;
- } else {
- if (!se->has_nodepath() || selection->singleItem() == NULL) {
- break;
- }
-
- bool over_stroke = false;
- over_stroke = se->is_over_stroke(Geom::Point(event->motion.x, event->motion.y), false);
-
- if (nc->cursor_drag && !over_stroke) {
- event_context->cursor_shape = cursor_node_xpm;
- event_context->hot_x = 1;
- event_context->hot_y = 1;
- sp_event_context_update_cursor(event_context);
- nc->cursor_drag = false;
- } else if (!nc->cursor_drag && over_stroke) {
- event_context->cursor_shape = cursor_node_d_xpm;
- event_context->hot_x = 1;
- event_context->hot_y = 1;
- sp_event_context_update_cursor(event_context);
- nc->cursor_drag = true;
- }
- }
- break;
-
- case GDK_2BUTTON_PRESS:
- case GDK_BUTTON_RELEASE:
- if ( (event->button.button == 1) && (!nc->drag) && !event_context->space_panning) {
- // find out clicked item, disregarding groups, honoring Alt
- SPItem *item_clicked = sp_event_context_find_item (desktop,
- Geom::Point(event->button.x, event->button.y),
- (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
-
- event_context->xp = event_context->yp = 0;
-
- bool over_stroke = false;
- if (se->has_nodepath()) {
- over_stroke = se->is_over_stroke(Geom::Point(event->button.x, event->button.y), false);
- }
-
- if (item_clicked || over_stroke) {
- if (over_stroke || nc->added_node) {
- switch (event->type) {
- case GDK_BUTTON_RELEASE:
- if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
- //add a node
- se->add_node_near_point();
- } else {
- if (nc->added_node) { // we just received double click, ignore release
- nc->added_node = false;
- break;
- }
- //select the segment
- if (event->button.state & GDK_SHIFT_MASK) {
- se->select_segment_near_point(true);
- } else {
- se->select_segment_near_point(false);
- }
- desktop->updateNow();
- }
- break;
- case GDK_2BUTTON_PRESS:
- //add a node
- se->add_node_near_point();
- nc->added_node = true;
- break;
- default:
- break;
- }
- } else if (event->button.state & GDK_SHIFT_MASK) {
- selection->toggle(item_clicked);
- desktop->updateNow();
- } else {
- selection->set(item_clicked);
- desktop->updateNow();
- }
- Inkscape::Rubberband::get(desktop)->stop();
- if (nc->grabbed) {
- sp_canvas_item_ungrab(nc->grabbed, event->button.time);
- nc->grabbed = NULL;
- }
- ret = TRUE;
- break;
- }
- }
- if (event->type == GDK_BUTTON_RELEASE) {
- event_context->xp = event_context->yp = 0;
- if (event->button.button == 1) {
- Geom::OptRect b = Inkscape::Rubberband::get(desktop)->getRectangle();
-
- if (se->hits_curve() && !event_context->within_tolerance) { //drag curve
- se->finish_drag();
- } else if (b && !event_context->within_tolerance) { // drag to select
- se->select_rect(*b, event->button.state & GDK_SHIFT_MASK);
- } else {
- if (!(nc->rb_escaped)) { // unless something was canceled
- if (se->has_selection())
- se->deselect();
- else
- sp_desktop_selection(desktop)->clear();
- }
- }
- ret = TRUE;
- Inkscape::Rubberband::get(desktop)->stop();
-
- if (nc->grabbed) {
- sp_canvas_item_ungrab(nc->grabbed, event->button.time);
- nc->grabbed = NULL;
- }
-
- desktop->updateNow();
- nc->rb_escaped = false;
- nc->drag = FALSE;
- se->cancel_hit();
- nc->current_state = SP_NODE_CONTEXT_INACTIVE;
- }
- }
- break;
- case GDK_KEY_PRESS:
- switch (get_group0_keyval(&event->key)) {
- case GDK_Insert:
- case GDK_KP_Insert:
- // with any modifiers
- se->add_node();
- ret = TRUE;
- break;
- case GDK_I:
- case GDK_i:
- // apple keyboards have no Insert
- if (MOD__SHIFT_ONLY) {
- se->add_node();
- ret = TRUE;
- }
- break;
- case GDK_Delete:
- case GDK_KP_Delete:
- case GDK_BackSpace:
- if (MOD__CTRL_ONLY) {
- se->delete_nodes();
- } else {
- se->delete_nodes_preserving_shape();
- }
- ret = TRUE;
- break;
- case GDK_C:
- case GDK_c:
- if (MOD__SHIFT_ONLY) {
- se->set_node_type(Inkscape::NodePath::NODE_CUSP);
- ret = TRUE;
- }
- break;
- case GDK_S:
- case GDK_s:
- if (MOD__SHIFT_ONLY) {
- se->set_node_type(Inkscape::NodePath::NODE_SMOOTH);
- ret = TRUE;
- }
- break;
- case GDK_A:
- case GDK_a:
- if (MOD__SHIFT_ONLY) {
- se->set_node_type(Inkscape::NodePath::NODE_AUTO);
- ret = TRUE;
- }
- break;
- case GDK_Y:
- case GDK_y:
- if (MOD__SHIFT_ONLY) {
- se->set_node_type(Inkscape::NodePath::NODE_SYMM);
- ret = TRUE;
- }
- break;
- case GDK_B:
- case GDK_b:
- if (MOD__SHIFT_ONLY) {
- se->break_at_nodes();
- ret = TRUE;
- }
- break;
- case GDK_J:
- case GDK_j:
- if (MOD__SHIFT_ONLY) {
- se->join_nodes();
- ret = TRUE;
- }
- break;
- case GDK_D:
- case GDK_d:
- if (MOD__SHIFT_ONLY) {
- se->duplicate_nodes();
- ret = TRUE;
- }
- break;
- case GDK_L:
- case GDK_l:
- if (MOD__SHIFT_ONLY) {
- se->set_type_of_segments(NR_LINETO);
- ret = TRUE;
- }
- break;
- case GDK_U:
- case GDK_u:
- if (MOD__SHIFT_ONLY) {
- se->set_type_of_segments(NR_CURVETO);
- ret = TRUE;
- }
- break;
- case GDK_R:
- case GDK_r:
- if (MOD__SHIFT_ONLY) {
- // FIXME: add top panel button
- sp_selected_path_reverse(desktop);
- ret = TRUE;
- }
- break;
- case GDK_x:
- case GDK_X:
- if (MOD__ALT_ONLY) {
- desktop->setToolboxFocusTo ("altx-nodes");
- ret = TRUE;
- }
- break;
- case GDK_Left: // move selection left
- case GDK_KP_Left:
- case GDK_KP_4:
- if (!MOD__CTRL) { // not ctrl
- gint mul = 1 + gobble_key_events(
- get_group0_keyval(&event->key), 0); // with any mask
- if (MOD__ALT) { // alt
- if (MOD__SHIFT) se->move_nodes_screen(desktop, mul*-10, 0); // shift
- else se->move_nodes_screen(desktop, mul*-1, 0); // no shift
- }
- else { // no alt
- if (MOD__SHIFT) se->move_nodes(mul*-10*nudge, 0); // shift
- else se->move_nodes(mul*-nudge, 0); // no shift
- }
- ret = TRUE;
- }
- break;
- case GDK_Up: // move selection up
- case GDK_KP_Up:
- case GDK_KP_8:
- if (!MOD__CTRL) { // not ctrl
- gint mul = 1 + gobble_key_events(
- get_group0_keyval(&event->key), 0); // with any mask
- if (MOD__ALT) { // alt
- if (MOD__SHIFT) se->move_nodes_screen(desktop, 0, mul*10); // shift
- else se->move_nodes_screen(desktop, 0, mul*1); // no shift
- }
- else { // no alt
- if (MOD__SHIFT) se->move_nodes(0, mul*10*nudge); // shift
- else se->move_nodes(0, mul*nudge); // no shift
- }
- ret = TRUE;
- }
- break;
- case GDK_Right: // move selection right
- case GDK_KP_Right:
- case GDK_KP_6:
- if (!MOD__CTRL) { // not ctrl
- gint mul = 1 + gobble_key_events(
- get_group0_keyval(&event->key), 0); // with any mask
- if (MOD__ALT) { // alt
- if (MOD__SHIFT) se->move_nodes_screen(desktop, mul*10, 0); // shift
- else se->move_nodes_screen(desktop, mul*1, 0); // no shift
- }
- else { // no alt
- if (MOD__SHIFT) se->move_nodes(mul*10*nudge, 0); // shift
- else se->move_nodes(mul*nudge, 0); // no shift
- }
- ret = TRUE;
- }
- break;
- case GDK_Down: // move selection down
- case GDK_KP_Down:
- case GDK_KP_2:
- if (!MOD__CTRL) { // not ctrl
- gint mul = 1 + gobble_key_events(
- get_group0_keyval(&event->key), 0); // with any mask
- if (MOD__ALT) { // alt
- if (MOD__SHIFT) se->move_nodes_screen(desktop, 0, mul*-10); // shift
- else se->move_nodes_screen(desktop, 0, mul*-1); // no shift
- }
- else { // no alt
- if (MOD__SHIFT) se->move_nodes(0, mul*-10*nudge); // shift
- else se->move_nodes(0, mul*-nudge); // no shift
- }
- ret = TRUE;
- }
- break;
- case GDK_Escape:
- {
- Geom::OptRect const b = Inkscape::Rubberband::get(desktop)->getRectangle();
- if (b) {
- Inkscape::Rubberband::get(desktop)->stop();
- nc->current_state = SP_NODE_CONTEXT_INACTIVE;
- nc->rb_escaped = true;
- } else {
- if (se->has_selection()) {
- se->deselect();
- } else {
- sp_desktop_selection(desktop)->clear();
- }
- }
- ret = TRUE;
- break;
- }
-
- case GDK_bracketleft:
- if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
- if (nc->leftctrl)
- se->rotate_nodes (M_PI/snaps, -1, false);
- if (nc->rightctrl)
- se->rotate_nodes (M_PI/snaps, 1, false);
- } else if ( MOD__ALT && !MOD__CTRL ) {
- if (nc->leftalt && nc->rightalt)
- se->rotate_nodes (1, 0, true);
- else {
- if (nc->leftalt)
- se->rotate_nodes (1, -1, true);
- if (nc->rightalt)
- se->rotate_nodes (1, 1, true);
- }
- } else if ( snaps != 0 ) {
- se->rotate_nodes (M_PI/snaps, 0, false);
- }
- ret = TRUE;
- break;
- case GDK_bracketright:
- if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
- if (nc->leftctrl)
- se->rotate_nodes (-M_PI/snaps, -1, false);
- if (nc->rightctrl)
- se->rotate_nodes (-M_PI/snaps, 1, false);
- } else if ( MOD__ALT && !MOD__CTRL ) {
- if (nc->leftalt && nc->rightalt)
- se->rotate_nodes (-1, 0, true);
- else {
- if (nc->leftalt)
- se->rotate_nodes (-1, -1, true);
- if (nc->rightalt)
- se->rotate_nodes (-1, 1, true);
- }
- } else if ( snaps != 0 ) {
- se->rotate_nodes (-M_PI/snaps, 0, false);
- }
- ret = TRUE;
- break;
- case GDK_less:
- case GDK_comma:
- if (MOD__CTRL) {
- if (nc->leftctrl)
- se->scale_nodes(-offset, -1);
- if (nc->rightctrl)
- se->scale_nodes(-offset, 1);
- } else if (MOD__ALT) {
- if (nc->leftalt && nc->rightalt)
- se->scale_nodes_screen (-1, 0);
- else {
- if (nc->leftalt)
- se->scale_nodes_screen (-1, -1);
- if (nc->rightalt)
- se->scale_nodes_screen (-1, 1);
- }
- } else {
- se->scale_nodes (-offset, 0);
- }
- ret = TRUE;
- break;
- case GDK_greater:
- case GDK_period:
- if (MOD__CTRL) {
- if (nc->leftctrl)
- se->scale_nodes (offset, -1);
- if (nc->rightctrl)
- se->scale_nodes (offset, 1);
- } else if (MOD__ALT) {
- if (nc->leftalt && nc->rightalt)
- se->scale_nodes_screen (1, 0);
- else {
- if (nc->leftalt)
- se->scale_nodes_screen (1, -1);
- if (nc->rightalt)
- se->scale_nodes_screen (1, 1);
- }
- } else {
- se->scale_nodes (offset, 0);
- }
- ret = TRUE;
- break;
-
- case GDK_Alt_L:
- nc->leftalt = TRUE;
- sp_node_context_show_modifier_tip(event_context, event);
- break;
- case GDK_Alt_R:
- nc->rightalt = TRUE;
- sp_node_context_show_modifier_tip(event_context, event);
- break;
- case GDK_Control_L:
- nc->leftctrl = TRUE;
- sp_node_context_show_modifier_tip(event_context, event);
- break;
- case GDK_Control_R:
- nc->rightctrl = TRUE;
- sp_node_context_show_modifier_tip(event_context, event);
- break;
- case GDK_Shift_L:
- case GDK_Shift_R:
- case GDK_Meta_L:
- case GDK_Meta_R:
- sp_node_context_show_modifier_tip(event_context, event);
- break;
- default:
- ret = node_key(event);
- break;
- }
- break;
- case GDK_KEY_RELEASE:
- switch (get_group0_keyval(&event->key)) {
- case GDK_Alt_L:
- nc->leftalt = FALSE;
- event_context->defaultMessageContext()->clear();
- break;
- case GDK_Alt_R:
- nc->rightalt = FALSE;
- event_context->defaultMessageContext()->clear();
- break;
- case GDK_Control_L:
- nc->leftctrl = FALSE;
- event_context->defaultMessageContext()->clear();
- break;
- case GDK_Control_R:
- nc->rightctrl = FALSE;
- event_context->defaultMessageContext()->clear();
- break;
- case GDK_Shift_L:
- case GDK_Shift_R:
- case GDK_Meta_L:
- case GDK_Meta_R:
- event_context->defaultMessageContext()->clear();
- break;
- }
- break;
- default:
- break;
- }
-
- if (!ret) {
- if (((SPEventContextClass *) parent_class)->root_handler)
- ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, 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:encoding=utf-8:textwidth=99 :
diff --git a/src/node-context.h b/src/node-context.h
deleted file mode 100644
index 2345ffc7e..000000000
--- a/src/node-context.h
+++ /dev/null
@@ -1,87 +0,0 @@
-#ifndef __SP_NODE_CONTEXT_H__
-#define __SP_NODE_CONTEXT_H__
-
-/*
- * Node editing context
- *
- * Authors:
- * Lauris Kaplinski <lauris@kaplinski.com>
- * bulia byak <buliabyak@users.sf.net>
- *
- * This code is in public domain
- */
-
-#include <gtk/gtktypeutils.h>
-#include <sigc++/sigc++.h>
-#include "event-context.h"
-#include "forward.h"
-#include "display/display-forward.h"
-#include "nodepath.h"
-namespace Inkscape { class Selection; }
-
-#define SP_TYPE_NODE_CONTEXT (sp_node_context_get_type ())
-#define SP_NODE_CONTEXT(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_NODE_CONTEXT, SPNodeContext))
-#define SP_NODE_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_NODE_CONTEXT, SPNodeContextClass))
-#define SP_IS_NODE_CONTEXT(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_NODE_CONTEXT))
-#define SP_IS_NODE_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_NODE_CONTEXT))
-
-enum { SP_NODE_CONTEXT_INACTIVE,
- SP_NODE_CONTEXT_NODE_DRAGGING,
- SP_NODE_CONTEXT_RUBBERBAND_DRAGGING };
-
-class SPNodeContext;
-class SPNodeContextClass;
-
-struct SPNodeContext {
- // FIXME: shouldn't this be a pointer???
- SPEventContext event_context;
-
- guint drag : 1;
-
- gboolean leftalt;
- gboolean rightalt;
- gboolean leftctrl;
- gboolean rightctrl;
-
- /// If true, rubberband was cancelled by esc, so the next button release should not deselect.
- bool rb_escaped;
-
- sigc::connection sel_changed_connection;
-
- Inkscape::MessageContext *_node_message_context;
-
- bool cursor_drag;
-
- bool added_node;
-
- unsigned int current_state;
-
- SPItem * flashed_item;
- SPCanvasItem *grabbed;
- Inkscape::Display::TemporaryItem * flash_tempitem;
- int remove_flash_counter;
-};
-
-struct SPNodeContextClass {
- SPEventContextClass parent_class;
-};
-
-/* Standard Gtk function */
-
-GtkType sp_node_context_get_type (void);
-
-void sp_node_context_selection_changed (Inkscape::Selection * selection, gpointer data);
-void sp_node_context_selection_modified (Inkscape::Selection * selection, guint flags, gpointer data);
-
-#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 :
diff --git a/src/nodepath.cpp b/src/nodepath.cpp
deleted file mode 100644
index 069b3c5bc..000000000
--- a/src/nodepath.cpp
+++ /dev/null
@@ -1,5146 +0,0 @@
-#define __SP_NODEPATH_C__
-
-/** \file
- * Path handler in node edit mode
- *
- * Authors:
- * Lauris Kaplinski <lauris@kaplinski.com>
- * bulia byak <buliabyak@users.sf.net>
- *
- * Portions of this code are in public domain; node sculpting functions written by bulia byak are under GNU GPL
- */
-
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
-
-#include <gdk/gdkkeysyms.h>
-#include "display/canvas-bpath.h"
-#include "display/curve.h"
-#include "display/sp-ctrlline.h"
-#include "display/sodipodi-ctrl.h"
-#include "display/sp-canvas-util.h"
-#include <glibmm/i18n.h>
-#include "2geom/pathvector.h"
-#include "2geom/sbasis-to-bezier.h"
-#include "2geom/bezier-curve.h"
-#include "2geom/hvlinesegment.h"
-#include "helper/units.h"
-#include "helper/geom.h"
-#include "knot.h"
-#include "inkscape.h"
-#include "document.h"
-#include "sp-namedview.h"
-#include "desktop.h"
-#include "desktop-handles.h"
-#include "snap.h"
-#include "message-stack.h"
-#include "message-context.h"
-#include "node-context.h"
-#include "lpe-tool-context.h"
-#include "shape-editor.h"
-#include "selection-chemistry.h"
-#include "selection.h"
-#include "xml/repr.h"
-#include "preferences.h"
-#include "sp-metrics.h"
-#include "sp-path.h"
-#include "sp-text.h"
-#include "sp-shape.h"
-#include "libnr/nr-matrix-ops.h"
-#include "svg/svg.h"
-#include "verbs.h"
-#include <2geom/bezier-utils.h>
-#include <vector>
-#include <algorithm>
-#include <cstring>
-#include <cmath>
-#include "live_effects/lpeobject.h"
-#include "live_effects/lpeobject-reference.h"
-#include "live_effects/effect.h"
-#include "live_effects/parameter/parameter.h"
-#include "live_effects/parameter/path.h"
-#include "util/mathfns.h"
-#include "display/snap-indicator.h"
-#include "snapped-point.h"
-
-namespace Geom { class Matrix; }
-
-/// \todo
-/// evil evil evil. FIXME: conflict of two different Path classes!
-/// There is a conflict in the namespace between two classes named Path.
-/// #include "sp-flowtext.h"
-/// #include "sp-flowregion.h"
-
-#define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
-#define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
-GType sp_flowregion_get_type (void);
-#define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
-#define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
-GType sp_flowtext_get_type (void);
-// end evil workaround
-
-#include "helper/stlport.h"
-
-
-/// \todo fixme: Implement these via preferences */
-
-#define NODE_FILL 0xbfbfbf00
-#define NODE_STROKE 0x000000ff
-#define NODE_FILL_HI 0xff000000
-#define NODE_STROKE_HI 0x000000ff
-#define NODE_FILL_SEL 0x0000ffff
-#define NODE_STROKE_SEL 0x000000ff
-#define NODE_FILL_SEL_HI 0xff000000
-#define NODE_STROKE_SEL_HI 0x000000ff
-#define KNOT_FILL 0xffffffff
-#define KNOT_STROKE 0x000000ff
-#define KNOT_FILL_HI 0xff000000
-#define KNOT_STROKE_HI 0x000000ff
-
-static GMemChunk *nodechunk = NULL;
-
-/* Creation from object */
-
-static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t);
-static Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length);
-
-/* Object updating */
-
-static void stamp_repr(Inkscape::NodePath::Path *np);
-static SPCurve *create_curve(Inkscape::NodePath::Path *np);
-static gchar *create_typestr(Inkscape::NodePath::Path *np);
-
-static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
-
-static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
-
-static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
-
-static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
-
-/* Adjust handle placement, if the node or the other handle is moved */
-static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
-static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
-static void sp_node_adjust_handles_auto(Inkscape::NodePath::Node *node);
-
-/* Node event callbacks */
-static void node_clicked(SPKnot *knot, guint state, gpointer data);
-static void node_grabbed(SPKnot *knot, guint state, gpointer data);
-static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
-static gboolean node_request(SPKnot *knot, Geom::Point const &p, guint state, gpointer data);
-
-/* Handle event callbacks */
-static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
-static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
-static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
-static gboolean node_handle_request(SPKnot *knot, Geom::Point &p, guint state, gpointer data);
-static void node_handle_moved(SPKnot *knot, Geom::Point const &p, guint state, gpointer data);
-static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
-
-/* Constructors and destructors */
-
-static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
-static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
-static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
-static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
-static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
- Geom::Point *ppos, Geom::Point *pos, Geom::Point *npos);
-static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
-
-/* Helpers */
-
-static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
-static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
-static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
-
-static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
-static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
-
-// active_node indicates mouseover node
-Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
-
-static SPCanvasItem *
-sp_nodepath_make_helper_item(Inkscape::NodePath::Path *np, /*SPDesktop *desktop, */const SPCurve *curve, bool show = false, guint32 color = 0xff0000ff) {
- SPCurve *helper_curve = curve->copy();
- helper_curve->transform(np->i2d);
- SPCanvasItem *helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
- sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(helper_path), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
- sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(helper_path), 0, SP_WIND_RULE_NONZERO);
- sp_canvas_item_move_to_z(helper_path, 0);
- if (show) {
- sp_canvas_item_show(helper_path);
- }
- helper_curve->unref();
- return helper_path;
-}
-
-static void
-sp_nodepath_create_helperpaths(Inkscape::NodePath::Path *np) {
- //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> > helper_path_vec;
- if (!SP_IS_LPE_ITEM(np->item)) {
- g_print ("Only LPEItems can have helperpaths!\n");
- return;
- }
-
- SPLPEItem *lpeitem = SP_LPE_ITEM(np->item);
- PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
- for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) {
- Inkscape::LivePathEffect::LPEObjectReference *lperef = (*i);
- Inkscape::LivePathEffect::Effect *lpe = lperef->lpeobject->get_lpe();
- if (lpe) {
- // create new canvas items from the effect's helper paths
- std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
- for (std::vector<Geom::PathVector>::iterator j = hpaths.begin(); j != hpaths.end(); ++j) {
- SPCurve *helper_curve = new SPCurve(*j);
- SPCanvasItem * canvasitem = sp_nodepath_make_helper_item(np, helper_curve, true, 0x509050dd);
- np->helper_path_vec[lpe].push_back(canvasitem);
- helper_curve->unref();
- }
- }
- }
-}
-
-static void
-sp_nodepath_destroy_helperpaths(Inkscape::NodePath::Path *np) {
- for (HelperPathList::iterator i = np->helper_path_vec.begin(); i != np->helper_path_vec.end(); ++i) {
- for (std::vector<SPCanvasItem *>::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j) {
- GtkObject *temp = *j;
- *j = NULL;
- gtk_object_destroy(temp);
- }
- }
- np->helper_path_vec.clear();
-}
-
-/** updates canvas items from the effect's helper paths */
-void
-sp_nodepath_update_helperpaths(Inkscape::NodePath::Path *np) {
- //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> > helper_path_vec;
- if (!SP_IS_LPE_ITEM(np->item)) {
- g_print ("Only LPEItems can have helperpaths!\n");
- return;
- }
-
- SPLPEItem *lpeitem = SP_LPE_ITEM(np->item);
- PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
-
- /* The number or type or LPEs may have changed, so we need to clear and recreate our
- * helper_path_vec to make sure it is in sync */
- sp_nodepath_destroy_helperpaths(np);
- sp_nodepath_create_helperpaths(np);
-
- for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) {
- Inkscape::LivePathEffect::Effect *lpe = (*i)->lpeobject->get_lpe();
- if (lpe) {
- std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
- for (unsigned int j = 0; j < hpaths.size(); ++j) {
- SPCurve *curve = new SPCurve(hpaths[j]);
- curve->transform(np->i2d);
- sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH((np->helper_path_vec[lpe])[j]), curve);
- curve = curve->unref();
- }
- }
- }
-}
-
-/**
- * \brief Creates new nodepath from item
- *
- * If repr_key_in is not NULL, object *has* to be a LivePathEffectObject !
- *
- * \todo create proper constructor for nodepath::path, this method returns null a constructor cannot so this cannot be simply converted to constructor.
- */
-Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
-{
- if (repr_key_in) {
- g_assert(IS_LIVEPATHEFFECT(object));
- }
-
- Inkscape::XML::Node *repr = object->repr;
-
- /** \todo
- * FIXME: remove this. We don't want to edit paths inside flowtext.
- * Instead we will build our flowtext with cloned paths, so that the
- * real paths are outside the flowtext and thus editable as usual.
- */
- if (SP_IS_FLOWTEXT(object)) {
- for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
- if SP_IS_FLOWREGION(child) {
- SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
- if (grandchild && SP_IS_PATH(grandchild)) {
- object = SP_ITEM(grandchild);
- break;
- }
- }
- }
- }
-
- SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
-
- if (curve == NULL) {
- return NULL;
- }
-
- if (curve->get_segment_count() < 1) {
- curve->unref();
- return NULL; // prevent crash for one-node paths
- }
-
- //Create new nodepath
- Inkscape::NodePath::Path *np = new Inkscape::NodePath::Path();
- if (!np) {
- curve->unref();
- return NULL;
- }
-
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
-
- // Set defaults
- np->desktop = desktop;
- np->object = object;
- np->subpaths = NULL;
- np->selected = NULL;
- np->shape_editor = NULL; //Let the shapeeditor that makes this set it
- np->local_change = 0;
- np->show_handles = show_handles;
- np->helper_path = NULL;
- np->helperpath_rgba = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff);
- np->helperpath_width = 1.0;
- np->curve = curve->copy();
- np->show_helperpath = prefs->getBool("/tools/nodes/show_helperpath");
- if (SP_IS_LPE_ITEM(object)) {
- Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
- if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
- np->show_helperpath = true;
- }
- }
- np->straight_path = false;
- if (IS_LIVEPATHEFFECT(object) && item) {
- np->item = item;
- } else {
- np->item = SP_ITEM(object);
- }
-
- np->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE);
-
- // we need to update item's transform from the repr here,
- // because they may be out of sync when we respond
- // to a change in repr by regenerating nodepath --bb
- sp_object_read_attr(SP_OBJECT(np->item), "transform");
-
- np->i2d = sp_item_i2d_affine(np->item);
- np->d2i = np->i2d.inverse();
-
- np->repr = repr;
- if (repr_key_in) { // apparently the object is an LPEObject
- np->repr_key = g_strdup(repr_key_in);
- np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
- Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(object)->get_lpe();
- if (!lpe) {
- g_error("sp_nodepath_new: lpeobject without real lpe passed as argument!");
- delete np;
- }
- Inkscape::LivePathEffect::Parameter *lpeparam = lpe->getParameter(repr_key_in);
- if (lpeparam) {
- lpeparam->param_setup_nodepath(np);
- }
- } else {
- np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
- if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
- np->repr_key = g_strdup("inkscape:original-d");
-
- Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
- if (lpe) {
- lpe->setup_nodepath(np);
- }
- } else {
- np->repr_key = g_strdup("d");
- }
- }
-
- /* Calculate length of the nodetype string. The closing/starting point for closed paths is counted twice.
- * So for example a closed rectangle has a nodetypestring of length 5.
- * To get the correct count, one can count all segments in the paths, and then add the total number of (non-empty) paths. */
- Geom::PathVector pathv_sanitized = pathv_to_linear_and_cubic_beziers(np->curve->get_pathvector());
- np->curve->set_pathvector(pathv_sanitized);
- guint length = np->curve->get_segment_count();
- for (Geom::PathVector::const_iterator pit = pathv_sanitized.begin(); pit != pathv_sanitized.end(); ++pit) {
- length += pit->empty() ? 0 : 1;
- }
-
- gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
- Inkscape::NodePath::NodeType *typestr = parse_nodetypes(nodetypes, length);
-
- // create the subpath(s) from the bpath
- subpaths_from_pathvector(np, pathv_sanitized, typestr);
-
- // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
- np->subpaths = g_list_reverse(np->subpaths);
-
- delete[] typestr;
- curve->unref();
-
- // Draw helper curve
- if (np->show_helperpath) {
- np->helper_path = sp_nodepath_make_helper_item(np, /*desktop, */np->curve, true, np->helperpath_rgba);
- }
-
- sp_nodepath_create_helperpaths(np);
-
- return np;
-}
-
-/**
- * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
- */
-Inkscape::NodePath::Path::~Path() {
- while (this->subpaths) {
- sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) this->subpaths->data);
- }
-
- //Inform the ShapeEditor that made me, if any, that I am gone.
- if (this->shape_editor)
- this->shape_editor->nodepath_destroyed();
-
- g_assert(!this->selected);
-
- if (this->helper_path) {
- GtkObject *temp = this->helper_path;
- this->helper_path = NULL;
- gtk_object_destroy(temp);
- }
- if (this->curve) {
- this->curve->unref();
- this->curve = NULL;
- }
-
- if (this->repr_key) {
- g_free(this->repr_key);
- this->repr_key = NULL;
- }
- if (this->repr_nodetypes_key) {
- g_free(this->repr_nodetypes_key);
- this->repr_nodetypes_key = NULL;
- }
-
- sp_nodepath_destroy_helperpaths(this);
-
- this->desktop = NULL;
-}
-
-/**
- * Return the node count of a given NodeSubPath.
- */
-static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
-{
- int nodeCount = 0;
-
- if (subpath) {
- nodeCount = g_list_length(subpath->nodes);
- }
-
- return nodeCount;
-}
-
-/**
- * Return the node count of a given NodePath.
- */
-static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
-{
- gint nodeCount = 0;
- if (np) {
- for (GList *item = np->subpaths ; item ; item=item->next) {
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
- nodeCount += g_list_length(subpath->nodes);
- }
- }
- return nodeCount;
-}
-
-/**
- * Return the subpath count of a given NodePath.
- */
-static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
-{
- gint nodeCount = 0;
- if (np) {
- nodeCount = g_list_length(np->subpaths);
- }
- return nodeCount;
-}
-
-/**
- * Return the selected node count of a given NodePath.
- */
-static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
-{
- gint nodeCount = 0;
- if (np) {
- nodeCount = g_list_length(np->selected);
- }
- return nodeCount;
-}
-
-/**
- * Return the number of subpaths where nodes are selected in a given NodePath.
- */
-static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
-{
- gint nodeCount = 0;
- if (np && np->selected) {
- if (!np->selected->next) {
- nodeCount = 1;
- } else {
- for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
- Inkscape::NodePath::SubPath *subpath = static_cast<Inkscape::NodePath::SubPath *>(spl->data);
- for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
- Inkscape::NodePath::Node *node = static_cast<Inkscape::NodePath::Node *>(nl->data);
- if (node->selected) {
- nodeCount++;
- break;
- }
- }
- }
- }
- }
- return nodeCount;
-}
-
-/**
- * Clean up a nodepath after editing.
- *
- * Currently we are deleting trivial subpaths.
- */
-static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
-{
- GList *badSubPaths = NULL;
-
- //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
- for (GList *l = nodepath->subpaths; l ; l=l->next) {
- Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
- if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
- badSubPaths = g_list_append(badSubPaths, sp);
- }
-
- //Delete them. This second step is because sp_nodepath_subpath_destroy()
- //also removes the subpath from nodepath->subpaths
- for (GList *l = badSubPaths; l ; l=l->next) {
- Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
- sp_nodepath_subpath_destroy(sp);
- }
-
- g_list_free(badSubPaths);
-}
-
-/**
- * Create new nodepaths from pathvector, make it subpaths of np.
- * \param t The node type array.
- */
-static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t)
-{
- guint i = 0; // index into node type array
- for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
- if (pit->empty())
- continue; // don't add single knot paths
-
- Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
-
- Geom::Point ppos = pit->initialPoint() * np->i2d;
- NRPathcode pcode = NR_MOVETO;
-
- /* Johan: Note that this is pretty arcane code. I am pretty sure it is working correctly, be very certain to change it! (better to just rewrite this whole method)*/
- for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) {
- if( dynamic_cast<Geom::LineSegment const*>(&*cit) ||
- dynamic_cast<Geom::HLineSegment const*>(&*cit) ||
- dynamic_cast<Geom::VLineSegment const*>(&*cit) )
- {
- Geom::Point pos = cit->initialPoint() * (Geom::Matrix)np->i2d;
- sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
-
- ppos = cit->finalPoint() * (Geom::Matrix)np->i2d;
- pcode = NR_LINETO;
- }
- else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&*cit)) {
- std::vector<Geom::Point> points = cubic_bezier->points();
- Geom::Point pos = points[0] * (Geom::Matrix)np->i2d;
- Geom::Point npos = points[1] * (Geom::Matrix)np->i2d;
- sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
-
- ppos = points[2] * (Geom::Matrix)np->i2d;
- pcode = NR_CURVETO;
- }
- }
-
- if (pit->closed()) {
- // Add last knot (because sp_nodepath_subpath_close kills the last knot)
- /* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already
- * If the length is zero, don't add it to the nodepath. */
- Geom::Curve const &closing_seg = pit->back_closed();
- // Don't use !closing_seg.isDegenerate() as it is too precise, and does not account for floating point rounding probs (LP bug #257289)
- if ( ! are_near(closing_seg.initialPoint(), closing_seg.finalPoint()) ) {
- Geom::Point pos = closing_seg.finalPoint() * (Geom::Matrix)np->i2d;
- sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
- }
-
- sp_nodepath_subpath_close(sp);
- }
- }
-}
-
-/**
- * Convert from sodipodi:nodetypes to new style type array.
- */
-static
-Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length)
-{
- Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
-
- guint pos = 0;
-
- if (types) {
- for (guint i = 0; types[i] && ( i < length ); i++) {
- while ((types[i] > '\0') && (types[i] <= ' ')) i++;
- if (types[i] != '\0') {
- switch (types[i]) {
- case 's':
- typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
- break;
- case 'a':
- typestr[pos++] =Inkscape::NodePath::NODE_AUTO;
- break;
- case 'z':
- typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
- break;
- case 'c':
- typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
- break;
- default:
- typestr[pos++] =Inkscape::NodePath::NODE_NONE;
- break;
- }
- }
- }
- }
-
- while (pos < length) {
- typestr[pos++] = Inkscape::NodePath::NODE_NONE;
- }
-
- return typestr;
-}
-
-/**
- * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
- * updated but repr is not (for speed). Used during curve and node drag.
- */
-static void update_object(Inkscape::NodePath::Path *np)
-{
- g_assert(np);
-
- np->curve->unref();
- np->curve = create_curve(np);
-
- sp_nodepath_set_curve(np, np->curve);
-
- if (np->show_helperpath) {
- SPCurve * helper_curve = np->curve->copy();
- helper_curve->transform(np->i2d);
- sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
- helper_curve->unref();
- }
-
- // updating helperpaths of LPEItems is now done in sp_lpe_item_update();
- //sp_nodepath_update_helperpaths(np);
-
- // now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too
- // TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder!
- np->shape_editor->update_knotholder();
-}
-
-/**
- * Update XML path node with data from path object.
- */
-static void update_repr_internal(Inkscape::NodePath::Path *np)
-{
- g_assert(np);
-
- Inkscape::XML::Node *repr = np->object->repr;
-
- np->curve->unref();
- np->curve = create_curve(np);
-
- gchar *typestr = create_typestr(np);
- gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
-
- // determine if path has an effect applied and write to correct "d" attribute.
- if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
- np->local_change++;
- repr->setAttribute(np->repr_key, svgpath);
- }
-
- if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
- np->local_change++;
- repr->setAttribute(np->repr_nodetypes_key, typestr);
- }
-
- g_free(svgpath);
- g_free(typestr);
-
- if (np->show_helperpath) {
- SPCurve * helper_curve = np->curve->copy();
- helper_curve->transform(np->i2d);
- sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
- helper_curve->unref();
- }
-
- // TODO: do we need this call here? after all, update_object() should have been called just before
- //sp_nodepath_update_helperpaths(np);
-}
-
-/**
- * Update XML path node with data from path object, commit changes forever.
- */
-void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
-{
- //fixme: np can be NULL, so check before proceeding
- g_return_if_fail(np != NULL);
-
- update_repr_internal(np);
- sp_canvas_end_forced_full_redraws(np->desktop->canvas);
-
- sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
- annotation);
-}
-
-/**
- * Update XML path node with data from path object, commit changes with undo.
- */
-static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
-{
- update_repr_internal(np);
- sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
- annotation);
-}
-
-/**
- * Make duplicate of path, replace corresponding XML node in tree, commit.
- */
-static void stamp_repr(Inkscape::NodePath::Path *np)
-{
- g_assert(np);
-
- Inkscape::XML::Node *old_repr = np->object->repr;
- Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
-
- // remember the position of the item
- gint pos = old_repr->position();
- // remember parent
- Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
-
- SPCurve *curve = create_curve(np);
- gchar *typestr = create_typestr(np);
-
- gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
-
- new_repr->setAttribute(np->repr_key, svgpath);
- new_repr->setAttribute(np->repr_nodetypes_key, typestr);
-
- // add the new repr to the parent
- parent->appendChild(new_repr);
- // move to the saved position
- new_repr->setPosition(pos > 0 ? pos : 0);
-
- sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
- _("Stamp"));
-
- Inkscape::GC::release(new_repr);
- g_free(svgpath);
- g_free(typestr);
- curve->unref();
-}
-
-/**
- * Create curve from path.
- */
-static SPCurve *create_curve(Inkscape::NodePath::Path *np)
-{
- SPCurve *curve = new SPCurve();
-
- for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
- Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
- curve->moveto(sp->first->pos * np->d2i);
- Inkscape::NodePath::Node *n = sp->first->n.other;
- while (n) {
- Geom::Point const end_pt = n->pos * np->d2i;
- if (!IS_FINITE(n->pos[0]) || !IS_FINITE(n->pos[1])){
- g_message("niet finite");
- }
- switch (n->code) {
- case NR_LINETO:
- curve->lineto(end_pt);
- break;
- case NR_CURVETO:
- curve->curveto(n->p.other->n.pos * np->d2i,
- n->p.pos * np->d2i,
- end_pt);
- break;
- default:
- g_assert_not_reached();
- break;
- }
- if (n != sp->last) {
- n = n->n.other;
- } else {
- n = NULL;
- }
- }
- if (sp->closed) {
- curve->closepath();
- }
- }
-
- return curve;
-}
-
-/**
- * Convert path type string to sodipodi:nodetypes style.
- */
-static gchar *create_typestr(Inkscape::NodePath::Path *np)
-{
- gchar *typestr = g_new(gchar, 32);
- gint len = 32;
- gint pos = 0;
-
- for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
- Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
-
- if (pos >= len) {
- typestr = g_renew(gchar, typestr, len + 32);
- len += 32;
- }
-
- typestr[pos++] = 'c';
-
- Inkscape::NodePath::Node *n;
- n = sp->first->n.other;
- while (n) {
- gchar code;
-
- switch (n->type) {
- case Inkscape::NodePath::NODE_CUSP:
- code = 'c';
- break;
- case Inkscape::NodePath::NODE_SMOOTH:
- code = 's';
- break;
- case Inkscape::NodePath::NODE_AUTO:
- code = 'a';
- break;
- case Inkscape::NodePath::NODE_SYMM:
- code = 'z';
- break;
- default:
- g_assert_not_reached();
- code = '\0';
- break;
- }
-
- if (pos >= len) {
- typestr = g_renew(gchar, typestr, len + 32);
- len += 32;
- }
-
- typestr[pos++] = code;
-
- if (n != sp->last) {
- n = n->n.other;
- } else {
- n = NULL;
- }
- }
- }
-
- if (pos >= len) {
- typestr = g_renew(gchar, typestr, len + 1);
- len += 1;
- }
-
- typestr[pos++] = '\0';
-
- return typestr;
-}
-
-// Returns different message contexts depending on the current context. This function should only
-// be called when ec is either a SPNodeContext or SPLPEToolContext, thus we return NULL in all
-// other cases.
-static Inkscape::MessageContext *
-get_message_context(SPEventContext *ec)
-{
- Inkscape::MessageContext *mc = 0;
-
- if (SP_IS_NODE_CONTEXT(ec)) {
- mc = SP_NODE_CONTEXT(ec)->_node_message_context;
- } else if (SP_IS_LPETOOL_CONTEXT(ec)) {
- mc = SP_LPETOOL_CONTEXT(ec)->_lpetool_message_context;
- } else {
- g_warning ("Nodepath should only be present in Node tool or Geometric tool.");
- }
-
- return mc;
-}
-
-/**
- \brief Fills node and handle positions for three nodes, splitting line
- marked by end at distance t.
- */
-static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
-{
- g_assert(new_path != NULL);
- g_assert(end != NULL);
-
- g_assert(end->p.other == new_path);
- Inkscape::NodePath::Node *start = new_path->p.other;
- g_assert(start);
-
- if (end->code == NR_LINETO) {
- new_path->type =Inkscape::NodePath::NODE_CUSP;
- new_path->code = NR_LINETO;
- new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
- } else {
- new_path->type =Inkscape::NodePath::NODE_SMOOTH;
- new_path->code = NR_CURVETO;
- gdouble s = 1 - t;
- for (int dim = 0; dim < 2; dim++) {
- Geom::Coord const f000 = start->pos[dim];
- Geom::Coord const f001 = start->n.pos[dim];
- Geom::Coord const f011 = end->p.pos[dim];
- Geom::Coord const f111 = end->pos[dim];
- Geom::Coord const f00t = s * f000 + t * f001;
- Geom::Coord const f01t = s * f001 + t * f011;
- Geom::Coord const f11t = s * f011 + t * f111;
- Geom::Coord const f0tt = s * f00t + t * f01t;
- Geom::Coord const f1tt = s * f01t + t * f11t;
- Geom::Coord const fttt = s * f0tt + t * f1tt;
- start->n.pos[dim] = f00t;
- new_path->p.pos[dim] = f0tt;
- new_path->pos[dim] = fttt;
- new_path->n.pos[dim] = f1tt;
- end->p.pos[dim] = f11t;
- }
- }
-}
-
-/**
- * Adds new node on direct line between two nodes, activates handles of all
- * three nodes.
- */
-static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
-{
- g_assert(end);
- g_assert(end->subpath);
- g_assert(g_list_find(end->subpath->nodes, end));
-
- Inkscape::NodePath::Node *start = end->p.other;
- g_assert( start->n.other == end );
- Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
- end,
- (NRPathcode)end->code == NR_LINETO?
- Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
- (NRPathcode)end->code,
- &start->pos, &start->pos, &start->n.pos);
- sp_nodepath_line_midpoint(newnode, end, t);
-
- sp_node_adjust_handles(start);
- sp_node_update_handles(start);
- sp_node_update_handles(newnode);
- sp_node_adjust_handles(end);
- sp_node_update_handles(end);
-
- return newnode;
-}
-
-/**
-\brief Break the path at the node: duplicate the argument node, start a new subpath with the duplicate, and copy all nodes after the argument node to it
-*/
-static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
-{
- g_assert(node);
- g_assert(node->subpath);
- g_assert(g_list_find(node->subpath->nodes, node));
-
- Inkscape::NodePath::Node* result = 0;
- Inkscape::NodePath::SubPath *sp = node->subpath;
- Inkscape::NodePath::Path *np = sp->nodepath;
-
- if (sp->closed) {
- sp_nodepath_subpath_open(sp, node);
- result = sp->first;
- } else if ( (node == sp->first) || (node == sp->last ) ){
- // no break for end nodes
- result = 0;
- } else {
- // create a new subpath
- Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
-
- // duplicate the break node as start of the new subpath
- Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL,
- static_cast<Inkscape::NodePath::NodeType>(node->type),
- NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
-
- // attach rest of curve to new node
- g_assert(node->n.other);
- newnode->n.other = node->n.other; node->n.other = NULL;
- newnode->n.other->p.other = newnode;
- newsubpath->last = sp->last;
- sp->last = node;
- node = newnode;
- while (node->n.other) {
- node = node->n.other;
- node->subpath = newsubpath;
- sp->nodes = g_list_remove(sp->nodes, node);
- newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
- }
-
-
- result = newnode;
- }
- return result;
-}
-
-/**
- * Duplicate node and connect to neighbours.
- */
-static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
-{
- g_assert(node);
- g_assert(node->subpath);
- g_assert(g_list_find(node->subpath->nodes, node));
-
- Inkscape::NodePath::SubPath *sp = node->subpath;
-
- NRPathcode code = (NRPathcode) node->code;
- if (code == NR_MOVETO) { // if node is the endnode,
- node->code = NR_LINETO; // new one is inserted before it, so change that to line
- }
-
- Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
-
- if (!node->n.other || !node->p.other) { // if node is an endnode, select it
- return node;
- } else {
- return newnode; // otherwise select the newly created node
- }
-}
-
-static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
-{
- node->p.pos = (node->pos + (node->pos - node->n.pos));
-}
-
-static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
-{
- node->n.pos = (node->pos + (node->pos - node->p.pos));
-}
-
-/**
- * Change line type at node, with side effects on neighbours.
- */
-static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
-{
- g_assert(end);
- g_assert(end->subpath);
- g_assert(end->p.other);
-
- if (end->code != static_cast<guint>(code) ) {
- Inkscape::NodePath::Node *start = end->p.other;
-
- end->code = code;
-
- if (code == NR_LINETO) {
- if (start->code == NR_LINETO) {
- sp_nodepath_set_node_type(start, Inkscape::NodePath::NODE_CUSP);
- }
- if (end->n.other) {
- if (end->n.other->code == NR_LINETO) {
- sp_nodepath_set_node_type(end, Inkscape::NodePath::NODE_CUSP);
- }
- }
-
- if (start->type == Inkscape::NodePath::NODE_AUTO)
- start->type = Inkscape::NodePath::NODE_SMOOTH;
- if (end->type == Inkscape::NodePath::NODE_AUTO)
- end->type = Inkscape::NodePath::NODE_SMOOTH;
-
- start->n.pos = start->pos;
- end->p.pos = end->pos;
-
- sp_node_adjust_handle(start, -1);
- sp_node_adjust_handle(end, 1);
-
- } else {
- Geom::Point delta = end->pos - start->pos;
- start->n.pos = start->pos + delta / 3;
- end->p.pos = end->pos - delta / 3;
- sp_node_adjust_handle(start, 1);
- sp_node_adjust_handle(end, -1);
- }
-
- sp_node_update_handles(start);
- sp_node_update_handles(end);
- }
-}
-
-static void
-sp_nodepath_update_node_knot(Inkscape::NodePath::Node *node)
-{
- if (node->type == Inkscape::NodePath::NODE_CUSP) {
- node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
- node->knot->setSize (node->selected? 11 : 9);
- sp_knot_update_ctrl(node->knot);
- } else if (node->type == Inkscape::NodePath::NODE_AUTO) {
- node->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
- node->knot->setSize (node->selected? 11 : 9);
- sp_knot_update_ctrl(node->knot);
- } else {
- node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
- node->knot->setSize (node->selected? 9 : 7);
- sp_knot_update_ctrl(node->knot);
- }
-}
-
-
-/**
- * Change node type, and its handles accordingly.
- */
-static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
-{
- g_assert(node);
- g_assert(node->subpath);
-
- if ((node->p.other != NULL) && (node->n.other != NULL)) {
- if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
- type =Inkscape::NodePath::NODE_CUSP;
- }
- }
-
- node->type = type;
-
- sp_nodepath_update_node_knot(node);
-
- // if one of handles is mouseovered, preserve its position
- if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
- sp_node_adjust_handle(node, 1);
- } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
- sp_node_adjust_handle(node, -1);
- } else {
- sp_node_adjust_handles(node);
- }
-
- sp_node_update_handles(node);
-
- sp_nodepath_update_statusbar(node->subpath->nodepath);
-
- return node;
-}
-
-bool
-sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
-{
-// TODO clean up multiple returns
- Inkscape::NodePath::Node *othernode = side->other;
- if (!othernode)
- return false;
- NRPathcode const code = sp_node_path_code_from_side(node, side);
- if (code == NR_LINETO)
- return true;
- Inkscape::NodePath::NodeSide *other_to_me = NULL;
- if (&node->p == side) {
- other_to_me = &othernode->n;
- } else if (&node->n == side) {
- other_to_me = &othernode->p;
- }
- if (!other_to_me)
- return false;
- bool is_line =
- (Geom::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
- Geom::L2(node->pos - side->pos) < 1e-6);
- return is_line;
-}
-
-/**
- * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
- * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
- * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
- * If already cusp and set to cusp, retracts handles.
-*/
-void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
-{
- if (type == Inkscape::NodePath::NODE_AUTO) {
- if (node->p.other != NULL)
- node->code = NR_CURVETO;
- if (node->n.other != NULL)
- node->n.other->code = NR_CURVETO;
- }
-
- if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
-
-/*
- Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
-
- if (two_handles) {
- // do nothing, adjust_handles called via set_node_type will line them up
- } else if (one_handle) {
- if (opposite_to_handle_is_line) {
- if (lined_up) {
- // already half-smooth; pull opposite handle too making it fully smooth
- } else {
- // do nothing, adjust_handles will line the handle up, producing a half-smooth node
- }
- } else {
- // pull opposite handle in line with the existing one
- }
- } else if (no_handles) {
- if (both_segments_are_lines
- OR both_segments_are_curves
- OR one_is_line_but_the_curveside_node_is_selected_and_has_two_handles) {
- //pull both handles
- } else {
- // pull the handle opposite to line segment, making node half-smooth
- }
- }
-*/
- bool p_has_handle = (Geom::L2(node->pos - node->p.pos) > 1e-6);
- bool n_has_handle = (Geom::L2(node->pos - node->n.pos) > 1e-6);
- bool p_is_line = sp_node_side_is_line(node, &node->p);
- bool n_is_line = sp_node_side_is_line(node, &node->n);
-
-#define NODE_HAS_BOTH_HANDLES(node) ((Geom::L2(node->pos - node->n.pos) > 1e-6) && (Geom::L2(node->pos - node->p.pos) > 1e-6))
-
- if (p_has_handle && n_has_handle) {
- // do nothing, adjust_handles will line them up
- } else if (p_has_handle || n_has_handle) {
- if (p_has_handle && n_is_line) {
- Radial line (node->n.other->pos - node->pos);
- Radial handle (node->pos - node->p.pos);
- if (fabs(line.a - handle.a) < 1e-3) { // lined up
- // already half-smooth; pull opposite handle too making it fully smooth
- node->n.other->code = NR_CURVETO;
- node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
- } else {
- // do nothing, adjust_handles will line the handle up, producing a half-smooth node
- }
- } else if (n_has_handle && p_is_line) {
- Radial line (node->p.other->pos - node->pos);
- Radial handle (node->pos - node->n.pos);
- if (fabs(line.a - handle.a) < 1e-3) { // lined up
- // already half-smooth; pull opposite handle too making it fully smooth
- node->code = NR_CURVETO;
- node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
- } else {
- // do nothing, adjust_handles will line the handle up, producing a half-smooth node
- }
- } else if (p_has_handle && node->n.other) {
- // pull n handle
- node->n.other->code = NR_CURVETO;
- double len = (type == Inkscape::NodePath::NODE_SYMM)?
- Geom::L2(node->p.pos - node->pos) :
- Geom::L2(node->n.other->pos - node->pos) / 3;
- node->n.pos = node->pos - (len / Geom::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
- } else if (n_has_handle && node->p.other) {
- // pull p handle
- node->code = NR_CURVETO;
- double len = (type == Inkscape::NodePath::NODE_SYMM)?
- Geom::L2(node->n.pos - node->pos) :
- Geom::L2(node->p.other->pos - node->pos) / 3;
- node->p.pos = node->pos - (len / Geom::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
- }
- } else if (!p_has_handle && !n_has_handle) {
- if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other) ||
- (n_is_line && node->p.other && node->p.other->selected && NODE_HAS_BOTH_HANDLES(node->p.other)) ||
- (p_is_line && node->n.other && node->n.other->selected && NODE_HAS_BOTH_HANDLES(node->n.other))
- ) {
- // no handles, but: both segments are either lines or curves; or: one is line and the
- // node at the other side is selected (so it was just smoothed too!) and now has both
- // handles: then pull both handles here
-
- // convert both to curves:
- node->code = NR_CURVETO;
- node->n.other->code = NR_CURVETO;
-
- sp_node_adjust_handles_auto(node);
- } else {
- // pull the handle opposite to line segment, making it half-smooth
- if (p_is_line && node->n.other) {
- if (type != Inkscape::NodePath::NODE_SYMM) {
- // pull n handle
- node->n.other->code = NR_CURVETO;
- double len = Geom::L2(node->n.other->pos - node->pos) / 3;
- node->n.pos = node->pos + (len / Geom::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
- }
- } else if (n_is_line && node->p.other) {
- if (type != Inkscape::NodePath::NODE_SYMM) {
- // pull p handle
- node->code = NR_CURVETO;
- double len = Geom::L2(node->p.other->pos - node->pos) / 3;
- node->p.pos = node->pos + (len / Geom::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
- }
- }
- }
- }
- } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
- // cusping a cusp: retract nodes
- node->p.pos = node->pos;
- node->n.pos = node->pos;
- }
-
- sp_nodepath_set_node_type (node, type);
-}
-
-/**
- * Move node to point, and adjust its and neighbouring handles.
- */
-void sp_node_moveto(Inkscape::NodePath::Node *node, Geom::Point p)
-{
- if (node->type == Inkscape::NodePath::NODE_AUTO) {
- node->pos = p;
- sp_node_adjust_handles_auto(node);
- } else {
- Geom::Point delta = p - node->pos;
- node->pos = p;
-
- node->p.pos += delta;
- node->n.pos += delta;
- }
-
- Inkscape::NodePath::Node *node_p = NULL;
- Inkscape::NodePath::Node *node_n = NULL;
-
- if (node->p.other) {
- if (sp_node_side_is_line(node, &node->p)) {
- sp_node_adjust_handle(node, 1);
- sp_node_adjust_handle(node->p.other, -1);
- node_p = node->p.other;
- }
- if (!node->p.other->selected && node->p.other->type == Inkscape::NodePath::NODE_AUTO) {
- sp_node_adjust_handles_auto(node->p.other);
- node_p = node->p.other;
- }
- }
- if (node->n.other) {
- if (sp_node_side_is_line(node, &node->n)) {
- sp_node_adjust_handle(node, -1);
- sp_node_adjust_handle(node->n.other, 1);
- node_n = node->n.other;
- }
- if (!node->n.other->selected && node->n.other->type == Inkscape::NodePath::NODE_AUTO) {
- sp_node_adjust_handles_auto(node->n.other);
- node_n = node->n.other;
- }
- }
-
- // this function is only called from batch movers that will update display at the end
- // themselves, so here we just move all the knots without emitting move signals, for speed
- sp_node_update_handles(node, false);
- if (node_n) {
- sp_node_update_handles(node_n, false);
- }
- if (node_p) {
- sp_node_update_handles(node_p, false);
- }
-}
-
-/**
- * Call sp_node_moveto() for node selection and handle possible snapping.
- */
-static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, Geom::Coord dx, Geom::Coord dy,
- bool const snap, bool constrained = false,
- Inkscape::Snapper::ConstraintLine const &constraint = Geom::Point())
-{
- Geom::Point delta(dx, dy);
- Geom::Point best_pt = delta;
- Inkscape::SnappedPoint best;
-
- if (snap) {
- /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
- * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
- * must provide that information. */
-
- // Build a list of the unselected nodes to which the snapper should snap
- std::vector<Inkscape::SnapCandidatePoint> unselected_nodes;
- for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
- for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
- Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
- if (!node->selected) {
- unselected_nodes.push_back(Inkscape::SnapCandidatePoint(node->pos, Inkscape::SNAPSOURCE_UNDEFINED, node->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPTARGET_NODE_SMOOTH : Inkscape::SNAPTARGET_NODE_CUSP));
- }
- }
- }
-
- SnapManager &m = nodepath->desktop->namedview->snap_manager;
-
- // When only the node closest to the mouse pointer is to be snapped
- // then we will not even try to snap to other points and discard those immediately
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- bool closest_only = prefs->getBool("/options/snapclosestonly/value", false);
-
- Inkscape::NodePath::Node *closest_node = NULL;
- Geom::Coord closest_dist = NR_HUGE;
-
- if (closest_only) {
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- Geom::Coord dist = Geom::L2(nodepath->drag_origin_mouse - n->origin);
- if (dist < closest_dist) {
- closest_node = n;
- closest_dist = dist;
- }
- }
- }
-
- // Iterate through all selected nodes
- m.setup(nodepath->desktop, false, nodepath->item, &unselected_nodes);
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- if (!closest_only || n == closest_node) { //try to snap either all selected nodes or only the closest one
- Inkscape::SnappedPoint s;
- Inkscape::SnapSourceType source_type = (n->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPSOURCE_NODE_SMOOTH : Inkscape::SNAPSOURCE_NODE_CUSP);
- if (constrained) {
- Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
- dedicated_constraint.setPoint(n->pos);
- s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(n->pos + delta, source_type), dedicated_constraint);
- } else {
- s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(n->pos + delta, source_type));
- }
-
- if (s.getSnapped()) {
- s.setPointerDistance(Geom::L2(nodepath->drag_origin_mouse - n->origin));
- if (!s.isOtherSnapBetter(best, true)) {
- best = s;
- best_pt = from_2geom(s.getPoint()) - n->pos;
- }
- }
- }
- }
-
- if (best.getSnapped()) {
- nodepath->desktop->snapindicator->set_new_snaptarget(best);
- } else {
- nodepath->desktop->snapindicator->remove_snaptarget();
- }
- }
-
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- sp_node_moveto(n, n->pos + best_pt);
- }
-
- // do not update repr here so that node dragging is acceptably fast
- update_object(nodepath);
-}
-
-/**
-Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
-curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
-near x = 0.
- */
-double
-sculpt_profile (double x, double alpha, guint profile)
-{
- double result = 1;
-
- if (x >= 1) {
- result = 0;
- } else if (x <= 0) {
- result = 1;
- } else {
- switch (profile) {
- case SCULPT_PROFILE_LINEAR:
- result = 1 - x;
- break;
- case SCULPT_PROFILE_BELL:
- result = (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
- break;
- case SCULPT_PROFILE_ELLIPTIC:
- result = sqrt(1 - x*x);
- break;
- default:
- g_assert_not_reached();
- }
- }
-
- return result;
-}
-
-double
-bezier_length (Geom::Point a, Geom::Point ah, Geom::Point bh, Geom::Point b)
-{
- // extremely primitive for now, don't have time to look for the real one
- double lower = Geom::L2(b - a);
- double upper = Geom::L2(ah - a) + Geom::L2(bh - ah) + Geom::L2(bh - b);
- return (lower + upper)/2;
-}
-
-void
-sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, Geom::Point delta, Geom::Point delta_n, Geom::Point delta_p)
-{
- n->pos = n->origin + delta;
- n->n.pos = n->n.origin + delta_n;
- n->p.pos = n->p.origin + delta_p;
- sp_node_adjust_handles(n);
- sp_node_update_handles(n, false);
-}
-
-/**
- * Displace selected nodes and their handles by fractions of delta (from their origins), depending
- * on how far they are from the dragged node n.
- */
-static void
-sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, Geom::Point delta)
-{
- g_assert (n);
- g_assert (nodepath);
- g_assert (n->subpath->nodepath == nodepath);
-
- double pressure = n->knot->pressure;
- if (pressure == 0)
- pressure = 0.5; // default
- pressure = CLAMP (pressure, 0.2, 0.8);
-
- // map pressure to alpha = 1/5 ... 5
- double alpha = 1 - 2 * fabs(pressure - 0.5);
- if (pressure > 0.5)
- alpha = 1/alpha;
-
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- guint profile = prefs->getInt("/tools/nodes/sculpting_profile", SCULPT_PROFILE_BELL);
-
- if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
- // Only one subpath has selected nodes:
- // use linear mode, where the distance from n to node being dragged is calculated along the path
-
- double n_sel_range = 0, p_sel_range = 0;
- guint n_nodes = 0, p_nodes = 0;
- guint n_sel_nodes = 0, p_sel_nodes = 0;
-
- // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
- {
- double n_range = 0, p_range = 0;
- bool n_going = true, p_going = true;
- Inkscape::NodePath::Node *n_node = n;
- Inkscape::NodePath::Node *p_node = n;
- do {
- // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
- if (n_node && n_going)
- n_node = n_node->n.other;
- if (n_node == NULL) {
- n_going = false;
- } else {
- n_nodes ++;
- n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
- if (n_node->selected) {
- n_sel_nodes ++;
- n_sel_range = n_range;
- }
- if (n_node == p_node) {
- n_going = false;
- p_going = false;
- }
- }
- if (p_node && p_going)
- p_node = p_node->p.other;
- if (p_node == NULL) {
- p_going = false;
- } else {
- p_nodes ++;
- p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
- if (p_node->selected) {
- p_sel_nodes ++;
- p_sel_range = p_range;
- }
- if (p_node == n_node) {
- n_going = false;
- p_going = false;
- }
- }
- } while (n_going || p_going);
- }
-
- // Second pass: actually move nodes in this subpath
- sp_nodepath_move_node_and_handles (n, delta, delta, delta);
- {
- double n_range = 0, p_range = 0;
- bool n_going = true, p_going = true;
- Inkscape::NodePath::Node *n_node = n;
- Inkscape::NodePath::Node *p_node = n;
- do {
- // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
- if (n_node && n_going)
- n_node = n_node->n.other;
- if (n_node == NULL) {
- n_going = false;
- } else {
- n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
- if (n_node->selected) {
- sp_nodepath_move_node_and_handles (n_node,
- sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
- sculpt_profile ((n_range + Geom::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
- sculpt_profile ((n_range - Geom::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
- }
- if (n_node == p_node) {
- n_going = false;
- p_going = false;
- }
- }
- if (p_node && p_going)
- p_node = p_node->p.other;
- if (p_node == NULL) {
- p_going = false;
- } else {
- p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
- if (p_node->selected) {
- sp_nodepath_move_node_and_handles (p_node,
- sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
- sculpt_profile ((p_range - Geom::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
- sculpt_profile ((p_range + Geom::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
- }
- if (p_node == n_node) {
- n_going = false;
- p_going = false;
- }
- }
- } while (n_going || p_going);
- }
-
- } else {
- // Multiple subpaths have selected nodes:
- // use spatial mode, where the distance from n to node being dragged is measured directly as Geom::L2.
- // TODO: correct these distances taking into account their angle relative to the bisector, so as to
- // fix the pear-like shape when sculpting e.g. a ring
-
- // First pass: calculate range
- gdouble direct_range = 0;
- for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
- for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
- Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
- if (node->selected) {
- direct_range = MAX(direct_range, Geom::L2(node->origin - n->origin));
- }
- }
- }
-
- // Second pass: actually move nodes
- for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
- for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
- Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
- if (node->selected) {
- if (direct_range > 1e-6) {
- sp_nodepath_move_node_and_handles (node,
- sculpt_profile (Geom::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
- sculpt_profile (Geom::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
- sculpt_profile (Geom::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
- } else {
- sp_nodepath_move_node_and_handles (node, delta, delta, delta);
- }
-
- }
- }
- }
- }
-
- // do not update repr here so that node dragging is acceptably fast
- update_object(nodepath);
-}
-
-
-/**
- * Move node selection to point, adjust its and neighbouring handles,
- * handle possible snapping, and commit the change with possible undo.
- */
-void
-sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
-{
- if (!nodepath) return;
-
- sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
-
- if (dx == 0) {
- sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
- } else if (dy == 0) {
- sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
- } else {
- sp_nodepath_update_repr(nodepath, _("Move nodes"));
- }
-}
-
-/**
- * Move node selection off screen and commit the change.
- */
-void
-sp_node_selected_move_screen(SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
-{
- // borrowed from sp_selection_move_screen in selection-chemistry.c
- // we find out the current zoom factor and divide deltas by it
-
- gdouble zoom = desktop->current_zoom();
- gdouble zdx = dx / zoom;
- gdouble zdy = dy / zoom;
-
- if (!nodepath) return;
-
- sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
-
- if (dx == 0) {
- sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
- } else if (dy == 0) {
- sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
- } else {
- sp_nodepath_update_repr(nodepath, _("Move nodes"));
- }
-}
-
-/**
- * Move selected nodes to the absolute position given
- */
-void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis)
-{
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- Geom::Point npos(axis == Geom::X ? val : n->pos[Geom::X], axis == Geom::Y ? val : n->pos[Geom::Y]);
- sp_node_moveto(n, npos);
- }
-
- sp_nodepath_update_repr(nodepath, _("Move nodes"));
-}
-
-/**
- * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return Geom::Nothing
- */
-boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
-{
- boost::optional<Geom::Coord> no_coord;
- g_return_val_if_fail(nodepath->selected, no_coord);
-
- // determine coordinate of first selected node
- GList *nsel = nodepath->selected;
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
- Geom::Coord coord = n->pos[axis];
- bool coincide = true;
-
- // compare it to the coordinates of all the other selected nodes
- for (GList *l = nsel->next; l != NULL; l = l->next) {
- n = (Inkscape::NodePath::Node *) l->data;
- if (n->pos[axis] != coord) {
- coincide = false;
- }
- }
- if (coincide) {
- return coord;
- } else {
- Geom::Rect bbox = sp_node_selected_bbox(nodepath);
- // currently we return the coordinate of the bounding box midpoint because I don't know how
- // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
- return bbox.midpoint()[axis];
- }
-}
-
-/** If they don't yet exist, creates knot and line for the given side of the node */
-static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
-{
- if (!side->knot) {
- side->knot = sp_knot_new(desktop, _("<b>Node handle</b>: drag to shape the curve; with <b>Ctrl</b> to snap angle; with <b>Alt</b> to lock length; with <b>Shift</b> to rotate both handles"));
-
- side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
- side->knot->setSize (7);
- side->knot->setAnchor (GTK_ANCHOR_CENTER);
- side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
- side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
- sp_knot_update_ctrl(side->knot);
-
- g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
- g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
- g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
- g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
- g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
- g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
- }
-
- if (!side->line) {
- side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
- SP_TYPE_CTRLLINE, NULL);
- }
-}
-
-/**
- * Ensure the given handle of the node is visible/invisible, update its screen position
- */
-static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
-{
- g_assert(node != NULL);
-
- Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
- NRPathcode code = sp_node_path_code_from_side(node, side);
-
- show_handle = show_handle && (code == NR_CURVETO) && (Geom::L2(side->pos - node->pos) > 1e-6);
-
- if (show_handle) {
- if (!side->knot) { // No handle knot at all
- sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
- // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
- side->knot->pos = side->pos;
- if (side->knot->item)
- SP_CTRL(side->knot->item)->moveto(side->pos);
- sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
- sp_knot_show(side->knot);
- } else {
- if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved
- if (fire_move_signals) {
- sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well
- } else {
- sp_knot_moveto(side->knot, side->pos);
- sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
- }
- }
- if (!SP_KNOT_IS_VISIBLE(side->knot)) {
- sp_knot_show(side->knot);
- }
- }
- sp_canvas_item_show(side->line);
- } else {
- if (side->knot) {
- if (SP_KNOT_IS_VISIBLE(side->knot)) {
- sp_knot_hide(side->knot);
- }
- }
- if (side->line) {
- sp_canvas_item_hide(side->line);
- }
- }
-}
-
-/**
- * Ensure the node itself is visible, its handles and those of the neighbours of the node are
- * visible if selected, update their screen positions. If fire_move_signals, move the node and its
- * handles so that the corresponding signals are fired, callbacks are activated, and curve is
- * updated; otherwise, just move the knots silently (used in batch moves).
- */
-static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
-{
- g_assert(node != NULL);
-
- if (!SP_KNOT_IS_VISIBLE(node->knot)) {
- sp_knot_show(node->knot);
- }
-
- if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update
- if (fire_move_signals)
- sp_knot_set_position(node->knot, node->pos, 0);
- else
- sp_knot_moveto(node->knot, node->pos);
- }
-
- gboolean show_handles = node->selected;
- if (node->p.other != NULL) {
- if (node->p.other->selected) show_handles = TRUE;
- }
- if (node->n.other != NULL) {
- if (node->n.other->selected) show_handles = TRUE;
- }
-
- if (node->subpath->nodepath->show_handles == false)
- show_handles = FALSE;
-
- sp_node_update_handle(node, -1, show_handles, fire_move_signals);
- sp_node_update_handle(node, 1, show_handles, fire_move_signals);
-}
-
-/**
- * Call sp_node_update_handles() for all nodes on subpath.
- */
-static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
-{
- g_assert(subpath != NULL);
-
- for (GList *l = subpath->nodes; l != NULL; l = l->next) {
- sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
- }
-}
-
-/**
- * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
- */
-static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
-{
- g_assert(nodepath != NULL);
-
- for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
- sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
- }
-}
-
-void
-sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
-{
- if (nodepath) {
- nodepath->show_handles = show;
- sp_nodepath_update_handles(nodepath);
- }
-}
-
-/**
- * Adds all selected nodes in nodepath to list.
- */
-void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
-{
- StlConv<Node *>::list(l, selected);
-/// \todo this adds a copying, rework when the selection becomes a stl list
-}
-
-/**
- * Align selected nodes on the specified axis.
- */
-void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
-{
- if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
- return;
- }
-
- if ( !nodepath->selected->next ) { // only one node selected
- return;
- }
- Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
- Geom::Point dest(pNode->pos);
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
- if (pNode) {
- dest[axis] = pNode->pos[axis];
- sp_node_moveto(pNode, dest);
- }
- }
-
- sp_nodepath_update_repr(nodepath, _("Align nodes"));
-}
-
-/// Helper struct.
-struct NodeSort
-{
- Inkscape::NodePath::Node *_node;
- Geom::Coord _coord;
- /// \todo use vectorof pointers instead of calling copy ctor
- NodeSort(Inkscape::NodePath::Node *node, Geom::Dim2 axis) :
- _node(node), _coord(node->pos[axis])
- {}
-
-};
-
-static bool operator<(NodeSort const &a, NodeSort const &b)
-{
- return (a._coord < b._coord);
-}
-
-/**
- * Distribute selected nodes on the specified axis.
- */
-void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
-{
- if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
- return;
- }
-
- if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
- return;
- }
-
- Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
- std::vector<NodeSort> sorted;
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
- if (pNode) {
- NodeSort n(pNode, axis);
- sorted.push_back(n);
- //dest[axis] = pNode->pos[axis];
- //sp_node_moveto(pNode, dest);
- }
- }
- std::sort(sorted.begin(), sorted.end());
- unsigned int len = sorted.size();
- //overall bboxes span
- float dist = (sorted.back()._coord -
- sorted.front()._coord);
- //new distance between each bbox
- float step = (dist) / (len - 1);
- float pos = sorted.front()._coord;
- for ( std::vector<NodeSort> ::iterator it(sorted.begin());
- it < sorted.end();
- it ++ )
- {
- Geom::Point dest((*it)._node->pos);
- dest[axis] = pos;
- sp_node_moveto((*it)._node, dest);
- pos += step;
- }
-
- sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
-}
-
-
-/**
- * Call sp_nodepath_line_add_node() for all selected segments.
- */
-void
-sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
-{
- if (!nodepath) {
- return;
- }
-
- GList *nl = NULL;
-
- int n_added = 0;
-
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
- g_assert(t->selected);
- if (t->p.other && t->p.other->selected) {
- nl = g_list_prepend(nl, t);
- }
- }
-
- while (nl) {
- Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
- Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
- sp_nodepath_node_select(n, TRUE, FALSE);
- n_added ++;
- nl = g_list_remove(nl, t);
- }
-
- /** \todo fixme: adjust ? */
- sp_nodepath_update_handles(nodepath);
-
- if (n_added > 1) {
- sp_nodepath_update_repr(nodepath, _("Add nodes"));
- } else if (n_added > 0) {
- sp_nodepath_update_repr(nodepath, _("Add node"));
- }
-
- sp_nodepath_update_statusbar(nodepath);
-}
-
-/**
- * Select segment nearest to point
- */
-void
-sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p, bool toggle)
-{
- if (!nodepath) {
- return;
- }
-
- SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
- Geom::PathVector const &pathv = curve->get_pathvector();
- boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
- if (!pvpos) {
- g_print ("Possible error?\n");
- return;
- }
-
- // calculate index for nodepath's representation.
- unsigned int segment_index = floor(pvpos->t) + 1;
- for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
- segment_index += pathv[i].size() + 1;
- if (pathv[i].closed()) {
- segment_index += 1;
- }
- }
-
- curve->unref();
-
- //find segment to segment
- Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
-
- //fixme: this can return NULL, so check before proceeding.
- g_return_if_fail(e != NULL);
-
- gboolean force = FALSE;
- if (!(e->selected && (!e->p.other || e->p.other->selected))) {
- force = TRUE;
- }
- sp_nodepath_node_select(e, (gboolean) toggle, force);
- if (e->p.other)
- sp_nodepath_node_select(e->p.other, TRUE, force);
-
- sp_nodepath_update_handles(nodepath);
-
- sp_nodepath_update_statusbar(nodepath);
-}
-
-/**
- * Add a node nearest to point
- */
-void
-sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p)
-{
- if (!nodepath) {
- return;
- }
-
- SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
- Geom::PathVector const &pathv = curve->get_pathvector();
- boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
- if (!pvpos) {
- g_print ("Possible error?\n");
- return;
- }
-
- // calculate index for nodepath's representation.
- double int_part;
- double t = std::modf(pvpos->t, &int_part);
- unsigned int segment_index = (unsigned int)int_part + 1;
- for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
- segment_index += pathv[i].size() + 1;
- if (pathv[i].closed()) {
- segment_index += 1;
- }
- }
-
- curve->unref();
-
- //find segment to split
- Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
- if (!e) {
- return;
- }
-
- //don't know why but t seems to flip for lines
- if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
- t = 1.0 - t;
- }
-
- Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
- sp_nodepath_node_select(n, FALSE, TRUE);
-
- /* fixme: adjust ? */
- sp_nodepath_update_handles(nodepath);
-
- sp_nodepath_update_repr(nodepath, _("Add node"));
-
- sp_nodepath_update_statusbar(nodepath);
-}
-
-/*
- * Adjusts a segment so that t moves by a certain delta for dragging
- * converts lines to curves
- *
- * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
- * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
- */
-void
-sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, Geom::Point delta)
-{
- Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, node);
-
- //fixme: e and e->p can be NULL, so check for those before proceeding
- g_return_if_fail(e != NULL);
- g_return_if_fail(&e->p != NULL);
-
- if (e->type == Inkscape::NodePath::NODE_AUTO) {
- e->type = Inkscape::NodePath::NODE_SMOOTH;
- sp_nodepath_update_node_knot (e);
- }
- if (e->p.other->type == Inkscape::NodePath::NODE_AUTO) {
- e->p.other->type = Inkscape::NodePath::NODE_SMOOTH;
- sp_nodepath_update_node_knot (e->p.other);
- }
-
- /* feel good is an arbitrary parameter that distributes the delta between handles
- * if t of the drag point is less than 1/6 distance form the endpoint only
- * the corresponding hadle is adjusted. This matches the behavior in GIMP
- */
- double feel_good;
- if (t <= 1.0 / 6.0)
- feel_good = 0;
- else if (t <= 0.5)
- feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
- else if (t <= 5.0 / 6.0)
- feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
- else
- feel_good = 1;
-
- //if we're dragging a line convert it to a curve
- if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
- sp_nodepath_set_line_type(e, NR_CURVETO);
- }
-
- Geom::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
- Geom::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
- e->p.other->n.pos += offsetcoord0;
- e->p.pos += offsetcoord1;
-
- // adjust handles of adjacent nodes where necessary
- sp_node_adjust_handle(e,1);
- sp_node_adjust_handle(e->p.other,-1);
-
- sp_nodepath_update_handles(e->subpath->nodepath);
-
- update_object(e->subpath->nodepath);
-
- sp_nodepath_update_statusbar(e->subpath->nodepath);
-}
-
-
-/**
- * Call sp_nodepath_break() for all selected segments.
- */
-void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
-{
- if (!nodepath) return;
-
- GList *tempin = g_list_copy(nodepath->selected);
- GList *temp = NULL;
- for (GList *l = tempin; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
- if (nn == NULL) continue; // no break, no new node
- temp = g_list_prepend(temp, nn);
- }
- g_list_free(tempin);
-
- if (temp) {
- sp_nodepath_deselect(nodepath);
- }
- for (GList *l = temp; l != NULL; l = l->next) {
- sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
- }
-
- sp_nodepath_update_handles(nodepath);
-
- sp_nodepath_update_repr(nodepath, _("Break path"));
-}
-
-/**
- * Duplicate the selected node(s).
- */
-void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
-{
- if (!nodepath) {
- return;
- }
-
- GList *temp = NULL;
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
- if (nn == NULL) continue; // could not duplicate
- temp = g_list_prepend(temp, nn);
- }
-
- if (temp) {
- sp_nodepath_deselect(nodepath);
- }
- for (GList *l = temp; l != NULL; l = l->next) {
- sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
- }
-
- sp_nodepath_update_handles(nodepath);
-
- sp_nodepath_update_repr(nodepath, _("Duplicate node"));
-}
-
-/**
- * Internal function to join two nodes by merging them into one.
- */
-static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
-{
- /* a and b are endpoints */
-
- // if one of the two nodes is mouseovered, fix its position
- Geom::Point c;
- if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
- c = a->pos;
- } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
- c = b->pos;
- } else {
- // otherwise, move joined node to the midpoint
- c = (a->pos + b->pos) / 2;
- }
-
- if (a->subpath == b->subpath) {
- Inkscape::NodePath::SubPath *sp = a->subpath;
- sp_nodepath_subpath_close(sp);
- sp_node_moveto (sp->first, c);
-
- sp_nodepath_update_handles(sp->nodepath);
- sp_nodepath_update_repr(nodepath, _("Close subpath"));
- return;
- }
-
- /* a and b are separate subpaths */
- Inkscape::NodePath::SubPath *sa = a->subpath;
- Inkscape::NodePath::SubPath *sb = b->subpath;
- Geom::Point p;
- Inkscape::NodePath::Node *n;
- NRPathcode code;
- if (a == sa->first) {
- // we will now reverse sa, so that a is its last node, not first, and drop that node
- p = sa->first->n.pos;
- code = (NRPathcode)sa->first->n.other->code;
- // create new subpath
- Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
- // create a first moveto node on it
- n = sa->last;
- sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
- n = n->p.other;
- if (n == sa->first) n = NULL;
- while (n) {
- // copy the rest of the nodes from sa to t, going backwards
- sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
- n = n->p.other;
- if (n == sa->first) n = NULL;
- }
- // replace sa with t
- sp_nodepath_subpath_destroy(sa);
- sa = t;
- } else if (a == sa->last) {
- // a is already last, just drop it
- p = sa->last->p.pos;
- code = (NRPathcode)sa->last->code;
- sp_nodepath_node_destroy(sa->last);
- } else {
- code = NR_END;
- g_assert_not_reached();
- }
-
- if (b == sb->first) {
- // copy all nodes from b to a, forward
- sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
- for (n = sb->first->n.other; n != NULL; n = n->n.other) {
- sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
- }
- } else if (b == sb->last) {
- // copy all nodes from b to a, backward
- sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
- for (n = sb->last->p.other; n != NULL; n = n->p.other) {
- sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
- }
- } else {
- g_assert_not_reached();
- }
- /* and now destroy sb */
-
- sp_nodepath_subpath_destroy(sb);
-
- sp_nodepath_update_handles(sa->nodepath);
-
- sp_nodepath_update_repr(nodepath, _("Join nodes"));
-
- sp_nodepath_update_statusbar(nodepath);
-}
-
-/**
- * Internal function to join two nodes by adding a segment between them.
- */
-static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
-{
- if (a->subpath == b->subpath) {
- Inkscape::NodePath::SubPath *sp = a->subpath;
-
- /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
- sp->closed = TRUE;
-
- sp->first->p.other = sp->last;
- sp->last->n.other = sp->first;
-
- sp_node_handle_mirror_p_to_n(sp->last);
- sp_node_handle_mirror_n_to_p(sp->first);
-
- sp->first->code = sp->last->code;
- sp->first = sp->last;
-
- sp_nodepath_update_handles(sp->nodepath);
-
- sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
-
- return;
- }
-
- /* a and b are separate subpaths */
- Inkscape::NodePath::SubPath *sa = a->subpath;
- Inkscape::NodePath::SubPath *sb = b->subpath;
-
- Inkscape::NodePath::Node *n;
- Geom::Point p;
- NRPathcode code;
- if (a == sa->first) {
- code = (NRPathcode) sa->first->n.other->code;
- Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
- n = sa->last;
- sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
- for (n = n->p.other; n != NULL; n = n->p.other) {
- sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
- }
- sp_nodepath_subpath_destroy(sa);
- sa = t;
- } else if (a == sa->last) {
- code = (NRPathcode)sa->last->code;
- } else {
- code = NR_END;
- g_assert_not_reached();
- }
-
- if (b == sb->first) {
- n = sb->first;
- sp_node_handle_mirror_p_to_n(sa->last);
- sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
- sp_node_handle_mirror_n_to_p(sa->last);
- for (n = n->n.other; n != NULL; n = n->n.other) {
- sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
- }
- } else if (b == sb->last) {
- n = sb->last;
- sp_node_handle_mirror_p_to_n(sa->last);
- sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
- sp_node_handle_mirror_n_to_p(sa->last);
- for (n = n->p.other; n != NULL; n = n->p.other) {
- sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
- }
- } else {
- g_assert_not_reached();
- }
- /* and now destroy sb */
-
- sp_nodepath_subpath_destroy(sb);
-
- sp_nodepath_update_handles(sa->nodepath);
-
- sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
-}
-
-enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
-
-/**
- * Internal function to handle joining two nodes.
- */
-static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
-{
- if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
-
- if (g_list_length(nodepath->selected) != 2) {
- nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
- return;
- }
-
- Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
- Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
-
- g_assert(a != b);
- if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
- // someone tried to join an orphan node (i.e. a single-node subpath).
- // this is not worth an error message, just fail silently.
- return;
- }
-
- if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
- nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
- return;
- }
-
- switch(mode) {
- case NODE_JOIN_ENDPOINTS:
- do_node_selected_join(nodepath, a, b);
- break;
- case NODE_JOIN_SEGMENT:
- do_node_selected_join_segment(nodepath, a, b);
- break;
- }
-}
-
-/**
- * Join two nodes by merging them into one.
- */
-void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
-{
- node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
-}
-
-/**
- * Join two nodes by adding a segment between them.
- */
-void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
-{
- node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
-}
-
-/**
- * Delete one or more selected nodes and preserve the shape of the path as much as possible.
- */
-void sp_node_delete_preserve(GList *nodes_to_delete)
-{
- GSList *nodepaths = NULL;
-
- while (nodes_to_delete) {
- Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
- Inkscape::NodePath::SubPath *sp = node->subpath;
- Inkscape::NodePath::Path *nodepath = sp->nodepath;
- Inkscape::NodePath::Node *sample_cursor = NULL;
- Inkscape::NodePath::Node *sample_end = NULL;
- Inkscape::NodePath::Node *delete_cursor = node;
- bool just_delete = false;
-
- //find the start of this contiguous selection
- //move left to the first node that is not selected
- //or the start of the non-closed path
- for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
- delete_cursor = curr;
- }
-
- //just delete at the beginning of an open path
- if (!delete_cursor->p.other) {
- sample_cursor = delete_cursor;
- just_delete = true;
- } else {
- sample_cursor = delete_cursor->p.other;
- }
-
- //calculate points for each segment
- int rate = 5;
- float period = 1.0 / rate;
- std::vector<Geom::Point> data;
- if (!just_delete) {
- data.push_back(sample_cursor->pos);
- for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
- //just delete at the end of an open path
- if (!sp->closed && curr == sp->last) {
- just_delete = true;
- break;
- }
-
- //sample points on the contiguous selected segment
- Geom::Point *bez;
- bez = new Geom::Point [4];
- bez[0] = curr->pos;
- bez[1] = curr->n.pos;
- bez[2] = curr->n.other->p.pos;
- bez[3] = curr->n.other->pos;
- for (int i=1; i<rate; i++) {
- gdouble t = i * period;
- Geom::Point p = bezier_pt(3, bez, t);
- data.push_back(p);
- }
- data.push_back(curr->n.other->pos);
-
- sample_end = curr->n.other;
- //break if we've come full circle or hit the end of the selection
- if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
- break;
- }
- }
- }
-
- if (!just_delete) {
- //calculate the best fitting single segment and adjust the endpoints
- Geom::Point *adata;
- adata = new Geom::Point [data.size()];
- copy(data.begin(), data.end(), adata);
-
- Geom::Point *bez;
- bez = new Geom::Point [4];
- //would decreasing error create a better fitting approximation?
- gdouble error = 1.0;
- gint ret;
- ret = Geom::bezier_fit_cubic (bez, adata, data.size(), error);
-
- //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
- //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
- //the resulting nodes behave as expected.
- if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
- sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
- if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
- sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
-
- //adjust endpoints
- sample_cursor->n.pos = bez[1];
- sample_end->p.pos = bez[2];
- }
-
- //destroy this contiguous selection
- while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
- Inkscape::NodePath::Node *temp = delete_cursor;
- if (delete_cursor->n.other == delete_cursor) {
- // delete_cursor->n points to itself, which means this is the last node on a closed subpath
- delete_cursor = NULL;
- } else {
- delete_cursor = delete_cursor->n.other;
- }
- nodes_to_delete = g_list_remove(nodes_to_delete, temp);
- sp_nodepath_node_destroy(temp);
- }
-
- sp_nodepath_update_handles(nodepath);
-
- if (!g_slist_find(nodepaths, nodepath))
- nodepaths = g_slist_prepend (nodepaths, nodepath);
- }
-
- for (GSList *i = nodepaths; i; i = i->next) {
- // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
- // different nodepaths will give us one undo event per nodepath
- Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
-
- // if the entire nodepath is removed, delete the selected object.
- if (nodepath->subpaths == NULL ||
- //FIXME: a closed path CAN legally have one node, it's only an open one which must be
- //at least 2
- sp_nodepath_get_node_count(nodepath) < 2) {
- SPDocument *document = sp_desktop_document (nodepath->desktop);
- //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
- //delete this nodepath's object, not the entire selection! (though at this time, this
- //does not matter)
- sp_selection_delete(nodepath->desktop);
- sp_document_done (document, SP_VERB_CONTEXT_NODE,
- _("Delete nodes"));
- } else {
- sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
- sp_nodepath_update_statusbar(nodepath);
- }
- }
-
- g_slist_free (nodepaths);
-}
-
-/**
- * Delete one or more selected nodes.
- */
-void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
-{
- if (!nodepath) return;
- if (!nodepath->selected) return;
-
- /** \todo fixme: do it the right way */
- while (nodepath->selected) {
- Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
- sp_nodepath_node_destroy(node);
- }
-
-
- //clean up the nodepath (such as for trivial subpaths)
- sp_nodepath_cleanup(nodepath);
-
- sp_nodepath_update_handles(nodepath);
-
- // if the entire nodepath is removed, delete the selected object.
- if (nodepath->subpaths == NULL ||
- sp_nodepath_get_node_count(nodepath) < 2) {
- SPDocument *document = sp_desktop_document (nodepath->desktop);
- sp_selection_delete(nodepath->desktop);
- sp_document_done (document, SP_VERB_CONTEXT_NODE,
- _("Delete nodes"));
- return;
- }
-
- sp_nodepath_update_repr(nodepath, _("Delete nodes"));
-
- sp_nodepath_update_statusbar(nodepath);
-}
-
-/**
- * Delete one or more segments between two selected nodes.
- * This is the code for 'split'.
- */
-void
-sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
-{
- Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
- Inkscape::NodePath::Node *curr, *next; //Iterators
-
- if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
-
- if (g_list_length(nodepath->selected) != 2) {
- nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
- _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
- return;
- }
-
- //Selected nodes, not inclusive
- Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
- Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
-
- if ( ( a==b) || //same node
- (a->subpath != b->subpath ) || //not the same path
- (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
- (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
- {
- nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
- _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
- return;
- }
-
- //###########################################
- //# BEGIN EDITS
- //###########################################
- //##################################
- //# CLOSED PATH
- //##################################
- if (a->subpath->closed) {
-
-
- gboolean reversed = FALSE;
-
- //Since we can go in a circle, we need to find the shorter distance.
- // a->b or b->a
- start = end = NULL;
- int distance = 0;
- int minDistance = 0;
- for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
- if (curr==b) {
- //printf("a to b:%d\n", distance);
- start = a;//go from a to b
- end = b;
- minDistance = distance;
- //printf("A to B :\n");
- break;
- }
- distance++;
- }
-
- //try again, the other direction
- distance = 0;
- for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
- if (curr==a) {
- //printf("b to a:%d\n", distance);
- if (distance < minDistance) {
- start = b; //we go from b to a
- end = a;
- reversed = TRUE;
- //printf("B to A\n");
- }
- break;
- }
- distance++;
- }
-
-
- //Copy everything from 'end' to 'start' to a new subpath
- Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
- for (curr=end ; curr ; curr=curr->n.other) {
- NRPathcode code = (NRPathcode) curr->code;
- if (curr == end)
- code = NR_MOVETO;
- sp_nodepath_node_new(t, NULL,
- (Inkscape::NodePath::NodeType)curr->type, code,
- &curr->p.pos, &curr->pos, &curr->n.pos);
- if (curr == start)
- break;
- }
- sp_nodepath_subpath_destroy(a->subpath);
-
-
- }
-
-
-
- //##################################
- //# OPEN PATH
- //##################################
- else {
-
- //We need to get the direction of the list between A and B
- //Can we walk from a to b?
- start = end = NULL;
- for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
- if (curr==b) {
- start = a; //did it! we go from a to b
- end = b;
- //printf("A to B\n");
- break;
- }
- }
- if (!start) {//didn't work? let's try the other direction
- for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
- if (curr==a) {
- start = b; //did it! we go from b to a
- end = a;
- //printf("B to A\n");
- break;
- }
- }
- }
- if (!start) {
- nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
- _("Cannot find path between nodes."));
- return;
- }
-
-
-
- //Copy everything after 'end' to a new subpath
- Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
- for (curr=end ; curr ; curr=curr->n.other) {
- NRPathcode code = (NRPathcode) curr->code;
- if (curr == end)
- code = NR_MOVETO;
- sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
- &curr->p.pos, &curr->pos, &curr->n.pos);
- }
-
- //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
- for (curr = start->n.other ; curr ; curr=next) {
- next = curr->n.other;
- sp_nodepath_node_destroy(curr);
- }
-
- }
- //###########################################
- //# END EDITS
- //###########################################
-
- //clean up the nodepath (such as for trivial subpaths)
- sp_nodepath_cleanup(nodepath);
-
- sp_nodepath_update_handles(nodepath);
-
- sp_nodepath_update_repr(nodepath, _("Delete segment"));
-
- sp_nodepath_update_statusbar(nodepath);
-}
-
-/**
- * Call sp_nodepath_set_line() for all selected segments.
- */
-void
-sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
-{
- if (nodepath == NULL) return;
-
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- g_assert(n->selected);
- if (n->p.other && n->p.other->selected) {
- sp_nodepath_set_line_type(n, code);
- }
- }
-
- sp_nodepath_update_repr(nodepath, _("Change segment type"));
-}
-
-/**
- * Call sp_nodepath_convert_node_type() for all selected nodes.
- */
-void
-sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
-{
- if (nodepath == NULL) return;
-
- if (nodepath->straight_path) return; // don't change type when it is a straight path!
-
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
- }
-
- sp_nodepath_update_repr(nodepath, _("Change node type"));
-}
-
-/**
- * Change select status of node, update its own and neighbour handles.
- */
-static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
-{
- node->selected = selected;
-
- if (selected) {
- node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 11 : 9);
- node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
- node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
- sp_knot_update_ctrl(node->knot);
- } else {
- node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 9 : 7);
- node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
- node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
- sp_knot_update_ctrl(node->knot);
- }
-
- sp_node_update_handles(node);
- if (node->n.other) sp_node_update_handles(node->n.other);
- if (node->p.other) sp_node_update_handles(node->p.other);
-}
-
-/**
-\brief Select a node
-\param node The node to select
-\param incremental If true, add to selection, otherwise deselect others
-\param override If true, always select this node, otherwise toggle selected status
-*/
-static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
-{
- Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
-
- if (incremental) {
- if (override) {
- if (!g_list_find(nodepath->selected, node)) {
- nodepath->selected = g_list_prepend(nodepath->selected, node);
- }
- sp_node_set_selected(node, TRUE);
- } else { // toggle
- if (node->selected) {
- g_assert(g_list_find(nodepath->selected, node));
- nodepath->selected = g_list_remove(nodepath->selected, node);
- } else {
- g_assert(!g_list_find(nodepath->selected, node));
- nodepath->selected = g_list_prepend(nodepath->selected, node);
- }
- sp_node_set_selected(node, !node->selected);
- }
- } else {
- sp_nodepath_deselect(nodepath);
- nodepath->selected = g_list_prepend(nodepath->selected, node);
- sp_node_set_selected(node, TRUE);
- }
-
- sp_nodepath_update_statusbar(nodepath);
-}
-
-
-/**
-\brief Deselect all nodes in the nodepath
-*/
-void
-sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
-{
- if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
-
- while (nodepath->selected) {
- sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
- nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
- }
- sp_nodepath_update_statusbar(nodepath);
-}
-
-/**
-\brief Select or invert selection of all nodes in the nodepath
-*/
-void
-sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
-{
- if (!nodepath) return;
-
- for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
- for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
- Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
- sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
- }
- }
-}
-
-/**
- * If nothing selected, does the same as sp_nodepath_select_all();
- * otherwise selects/inverts all nodes in all subpaths that have selected nodes
- * (i.e., similar to "select all in layer", with the "selected" subpaths
- * being treated as "layers" in the path).
- */
-void
-sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
-{
- if (!nodepath) return;
-
- if (g_list_length (nodepath->selected) == 0) {
- sp_nodepath_select_all (nodepath, invert);
- return;
- }
-
- GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
- GSList *subpaths = NULL;
-
- for (GList *l = copy; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- Inkscape::NodePath::SubPath *subpath = n->subpath;
- if (!g_slist_find (subpaths, subpath))
- subpaths = g_slist_prepend (subpaths, subpath);
- }
-
- for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
- for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
- Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
- sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
- }
- }
-
- g_slist_free (subpaths);
- g_list_free (copy);
-}
-
-/**
- * \brief Select the node after the last selected; if none is selected,
- * select the first within path.
- */
-void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
-{
- if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
-
- Inkscape::NodePath::Node *last = NULL;
- if (nodepath->selected) {
- for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
- Inkscape::NodePath::SubPath *subpath, *subpath_next;
- subpath = (Inkscape::NodePath::SubPath *) spl->data;
- for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
- Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
- if (node->selected) {
- if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
- if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
- if (spl->next) { // there's a next subpath
- subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
- last = subpath_next->first;
- } else if (spl->prev) { // there's a previous subpath
- last = NULL; // to be set later to the first node of first subpath
- } else {
- last = node->n.other;
- }
- } else {
- last = node->n.other;
- }
- } else {
- if (node->n.other) {
- last = node->n.other;
- } else {
- if (spl->next) { // there's a next subpath
- subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
- last = subpath_next->first;
- } else if (spl->prev) { // there's a previous subpath
- last = NULL; // to be set later to the first node of first subpath
- } else {
- last = (Inkscape::NodePath::Node *) subpath->first;
- }
- }
- }
- }
- }
- }
- sp_nodepath_deselect(nodepath);
- }
-
- if (last) { // there's at least one more node after selected
- sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
- } else { // no more nodes, select the first one in first subpath
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
- sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
- }
-}
-
-/**
- * \brief Select the node before the first selected; if none is selected,
- * select the last within path
- */
-void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
-{
- if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
-
- Inkscape::NodePath::Node *last = NULL;
- if (nodepath->selected) {
- for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
- for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
- Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
- if (node->selected) {
- if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
- if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
- if (spl->prev) { // there's a prev subpath
- Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
- last = subpath_prev->last;
- } else if (spl->next) { // there's a next subpath
- last = NULL; // to be set later to the last node of last subpath
- } else {
- last = node->p.other;
- }
- } else {
- last = node->p.other;
- }
- } else {
- if (node->p.other) {
- last = node->p.other;
- } else {
- if (spl->prev) { // there's a prev subpath
- Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
- last = subpath_prev->last;
- } else if (spl->next) { // there's a next subpath
- last = NULL; // to be set later to the last node of last subpath
- } else {
- last = (Inkscape::NodePath::Node *) subpath->last;
- }
- }
- }
- }
- }
- }
- sp_nodepath_deselect(nodepath);
- }
-
- if (last) { // there's at least one more node before selected
- sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
- } else { // no more nodes, select the last one in last subpath
- GList *spl = g_list_last(nodepath->subpaths);
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
- sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
- }
-}
-
-/**
- * \brief Select all nodes that are within the rectangle.
- */
-void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, Geom::Rect const &b, gboolean incremental)
-{
- if (!incremental) {
- sp_nodepath_deselect(nodepath);
- }
-
- for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
- for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
- Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
-
- if (b.contains(node->pos)) {
- sp_nodepath_node_select(node, TRUE, TRUE);
- }
- }
- }
-}
-
-
-void
-nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
-{
- g_assert (n);
- g_assert (nodepath);
- g_assert (n->subpath->nodepath == nodepath);
-
- if (g_list_length (nodepath->selected) == 0) {
- if (grow > 0) {
- sp_nodepath_node_select(n, TRUE, TRUE);
- }
- return;
- }
-
- if (g_list_length (nodepath->selected) == 1) {
- if (grow < 0) {
- sp_nodepath_deselect (nodepath);
- return;
- }
- }
-
- double n_sel_range = 0, p_sel_range = 0;
- Inkscape::NodePath::Node *farthest_n_node = n;
- Inkscape::NodePath::Node *farthest_p_node = n;
-
- // Calculate ranges
- {
- double n_range = 0, p_range = 0;
- bool n_going = true, p_going = true;
- Inkscape::NodePath::Node *n_node = n;
- Inkscape::NodePath::Node *p_node = n;
- do {
- // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
- if (n_node && n_going)
- n_node = n_node->n.other;
- if (n_node == NULL) {
- n_going = false;
- } else {
- n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
- if (n_node->selected) {
- n_sel_range = n_range;
- farthest_n_node = n_node;
- }
- if (n_node == p_node) {
- n_going = false;
- p_going = false;
- }
- }
- if (p_node && p_going)
- p_node = p_node->p.other;
- if (p_node == NULL) {
- p_going = false;
- } else {
- p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
- if (p_node->selected) {
- p_sel_range = p_range;
- farthest_p_node = p_node;
- }
- if (p_node == n_node) {
- n_going = false;
- p_going = false;
- }
- }
- } while (n_going || p_going);
- }
-
- if (grow > 0) {
- if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
- sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
- } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
- sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
- }
- } else {
- if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
- sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
- } else if (farthest_p_node && farthest_p_node->selected) {
- sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
- }
- }
-}
-
-void
-nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
-{
- g_assert (n);
- g_assert (nodepath);
- g_assert (n->subpath->nodepath == nodepath);
-
- if (g_list_length (nodepath->selected) == 0) {
- if (grow > 0) {
- sp_nodepath_node_select(n, TRUE, TRUE);
- }
- return;
- }
-
- if (g_list_length (nodepath->selected) == 1) {
- if (grow < 0) {
- sp_nodepath_deselect (nodepath);
- return;
- }
- }
-
- Inkscape::NodePath::Node *farthest_selected = NULL;
- double farthest_dist = 0;
-
- Inkscape::NodePath::Node *closest_unselected = NULL;
- double closest_dist = NR_HUGE;
-
- for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
- for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
- Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
- if (node == n)
- continue;
- if (node->selected) {
- if (Geom::L2(node->pos - n->pos) > farthest_dist) {
- farthest_dist = Geom::L2(node->pos - n->pos);
- farthest_selected = node;
- }
- } else {
- if (Geom::L2(node->pos - n->pos) < closest_dist) {
- closest_dist = Geom::L2(node->pos - n->pos);
- closest_unselected = node;
- }
- }
- }
- }
-
- if (grow > 0) {
- if (closest_unselected) {
- sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
- }
- } else {
- if (farthest_selected) {
- sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
- }
- }
-}
-
-
-/**
-\brief Saves all nodes' and handles' current positions in their origin members
-*/
-void
-sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
-{
- for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
- for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
- n->origin = n->pos;
- n->p.origin = n->p.pos;
- n->n.origin = n->n.pos;
- }
- }
-}
-
-/**
-\brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
-*/
-GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
-{
- GList *r = NULL;
- if (nodepath->selected) {
- guint i = 0;
- for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
- for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
- Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
- i++;
- if (node->selected) {
- r = g_list_append(r, GINT_TO_POINTER(i));
- }
- }
- }
- }
- return r;
-}
-
-/**
-\brief Restores selection by selecting nodes whose positions are in the list
-*/
-void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
-{
- sp_nodepath_deselect(nodepath);
-
- guint i = 0;
- for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
- Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
- for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
- Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
- i++;
- if (g_list_find(r, GINT_TO_POINTER(i))) {
- sp_nodepath_node_select(node, TRUE, TRUE);
- }
- }
- }
-}
-
-
-/**
-\brief Adjusts handle according to node type and line code.
-*/
-static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
-{
- g_assert(node);
-
- // nothing to do for auto nodes (sp_node_adjust_handles() does the job)
- if (node->type == Inkscape::NodePath::NODE_AUTO)
- return;
-
- Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
- Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
-
- // nothing to do if we are an end node
- if (me->other == NULL) return;
- if (other->other == NULL) return;
-
- // nothing to do if we are a cusp node
- if (node->type == Inkscape::NodePath::NODE_CUSP) return;
-
- // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
- NRPathcode mecode;
- if (which_adjust == 1) {
- mecode = (NRPathcode)me->other->code;
- } else {
- mecode = (NRPathcode)node->code;
- }
- if (mecode == NR_LINETO) return;
-
- if (sp_node_side_is_line(node, other)) {
- // other is a line, and we are either smooth or symm
- Inkscape::NodePath::Node *othernode = other->other;
- double len = Geom::L2(me->pos - node->pos);
- Geom::Point delta = node->pos - othernode->pos;
- double linelen = Geom::L2(delta);
- if (linelen < 1e-18)
- return;
- me->pos = node->pos + (len / linelen)*delta;
- return;
- }
-
- if (node->type == Inkscape::NodePath::NODE_SYMM) {
- // symmetrize
- me->pos = 2 * node->pos - other->pos;
- return;
- } else {
- // smoothify
- double len = Geom::L2(me->pos - node->pos);
- Geom::Point delta = other->pos - node->pos;
- double otherlen = Geom::L2(delta);
- if (otherlen < 1e-18) return;
- me->pos = node->pos - (len / otherlen) * delta;
- }
-}
-
-/**
- \brief Adjusts both handles according to node type and line code
- */
-static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
-{
- g_assert(node);
-
- if (node->type == Inkscape::NodePath::NODE_CUSP) return;
-
- /* we are either smooth or symm */
-
- if (node->p.other == NULL) return;
- if (node->n.other == NULL) return;
-
- if (node->type == Inkscape::NodePath::NODE_AUTO) {
- sp_node_adjust_handles_auto(node);
- return;
- }
-
- if (sp_node_side_is_line(node, &node->p)) {
- sp_node_adjust_handle(node, 1);
- return;
- }
-
- if (sp_node_side_is_line(node, &node->n)) {
- sp_node_adjust_handle(node, -1);
- return;
- }
-
- /* both are curves */
- Geom::Point const delta( node->n.pos - node->p.pos );
-
- if (node->type == Inkscape::NodePath::NODE_SYMM) {
- node->p.pos = node->pos - delta / 2;
- node->n.pos = node->pos + delta / 2;
- return;
- }
-
- /* We are smooth */
- double plen = Geom::L2(node->p.pos - node->pos);
- if (plen < 1e-18) return;
- double nlen = Geom::L2(node->n.pos - node->pos);
- if (nlen < 1e-18) return;
- node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
- node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
-}
-
-static void sp_node_adjust_handles_auto(Inkscape::NodePath::Node *node)
-{
- if (node->p.other == NULL || node->n.other == NULL) {
- node->p.pos = node->pos;
- node->n.pos = node->pos;
- return;
- }
-
- Geom::Point leg_prev = to_2geom(node->p.other->pos - node->pos);
- Geom::Point leg_next = to_2geom(node->n.other->pos - node->pos);
-
- double norm_leg_prev = Geom::L2(leg_prev);
- double norm_leg_next = Geom::L2(leg_next);
-
- Geom::Point delta;
- if (norm_leg_next > 0.0) {
- delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
- delta.normalize();
- }
-
- node->p.pos = node->pos - norm_leg_prev / 3 * delta;
- node->n.pos = node->pos + norm_leg_next / 3 * delta;
-}
-
-/**
- * Node event callback.
- */
-static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
-{
- gboolean ret = FALSE;
- switch (event->type) {
- case GDK_ENTER_NOTIFY:
- Inkscape::NodePath::Path::active_node = n;
- break;
- case GDK_LEAVE_NOTIFY:
- Inkscape::NodePath::Path::active_node = NULL;
- break;
- case GDK_SCROLL:
- if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
- switch (event->scroll.direction) {
- case GDK_SCROLL_UP:
- nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
- break;
- case GDK_SCROLL_DOWN:
- nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
- break;
- default:
- break;
- }
- ret = TRUE;
- } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
- switch (event->scroll.direction) {
- case GDK_SCROLL_UP:
- nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
- break;
- case GDK_SCROLL_DOWN:
- nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
- break;
- default:
- break;
- }
- ret = TRUE;
- }
- break;
- case GDK_KEY_PRESS:
- switch (get_group0_keyval (&event->key)) {
- case GDK_space:
- if (event->key.state & GDK_BUTTON1_MASK) {
- Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
- stamp_repr(nodepath);
- ret = TRUE;
- }
- break;
- case GDK_Page_Up:
- if (event->key.state & GDK_CONTROL_MASK) {
- nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
- } else {
- nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
- }
- break;
- case GDK_Page_Down:
- if (event->key.state & GDK_CONTROL_MASK) {
- nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
- } else {
- nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
- }
- break;
- default:
- break;
- }
- break;
- default:
- break;
- }
-
- return ret;
-}
-
-/**
- * Handle keypress on node; directly called.
- */
-gboolean node_key(GdkEvent *event)
-{
- Inkscape::NodePath::Path *np;
-
- // there is no way to verify nodes so set active_node to nil when deleting!!
- if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
-
- if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
- gint ret = FALSE;
- switch (get_group0_keyval (&event->key)) {
- /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
- case GDK_BackSpace:
- np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
- sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
- sp_nodepath_update_repr(np, _("Delete node"));
- Inkscape::NodePath::Path::active_node = NULL;
- ret = TRUE;
- break;
- case GDK_c:
- sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
- ret = TRUE;
- break;
- case GDK_s:
- sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
- ret = TRUE;
- break;
- case GDK_a:
- sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_AUTO);
- ret = TRUE;
- break;
- case GDK_y:
- sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
- ret = TRUE;
- break;
- case GDK_b:
- sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
- ret = TRUE;
- break;
- }
- return ret;
- }
- return FALSE;
-}
-
-/**
- * Mouseclick on node callback.
- */
-static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
-{
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
-
- if (state & GDK_CONTROL_MASK) {
- Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
-
- if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
- if (n->type == Inkscape::NodePath::NODE_CUSP) {
- sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
- } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
- sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
- } else if (n->type == Inkscape::NodePath::NODE_SYMM) {
- sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_AUTO);
- } else {
- sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
- }
- sp_nodepath_update_repr(nodepath, _("Change node type"));
- sp_nodepath_update_statusbar(nodepath);
-
- } else { //ctrl+alt+click: delete node
- GList *node_to_delete = NULL;
- node_to_delete = g_list_append(node_to_delete, n);
- sp_node_delete_preserve(node_to_delete);
- }
-
- } else {
- sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
- }
-}
-
-/**
- * Mouse grabbed node callback.
- */
-static void node_grabbed(SPKnot *knot, guint state, gpointer data)
-{
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
-
- if (!n->selected) {
- sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
- }
-
- n->is_dragging = true;
- // Reconstruct and store the location of the mouse pointer at the time when we started dragging (needed for snapping)
- n->subpath->nodepath->drag_origin_mouse = knot->grabbed_rel_pos + knot->drag_origin;
-
- sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
-
- sp_nodepath_remember_origins (n->subpath->nodepath);
-}
-
-/**
- * Mouse ungrabbed node callback.
- */
-static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
-{
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
-
- n->dragging_out = NULL;
- n->is_dragging = false;
- n->subpath->nodepath->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE);
- sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
-
- sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
-}
-
-/**
- * The point on a line, given by its angle, closest to the given point.
- * \param p A point.
- * \param a Angle of the line; it is assumed to go through coordinate origin.
- * \param closest Pointer to the point struct where the result is stored.
- * \todo FIXME: use dot product perhaps?
- */
-static void point_line_closest(Geom::Point *p, double a, Geom::Point *closest)
-{
- if (a == HUGE_VAL) { // vertical
- *closest = Geom::Point(0, (*p)[Geom::Y]);
- } else {
- (*closest)[Geom::X] = ( a * (*p)[Geom::Y] + (*p)[Geom::X]) / (a*a + 1);
- (*closest)[Geom::Y] = a * (*closest)[Geom::X];
- }
-}
-
-/**
- * Distance from the point to a line given by its angle.
- * \param p A point.
- * \param a Angle of the line; it is assumed to go through coordinate origin.
- */
-static double point_line_distance(Geom::Point *p, double a)
-{
- Geom::Point c;
- point_line_closest(p, a, &c);
- return sqrt(((*p)[Geom::X] - c[Geom::X])*((*p)[Geom::X] - c[Geom::X]) + ((*p)[Geom::Y] - c[Geom::Y])*((*p)[Geom::Y] - c[Geom::Y]));
-}
-
-/**
- * Callback for node "request" signal.
- * \todo fixme: This goes to "moved" event? (lauris)
- */
-static gboolean
-node_request(SPKnot */*knot*/, Geom::Point const &p, guint state, gpointer data)
-{
- double yn, xn, yp, xp;
- double an, ap, na, pa;
- double d_an, d_ap, d_na, d_pa;
- gboolean collinear = FALSE;
- Geom::Point c;
- Geom::Point pr;
-
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
-
- n->subpath->nodepath->desktop->snapindicator->remove_snaptarget();
-
- // If either (Shift and some handle retracted), or (we're already dragging out a handle)
- if ( (!n->subpath->nodepath->straight_path) &&
- ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
- || n->dragging_out ) )
- {
- Geom::Point mouse = p;
-
- if (!n->dragging_out) {
- // This is the first drag-out event; find out which handle to drag out
- double appr_n = (n->n.other ? Geom::L2(n->n.other->pos - n->pos) - Geom::L2(n->n.other->pos - p) : -HUGE_VAL);
- double appr_p = (n->p.other ? Geom::L2(n->p.other->pos - n->pos) - Geom::L2(n->p.other->pos - p) : -HUGE_VAL);
-
- if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
- return FALSE;
-
- Inkscape::NodePath::NodeSide *opposite;
- if (appr_p > appr_n) { // closer to p
- n->dragging_out = &n->p;
- opposite = &n->n;
- n->code = NR_CURVETO;
- } else if (appr_p < appr_n) { // closer to n
- n->dragging_out = &n->n;
- opposite = &n->p;
- n->n.other->code = NR_CURVETO;
- } else { // p and n nodes are the same
- if (n->n.pos != n->pos) { // n handle already dragged, drag p
- n->dragging_out = &n->p;
- opposite = &n->n;
- n->code = NR_CURVETO;
- } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
- n->dragging_out = &n->n;
- opposite = &n->p;
- n->n.other->code = NR_CURVETO;
- } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
- double appr_other_n = (n->n.other ? Geom::L2(n->n.other->n.pos - n->pos) - Geom::L2(n->n.other->n.pos - p) : -HUGE_VAL);
- double appr_other_p = (n->n.other ? Geom::L2(n->n.other->p.pos - n->pos) - Geom::L2(n->n.other->p.pos - p) : -HUGE_VAL);
- if (appr_other_p > appr_other_n) { // closer to other's p handle
- n->dragging_out = &n->n;
- opposite = &n->p;
- n->n.other->code = NR_CURVETO;
- } else { // closer to other's n handle
- n->dragging_out = &n->p;
- opposite = &n->n;
- n->code = NR_CURVETO;
- }
- }
- }
-
- // if there's another handle, make sure the one we drag out starts parallel to it
- if (opposite->pos != n->pos) {
- mouse = n->pos - Geom::L2(mouse - n->pos) * Geom::unit_vector(opposite->pos - n->pos);
- }
-
- // knots might not be created yet!
- sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
- sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
- }
-
- // pass this on to the handle-moved callback
- node_handle_moved(n->dragging_out->knot, mouse, state, (gpointer) n);
- sp_node_update_handles(n);
- return TRUE;
- }
-
- if (state & GDK_CONTROL_MASK) { // constrained motion
-
- // calculate relative distances of handles
- // n handle:
- yn = n->n.pos[Geom::Y] - n->pos[Geom::Y];
- xn = n->n.pos[Geom::X] - n->pos[Geom::X];
- // if there's no n handle (straight line), see if we can use the direction to the next point on path
- if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
- if (n->n.other) { // if there is the next point
- if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
- yn = n->n.other->origin[Geom::Y] - n->origin[Geom::Y]; // use origin because otherwise the direction will change as you drag
- xn = n->n.other->origin[Geom::X] - n->origin[Geom::X];
- }
- }
- if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
- if (yn < 0) { xn = -xn; yn = -yn; }
-
- // p handle:
- yp = n->p.pos[Geom::Y] - n->pos[Geom::Y];
- xp = n->p.pos[Geom::X] - n->pos[Geom::X];
- // if there's no p handle (straight line), see if we can use the direction to the prev point on path
- if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
- if (n->p.other) {
- if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
- yp = n->p.other->origin[Geom::Y] - n->origin[Geom::Y];
- xp = n->p.other->origin[Geom::X] - n->origin[Geom::X];
- }
- }
- if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
- if (yp < 0) { xp = -xp; yp = -yp; }
-
- if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
- // sliding on handles, only if at least one of the handles is non-vertical
- // (otherwise it's the same as ctrl+drag anyway)
-
- // calculate angles of the handles
- if (xn == 0) {
- if (yn == 0) { // no handle, consider it the continuation of the other one
- an = 0;
- collinear = TRUE;
- }
- else an = 0; // vertical; set the angle to horizontal
- } else an = yn/xn;
-
- if (xp == 0) {
- if (yp == 0) { // no handle, consider it the continuation of the other one
- ap = an;
- }
- else ap = 0; // vertical; set the angle to horizontal
- } else ap = yp/xp;
-
- if (collinear) an = ap;
-
- // angles of the perpendiculars; HUGE_VAL means vertical
- if (an == 0) na = HUGE_VAL; else na = -1/an;
- if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
-
- // mouse point relative to the node's original pos
- pr = p - n->origin;
-
- // distances to the four lines (two handles and two perpendiculars)
- d_an = point_line_distance(&pr, an);
- d_na = point_line_distance(&pr, na);
- d_ap = point_line_distance(&pr, ap);
- d_pa = point_line_distance(&pr, pa);
-
- // find out which line is the closest, save its closest point in c
- if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
- point_line_closest(&pr, an, &c);
- } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
- point_line_closest(&pr, ap, &c);
- } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
- point_line_closest(&pr, na, &c);
- } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
- point_line_closest(&pr, pa, &c);
- }
-
- // move the node to the closest point
- sp_nodepath_selected_nodes_move(n->subpath->nodepath,
- n->origin[Geom::X] + c[Geom::X] - n->pos[Geom::X],
- n->origin[Geom::Y] + c[Geom::Y] - n->pos[Geom::Y],
- true);
-
- } else { // constraining to hor/vert
-
- if (fabs(p[Geom::X] - n->origin[Geom::X]) > fabs(p[Geom::Y] - n->origin[Geom::Y])) { // snap to hor
- sp_nodepath_selected_nodes_move(n->subpath->nodepath,
- p[Geom::X] - n->pos[Geom::X],
- n->origin[Geom::Y] - n->pos[Geom::Y],
- true,
- true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::X]));
- } else { // snap to vert
- sp_nodepath_selected_nodes_move(n->subpath->nodepath,
- n->origin[Geom::X] - n->pos[Geom::X],
- p[Geom::Y] - n->pos[Geom::Y],
- true,
- true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::Y]));
- }
- }
- } else { // move freely
- if (n->is_dragging) {
- if (state & GDK_MOD1_MASK) { // sculpt
- sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, p - n->origin);
- } else {
- sp_nodepath_selected_nodes_move(n->subpath->nodepath,
- p[Geom::X] - n->pos[Geom::X],
- p[Geom::Y] - n->pos[Geom::Y],
- (state & GDK_SHIFT_MASK) == 0);
- }
- }
- }
-
- n->subpath->nodepath->desktop->scroll_to_point(p);
-
- return TRUE;
-}
-
-/**
- * Node handle clicked callback.
- */
-static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
-{
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
-
- if (state & GDK_CONTROL_MASK) { // "delete" handle
- if (n->p.knot == knot) {
- n->p.pos = n->pos;
- } else if (n->n.knot == knot) {
- n->n.pos = n->pos;
- }
- sp_node_update_handles(n);
- Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
- sp_nodepath_update_repr(nodepath, _("Retract handle"));
- sp_nodepath_update_statusbar(nodepath);
-
- } else { // just select or add to selection, depending in Shift
- sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
- }
-}
-
-/**
- * Node handle grabbed callback.
- */
-static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
-{
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
-
- // convert auto -> smooth when dragging handle
- if (n->type == Inkscape::NodePath::NODE_AUTO) {
- n->type = Inkscape::NodePath::NODE_SMOOTH;
- sp_nodepath_update_node_knot (n);
- }
-
- if (!n->selected) {
- sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
- }
-
- // remember the origin point of the handle
- if (n->p.knot == knot) {
- n->p.origin_radial = n->p.pos - n->pos;
- } else if (n->n.knot == knot) {
- n->n.origin_radial = n->n.pos - n->pos;
- } else {
- g_assert_not_reached();
- }
-
- sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
-}
-
-/**
- * Node handle ungrabbed callback.
- */
-static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
-{
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
-
- // forget origin and set knot position once more (because it can be wrong now due to restrictions)
- if (n->p.knot == knot) {
- n->p.origin_radial.a = 0;
- sp_knot_set_position(knot, n->p.pos, state);
- } else if (n->n.knot == knot) {
- n->n.origin_radial.a = 0;
- sp_knot_set_position(knot, n->n.pos, state);
- } else {
- g_assert_not_reached();
- }
-
- sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
-}
-
-/**
- * Node handle "request" signal callback.
- */
-static gboolean node_handle_request(SPKnot *knot, Geom::Point &p, guint state, gpointer data)
-{
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
-
- Inkscape::NodePath::NodeSide *me, *opposite;
- gint which;
- if (n->p.knot == knot) {
- me = &n->p;
- opposite = &n->n;
- which = -1;
- } else if (n->n.knot == knot) {
- me = &n->n;
- opposite = &n->p;
- which = 1;
- } else {
- me = opposite = NULL;
- which = 0;
- g_assert_not_reached();
- }
-
- SPDesktop *desktop = n->subpath->nodepath->desktop;
- SnapManager &m = desktop->namedview->snap_manager;
- m.setup(desktop, true, n->subpath->nodepath->item);
- Inkscape::SnappedPoint s;
-
- if ((state & GDK_SHIFT_MASK) != 0) {
- // We will not try to snap when the shift-key is pressed
- // so remove the old snap indicator and don't wait for it to time-out
- desktop->snapindicator->remove_snaptarget();
- }
-
- Inkscape::NodePath::Node *othernode = opposite->other;
- Inkscape::SnapSourceType source_type = (n->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPSOURCE_NODE_SMOOTH : Inkscape::SNAPSOURCE_NODE_CUSP);
- if (othernode) {
- if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
- /* We are smooth node adjacent with line */
- Geom::Point const delta = p - n->pos;
- Geom::Coord const len = Geom::L2(delta);
- Inkscape::NodePath::Node *othernode = opposite->other;
- Geom::Point const ndelta = n->pos - othernode->pos;
- Geom::Coord const linelen = Geom::L2(ndelta);
- if (len > NR_EPSILON && linelen > NR_EPSILON) {
- Geom::Coord const scal = dot(delta, ndelta) / linelen;
- p = n->pos + (scal / linelen) * ndelta;
- }
- if ((state & GDK_SHIFT_MASK) == 0) {
- s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(p, source_type), Inkscape::Snapper::ConstraintLine(p, ndelta));
- }
- } else {
- if ((state & GDK_SHIFT_MASK) == 0) {
- s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(p, source_type));
- }
- }
- } else {
- if ((state & GDK_SHIFT_MASK) == 0) {
- s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(p, source_type));
- }
- }
-
- s.getPoint(p);
-
- sp_node_adjust_handle(n, -which);
-
- return FALSE;
-}
-
-/**
- * Node handle moved callback.
- */
-static void node_handle_moved(SPKnot *knot, Geom::Point const &p, guint state, gpointer data)
-{
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
-
- Inkscape::NodePath::NodeSide *me;
- Inkscape::NodePath::NodeSide *other;
- if (n->p.knot == knot) {
- me = &n->p;
- other = &n->n;
- } else if (n->n.knot == knot) {
- me = &n->n;
- other = &n->p;
- } else {
- me = NULL;
- other = NULL;
- g_assert_not_reached();
- }
-
- // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
- Radial rme(me->pos - n->pos);
- Radial rother(other->pos - n->pos);
- Radial rnew(p - n->pos);
-
- if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
- int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
- /* 0 interpreted as "no snapping". */
-
- // 1. Snap to the closest PI/snaps angle, starting from zero.
- double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
-
- // 2. Snap to the original angle, its opposite and perpendiculars
- if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
- /* The closest PI/2 angle, starting from original angle */
- double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
-
- // Snap to the closest.
- a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
- ? a_snapped
- : a_ortho );
- }
-
- // 3. Snap to the angle of the opposite line, if any
- Inkscape::NodePath::Node *othernode = other->other;
- if (othernode) {
- Geom::Point other_to_snap(0,0);
- if (sp_node_side_is_line(n, other)) {
- other_to_snap = othernode->pos - n->pos;
- } else {
- other_to_snap = other->pos - n->pos;
- }
- if (Geom::L2(other_to_snap) > 1e-3) {
- Radial rother_to_snap(other_to_snap);
- /* The closest PI/2 angle, starting from the angle of the opposite line segment */
- double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
-
- // Snap to the closest.
- a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
- ? a_snapped
- : a_oppo );
- }
- }
-
- rnew.a = a_snapped;
- }
-
- if (state & GDK_MOD1_MASK) {
- // lock handle length
- rnew.r = me->origin_radial.r;
- }
-
- if (( n->type !=Inkscape::NodePath::NODE_CUSP || (!n->dragging_out && (state & GDK_SHIFT_MASK)))
- && (rme.a != HUGE_VAL) && (rnew.a != HUGE_VAL) && ((fabs(rme.a - rnew.a) > 0.001) || (n->type ==Inkscape::NodePath::NODE_SYMM))) {
- // rotate the other handle correspondingly, if both old and new angles exist and are not the same
- rother.a += rnew.a - rme.a;
- other->pos = Geom::Point(rother) + n->pos;
- if (other->knot) {
- sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
- sp_knot_moveto(other->knot, other->pos);
- }
- }
-
- me->pos = Geom::Point(rnew) + n->pos;
- sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
-
- // move knot, but without emitting the signal:
- // we cannot emit a "moved" signal because we're now processing it
- sp_knot_moveto(me->knot, me->pos);
-
- update_object(n->subpath->nodepath);
-
- /* status text */
- SPDesktop *desktop = n->subpath->nodepath->desktop;
- if (!desktop) return;
- SPEventContext *ec = desktop->event_context;
- if (!ec) return;
-
- Inkscape::MessageContext *mc = get_message_context(ec);
-
- if (!mc) return;
-
- double degrees = 180 / M_PI * rnew.a;
- if (degrees > 180) degrees -= 360;
- if (degrees < -180) degrees += 360;
- if (prefs->getBool("/options/compassangledisplay/value"))
- degrees = angle_to_compass (degrees);
-
- GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
-
- mc->setF(Inkscape::IMMEDIATE_MESSAGE,
- _("<b>Node handle</b>: angle %0.2f&#176;, length %s; with <b>Ctrl</b> to snap angle; with <b>Alt</b> to lock length; with <b>Shift</b> to rotate both handles"), degrees, length->str);
-
- g_string_free(length, TRUE);
-}
-
-/**
- * Node handle event callback.
- */
-static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
-{
- gboolean ret = FALSE;
- switch (event->type) {
- case GDK_KEY_PRESS:
- switch (get_group0_keyval (&event->key)) {
- case GDK_space:
- if (event->key.state & GDK_BUTTON1_MASK) {
- Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
- stamp_repr(nodepath);
- ret = TRUE;
- }
- break;
- default:
- break;
- }
- break;
- case GDK_ENTER_NOTIFY:
- // we use an experimentally determined threshold that seems to work fine
- if (Geom::L2(n->pos - knot->pos) < 0.75)
- Inkscape::NodePath::Path::active_node = n;
- break;
- case GDK_LEAVE_NOTIFY:
- // we use an experimentally determined threshold that seems to work fine
- if (Geom::L2(n->pos - knot->pos) < 0.75)
- Inkscape::NodePath::Path::active_node = NULL;
- break;
- default:
- break;
- }
-
- return ret;
-}
-
-static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
- Radial &rme, Radial &rother, gboolean const both)
-{
- rme.a += angle;
- if ( both
- || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
- || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
- {
- rother.a += angle;
- }
-}
-
-static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
- Radial &rme, Radial &rother, gboolean const both)
-{
- gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
-
- gdouble r;
- if ( both
- || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
- || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
- {
- r = MAX(rme.r, rother.r);
- } else {
- r = rme.r;
- }
-
- gdouble const weird_angle = atan2(norm_angle, r);
-/* Bulia says norm_angle is just the visible distance that the
- * object's end must travel on the screen. Left as 'angle' for want of
- * a better name.*/
-
- rme.a += weird_angle;
- if ( both
- || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
- || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
- {
- rother.a += weird_angle;
- }
-}
-
-/**
- * Rotate one node.
- */
-static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
-{
- Inkscape::NodePath::NodeSide *me, *other;
- bool both = false;
-
- double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
- double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
-
- if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
- me = &(n->p);
- other = &(n->n);
- } else if (!n->p.other) {
- me = &(n->n);
- other = &(n->p);
- } else {
- if (which > 0) { // right handle
- if (xn > xp) {
- me = &(n->n);
- other = &(n->p);
- } else {
- me = &(n->p);
- other = &(n->n);
- }
- } else if (which < 0){ // left handle
- if (xn <= xp) {
- me = &(n->n);
- other = &(n->p);
- } else {
- me = &(n->p);
- other = &(n->n);
- }
- } else { // both handles
- me = &(n->n);
- other = &(n->p);
- both = true;
- }
- }
-
- Radial rme(me->pos - n->pos);
- Radial rother(other->pos - n->pos);
-
- if (screen) {
- node_rotate_one_internal_screen (*n, angle, rme, rother, both);
- } else {
- node_rotate_one_internal (*n, angle, rme, rother, both);
- }
-
- me->pos = n->pos + Geom::Point(rme);
-
- if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
- other->pos = n->pos + Geom::Point(rother);
- }
-
- // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
- // so here we just move all the knots without emitting move signals, for speed
- sp_node_update_handles(n, false);
-}
-
-/**
- * Rotate selected nodes.
- */
-void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
-{
- if (!nodepath || !nodepath->selected) return;
-
- if (g_list_length(nodepath->selected) == 1) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
- node_rotate_one (n, angle, which, screen);
- } else {
- // rotate as an object:
-
- Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
- Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- box.expandTo (n->pos); // contain all selected nodes
- }
-
- gdouble rot;
- if (screen) {
- gdouble const zoom = nodepath->desktop->current_zoom();
- gdouble const zmove = angle / zoom;
- gdouble const r = Geom::L2(box.max() - box.midpoint());
- rot = atan2(zmove, r);
- } else {
- rot = angle;
- }
-
- Geom::Point rot_center;
- if (Inkscape::NodePath::Path::active_node == NULL)
- rot_center = box.midpoint();
- else
- rot_center = Inkscape::NodePath::Path::active_node->pos;
-
- Geom::Matrix t =
- Geom::Matrix (Geom::Translate(-rot_center)) *
- Geom::Matrix (Geom::Rotate(rot)) *
- Geom::Matrix (Geom::Translate(rot_center));
-
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- n->pos *= t;
- n->n.pos *= t;
- n->p.pos *= t;
- sp_node_update_handles(n, false);
- }
- }
-
- sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
-}
-
-/**
- * Scale one node.
- */
-static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
-{
- bool both = false;
- Inkscape::NodePath::NodeSide *me, *other;
-
- double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
- double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
-
- if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
- me = &(n->p);
- other = &(n->n);
- n->code = NR_CURVETO;
- } else if (!n->p.other) {
- me = &(n->n);
- other = &(n->p);
- if (n->n.other)
- n->n.other->code = NR_CURVETO;
- } else {
- if (which > 0) { // right handle
- if (xn > xp) {
- me = &(n->n);
- other = &(n->p);
- if (n->n.other)
- n->n.other->code = NR_CURVETO;
- } else {
- me = &(n->p);
- other = &(n->n);
- n->code = NR_CURVETO;
- }
- } else if (which < 0){ // left handle
- if (xn <= xp) {
- me = &(n->n);
- other = &(n->p);
- if (n->n.other)
- n->n.other->code = NR_CURVETO;
- } else {
- me = &(n->p);
- other = &(n->n);
- n->code = NR_CURVETO;
- }
- } else { // both handles
- me = &(n->n);
- other = &(n->p);
- both = true;
- n->code = NR_CURVETO;
- if (n->n.other)
- n->n.other->code = NR_CURVETO;
- }
- }
-
- Radial rme(me->pos - n->pos);
- Radial rother(other->pos - n->pos);
-
- rme.r += grow;
- if (rme.r < 0) rme.r = 0;
- if (rme.a == HUGE_VAL) {
- if (me->other) { // if direction is unknown, initialize it towards the next node
- Radial rme_next(me->other->pos - n->pos);
- rme.a = rme_next.a;
- } else { // if there's no next, initialize to 0
- rme.a = 0;
- }
- }
- if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
- rother.r += grow;
- if (rother.r < 0) rother.r = 0;
- if (rother.a == HUGE_VAL) {
- rother.a = rme.a + M_PI;
- }
- }
-
- me->pos = n->pos + Geom::Point(rme);
-
- if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
- other->pos = n->pos + Geom::Point(rother);
- }
-
- // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
- // so here we just move all the knots without emitting move signals, for speed
- sp_node_update_handles(n, false);
-}
-
-/**
- * Scale selected nodes.
- */
-void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
-{
- if (!nodepath || !nodepath->selected) return;
-
- if (g_list_length(nodepath->selected) == 1) {
- // scale handles of the single selected node
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
- node_scale_one (n, grow, which);
- } else {
- // scale nodes as an "object":
-
- Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
- Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- box.expandTo (n->pos); // contain all selected nodes
- }
-
- if ( Geom::are_near(box.maxExtent(), 0) ) {
- SPEventContext *ec = nodepath->desktop->event_context;
- if (!ec) return;
- Inkscape::MessageContext *mc = get_message_context(ec);
- if (!mc) return;
- mc->setF(Inkscape::WARNING_MESSAGE,
- _("Cannot scale nodes when all are at the same location."));
- return;
- }
- double scale = (box.maxExtent() + grow)/box.maxExtent();
-
-
- Geom::Point scale_center;
- if (Inkscape::NodePath::Path::active_node == NULL)
- scale_center = box.midpoint();
- else
- scale_center = Inkscape::NodePath::Path::active_node->pos;
-
- Geom::Matrix t =
- Geom::Translate(-scale_center) *
- Geom::Scale(scale, scale) *
- Geom::Translate(scale_center);
-
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- n->pos *= t;
- n->n.pos *= t;
- n->p.pos *= t;
- sp_node_update_handles(n, false);
- }
- }
-
- sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
-}
-
-void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
-{
- if (!nodepath) return;
- sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
-}
-
-/**
- * Flip selected nodes horizontally/vertically.
- */
-void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis, boost::optional<Geom::Point> center)
-{
- if (!nodepath || !nodepath->selected) return;
-
- if (g_list_length(nodepath->selected) == 1 && !center) {
- // flip handles of the single selected node
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
- double temp = n->p.pos[axis];
- n->p.pos[axis] = n->n.pos[axis];
- n->n.pos[axis] = temp;
- sp_node_update_handles(n, false);
- } else {
- // scale nodes as an "object":
-
- Geom::Rect box = sp_node_selected_bbox (nodepath);
- if (!center) {
- center = box.midpoint();
- }
- Geom::Matrix t =
- Geom::Matrix (Geom::Translate(- *center)) *
- Geom::Matrix ((axis == Geom::X)? Geom::Scale(-1, 1) : Geom::Scale(1, -1)) *
- Geom::Matrix (Geom::Translate(*center));
-
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- n->pos *= t;
- n->n.pos *= t;
- n->p.pos *= t;
- sp_node_update_handles(n, false);
- }
- }
-
- sp_nodepath_update_repr(nodepath, _("Flip nodes"));
-}
-
-Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
-{
- g_assert (nodepath->selected);
-
- Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
- Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
- for (GList *l = nodepath->selected; l != NULL; l = l->next) {
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- box.expandTo (n->pos); // contain all selected nodes
- }
- return box;
-}
-
-//-----------------------------------------------
-/**
- * Return new subpath under given nodepath.
- */
-static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
-{
- g_assert(nodepath);
- g_assert(nodepath->desktop);
-
- Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
-
- s->nodepath = nodepath;
- s->closed = FALSE;
- s->nodes = NULL;
- s->first = NULL;
- s->last = NULL;
-
- // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
- // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
- nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
-
- return s;
-}
-
-/**
- * Destroy nodes in subpath, then subpath itself.
- */
-static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
-{
- g_assert(subpath);
- g_assert(subpath->nodepath);
- g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
-
- while (subpath->nodes) {
- sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
- }
-
- subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
-
- g_free(subpath);
-}
-
-/**
- * Link head to tail in subpath.
- */
-static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
-{
- g_assert(!sp->closed);
- g_assert(sp->last != sp->first);
- g_assert(sp->first->code == NR_MOVETO);
-
- sp->closed = TRUE;
-
- //Link the head to the tail
- sp->first->p.other = sp->last;
- sp->last->n.other = sp->first;
- sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
- sp->first = sp->last;
-
- //Remove the extra end node
- sp_nodepath_node_destroy(sp->last->n.other);
-}
-
-/**
- * Open closed (loopy) subpath at node.
- */
-static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
-{
- g_assert(sp->closed);
- g_assert(n->subpath == sp);
- g_assert(sp->first == sp->last);
-
- /* We create new startpoint, current node will become last one */
-
- Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
- &n->pos, &n->pos, &n->n.pos);
-
-
- sp->closed = FALSE;
-
- //Unlink to make a head and tail
- sp->first = new_path;
- sp->last = n;
- n->n.other = NULL;
- new_path->p.other = NULL;
-}
-
-/**
- * Return new node in subpath with given properties.
- * \param pos Position of node.
- * \param ppos Handle position in previous direction
- * \param npos Handle position in previous direction
- */
-Inkscape::NodePath::Node *
-sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp, Inkscape::NodePath::Node *next, Inkscape::NodePath::NodeType type, NRPathcode code, Geom::Point *ppos, Geom::Point *pos, Geom::Point *npos)
-{
- g_assert(sp);
- g_assert(sp->nodepath);
- g_assert(sp->nodepath->desktop);
-
- if (nodechunk == NULL)
- nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
-
- Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
-
- n->subpath = sp;
-
- if (type != Inkscape::NodePath::NODE_NONE) {
- // use the type from sodipodi:nodetypes
- n->type = type;
- } else {
- if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
- // points are (almost) collinear
- if (Geom::L2(*pos - *ppos) < 1e-6 || Geom::L2(*pos - *npos) < 1e-6) {
- // endnode, or a node with a retracted handle
- n->type = Inkscape::NodePath::NODE_CUSP;
- } else {
- n->type = Inkscape::NodePath::NODE_SMOOTH;
- }
- } else {
- n->type = Inkscape::NodePath::NODE_CUSP;
- }
- }
-
- n->code = code;
- n->selected = FALSE;
- n->pos = *pos;
- n->p.pos = *ppos;
- n->n.pos = *npos;
-
- n->dragging_out = NULL;
-
- Inkscape::NodePath::Node *prev;
- if (next) {
- //g_assert(g_list_find(sp->nodes, next));
- prev = next->p.other;
- } else {
- prev = sp->last;
- }
-
- if (prev)
- prev->n.other = n;
- else
- sp->first = n;
-
- if (next)
- next->p.other = n;
- else
- sp->last = n;
-
- n->p.other = prev;
- n->n.other = next;
-
- n->knot = sp_knot_new(sp->nodepath->desktop, _("<b>Node</b>: drag to edit the path; with <b>Ctrl</b> to snap to horizontal/vertical; with <b>Ctrl+Alt</b> to snap to handles' directions"));
- sp_knot_set_position(n->knot, *pos, 0);
-
- n->knot->setAnchor (GTK_ANCHOR_CENTER);
- n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
- n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
-
- sp_nodepath_update_node_knot(n);
-
- g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
- g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
- g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
- g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
- g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
- sp_knot_show(n->knot);
-
- // We only create handle knots and lines on demand
- n->p.knot = NULL;
- n->p.line = NULL;
- n->n.knot = NULL;
- n->n.line = NULL;
-
- sp->nodes = g_list_prepend(sp->nodes, n);
-
- return n;
-}
-
-/**
- * Destroy node and its knots, link neighbors in subpath.
- */
-static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
-{
- g_assert(node);
- g_assert(node->subpath);
- g_assert(SP_IS_KNOT(node->knot));
-
- Inkscape::NodePath::SubPath *sp = node->subpath;
-
- if (node->selected) { // first, deselect
- g_assert(g_list_find(node->subpath->nodepath->selected, node));
- node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
- }
-
- node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
-
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
- g_object_unref(G_OBJECT(node->knot));
-
- if (node->p.knot) {
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
- g_object_unref(G_OBJECT(node->p.knot));
- node->p.knot = NULL;
- }
-
- if (node->n.knot) {
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
- g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
- g_object_unref(G_OBJECT(node->n.knot));
- node->n.knot = NULL;
- }
-
- if (node->p.line)
- gtk_object_destroy(GTK_OBJECT(node->p.line));
- if (node->n.line)
- gtk_object_destroy(GTK_OBJECT(node->n.line));
-
- if (sp->nodes) { // there are others nodes on the subpath
- if (sp->closed) {
- if (sp->first == node) {
- g_assert(sp->last == node);
- sp->first = node->n.other;
- sp->last = sp->first;
- }
- node->p.other->n.other = node->n.other;
- node->n.other->p.other = node->p.other;
- } else {
- if (sp->first == node) {
- sp->first = node->n.other;
- sp->first->code = NR_MOVETO;
- }
- if (sp->last == node) sp->last = node->p.other;
- if (node->p.other) node->p.other->n.other = node->n.other;
- if (node->n.other) node->n.other->p.other = node->p.other;
- }
- } else { // this was the last node on subpath
- sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
- }
-
- g_mem_chunk_free(nodechunk, node);
-}
-
-/**
- * Returns one of the node's two sides.
- * \param which Indicates which side.
- * \return Pointer to previous node side if which==-1, next if which==1.
- */
-static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
-{
- g_assert(node);
- Inkscape::NodePath::NodeSide * result = 0;
- switch (which) {
- case -1:
- result = &node->p;
- break;
- case 1:
- result = &node->n;
- break;
- default:
- g_assert_not_reached();
- }
-
- return result;
-}
-
-/**
- * Return the other side of the node, given one of its sides.
- */
-static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
-{
- g_assert(node);
- Inkscape::NodePath::NodeSide *result = 0;
-
- if (me == &node->p) {
- result = &node->n;
- } else if (me == &node->n) {
- result = &node->p;
- } else {
- g_assert_not_reached();
- }
-
- return result;
-}
-
-/**
- * Return NRPathcode on the given side of the node.
- */
-static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
-{
- g_assert(node);
-
- NRPathcode result = NR_END;
- if (me == &node->p) {
- if (node->p.other) {
- result = (NRPathcode)node->code;
- } else {
- result = NR_MOVETO;
- }
- } else if (me == &node->n) {
- if (node->n.other) {
- result = (NRPathcode)node->n.other->code;
- } else {
- result = NR_MOVETO;
- }
- } else {
- g_assert_not_reached();
- }
-
- return result;
-}
-
-/**
- * Return node with the given index
- */
-Inkscape::NodePath::Node *
-sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *nodepath, int index)
-{
- Inkscape::NodePath::Node *e = NULL;
-
- if (!nodepath) {
- return e;
- }
-
- //find segment
- for (GList *l = nodepath->subpaths; l ; l=l->next) {
-
- Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
- int n = g_list_length(sp->nodes);
- if (sp->closed) {
- n++;
- }
-
- //if the piece belongs to this subpath grab it
- //otherwise move onto the next subpath
- if (index < n) {
- e = sp->first;
- for (int i = 0; i < index; ++i) {
- e = e->n.other;
- }
- break;
- } else {
- if (sp->closed) {
- index -= (n+1);
- } else {
- index -= n;
- }
- }
- }
-
- return e;
-}
-
-/**
- * Returns plain text meaning of node type.
- */
-static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
-{
- unsigned retracted = 0;
- bool endnode = false;
-
- for (int which = -1; which <= 1; which += 2) {
- Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
- if (side->other && Geom::L2(side->pos - node->pos) < 1e-6)
- retracted ++;
- if (!side->other)
- endnode = true;
- }
-
- if (retracted == 0) {
- if (endnode) {
- // TRANSLATORS: "end" is an adjective here (NOT a verb)
- return _("end node");
- } else {
- switch (node->type) {
- case Inkscape::NodePath::NODE_CUSP:
- // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
- return _("cusp");
- case Inkscape::NodePath::NODE_SMOOTH:
- // TRANSLATORS: "smooth" is an adjective here
- return _("smooth");
- case Inkscape::NodePath::NODE_AUTO:
- return _("auto");
- case Inkscape::NodePath::NODE_SYMM:
- return _("symmetric");
- }
- }
- } else if (retracted == 1) {
- if (endnode) {
- // TRANSLATORS: "end" is an adjective here (NOT a verb)
- return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
- } else {
- return _("one handle retracted (drag with <b>Shift</b> to extend)");
- }
- } else {
- return _("both handles retracted (drag with <b>Shift</b> to extend)");
- }
-
- return NULL;
-}
-
-/**
- * Handles content of statusbar as long as node tool is active.
- */
-void
-sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
-{
- gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>Alt+drag</b> nodes to sculpt; <b>arrow</b> keys to move nodes, <b>&lt; &gt;</b> to scale, <b>[ ]</b> to rotate");
- gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
-
- gint total_nodes = sp_nodepath_get_node_count(nodepath);
- gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
- gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
- gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
-
- SPDesktop *desktop = NULL;
- if (nodepath) {
- desktop = nodepath->desktop;
- } else {
- desktop = SP_ACTIVE_DESKTOP; // when this is eliminated also remove #include "inkscape.h" above
- }
-
- SPEventContext *ec = desktop->event_context;
- if (!ec) return;
-
- Inkscape::MessageContext *mc = get_message_context(ec);
- if (!mc) return;
-
- inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
-
- if (selected_nodes == 0) {
- Inkscape::Selection *sel = desktop->selection;
- if (!sel || sel->isEmpty()) {
- mc->setF(Inkscape::NORMAL_MESSAGE,
- _("Select a single object to edit its nodes or handles."));
- } else {
- if (nodepath) {
- mc->setF(Inkscape::NORMAL_MESSAGE,
- ngettext("<b>0</b> out of <b>%i</b> node selected. <b>Click</b>, <b>Shift+click</b>, or <b>drag around</b> nodes to select.",
- "<b>0</b> out of <b>%i</b> nodes selected. <b>Click</b>, <b>Shift+click</b>, or <b>drag around</b> nodes to select.",
- total_nodes),
- total_nodes);
- } else {
- if (g_slist_length((GSList *)sel->itemList()) == 1) {
- mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
- } else {
- mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
- }
- }
- }
- } else if (nodepath && selected_nodes == 1) {
- mc->setF(Inkscape::NORMAL_MESSAGE,
- ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
- "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
- total_nodes),
- selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
- } else {
- if (selected_subpaths > 1) {
- mc->setF(Inkscape::NORMAL_MESSAGE,
- ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
- "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
- total_nodes),
- selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
- } else {
- mc->setF(Inkscape::NORMAL_MESSAGE,
- ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
- "<b>%i</b> of <b>%i</b> nodes selected. %s.",
- total_nodes),
- selected_nodes, total_nodes, when_selected);
- }
- }
-}
-
-/*
- * returns a *copy* of the curve of that object.
- */
-SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
- if (!object)
- return NULL;
-
- SPCurve *curve = NULL;
- if (SP_IS_PATH(object)) {
- SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
- curve = curve_new->copy();
- } else if ( IS_LIVEPATHEFFECT(object) && key) {
- const gchar *svgd = object->repr->attribute(key);
- if (svgd) {
- Geom::PathVector pv = sp_svg_read_pathv(svgd);
- SPCurve *curve_new = new SPCurve(pv);
- if (curve_new) {
- curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
- }
- }
- }
-
- return curve;
-}
-
-void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
- if (!np || !np->object || !curve)
- return;
-
- if (SP_IS_PATH(np->object)) {
- if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
- sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
- } else {
- sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
- }
- } else if ( IS_LIVEPATHEFFECT(np->object) ) {
- Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(np->object)->get_lpe();
- if (lpe) {
- Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( lpe->getParameter(np->repr_key) );
- if (pathparam) {
- pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
- np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
- }
- }
- }
-}
-
-/*
-SPCanvasItem *
-sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
- return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
-}
-*/
-
-
-/// \todo this code to generate a helper canvasitem from an spcurve should be moved to different file
-SPCanvasItem *
-sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const Geom::Matrix & i2d, guint32 color = 0xff0000ff) {
- SPCurve *flash_curve = curve->copy();
- flash_curve->transform(i2d);
- SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
- // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
- // unless we also flash the nodes...
- sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
- sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
- sp_canvas_item_show(canvasitem);
- flash_curve->unref();
- return canvasitem;
-}
-
-SPCanvasItem *
-sp_nodepath_generate_helperpath(SPDesktop *desktop, SPItem *item) {
- if (!item || !desktop) {
- return NULL;
- }
-
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- guint32 color = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff);
-
- Geom::Matrix i2d = sp_item_i2d_affine(item);
-
- SPCurve *curve = NULL;
- if (SP_IS_PATH(item)) {
- curve = sp_path_get_curve_for_edit(SP_PATH(item));
- } else if ( SP_IS_SHAPE(item) && SP_SHAPE(item)->curve ) {
- curve = sp_shape_get_curve (SP_SHAPE(item));
- } else if ( SP_IS_TEXT(item) ) {
- // do not display helperpath for text - we cannot do anything with it in Node tool anyway
- // curve = SP_TEXT(item)->getNormalizedBpath();
- return NULL;
- } else {
- g_warning ("-----> sp_nodepath_generate_helperpath(SPDesktop *desktop, SPItem *item): TODO: generate the helper path for this item type!\n");
- return NULL;
- }
-
- SPCanvasItem * helperpath = sp_nodepath_generate_helperpath(desktop, curve, i2d, color);
-
- curve->unref();
-
- return helperpath;
-}
-
-
-// TODO: Merge this with sp_nodepath_make_helper_item()!
-void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
- np->show_helperpath = show;
-
- if (show) {
- SPCurve *helper_curve = np->curve->copy();
- helper_curve->transform(np->i2d);
- if (!np->helper_path) {
- //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
-
- np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
- sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
- sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
- sp_canvas_item_move_to_z(np->helper_path, 0);
- sp_canvas_item_show(np->helper_path);
- } else {
- sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
- }
- helper_curve->unref();
- } else {
- if (np->helper_path) {
- GtkObject *temp = np->helper_path;
- np->helper_path = NULL;
- gtk_object_destroy(temp);
- }
- }
-}
-
-/* sp_nodepath_make_straight_path:
- * Prevents user from curving the path by dragging a segment or activating handles etc.
- * The resulting path is a linear interpolation between nodal points, with only straight segments.
- * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
- */
-void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
- np->straight_path = true;
- np->show_handles = false;
- g_message("add code to make the path straight.");
- // do sp_nodepath_convert_node_type on all nodes?
- // coding tip: search for this text : "Make selected segments lines"
-}
-
-/*
- 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:encoding=utf-8:textwidth=99 :
diff --git a/src/nodepath.h b/src/nodepath.h
deleted file mode 100644
index 1dcb4527c..000000000
--- a/src/nodepath.h
+++ /dev/null
@@ -1,345 +0,0 @@
-/** @file
- * @brief Path handler in node edit mode
- */
-/* Authors:
- * Lauris Kaplinski <lauris@kaplinski.com>
- *
- * This code is in public domain
- */
-
-#ifndef SEEN_SP_NODEPATH_H
-#define SEEN_SP_NODEPATH_H
-
-#include "libnr/nr-path-code.h"
-#include <glibmm/ustring.h>
-#include <gdk/gdkevents.h>
-#include <list>
-#include <2geom/point.h>
-#include <2geom/matrix.h>
-#include <boost/optional.hpp>
-
-
-struct SPCanvasItem;
-class SPCurve;
-struct SPItem;
-class SPObject;
-class SPDesktop;
-class SPPath;
-class SPKnot;
-class LivePathEffectObject;
-
-namespace Inkscape {
- namespace XML {
- class Node;
- }
-
- namespace LivePathEffect {
- class Effect;
- }
-}
-
-typedef std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> > HelperPathList;
-
-/**
- * Radial objects are represented by an angle and a distance from
- * 0,0. 0,0 is represented by a == big_num.
- */
-class Radial{
- public:
-/** Radius */
- double r;
-/** Amplitude */
- double a;
- Radial() {}
- // Radial(Geom::Point const &p); // Convert a point to radial coordinates
- Radial(Radial &p) : r(p.r),a(p.a) {}
- // operator Geom::Point() const;
-
-/**
- * Construct Radial from Geom::Point.
- */
-Radial(Geom::Point const &p)
-{
- r = Geom::L2(p);
- if (r > 0) {
- a = Geom::atan2 (p);
- } else {
- a = HUGE_VAL; //undefined
- }
-}
-
-/**
- * Cast Radial to cartesian Geom::Point.
- */
-operator Geom::Point() const
-{
- if (a == HUGE_VAL) {
- return Geom::Point(0,0);
- } else {
- return r*Geom::Point(cos(a), sin(a));
- }
-}
-
-};
-
-class ShapeEditor;
-
-namespace Inkscape {
-namespace NodePath {
-
-/**
- * The entire nodepath, containing multiple subpaths
- */
-class Path;
-
-/**
- * A subpath is a continuous chain of linked nodes
- */
-class SubPath;
-
-/**
- * One side of a node, i.e. prev or next
- */
-class NodeSide;
-
-/**
- * A node on a subpath
- */
-class Node;
-
-
-/**
- * This is the lowest list item, a simple list of nodes.
- */
-class SubPath {
- public:
-/** The parent of this subpath */
- Path * nodepath;
-/** Is this path closed (no endpoints) or not?*/
- gboolean closed;
-/** The nodes in this subpath. */
- GList * nodes;
-/** The first node of the subpath (does not imply open/closed)*/
- Node * first;
-/** The last node of the subpath */
- Node * last;
-};
-
-
-
-/**
- * What kind of node is this? This is the value for the node->type
- * field. NodeType indicates the degree of continuity required for
- * the node. I think that the corresponding integer indicates which
- * derivate is connected. (Thus 2 means that the node is continuous
- * to the second derivative, i.e. has matching endpoints and tangents)
- */
-typedef enum {
-/** A normal node */
- NODE_NONE,
-/** This node non-continuously joins two segments.*/
- NODE_CUSP,
-/** This node continuously joins two segments. */
- NODE_SMOOTH,
-/** This node has automatic handles. */
- NODE_AUTO,
-/** This node is symmetric. */
- NODE_SYMM
-} NodeType;
-
-
-
-/**
- * A NodeSide is a datarecord which may be on either side (n or p) of a node,
- * which describes the segment going to the next node.
- */
-class NodeSide{
- public:
-/** Pointer to the next node, */
- Node * other;
-/** Position */
- Geom::Point pos;
-/** Origin (while dragging) in radial notation */
- Radial origin_radial;
-/** Origin (while dragging) in x/y notation */
- Geom::Point origin;
-/** Knots are Inkscape's way of providing draggable points. This
- * Knot is the point on the curve representing the control point in a
- * bezier curve.*/
- SPKnot * knot;
-/** What kind of rendering? */
- SPCanvasItem * line;
-};
-
-/**
- * A node along a NodePath
- */
-class Node {
- public:
-/** The parent subpath of this node */
- SubPath * subpath;
-/** Type is selected from NodeType.*/
- guint type : 4;
-/** Code refers to which ArtCode is used to represent the segment
- * (which segment?).*/
- guint code : 4;
-/** Boolean. Am I currently selected or not? */
- guint selected : 1;
-/** */
- Geom::Point pos;
-/** */
- Geom::Point origin;
-/** Knots are Inkscape's way of providing draggable points. This
- * Knot is the point on the curve representing the endpoint.*/
- SPKnot * knot;
-/** The NodeSide in the 'next' direction */
- NodeSide n;
-/** The NodeSide in the 'previous' direction */
- NodeSide p;
-
- /** The pointer to the nodeside which we are dragging out with Shift */
- NodeSide *dragging_out;
-
- /** Boolean. Am I being dragged? */
- guint is_dragging : 1;
-};
-
-/**
- * This is a collection of subpaths which contain nodes
- *
- * In the following data model. Nodepaths are made up of subpaths which
- * are comprised of nodes.
- *
- * Nodes are linked thus:
- * \verbatim
- n other
- node -----> nodeside ------> node \endverbatim
- */
-class Path {
- public:
- /** Constructor should private, people should create new nodepaths using sp_nodepath_new
- * But for some reason I cannot make sp_nodepath_new a friend :-(
- */
- Path() {};
- /** Destructor */
- ~Path();
-
-/** Pointer to the current desktop, for reporting purposes */
- SPDesktop * desktop;
-/** The parent path of this nodepath */
- SPObject * object;
-/** The parent livepatheffect of this nodepath, if applicable */
- SPItem * item;
-/** The context which created this nodepath. Important if this nodepath is deleted */
- ShapeEditor *shape_editor;
-/** The subpaths which comprise this NodePath */
- GList * subpaths;
-/** A list of nodes which are currently selected */
- GList * selected;
-/** Transforms (userspace <---> virtual space? someone please describe )
- njh: I'd be guessing that these are item <-> desktop transforms.*/
- Geom::Matrix i2d, d2i;
-/** The DOM node which describes this NodePath */
- Inkscape::XML::Node *repr;
- gchar *repr_key;
- gchar *repr_nodetypes_key;
- //STL compliant method to get the selected nodes
- void selection(std::list<Node *> &l);
-
- guint numSelected() {return (selected? g_list_length(selected) : 0);}
- Geom::Point& singleSelectedCoords() {return (((Node *) selected->data)->pos);}
-
- /// draw a "sketch" of the path by using these variables
- SPCanvasItem *helper_path;
- SPCurve *curve;
- bool show_helperpath;
- guint32 helperpath_rgba;
- gdouble helperpath_width;
-
- // the helperpaths provided by all LPEs (and their paramaters) of the current item
- HelperPathList helper_path_vec;
-
- /// true if we changed repr, to tell this change from an external one such as from undo, simplify, or another desktop
- unsigned int local_change;
-
- /// true if we're showing selected nodes' handles
- bool show_handles;
-
- /// true if the path cannot contain curves, just straight lines
- bool straight_path;
-
- /// active_node points to the node that is currently mouseovered (= NULL if
- /// there isn't any); we also consider the node mouseovered if it is covered
- /// by one of its handles and the latter is mouseovered
- static Node *active_node;
-
- /// Location of mouse pointer when we started dragging, needed for snapping
- Geom::Point drag_origin_mouse;
-
-};
-
-} // namespace NodePath
-} // namespace Inkscape
-
-enum {
- SCULPT_PROFILE_LINEAR,
- SCULPT_PROFILE_BELL,
- SCULPT_PROFILE_ELLIPTIC
-};
-
-// Do function documentation in nodepath.cpp
-Inkscape::NodePath::Path * sp_nodepath_new (SPDesktop * desktop, SPObject *object, bool show_handles, const char * repr_key = NULL, SPItem *item = NULL);
-void sp_nodepath_deselect (Inkscape::NodePath::Path *nodepath);
-void sp_nodepath_select_all (Inkscape::NodePath::Path *nodepath, bool invert);
-void sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert);
-void sp_nodepath_select_next (Inkscape::NodePath::Path *nodepath);
-void sp_nodepath_select_prev (Inkscape::NodePath::Path *nodepath);
-void sp_nodepath_select_rect (Inkscape::NodePath::Path * nodepath, Geom::Rect const &b, gboolean incremental);
-GList *save_nodepath_selection (Inkscape::NodePath::Path *nodepath);
-void restore_nodepath_selection (Inkscape::NodePath::Path *nodepath, GList *r);
-gboolean nodepath_repr_d_changed (Inkscape::NodePath::Path * np, const char *newd);
-gboolean nodepath_repr_typestr_changed (Inkscape::NodePath::Path * np, const char *newtypestr);
-gboolean node_key (GdkEvent * event);
-void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation);
-void sp_nodepath_update_statusbar (Inkscape::NodePath::Path *nodepath);
-void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis);
-void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis);
-void sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p, bool toggle);
-void sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p);
-void sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, Geom::Point delta);
-Inkscape::NodePath::Node * sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *np, int index);
-bool sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side);
-
-/* possibly private functions */
-
-void sp_node_selected_add_node (Inkscape::NodePath::Path *nodepath);
-void sp_node_selected_break (Inkscape::NodePath::Path *nodepath);
-void sp_node_selected_duplicate (Inkscape::NodePath::Path *nodepath);
-void sp_node_selected_join (Inkscape::NodePath::Path *nodepath);
-void sp_node_selected_join_segment (Inkscape::NodePath::Path *nodepath);
-void sp_node_delete_preserve (GList *nodes_to_delete);
-void sp_node_selected_delete (Inkscape::NodePath::Path *nodepath);
-void sp_node_selected_delete_segment (Inkscape::NodePath::Path *nodepath);
-void sp_node_selected_set_type (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type);
-void sp_node_selected_set_line_type (Inkscape::NodePath::Path *nodepath, NRPathcode code);
-void sp_node_selected_move (Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy);
-void sp_node_selected_move_screen (SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy);
-void sp_node_selected_move_absolute (Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis);
-Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath);
-boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis);
-
-void sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show);
-SPCanvasItem *sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const Geom::Matrix & i2d, guint32 color);
-SPCanvasItem *sp_nodepath_generate_helperpath(SPDesktop *desktop, SPItem *item);
-void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *nodepath, bool show);
-void sp_nodepath_update_helperpaths(Inkscape::NodePath::Path *np);
-void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np);
-
-void sp_nodepath_selected_nodes_rotate (Inkscape::NodePath::Path * nodepath, gdouble angle, int which, bool screen);
-
-void sp_nodepath_selected_nodes_scale (Inkscape::NodePath::Path * nodepath, gdouble grow, int which);
-void sp_nodepath_selected_nodes_scale_screen (Inkscape::NodePath::Path * nodepath, gdouble grow, int which);
-
-void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis, boost::optional<Geom::Point> center);
-
-#endif
diff --git a/src/preferences-skeleton.h b/src/preferences-skeleton.h
index 4e5291baf..90fc85757 100644
--- a/src/preferences-skeleton.h
+++ b/src/preferences-skeleton.h
@@ -113,7 +113,7 @@ static char const preferences_skeleton[] =
" font_sample=\"AaBbCcIiPpQq12369$\342\202\254\302\242?.;/()\"\n"
" show_sample_in_list=\"1\"\n"
" style=\"fill:black;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;font-style:normal;font-weight:normal;font-size:40px;\" selcue=\"1\"/>\n"
-" <eventcontext id=\"nodes\" selcue=\"1\" gradientdrag=\"1\" highlight_color=\"4278190335\" pathflash_enabled=\"1\" pathflash_unselected=\"0\" pathflash_timeout=\"500\" show_handles=\"1\" show_helperpath=\"0\" sculpting_profile=\"1\" />\n"
+" <eventcontext id=\"nodes\" selcue=\"1\" gradientdrag=\"1\" highlight_color=\"4278190335\" pathflash_enabled=\"1\" pathflash_unselected=\"0\" pathflash_timeout=\"500\" show_handles=\"1\" show_outline=\"0\" sculpting_profile=\"1\" />\n"
" <eventcontext id=\"tweak\" selcue=\"0\" gradientdrag=\"0\" show_handles=\"0\" width=\"0.2\" force=\"0.2\" fidelity=\"0.5\" usepressure=\"1\" style=\"fill:red;stroke:none;\" usecurrent=\"0\"/>\n"
" <eventcontext id=\"spray\" selcue=\"0\" gradientdrag=\"0\" show_handles=\"0\" width=\"0.2\" force=\"0.2\" fidelity=\"0.5\" usepressure=\"1\" style=\"fill:red;stroke:none;\" usecurrent=\"0\"/>\n"
" <eventcontext id=\"gradient\" selcue=\"1\"/>\n"
diff --git a/src/preferences.cpp b/src/preferences.cpp
index 39a9e4d69..315c668b4 100644
--- a/src/preferences.cpp
+++ b/src/preferences.cpp
@@ -11,6 +11,7 @@
*/
#include <cstring>
+#include <sstream>
#include <glibmm/fileutils.h>
#include <glibmm/i18n.h>
#include <glib.h>
@@ -446,6 +447,13 @@ void Preferences::setDouble(Glib::ustring const &pref_path, double value)
_setRawValue(pref_path, buf);
}
+void Preferences::setColor(Glib::ustring const &pref_path, guint32 value)
+{
+ gchar buf[16];
+ g_snprintf(buf, 16, "#%08x", value);
+ _setRawValue(pref_path, buf);
+}
+
/**
* @brief Set a string attribute of a preference
* @param pref_path Path of the preference to modify
@@ -732,6 +740,20 @@ Glib::ustring Preferences::_extractString(Entry const &v)
return Glib::ustring(static_cast<gchar const *>(v._value));
}
+guint32 Preferences::_extractColor(Entry const &v)
+{
+ gchar const *s = static_cast<gchar const *>(v._value);
+ std::istringstream hr(s);
+ guint32 color;
+ if (s[0] == '#') {
+ hr.ignore(1);
+ hr >> std::hex >> color;
+ } else {
+ hr >> color;
+ }
+ return color;
+}
+
SPCSSAttr *Preferences::_extractStyle(Entry const &v)
{
SPCSSAttr *style = sp_repr_css_attr_new();
diff --git a/src/preferences.h b/src/preferences.h
index a7be08009..5e1ccf9d6 100644
--- a/src/preferences.h
+++ b/src/preferences.h
@@ -178,6 +178,11 @@ public:
inline Glib::ustring getString() const;
/**
+ * @brief Interpret the preference as an RGBA color value.
+ */
+ inline guint32 getColor(guint32 def) const;
+
+ /**
* @brief Interpret the preference as a CSS style.
* @return A CSS style that has to be unrefed when no longer necessary. Never NULL.
*/
@@ -329,6 +334,10 @@ public:
return getEntry(pref_path).getString();
}
+ guint32 getColor(Glib::ustring const &pref_path, guint32 def=0x000000ff) {
+ return getEntry(pref_path).getColor(def);
+ }
+
/**
* @brief Retrieve a CSS style
* @param pref_path Path to the retrieved preference
@@ -384,6 +393,11 @@ public:
void setString(Glib::ustring const &pref_path, Glib::ustring const &value);
/**
+ * @brief Set an RGBA color value
+ */
+ void setColor(Glib::ustring const &pref_path, guint32 value);
+
+ /**
* @brief Set a CSS style
*/
void setStyle(Glib::ustring const &pref_path, SPCSSAttr *style);
@@ -459,6 +473,7 @@ protected:
int _extractInt(Entry const &v);
double _extractDouble(Entry const &v);
Glib::ustring _extractString(Entry const &v);
+ guint32 _extractColor(Entry const &v);
SPCSSAttr *_extractStyle(Entry const &v);
SPCSSAttr *_extractInheritedStyle(Entry const &v);
@@ -567,6 +582,15 @@ inline Glib::ustring Preferences::Entry::getString() const
}
}
+inline guint32 Preferences::Entry::getColor(guint32 def) const
+{
+ if (!this->isValid()) {
+ return def;
+ } else {
+ return Inkscape::Preferences::get()->_extractColor(*this);
+ }
+}
+
inline SPCSSAttr *Preferences::Entry::getStyle() const
{
if (!this->isValid()) {
diff --git a/src/selection-chemistry.cpp b/src/selection-chemistry.cpp
index d97687267..d4978af3b 100644
--- a/src/selection-chemistry.cpp
+++ b/src/selection-chemistry.cpp
@@ -89,9 +89,7 @@
// For clippath editing
#include "tools-switch.h"
-#include "shape-editor.h"
-#include "node-context.h"
-#include "nodepath.h"
+#include "ui/tool/node-tool.h"
#include "ui/clipboard.h"
@@ -1739,53 +1737,52 @@ void sp_selection_next_patheffect_param(SPDesktop * dt)
}
}
+/*bool has_path_recursive(SPObject *obj)
+{
+ if (!obj) return false;
+ if (SP_IS_PATH(obj)) {
+ return true;
+ }
+ if (SP_IS_GROUP(obj) || SP_IS_OBJECTGROUP(obj)) {
+ for (SPObject *c = obj->children; c; c = c->next) {
+ if (has_path_recursive(c)) return true;
+ }
+ }
+ return false;
+}*/
+
void sp_selection_edit_clip_or_mask(SPDesktop * dt, bool clip)
{
- if (!dt) return;
+ return;
+ /*if (!dt) return;
+ using namespace Inkscape::UI;
Inkscape::Selection *selection = sp_desktop_selection(dt);
- if ( selection && !selection->isEmpty() ) {
- SPItem *item = selection->singleItem();
- if ( item ) {
- SPObject *obj = NULL;
- if (clip)
- obj = item->clip_ref ? SP_OBJECT(item->clip_ref->getObject()) : NULL;
- else
- obj = item->mask_ref ? SP_OBJECT(item->mask_ref->getObject()) : NULL;
-
- if (obj) {
- // obj is a group object, the children are the actual clippers
- for ( SPObject *child = obj->children ; child ; child = child->next ) {
- if ( SP_IS_ITEM(child) ) {
- // If not already in nodecontext, goto it!
- if (!tools_isactive(dt, TOOLS_NODES)) {
- tools_switch(dt, TOOLS_NODES);
- }
-
- ShapeEditor * shape_editor = dt->event_context->shape_editor;
- // TODO: should we set the item for nodepath or knotholder or both? seems to work with both.
- shape_editor->set_item(SP_ITEM(child), SH_NODEPATH);
- shape_editor->set_item(SP_ITEM(child), SH_KNOTHOLDER);
- Inkscape::NodePath::Path *np = shape_editor->get_nodepath();
- if (np) {
- // take colors from prefs (same as used in outline mode)
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- np->helperpath_rgba = clip ?
- prefs->getInt("/options/wireframecolors/clips", 0x00ff00ff) :
- prefs->getInt("/options/wireframecolors/masks", 0x0000ffff);
- np->helperpath_width = 1.0;
- sp_nodepath_show_helperpath(np, true);
- }
- break; // break out of for loop after 1st encountered item
- }
- }
- } else if (clip) {
- dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("The selection has no applied clip path."));
- } else {
- dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("The selection has no applied mask."));
- }
- }
- }
+ if (!selection || selection->isEmpty()) return;
+
+ GSList const *items = selection->itemList();
+ bool has_path = false;
+ for (GSList *i = const_cast<GSList*>(items); i; i= i->next) {
+ SPItem *item = SP_ITEM(i->data);
+ SPObject *search = clip
+ ? SP_OBJECT(item->clip_ref ? item->clip_ref->getObject() : NULL)
+ : SP_OBJECT(item->mask_ref ? item->mask_ref->getObject() : NULL);
+ has_path |= has_path_recursive(search);
+ if (has_path) break;
+ }
+ if (has_path) {
+ if (!tools_isactive(dt, TOOLS_NODES)) {
+ tools_switch(dt, TOOLS_NODES);
+ }
+ ink_node_tool_set_mode(INK_NODE_TOOL(dt->event_context),
+ clip ? NODE_TOOL_EDIT_CLIPPING_PATHS : NODE_TOOL_EDIT_MASKS);
+ } else if (clip) {
+ dt->messageStack()->flash(Inkscape::WARNING_MESSAGE,
+ _("The selection has no applied clip path."));
+ } else {
+ dt->messageStack()->flash(Inkscape::WARNING_MESSAGE,
+ _("The selection has no applied mask."));
+ }*/
}
diff --git a/src/shape-editor.cpp b/src/shape-editor.cpp
index 44ad9dc9e..fa4360137 100644
--- a/src/shape-editor.cpp
+++ b/src/shape-editor.cpp
@@ -1,10 +1,9 @@
-#define __SHAPE_EDITOR_CPP__
-
/*
* Inkscape::ShapeEditor
*
* Authors:
* bulia byak <buliabyak@users.sf.net>
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
*
*/
@@ -25,7 +24,6 @@
#include "desktop-handles.h"
#include "knotholder.h"
#include "live_effects/parameter/point.h"
-#include "nodepath.h"
#include "xml/node-event-vector.h"
#include "preferences.h"
#include "object-edit.h"
@@ -35,33 +33,14 @@
#include "shape-editor.h"
-
-ShapeEditorsCollective::ShapeEditorsCollective(SPDesktop */*dt*/) {
-}
-
-ShapeEditorsCollective::~ShapeEditorsCollective() {
-}
-
-
-void ShapeEditorsCollective::update_statusbar() {
-
-//!!! move from nodepath: sp_nodepath_update_statusbar but summing for all nodepaths
-
-}
-
ShapeEditor::ShapeEditor(SPDesktop *dt) {
this->desktop = dt;
- this->grab_node = -1;
- this->nodepath = NULL;
this->knotholder = NULL;
- this->hit = false;
this->knotholder_listener_attached_for = NULL;
- this->nodepath_listener_attached_for = NULL;
}
ShapeEditor::~ShapeEditor() {
unset_item(SH_KNOTHOLDER);
- unset_item(SH_NODEPATH);
}
void ShapeEditor::unset_item(SubType type, bool keep_knotholder) {
@@ -69,18 +48,7 @@ void ShapeEditor::unset_item(SubType type, bool keep_knotholder) {
switch (type) {
case SH_NODEPATH:
- if (this->nodepath) {
- old_repr = this->nodepath->repr;
- if (old_repr && old_repr == nodepath_listener_attached_for) {
- sp_repr_remove_listener_by_data(old_repr, this);
- Inkscape::GC::release(old_repr);
- nodepath_listener_attached_for = NULL;
- }
-
- this->grab_node = -1;
- delete this->nodepath;
- this->nodepath = NULL;
- }
+ // defunct
break;
case SH_KNOTHOLDER:
if (this->knotholder) {
@@ -101,7 +69,7 @@ void ShapeEditor::unset_item(SubType type, bool keep_knotholder) {
}
bool ShapeEditor::has_nodepath () {
- return (this->nodepath != NULL);
+ return false;
}
bool ShapeEditor::has_knotholder () {
@@ -116,7 +84,8 @@ void ShapeEditor::update_knotholder () {
bool ShapeEditor::has_local_change (SubType type) {
switch (type) {
case SH_NODEPATH:
- return (this->nodepath && this->nodepath->local_change);
+ // defunct
+ return false;
case SH_KNOTHOLDER:
return (this->knotholder && this->knotholder->local_change != 0);
default:
@@ -127,9 +96,7 @@ bool ShapeEditor::has_local_change (SubType type) {
void ShapeEditor::decrement_local_change (SubType type) {
switch (type) {
case SH_NODEPATH:
- if (this->nodepath && this->nodepath->local_change > 0) {
- this->nodepath->local_change--;
- }
+ // defunct
break;
case SH_KNOTHOLDER:
if (this->knotholder) {
@@ -145,9 +112,7 @@ const SPItem *ShapeEditor::get_item (SubType type) {
const SPItem *item = NULL;
switch (type) {
case SH_NODEPATH:
- if (this->has_nodepath()) {
- item = this->nodepath->item;
- }
+ // defunct
break;
case SH_KNOTHOLDER:
if (this->has_knotholder()) {
@@ -159,50 +124,18 @@ const SPItem *ShapeEditor::get_item (SubType type) {
}
GList *ShapeEditor::save_nodepath_selection () {
- if (this->nodepath)
- return ::save_nodepath_selection (this->nodepath);
+ // defunct stub
return NULL;
}
void ShapeEditor::restore_nodepath_selection (GList *saved) {
- if (this->nodepath && saved)
- ::restore_nodepath_selection (this->nodepath, saved);
+ // defunct stub
}
-bool ShapeEditor::nodepath_edits_repr_key(gchar const *name) {
- if (nodepath && name) {
- return ( !strcmp(name, nodepath->repr_key) || !strcmp(name, nodepath->repr_nodetypes_key) );
- }
-
- return false;
-}
-
-
void ShapeEditor::shapeeditor_event_attr_changed(gchar const *name)
{
- gboolean changed_np = FALSE;
gboolean changed_kh = FALSE;
- if (has_nodepath() && nodepath_edits_repr_key(name))
- {
- changed_np = !has_local_change(SH_NODEPATH);
- decrement_local_change(SH_NODEPATH);
- }
-
- if (changed_np) {
- GList *saved = NULL;
- if (has_nodepath()) {
- saved = save_nodepath_selection();
- }
-
- reset_item(SH_NODEPATH);
-
- if (has_nodepath() && saved) {
- restore_nodepath_selection(saved);
- g_list_free (saved);
- }
- }
-
if (has_knotholder())
{
changed_kh = !has_local_change(SH_KNOTHOLDER);
@@ -213,8 +146,6 @@ void ShapeEditor::shapeeditor_event_attr_changed(gchar const *name)
reset_item(SH_KNOTHOLDER, !strcmp(name, "d"));
}
}
-
- update_statusbar(); //TODO: get_container()->update_statusbar();
}
@@ -243,27 +174,11 @@ void ShapeEditor::set_item(SPItem *item, SubType type, bool keep_knotholder) {
// since this freezes the handles
unset_item(type, keep_knotholder);
- this->grab_node = -1;
-
if (item) {
Inkscape::XML::Node *repr;
switch(type) {
case SH_NODEPATH:
- if (SP_IS_LPE_ITEM(item)) {
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- this->nodepath = sp_nodepath_new(desktop, item, (prefs->getBool("/tools/nodes/show_handles", true)));
- }
- if (this->nodepath) {
- this->nodepath->shape_editor = this;
-
- // setting new listener
- repr = SP_OBJECT_REPR(item);
- if (repr != nodepath_listener_attached_for) {
- Inkscape::GC::anchor(repr);
- sp_repr_add_listener(repr, &shapeeditor_repr_events, this);
- nodepath_listener_attached_for = repr;
- }
- }
+ // defunct
break;
case SH_KNOTHOLDER:
@@ -286,34 +201,6 @@ void ShapeEditor::set_item(SPItem *item, SubType type, bool keep_knotholder) {
}
}
-/** Please note that this function only works for path parameters.
-* All other parameters probably will crash Inkscape!
-*/
-void ShapeEditor::set_item_lpe_path_parameter(SPItem *item, LivePathEffectObject *lpeobject, const char * key)
-{
- unset_item(SH_NODEPATH);
-
- this->grab_node = -1;
-
- if (lpeobject) {
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- this->nodepath = sp_nodepath_new( desktop, lpeobject,
- (prefs->getInt("/tools/nodes/show_handles", true)),
- key, item);
- if (this->nodepath) {
- this->nodepath->shape_editor = this;
-
- // setting new listener
- Inkscape::XML::Node *repr = SP_OBJECT_REPR(lpeobject);
- if (repr && repr != nodepath_listener_attached_for) {
- Inkscape::GC::anchor(repr);
- sp_repr_add_listener(repr, &shapeeditor_repr_events, this);
- nodepath_listener_attached_for = repr;
- }
- }
- }
-}
-
/** FIXME: This thing is only called when the item needs to be updated in response to repr change.
Why not make a reload function in NodePath and in KnotHolder? */
@@ -321,14 +208,7 @@ void ShapeEditor::reset_item (SubType type, bool keep_knotholder)
{
switch (type) {
case SH_NODEPATH:
- if ( (nodepath) && (IS_LIVEPATHEFFECT(nodepath->object)) ) {
- char * key = g_strdup(nodepath->repr_key);
- set_item_lpe_path_parameter(nodepath->item, LIVEPATHEFFECT(nodepath->object), key); // the above checks for nodepath, so it is indeed a path that we are editing
- g_free(key);
- } else {
- SPObject *obj = sp_desktop_document(desktop)->getObjectByRepr(nodepath_listener_attached_for); /// note that it is not certain that this is an SPItem; it could be a LivePathEffectObject.
- set_item(SP_ITEM(obj), SH_NODEPATH);
- }
+ // defunct
break;
case SH_KNOTHOLDER:
if ( knotholder ) {
@@ -340,267 +220,12 @@ void ShapeEditor::reset_item (SubType type, bool keep_knotholder)
}
void ShapeEditor::nodepath_destroyed () {
- this->nodepath = NULL;
-}
-
-void ShapeEditor::update_statusbar () {
- if (this->nodepath)
- sp_nodepath_update_statusbar(this->nodepath);
-}
-
-bool ShapeEditor::is_over_stroke (Geom::Point event_p, bool remember) {
- if (!this->nodepath)
- return false; // no stroke in knotholder
-
- const SPItem *item = get_item(SH_NODEPATH);
-
- if (!item || !SP_IS_ITEM(item))
- return false;
-
- //Translate click point into proper coord system
- this->curvepoint_doc = desktop->w2d(event_p);
- this->curvepoint_doc *= sp_item_dt2i_affine(item);
-
- SPCurve *curve = this->nodepath->curve; // not sure if np->curve is always up to date...
- Geom::PathVector const &pathv = curve->get_pathvector();
- boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, this->curvepoint_doc);
- if (!pvpos) {
- g_print("Warning! Possible error?\n");
- return false;
- }
-
- Geom::Point nearest = pathv[pvpos->path_nr].pointAt(pvpos->t);
- Geom::Point delta = nearest - this->curvepoint_doc;
-
- delta = desktop->d2w(delta);
-
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- double stroke_tolerance =
- (( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
- desktop->current_zoom() *
- SP_OBJECT_STYLE (item)->stroke_width.computed * 0.5 *
- to_2geom(sp_item_i2d_affine(item)).descrim()
- : 0.0)
- + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100)) / to_2geom(sp_item_i2d_affine(item)).descrim();
- bool close = (Geom::L2 (delta) < stroke_tolerance);
-
- if (remember && close) {
- // calculate index for nodepath's representation.
- double int_part;
- double t = std::modf(pvpos->t, &int_part);
- unsigned int segment_index = (unsigned int)int_part + 1;
- for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
- segment_index += pathv[i].size() + 1;
- if (pathv[i].closed())
- segment_index += 1;
- }
-
- this->curvepoint_event[Geom::X] = (gint) event_p [Geom::X];
- this->curvepoint_event[Geom::Y] = (gint) event_p [Geom::Y];
- this->hit = true;
- this->grab_t = t;
- this->grab_node = segment_index;
- }
-
- return close;
-}
-
-void ShapeEditor::add_node_near_point() {
- if (this->nodepath) {
- sp_nodepath_add_node_near_point(this->nodepath, this->curvepoint_doc);
- } else if (this->knotholder) {
- // we do not add nodes in knotholder... yet
- }
-}
-
-void ShapeEditor::select_segment_near_point(bool toggle) {
- if (this->nodepath) {
- sp_nodepath_select_segment_near_point(this->nodepath, this->curvepoint_doc, toggle);
- }
- if (this->knotholder) {
- // we do not select segments in knotholder... yet?
- }
-}
-
-void ShapeEditor::cancel_hit() {
- this->hit = false;
-}
-
-bool ShapeEditor::hits_curve() {
- return (this->hit);
-}
-
-
-void ShapeEditor::curve_drag(gdouble eventx, gdouble eventy) {
- if (this->nodepath && !this->nodepath->straight_path) {
-
- if (this->grab_node == -1) // don't know which segment to drag
- return;
-
- // We round off the extra precision in the motion coordinates provided
- // by some input devices (like tablets). As we'll store the coordinates
- // as integers in curvepoint_event we need to do this rounding before
- // comparing them with the last coordinates from curvepoint_event.
- // See bug #1593499 for details.
-
- gint x = (gint) Inkscape::round(eventx);
- gint y = (gint) Inkscape::round(eventy);
-
-
- // The coordinates hasn't changed since the last motion event, abort
- if (this->curvepoint_event[Geom::X] == x &&
- this->curvepoint_event[Geom::Y] == y)
- return;
-
- Geom::Point const delta_w(eventx - this->curvepoint_event[Geom::X],
- eventy - this->curvepoint_event[Geom::Y]);
- Geom::Point const delta_dt(this->desktop->w2d(delta_w));
-
- sp_nodepath_curve_drag (this->nodepath, this->grab_node, this->grab_t, delta_dt);
- this->curvepoint_event[Geom::X] = x;
- this->curvepoint_event[Geom::Y] = y;
-
- }
- if (this->knotholder) {
- // we do not drag curve in knotholder
- }
-
-}
-
-void ShapeEditor::finish_drag() {
- if (this->nodepath && this->hit) {
- sp_nodepath_update_repr (this->nodepath, _("Drag curve"));
- }
-}
-
-void ShapeEditor::select_rect(Geom::Rect const &rect, bool add) {
- if (this->nodepath) {
- sp_nodepath_select_rect(this->nodepath, rect, add);
- }
}
bool ShapeEditor::has_selection() {
- if (this->nodepath)
- return this->nodepath->selected;
return false; // so far, knotholder cannot have selection
}
-void ShapeEditor::deselect() {
- if (this->nodepath)
- sp_nodepath_deselect(this->nodepath);
-}
-
-void ShapeEditor::add_node () {
- sp_node_selected_add_node(this->nodepath);
-}
-
-void ShapeEditor::delete_nodes () {
- sp_node_selected_delete(this->nodepath);
-}
-
-void ShapeEditor::delete_nodes_preserving_shape () {
- if (this->nodepath && this->nodepath->selected) {
- sp_node_delete_preserve(g_list_copy(this->nodepath->selected));
- }
-}
-
-void ShapeEditor::delete_segment () {
- sp_node_selected_delete_segment(this->nodepath);
-}
-
-void ShapeEditor::set_node_type(int type) {
- sp_node_selected_set_type(this->nodepath, (Inkscape::NodePath::NodeType) type);
-}
-
-void ShapeEditor::break_at_nodes() {
- sp_node_selected_break(this->nodepath);
-}
-
-void ShapeEditor::join_nodes() {
- sp_node_selected_join(this->nodepath);
-}
-
-void ShapeEditor::join_segments() {
- sp_node_selected_join_segment(this->nodepath);
-}
-
-void ShapeEditor::duplicate_nodes() {
- sp_node_selected_duplicate(this->nodepath);
-}
-
-void ShapeEditor::set_type_of_segments(NRPathcode code) {
- sp_node_selected_set_line_type(this->nodepath, code);
-}
-
-void ShapeEditor::move_nodes_screen(SPDesktop *desktop, gdouble dx, gdouble dy) {
- sp_node_selected_move_screen(desktop, this->nodepath, dx, dy);
-}
-void ShapeEditor::move_nodes(gdouble dx, gdouble dy) {
- sp_node_selected_move(this->nodepath, dx, dy);
-}
-
-void ShapeEditor::rotate_nodes(gdouble angle, int which, bool screen) {
- if (this->nodepath)
- sp_nodepath_selected_nodes_rotate (this->nodepath, angle, which, screen);
-}
-
-void ShapeEditor::scale_nodes(gdouble const grow, int const which) {
- sp_nodepath_selected_nodes_scale(this->nodepath, grow, which);
-}
-void ShapeEditor::scale_nodes_screen(gdouble const grow, int const which) {
- sp_nodepath_selected_nodes_scale_screen(this->nodepath, grow, which);
-}
-
-void ShapeEditor::select_all (bool invert) {
- if (this->nodepath)
- sp_nodepath_select_all (this->nodepath, invert);
-}
-void ShapeEditor::select_all_from_subpath (bool invert) {
- if (this->nodepath)
- sp_nodepath_select_all_from_subpath (this->nodepath, invert);
-}
-void ShapeEditor::select_next () {
- if (this->nodepath) {
- sp_nodepath_select_next (this->nodepath);
- if (this->nodepath->numSelected() >= 1) {
- this->desktop->scroll_to_point(this->nodepath->singleSelectedCoords(), 1.0);
- }
- }
-}
-void ShapeEditor::select_prev () {
- if (this->nodepath) {
- sp_nodepath_select_prev (this->nodepath);
- if (this->nodepath->numSelected() >= 1) {
- this->desktop->scroll_to_point(this->nodepath->singleSelectedCoords(), 1.0);
- }
- }
-}
-
-void ShapeEditor::show_handles (bool show) {
- if (this->nodepath && !this->nodepath->straight_path)
- sp_nodepath_show_handles (this->nodepath, show);
-}
-
-void ShapeEditor::show_helperpath (bool show) {
- if (this->nodepath)
- sp_nodepath_show_helperpath (this->nodepath, show);
-}
-
-void ShapeEditor::flip (Geom::Dim2 axis, boost::optional<Geom::Point> center) {
- if (this->nodepath)
- sp_nodepath_flip (this->nodepath, axis, center);
-}
-
-void ShapeEditor::distribute (Geom::Dim2 axis) {
- if (this->nodepath)
- sp_nodepath_selected_distribute (this->nodepath, axis);
-}
-void ShapeEditor::align (Geom::Dim2 axis) {
- if (this->nodepath)
- sp_nodepath_selected_align (this->nodepath, axis);
-}
-
-
/*
Local Variables:
mode:c++
diff --git a/src/shape-editor.h b/src/shape-editor.h
index 98dbb35d7..2374d6ac6 100644
--- a/src/shape-editor.h
+++ b/src/shape-editor.h
@@ -41,7 +41,6 @@ public:
~ShapeEditor();
void set_item (SPItem *item, SubType type, bool keep_knotholder = false);
- void set_item_lpe_path_parameter(SPItem *item, LivePathEffectObject *lpeobject, const char * key);
void unset_item (SubType type, bool keep_knotholder = false);
bool has_nodepath (); //((deprecated))
@@ -55,68 +54,10 @@ public:
void nodepath_destroyed ();
- void update_statusbar ();
-
- bool is_over_stroke (Geom::Point event_p, bool remember);
-
- void add_node_near_point(); // uses the shapeeditor's remembered point, if any
-
- void select_segment_near_point(bool toggle); // uses the shapeeditor's remembered point, if any
-
- void cancel_hit ();
-
- bool hits_curve ();
-
- void curve_drag (gdouble eventx, gdouble eventy);
-
- void finish_drag ();
-
- void select_rect (Geom::Rect const &rect, bool add);
-
bool has_selection ();
- void deselect ();
-
- Inkscape::NodePath::Path *get_nodepath() {return nodepath;} //((deprecated))
- ShapeEditorsCollective *get_container() {return container;}
-
- void add_node();
-
- void delete_nodes();
- void delete_nodes_preserving_shape();
- void delete_segment();
-
- void set_node_type(int type);
-
- void break_at_nodes();
- void join_nodes();
- void join_segments();
-
- void duplicate_nodes();
-
- void set_type_of_segments(NRPathcode code);
-
- void move_nodes(gdouble dx, gdouble dy);
- void move_nodes_screen(SPDesktop *desktop, gdouble dx, gdouble dy);
-
- void rotate_nodes(gdouble angle, int which, bool screen);
-
- void scale_nodes(gdouble const grow, int const which);
- void scale_nodes_screen(gdouble const grow, int const which);
-
- void select_all (bool invert);
- void select_all_from_subpath (bool invert);
- void select_next ();
- void select_prev ();
- void show_handles (bool show);
- void show_helperpath (bool show);
-
- void flip (Geom::Dim2 axis, boost::optional<Geom::Point> center = boost::optional<Geom::Point>());
-
- void distribute (Geom::Dim2 axis);
- void align (Geom::Dim2 axis);
-
- bool nodepath_edits_repr_key(gchar const *name);
+ Inkscape::NodePath::Path *get_nodepath() {return NULL;} //((deprecated))
+ ShapeEditorsCollective *get_container() {return NULL;}
// this one is only public because it's called from non-C++ repr changed callback
void shapeeditor_event_attr_changed(gchar const *name);
@@ -127,44 +68,8 @@ private:
const SPItem *get_item (SubType type);
SPDesktop *desktop;
-
- Inkscape::NodePath::Path *nodepath;
-
- // TODO: std::list<KnotHolder *> knotholders;
KnotHolder *knotholder;
-
- ShapeEditorsCollective *container;
-
- //Inkscape::XML::Node *lidtened_repr;
-
- double grab_t;
- int grab_node; // number of node grabbed by sp_node_context_is_over_stroke
- bool hit;
- Geom::Point curvepoint_event; // int coords from event
- Geom::Point curvepoint_doc; // same, in doc coords
-
Inkscape::XML::Node *knotholder_listener_attached_for;
- Inkscape::XML::Node *nodepath_listener_attached_for;
-};
-
-
-/* As the next stage, this will be a collection of multiple ShapeEditors,
-with the same interface as the single ShapeEditor, passing the actions to all its
-contained ShapeEditors. Thus it should be easy to switch node context from
-using a single ShapeEditor to using a ShapeEditorsCollective. */
-
-class ShapeEditorsCollective {
-public:
-
- ShapeEditorsCollective(SPDesktop *desktop);
- ~ShapeEditorsCollective();
-
- void update_statusbar();
-
-private:
- std::vector<ShapeEditor> editors;
-
- SPNodeContext *nc; // who holds us
};
#endif
diff --git a/src/snap.cpp b/src/snap.cpp
index 970f29ece..c39cd9e04 100644
--- a/src/snap.cpp
+++ b/src/snap.cpp
@@ -30,6 +30,7 @@
#include "inkscape.h"
#include "desktop.h"
+#include "selection.h"
#include "sp-guide.h"
#include "preferences.h"
#include "event-context.h"
@@ -207,25 +208,11 @@ Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::SnapPreferences::PointTyp
return Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false);
}
- std::vector<SPItem const *> *items_to_ignore;
- if (_item_to_ignore) { // If we have only a single item to ignore
- // then build a list containing this single item;
- // This single-item list will prevail over any other _items_to_ignore list, should that exist
- items_to_ignore = new std::vector<SPItem const *>;
- items_to_ignore->push_back(_item_to_ignore);
- } else {
- items_to_ignore = _items_to_ignore;
- }
-
SnappedConstraints sc;
SnapperList const snappers = getSnappers();
for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
- (*i)->freeSnap(sc, point_type, p, bbox_to_snap, items_to_ignore, _unselected_nodes);
- }
-
- if (_item_to_ignore) {
- delete items_to_ignore;
+ (*i)->freeSnap(sc, point_type, p, bbox_to_snap, &_items_to_ignore, _unselected_nodes);
}
return findBestSnap(p, sc, false);
@@ -363,17 +350,6 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::P
return Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false);
}
- std::vector<SPItem const *> *items_to_ignore;
- if (_item_to_ignore) { // If we have only a single item to ignore
- // then build a list containing this single item;
- // This single-item list will prevail over any other _items_to_ignore list, should that exist
- items_to_ignore = new std::vector<SPItem const *>;
- items_to_ignore->push_back(_item_to_ignore);
- } else {
- items_to_ignore = _items_to_ignore;
- }
-
-
// First project the mouse pointer onto the constraint
Geom::Point pp = constraint.projection(p.getPoint());
// Then try to snap the projected point
@@ -382,11 +358,7 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::P
SnappedConstraints sc;
SnapperList const snappers = getSnappers();
for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) {
- (*i)->constrainedSnap(sc, point_type, candidate, bbox_to_snap, constraint, items_to_ignore);
- }
-
- if (_item_to_ignore) {
- delete items_to_ignore;
+ (*i)->constrainedSnap(sc, point_type, candidate, bbox_to_snap, constraint, &_items_to_ignore);
}
return findBestSnap(candidate, sc, true);
@@ -1036,22 +1008,7 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Inkscape::SnapCandidatePoint co
return bestSnappedPoint;
}
-/**
- * \brief Prepare the snap manager for the actual snapping, which includes building a list of snap targets
- * to ignore and toggling the snap indicator
- *
- * There are two overloaded setup() methods, of which this one only allows for a single item to be ignored
- * whereas the other one will take a list of items to ignore
- *
- * \param desktop Reference to the desktop to which this snap manager is attached
- * \param snapindicator If true then a snap indicator will be displayed automatically (when enabled in the preferences)
- * \param item_to_ignore This item will not be snapped to, e.g. the item that is currently being dragged. This avoids "self-snapping"
- * \param unselected_nodes Stationary nodes of the path that is currently being edited in the node tool and
- * that can be snapped too. Nodes not in this list will not be snapped to, to avoid "self-snapping". Of each
- * unselected node both the position (Geom::Point) and the type (Inkscape::SnapTargetType) will be stored
- * \param guide_to_ignore Guide that is currently being dragged and should not be snapped to
- */
-
+/// Convenience shortcut when there is only one item to ignore
void SnapManager::setup(SPDesktop const *desktop,
bool snapindicator,
SPItem const *item_to_ignore,
@@ -1059,8 +1016,8 @@ void SnapManager::setup(SPDesktop const *desktop,
SPGuide *guide_to_ignore)
{
g_assert(desktop != NULL);
- _item_to_ignore = item_to_ignore;
- _items_to_ignore = NULL;
+ _items_to_ignore.clear();
+ _items_to_ignore.push_back(item_to_ignore);
_desktop = desktop;
_snapindicator = snapindicator;
_unselected_nodes = unselected_nodes;
@@ -1090,14 +1047,32 @@ void SnapManager::setup(SPDesktop const *desktop,
SPGuide *guide_to_ignore)
{
g_assert(desktop != NULL);
- _item_to_ignore = NULL;
- _items_to_ignore = &items_to_ignore;
+ _items_to_ignore = items_to_ignore;
_desktop = desktop;
_snapindicator = snapindicator;
_unselected_nodes = unselected_nodes;
_guide_to_ignore = guide_to_ignore;
}
+/// Setup, taking the list of items to ignore from the desktop's selection.
+void SnapManager::setupIgnoreSelection(SPDesktop const *desktop,
+ bool snapindicator,
+ std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes,
+ SPGuide *guide_to_ignore)
+{
+ _desktop = desktop;
+ _snapindicator = snapindicator;
+ _unselected_nodes = unselected_nodes;
+ _guide_to_ignore = guide_to_ignore;
+ _items_to_ignore.clear();
+
+ Inkscape::Selection *sel = _desktop->selection;
+ GSList const *items = sel->itemList();
+ for (GSList *i = const_cast<GSList*>(items); i; i = i->next) {
+ _items_to_ignore.push_back(static_cast<SPItem const *>(i->data));
+ }
+}
+
SPDocument *SnapManager::getDocument() const
{
return _named_view->document;
diff --git a/src/snap.h b/src/snap.h
index 40bf0996e..ae136a355 100644
--- a/src/snap.h
+++ b/src/snap.h
@@ -1,17 +1,7 @@
-#ifndef SEEN_SNAP_H
-#define SEEN_SNAP_H
-
/**
* \file snap.h
- * \brief SnapManager class.
- *
- * The SnapManager class handles most (if not all) of the interfacing of the snapping mechanisms with the
- * other parts of the code base. It stores the references to the various types of snappers for grid, guides
- * and objects, and it stores most of the snapping preferences. Besides that it provides methods to setup
- * the snapping environment (e.g. keeps a list of the items to ignore when looking for snap target candidates,
- * and toggling of the snap indicator), and it provides many different methods for the snapping itself (free
- * snapping vs. constrained snapping, returning the result by reference or through a return statement, etc.)
- *
+ * \brief Per-desktop object that handles snapping queries
+ *//*
* Authors:
* Lauris Kaplinski <lauris@kaplinski.com>
* Frank Felfe <innerspace@iname.com>
@@ -25,8 +15,10 @@
* Released under GNU GPL, read the file 'COPYING' for more information
*/
-#include <vector>
+#ifndef SEEN_SNAP_H
+#define SEEN_SNAP_H
+#include <vector>
#include "guide-snapper.h"
#include "object-snapper.h"
#include "snap-preferences.h"
@@ -42,11 +34,33 @@ enum SPGuideDragType { // used both here and in desktop-events.cpp
class SPNamedView;
/// Class to coordinate snapping operations
-
/**
- * Each SPNamedView has one of these. It offers methods to snap points to whatever
- * snappers are defined (e.g. grid, guides etc.). It also allows callers to snap
- * points which have undergone some transformation (e.g. translation, scaling etc.)
+ * The SnapManager class handles most (if not all) of the interfacing of the snapping mechanisms
+ * with the other parts of the code base. It stores the references to the various types of snappers
+ * for grid, guides and objects, and it stores most of the snapping preferences. Besides that
+ * it provides methods to setup the snapping environment (e.g. keeps a list of the items to ignore
+ * when looking for snap target candidates, and toggling of the snap indicator), and it provides
+ * many different methods for snapping queries (free snapping vs. constrained snapping,
+ * returning the result by reference or through a return statement, etc.)
+ *
+ * Each SPNamedView has one of these. It offers methods to snap points to whatever
+ * snappers are defined (e.g. grid, guides etc.). It also allows callers to snap
+ * points which have undergone some transformation (e.g. translation, scaling etc.)
+ *
+ * \par How snapping is implemented in Inkscape
+ * \par
+ * The snapping system consists of two key elements. The first one is the snap manager
+ * (this class), which keeps some data about objects in the document and answers queries
+ * of the type "given this point and type of transformation, what is the best place
+ * to snap to?".
+ *
+ * The second is in event-context.cpp and implements the snapping timeout. Whenever a motion
+ * events happens over the canvas, it stores it for later use and initiates a timeout.
+ * This timeout is discarded whenever a new motion event occurs. When the timeout expires,
+ * a global flag in SnapManager, accessed via getSnapPostponedGlobally(), is set to true
+ * and the stored event is replayed, but this time with snapping enabled. This way you can
+ * write snapping code directly in your control point's dragged handler as if there was
+ * no timeout.
*/
class SnapManager
@@ -73,10 +87,14 @@ public:
SPGuide *guide_to_ignore = NULL);
void setup(SPDesktop const *desktop,
- bool snapindicator,
- std::vector<SPItem const *> &items_to_ignore,
- std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes = NULL,
- SPGuide *guide_to_ignore = NULL);
+ bool snapindicator,
+ std::vector<SPItem const *> &items_to_ignore,
+ std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes = NULL,
+ SPGuide *guide_to_ignore = NULL);
+ void setupIgnoreSelection(SPDesktop const *desktop,
+ bool snapindicator = true,
+ std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes = NULL,
+ SPGuide *guide_to_ignore = NULL);
// freeSnapReturnByRef() is preferred over freeSnap(), because it only returns a
// point if snapping has occurred (by overwriting p); otherwise p is untouched
@@ -166,8 +184,7 @@ protected:
SPNamedView const *_named_view;
private:
- std::vector<SPItem const *> *_items_to_ignore; ///< Items that should not be snapped to, for example the items that are currently being dragged. Set using the setup() method
- SPItem const *_item_to_ignore; ///< Single item that should not be snapped to. If not NULL then this takes precedence over _items_to_ignore. Set using the setup() method
+ std::vector<SPItem const *> _items_to_ignore; ///< Items that should not be snapped to, for example the items that are currently being dragged. Set using the setup() method
SPGuide *_guide_to_ignore; ///< A guide that should not be snapped to, e.g. the guide that is currently being dragged
SPDesktop const *_desktop;
bool _snapindicator; ///< When true, an indicator will be drawn at the position that was being snapped to
diff --git a/src/snapper.h b/src/snapper.h
index dbbf7ebe9..47c1c514e 100644
--- a/src/snapper.h
+++ b/src/snapper.h
@@ -69,6 +69,7 @@ public:
public:
ConstraintLine(Geom::Point const &d) : _has_point(false), _direction(d) {}
ConstraintLine(Geom::Point const &p, Geom::Point const &d) : _has_point(true), _point(p), _direction(d) {}
+ ConstraintLine(Geom::Line const &l) : _has_point(true), _point(l.origin()), _direction(l.versor()) {}
bool hasPoint() const {
return _has_point;
diff --git a/src/sp-lpe-item.cpp b/src/sp-lpe-item.cpp
index 6b71541e6..1bb500dd2 100644
--- a/src/sp-lpe-item.cpp
+++ b/src/sp-lpe-item.cpp
@@ -33,7 +33,6 @@
#include "message-stack.h"
#include "inkscape.h"
#include "desktop.h"
-#include "node-context.h"
#include "shape-editor.h"
#include <algorithm>
@@ -261,18 +260,7 @@ sp_lpe_item_update(SPObject *object, SPCtx *ctx, guint flags)
}
// update the helperpaths of all LPEs applied to the item
- // TODO: is there a more canonical place for this, since we don't have instant access to the item's nodepath?
- // FIXME: this is called multiple (at least 3) times; how can we avoid this?
-
- // FIXME: ditch inkscape_active_event_context()
- SPEventContext *ec = inkscape_active_event_context();
- if (!SP_IS_NODE_CONTEXT(ec)) return;
- ShapeEditor *sh = ec->shape_editor;
- g_assert(sh);
- if (!sh->has_nodepath()) return;
-
- Inkscape::NodePath::Path *np = sh->get_nodepath();
- sp_nodepath_update_helperpaths(np);
+ // TODO: re-add for the new node tool
}
/**
@@ -395,7 +383,8 @@ sp_lpe_item_update_patheffect (SPLPEItem *lpeitem, bool wholetree, bool write)
if (dynamic_cast<Inkscape::LivePathEffect::LPEPathLength *>(lpe)) {
if (!lpe->isVisible()) {
// we manually disable text for LPEPathLength
- dynamic_cast<Inkscape::LivePathEffect::LPEPathLength *>(lpe)->hideCanvasText();
+ // use static_cast, because we already checked for the right type above
+ static_cast<Inkscape::LivePathEffect::LPEPathLength *>(lpe)->hideCanvasText();
}
}
}
diff --git a/src/tools-switch.cpp b/src/tools-switch.cpp
index 6c53ce61c..5f33453f0 100644
--- a/src/tools-switch.cpp
+++ b/src/tools-switch.cpp
@@ -26,7 +26,7 @@
#include <xml/repr.h>
#include "select-context.h"
-#include "node-context.h"
+#include "ui/tool/node-tool.h"
#include "tweak-context.h"
#include "spray-context.h"
#include "sp-path.h"
@@ -126,10 +126,9 @@ tools_switch(SPDesktop *dt, int num)
inkscape_eventcontext_set(sp_desktop_event_context(dt));
break;
case TOOLS_NODES:
- dt->set_event_context(SP_TYPE_NODE_CONTEXT, tool_names[num]);
+ dt->set_event_context(INK_TYPE_NODE_TOOL, tool_names[num]);
dt->activate_guides(true);
inkscape_eventcontext_set(sp_desktop_event_context(dt));
- dt->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("To edit a path, <b>click</b>, <b>Shift+click</b>, or <b>drag around</b> nodes to select them, then <b>drag</b> nodes and handles. <b>Click</b> on an object to select."));
break;
case TOOLS_TWEAK:
dt->set_event_context(SP_TYPE_TWEAK_CONTEXT, tool_names[num]);
diff --git a/src/ui/dialog/aboutbox.cpp b/src/ui/dialog/aboutbox.cpp
index 025bec37a..bbd02fa5d 100644
--- a/src/ui/dialog/aboutbox.cpp
+++ b/src/ui/dialog/aboutbox.cpp
@@ -103,8 +103,8 @@ AboutBox::AboutBox() : Gtk::Dialog(_("About Inkscape")) {
Gtk::Label *label=new Gtk::Label();
gchar *label_text =
- g_strdup_printf("<small><i>Inkscape %s, built %s</i></small>",
- Inkscape::version_string, __DATE__);
+ g_strdup_printf("<small><i>Inkscape %s</i></small>",
+ Inkscape::version_string);
label->set_markup(label_text);
label->set_alignment(Gtk::ALIGN_RIGHT, Gtk::ALIGN_CENTER);
g_free(label_text);
diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp
index 2bba0a0f8..8c8d64ec0 100644
--- a/src/ui/dialog/align-and-distribute.cpp
+++ b/src/ui/dialog/align-and-distribute.cpp
@@ -27,17 +27,17 @@
#include "graphlayout/graphlayout.h"
#include "inkscape.h"
#include "macros.h"
-#include "node-context.h" //For access to ShapeEditor
#include "preferences.h"
#include "removeoverlap/removeoverlap.h"
#include "selection.h"
-#include "shape-editor.h" //For node align/distribute methods
#include "sp-flowtext.h"
#include "sp-item-transform.h"
#include "sp-text.h"
#include "text-editing.h"
#include "tools-switch.h"
#include "ui/icon-names.h"
+#include "ui/tool/node-tool.h"
+#include "ui/tool/multi-path-manipulator.h"
#include "util/glib-list-iterators.h"
#include "verbs.h"
#include "widgets/icon.h"
@@ -429,12 +429,13 @@ private :
if (!_dialog.getDesktop()) return;
SPEventContext *event_context = sp_desktop_event_context(_dialog.getDesktop());
- if (!SP_IS_NODE_CONTEXT (event_context)) return ;
+ if (!INK_IS_NODE_TOOL (event_context)) return;
+ InkNodeTool *nt = INK_NODE_TOOL(event_context);
if (_distribute)
- event_context->shape_editor->distribute((Geom::Dim2)_orientation);
+ nt->_multipath->distributeNodes(_orientation);
else
- event_context->shape_editor->align((Geom::Dim2)_orientation);
+ nt->_multipath->alignNodes(_orientation);
}
};
diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp
index 90516063c..11850cffc 100644
--- a/src/ui/dialog/inkscape-preferences.cpp
+++ b/src/ui/dialog/inkscape-preferences.cpp
@@ -435,13 +435,22 @@ void InkscapePreferences::initPageTools()
AddGradientCheckbox(_page_node, "/tools/nodes", true);
_page_node.add_group_header( _("Path outline:"));
_t_node_pathoutline_color.init(_("Path outline color"), "/tools/nodes/highlight_color", 0xff0000ff);
- _page_node.add_line( false, _("Path outline color"), _t_node_pathoutline_color, "", _("Selects the color used for showing the path outline."), false);
- _t_node_pathflash_enabled.init ( _("Path outline flash on mouse-over"), "/tools/nodes/pathflash_enabled", false);
+ _page_node.add_line( false, "", _t_node_pathoutline_color, "", _("Selects the color used for showing the path outline."), false);
+ _t_node_show_outline.init(_("Always show outline"), "/tools/nodes/show_outline", false);
+ _page_node.add_line( true, "", _t_node_show_outline, "", _("Show outlines for all paths, not only invisible paths"));
+ _t_node_show_path_direction.init(_("Show path direction on outlines"), "/tools/nodes/show_path_direction", false);
+ _page_node.add_line( true, "", _t_node_show_path_direction, "", _("Visualize the direction of selected paths by drawing small arrows in the middle of each outline segment"));
+ _t_node_pathflash_enabled.init ( _("Show temporary path outline"), "/tools/nodes/pathflash_enabled", false);
_page_node.add_line( true, "", _t_node_pathflash_enabled, "", _("When hovering over a path, briefly flash its outline."));
- _t_node_pathflash_unselected.init ( _("Suppress path outline flash when one path selected"), "/tools/nodes/pathflash_unselected", false);
- _page_node.add_line( true, "", _t_node_pathflash_unselected, "", _("If a path is selected, do not continue flashing path outlines."));
+ _t_node_pathflash_selected.init ( _("Show temporary outline for selected paths"), "/tools/nodes/pathflash_selected", false);
+ _page_node.add_line( true, "", _t_node_pathflash_selected, "", _("Show temporary outline even when a path is selected for editing"));
_t_node_pathflash_timeout.init("/tools/nodes/pathflash_timeout", 0, 10000.0, 100.0, 100.0, 1000.0, true, false);
_page_node.add_line( false, _("Flash time"), _t_node_pathflash_timeout, "ms", _("Specifies how long the path outline will be visible after a mouse-over (in milliseconds). Specify 0 to have the outline shown until mouse leaves the path."), false);
+ _page_node.add_group_header(_("Transform Handles:"));
+ _t_node_show_transform_handles.init(_("Show transform handles"), "/tools/nodes/show_transform_handles", true);
+ _page_node.add_line( true, "", _t_node_show_transform_handles, "", _("Show scaling, rotation and skew handles for node selections."));
+ _t_node_single_node_transform_handles.init(_("Show transform handles for single nodes"), "/tools/nodes/single_node_transform_handles", false);
+ _page_node.add_line( true, "", _t_node_single_node_transform_handles, "", _("Show transform handles even when only a single node is selected."));
//Tweak
this->AddPage(_page_tweak, _("Tweak"), iter_tools, PREFS_PAGE_TOOLS_TWEAK);
diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h
index 16e62df59..638c84598 100644
--- a/src/ui/dialog/inkscape-preferences.h
+++ b/src/ui/dialog/inkscape-preferences.h
@@ -141,9 +141,13 @@ protected:
PrefRadioButton _t_sel_trans_obj, _t_sel_trans_outl, _t_sel_cue_none, _t_sel_cue_mark,
_t_sel_cue_box, _t_bbox_visual, _t_bbox_geometric;
PrefCheckButton _t_cvg_keep_objects, _t_cvg_convert_whole_groups;
+ PrefCheckButton _t_node_show_outline;
PrefCheckButton _t_node_pathflash_enabled;
- PrefCheckButton _t_node_pathflash_unselected;
+ PrefCheckButton _t_node_pathflash_selected;
PrefSpinButton _t_node_pathflash_timeout;
+ PrefCheckButton _t_node_show_path_direction;
+ PrefCheckButton _t_node_show_transform_handles;
+ PrefCheckButton _t_node_single_node_transform_handles;
PrefColorPicker _t_node_pathoutline_color;
PrefRadioButton _win_dockable, _win_floating;
diff --git a/src/ui/tool/Makefile_insert b/src/ui/tool/Makefile_insert
new file mode 100644
index 000000000..e14943021
--- /dev/null
+++ b/src/ui/tool/Makefile_insert
@@ -0,0 +1,29 @@
+## Makefile.am fragment sourced by src/Makefile.am.
+
+ink_common_sources += \
+ ui/tool/control-point.cpp \
+ ui/tool/control-point.h \
+ ui/tool/control-point-selection.cpp \
+ ui/tool/control-point-selection.h \
+ ui/tool/commit-events.h \
+ ui/tool/curve-drag-point.cpp \
+ ui/tool/curve-drag-point.h \
+ ui/tool/event-utils.cpp \
+ ui/tool/event-utils.h \
+ ui/tool/manipulator.cpp \
+ ui/tool/manipulator.h \
+ ui/tool/multi-path-manipulator.cpp \
+ ui/tool/multi-path-manipulator.h \
+ ui/tool/node.cpp \
+ ui/tool/node.h \
+ ui/tool/node-types.h \
+ ui/tool/node-tool.cpp \
+ ui/tool/node-tool.h \
+ ui/tool/path-manipulator.cpp \
+ ui/tool/path-manipulator.h \
+ ui/tool/selectable-control-point.cpp \
+ ui/tool/selectable-control-point.h \
+ ui/tool/selector.cpp \
+ ui/tool/selector.h \
+ ui/tool/transform-handle-set.cpp \
+ ui/tool/transform-handle-set.h
diff --git a/src/ui/tool/commit-events.h b/src/ui/tool/commit-events.h
new file mode 100644
index 000000000..d99872766
--- /dev/null
+++ b/src/ui/tool/commit-events.h
@@ -0,0 +1,51 @@
+/** @file
+ * Commit events.
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_COMMIT_EVENTS_H
+#define SEEN_UI_TOOL_COMMIT_EVENTS_H
+
+namespace Inkscape {
+namespace UI {
+
+/// This is used to provide sensible messages on the undo stack.
+enum CommitEvent {
+ COMMIT_MOUSE_MOVE,
+ COMMIT_KEYBOARD_MOVE_X,
+ COMMIT_KEYBOARD_MOVE_Y,
+ COMMIT_MOUSE_SCALE,
+ COMMIT_MOUSE_SCALE_UNIFORM,
+ COMMIT_KEYBOARD_SCALE_UNIFORM,
+ COMMIT_KEYBOARD_SCALE_X,
+ COMMIT_KEYBOARD_SCALE_Y,
+ COMMIT_MOUSE_ROTATE,
+ COMMIT_KEYBOARD_ROTATE,
+ COMMIT_MOUSE_SKEW_X,
+ COMMIT_MOUSE_SKEW_Y,
+ COMMIT_KEYBOARD_SKEW_X,
+ COMMIT_KEYBOARD_SKEW_Y,
+ COMMIT_FLIP_X,
+ COMMIT_FLIP_Y
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/control-point-selection.cpp b/src/ui/tool/control-point-selection.cpp
new file mode 100644
index 000000000..5a84592b6
--- /dev/null
+++ b/src/ui/tool/control-point-selection.cpp
@@ -0,0 +1,583 @@
+/** @file
+ * Node selection - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <2geom/transforms.h>
+#include "desktop.h"
+#include "preferences.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/selectable-control-point.h"
+#include "ui/tool/transform-handle-set.h"
+
+namespace Inkscape {
+namespace UI {
+
+/**
+ * @class ControlPointSelection
+ * @brief Group of selected control points.
+ *
+ * Some operations can be performed on all selected points regardless of their type, therefore
+ * this class is also a Manipulator. It handles the transformations of points using
+ * the keyboard.
+ *
+ * The exposed interface is similar to that of an STL set. Internally, a hash map is used.
+ * @todo Correct iterators (that don't expose the connection list)
+ */
+
+/** @var ControlPointSelection::signal_update
+ * Fires when the display needs to be updated to reflect changes.
+ */
+/** @var ControlPointSelection::signal_point_changed
+ * Fires when a control point is added to or removed from the selection.
+ * The first param contains a pointer to the control point that changed sel. state.
+ * The second says whether the point is currently selected.
+ */
+/** @var ControlPointSelection::signal_commit
+ * Fires when a change that needs to be committed to XML happens.
+ */
+
+ControlPointSelection::ControlPointSelection(SPDesktop *d, SPCanvasGroup *th_group)
+ : Manipulator(d)
+ , _handles(new TransformHandleSet(d, th_group))
+ , _dragging(false)
+ , _handles_visible(true)
+ , _one_node_handles(false)
+ , _sculpt_enabled(false)
+ , _sculpting(false)
+{
+ signal_update.connect( sigc::bind(
+ sigc::mem_fun(*this, &ControlPointSelection::_updateTransformHandles),
+ true));
+ signal_point_changed.connect(
+ sigc::hide( sigc::hide(
+ sigc::bind(
+ sigc::mem_fun(*this, &ControlPointSelection::_updateTransformHandles),
+ false))));
+ _handles->signal_transform.connect(
+ sigc::mem_fun(*this, &ControlPointSelection::transform));
+ _handles->signal_commit.connect(
+ sigc::mem_fun(*this, &ControlPointSelection::_commitTransform));
+}
+
+ControlPointSelection::~ControlPointSelection()
+{
+ clear();
+ delete _handles;
+}
+
+/** Add a control point to the selection. */
+std::pair<ControlPointSelection::iterator, bool> ControlPointSelection::insert(const value_type &x)
+{
+ iterator found = _points.find(x);
+ if (found != _points.end()) {
+ return std::pair<iterator, bool>(found, false);
+ }
+
+ boost::shared_ptr<connlist_type> clist(new connlist_type());
+
+ // hide event param and always return false
+ clist->push_back(
+ x->signal_grabbed.connect(
+ sigc::bind_return(
+ sigc::bind<0>(
+ sigc::mem_fun(*this, &ControlPointSelection::_selectionGrabbed),
+ x),
+ false)));
+ clist->push_back(
+ x->signal_dragged.connect(
+ sigc::mem_fun(*this, &ControlPointSelection::_selectionDragged)));
+ // hide event parameter
+ clist->push_back(
+ x->signal_ungrabbed.connect(
+ sigc::hide(
+ sigc::mem_fun(*this, &ControlPointSelection::_selectionUngrabbed))));
+
+ found = _points.insert(std::make_pair(x, clist)).first;
+
+ x->updateState();
+ _rot_radius.reset();
+ signal_point_changed.emit(x, true);
+
+ return std::pair<iterator, bool>(found, true);
+}
+
+/** Remove a point from the selection. */
+void ControlPointSelection::erase(iterator pos)
+{
+ SelectableControlPoint *erased = pos->first;
+ boost::shared_ptr<connlist_type> clist = pos->second;
+ for (connlist_type::iterator i = clist->begin(); i != clist->end(); ++i) {
+ i->disconnect();
+ }
+ _points.erase(pos);
+ erased->updateState();
+ _rot_radius.reset();
+ signal_point_changed.emit(erased, false);
+}
+ControlPointSelection::size_type ControlPointSelection::erase(const key_type &k)
+{
+ iterator pos = _points.find(k);
+ if (pos == _points.end()) return 0;
+ erase(pos);
+ return 1;
+}
+void ControlPointSelection::erase(iterator first, iterator last)
+{
+ while (first != last) erase(first++);
+}
+
+/** Remove all points from the selection, making it empty. */
+void ControlPointSelection::clear()
+{
+ for (iterator i = begin(); i != end(); )
+ erase(i++);
+}
+
+/** Select all points that this selection can contain. */
+void ControlPointSelection::selectAll()
+{
+ for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
+ insert(*i);
+ }
+}
+/** Select all points inside the given rectangle (in desktop coordinates). */
+void ControlPointSelection::selectArea(Geom::Rect const &r)
+{
+ for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
+ if (r.contains(**i))
+ insert(*i);
+ }
+}
+/** Unselect all selected points and select all unselected points. */
+void ControlPointSelection::invertSelection()
+{
+ for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
+ if ((*i)->selected()) erase(*i);
+ else insert(*i);
+ }
+}
+void ControlPointSelection::spatialGrow(SelectableControlPoint *origin, int dir)
+{
+ bool grow = (dir > 0);
+ Geom::Point p = origin->position();
+ double best_dist = grow ? HUGE_VAL : 0;
+ SelectableControlPoint *match = NULL;
+ for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
+ bool selected = (*i)->selected();
+ if (grow && !selected) {
+ double dist = Geom::distance((*i)->position(), p);
+ if (dist < best_dist) {
+ best_dist = dist;
+ match = *i;
+ }
+ }
+ if (!grow && selected) {
+ double dist = Geom::distance((*i)->position(), p);
+ // use >= to also deselect the origin node when it's the last one selected
+ if (dist >= best_dist) {
+ best_dist = dist;
+ match = *i;
+ }
+ }
+ }
+ if (match) {
+ if (grow) insert(match);
+ else erase(match);
+ }
+}
+
+/** Transform all selected control points by the given affine transformation. */
+void ControlPointSelection::transform(Geom::Matrix const &m)
+{
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ SelectableControlPoint *cur = i->first;
+ cur->transform(m);
+ }
+ // TODO preserving the rotation radius needs some rethinking...
+ if (_rot_radius) (*_rot_radius) *= m.descrim();
+ signal_update.emit();
+}
+
+/** Align control points on the specified axis. */
+void ControlPointSelection::align(Geom::Dim2 axis)
+{
+ if (empty()) return;
+ Geom::Dim2 d = static_cast<Geom::Dim2>((axis + 1) % 2);
+
+ Geom::OptInterval bound;
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ bound.unionWith(Geom::OptInterval(i->first->position()[d]));
+ }
+
+ double new_coord = bound->middle();
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ Geom::Point pos = i->first->position();
+ pos[d] = new_coord;
+ i->first->move(pos);
+ }
+}
+
+/** Equdistantly distribute control points by moving them in the specified dimension. */
+void ControlPointSelection::distribute(Geom::Dim2 d)
+{
+ if (empty()) return;
+
+ // this needs to be a multimap, otherwise it will fail when some points have the same coord
+ typedef std::multimap<double, SelectableControlPoint*> SortMap;
+
+ SortMap sm;
+ Geom::OptInterval bound;
+ // first we insert all points into a multimap keyed by the aligned coord to sort them
+ // simultaneously we compute the extent of selection
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ Geom::Point pos = i->first->position();
+ sm.insert(std::make_pair(pos[d], i->first));
+ bound.unionWith(Geom::OptInterval(pos[d]));
+ }
+
+ // now we iterate over the multimap and set aligned positions.
+ double step = size() == 1 ? 0 : bound->extent() / (size() - 1);
+ double start = bound->min();
+ unsigned num = 0;
+ for (SortMap::iterator i = sm.begin(); i != sm.end(); ++i, ++num) {
+ Geom::Point pos = i->second->position();
+ pos[d] = start + num * step;
+ i->second->move(pos);
+ }
+}
+
+/** Get the bounds of the selection.
+ * @return Smallest rectangle containing the positions of all selected points,
+ * or nothing if the selection is empty */
+Geom::OptRect ControlPointSelection::pointwiseBounds()
+{
+ Geom::OptRect bound;
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ SelectableControlPoint *cur = i->first;
+ Geom::Point p = cur->position();
+ if (!bound) {
+ bound = Geom::Rect(p, p);
+ } else {
+ bound->expandTo(p);
+ }
+ }
+ return bound;
+}
+
+Geom::OptRect ControlPointSelection::bounds()
+{
+ Geom::OptRect bound;
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ SelectableControlPoint *cur = i->first;
+ Geom::OptRect r = cur->bounds();
+ bound.unionWith(r);
+ }
+ return bound;
+}
+
+void ControlPointSelection::showTransformHandles(bool v, bool one_node)
+{
+ _one_node_handles = one_node;
+ _handles_visible = v;
+ _updateTransformHandles(false);
+}
+
+void ControlPointSelection::hideTransformHandles()
+{
+ _handles->setVisible(false);
+}
+void ControlPointSelection::restoreTransformHandles()
+{
+ _updateTransformHandles(true);
+}
+
+void ControlPointSelection::_selectionGrabbed(SelectableControlPoint *p, GdkEventMotion *event)
+{
+ hideTransformHandles();
+ _dragging = true;
+ if (held_alt(*event) && _sculpt_enabled) {
+ _sculpting = true;
+ _grabbed_point = p;
+ } else {
+ _sculpting = false;
+ }
+}
+
+void ControlPointSelection::_selectionDragged(Geom::Point const &old_pos, Geom::Point &new_pos,
+ GdkEventMotion *event)
+{
+ Geom::Point delta = new_pos - old_pos;
+ /*if (_sculpting) {
+ // for now we only support the default sculpting profile (bell)
+ // others will be added when preferences will be able to store enumerated values
+ double pressure, alpha;
+ if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &pressure)) {
+ pressure = CLAMP(pressure, 0.2, 0.8);
+ } else {
+ pressure = 0.5;
+ }
+
+ alpha = 1 - 2 * fabs(pressure - 0.5);
+ if (pressure > 0.5) alpha = 1/alpha;
+
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ SelectableControlPoint *cur = i->first;
+ double dist = Geom::distance(cur->position(), _grabbed_point->position());
+
+ cur->move(cur->position() + delta);
+ }
+ } else*/ {
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ SelectableControlPoint *cur = i->first;
+ cur->move(cur->position() + delta);
+ }
+ _handles->rotationCenter().move(_handles->rotationCenter().position() + delta);
+ }
+ signal_update.emit();
+}
+
+void ControlPointSelection::_selectionUngrabbed()
+{
+ _dragging = false;
+ _grabbed_point = NULL;
+ restoreTransformHandles();
+ signal_commit.emit(COMMIT_MOUSE_MOVE);
+}
+
+void ControlPointSelection::_updateTransformHandles(bool preserve_center)
+{
+ if (_dragging) return;
+
+ if (_handles_visible && size() > 1) {
+ Geom::OptRect b = pointwiseBounds();
+ _handles->setBounds(*b, preserve_center);
+ _handles->setVisible(true);
+ } else if (_one_node_handles && size() == 1) { // only one control point in selection
+ SelectableControlPoint *p = begin()->first;
+ _handles->setBounds(p->bounds());
+ _handles->rotationCenter().move(p->position());
+ _handles->rotationCenter().setVisible(false);
+ _handles->setVisible(true);
+ } else {
+ _handles->setVisible(false);
+ }
+}
+
+/** Moves the selected points along the supplied unit vector according to
+ * the modifier state of the supplied event. */
+bool ControlPointSelection::_keyboardMove(GdkEventKey const &event, Geom::Point const &dir)
+{
+ if (held_control(event)) return false;
+ unsigned num = 1 + consume_same_key_events(shortcut_key(event), 0);
+
+ Geom::Point delta = dir * num;
+ if (held_shift(event)) delta *= 10;
+ if (held_alt(event)) {
+ delta /= _desktop->current_zoom();
+ } else {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000);
+ delta *= nudge;
+ }
+
+ transform(Geom::Translate(delta));
+ if (fabs(dir[Geom::X]) > 0) {
+ signal_commit.emit(COMMIT_KEYBOARD_MOVE_X);
+ } else {
+ signal_commit.emit(COMMIT_KEYBOARD_MOVE_Y);
+ }
+ return true;
+}
+
+/** Rotates the selected points in the given direction according to the modifier state
+ * from the supplied event.
+ * @param event Key event to take modifier state from
+ * @param dir Direction of rotation (math convention: 1 = counterclockwise, -1 = clockwise)
+ */
+bool ControlPointSelection::_keyboardRotate(GdkEventKey const &event, int dir)
+{
+ if (empty()) return false;
+
+ Geom::Point rc = _handles->rotationCenter();
+ if (!_rot_radius) {
+ Geom::Rect b = *(size() == 1 ? bounds() : pointwiseBounds());
+ double maxlen = 0;
+ for (unsigned i = 0; i < 4; ++i) {
+ double len = (b.corner(i) - rc).length();
+ if (len > maxlen) maxlen = len;
+ }
+ _rot_radius = maxlen;
+ }
+
+ double angle;
+ if (held_alt(event)) {
+ // Rotate by "one pixel". We interpret this as rotating by an angle that causes
+ // the topmost point of a circle circumscribed about the selection's bounding box
+ // to move on an arc 1 screen pixel long.
+ angle = atan2(1.0 / _desktop->current_zoom(), *_rot_radius) * dir;
+ } else {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
+ angle = M_PI * dir / snaps;
+ }
+
+ // translate to origin, rotate, translate back to original position
+ Geom::Matrix m = Geom::Translate(-rc)
+ * Geom::Rotate(angle) * Geom::Translate(rc);
+ transform(m);
+ signal_commit.emit(COMMIT_KEYBOARD_ROTATE);
+ return true;
+}
+
+
+bool ControlPointSelection::_keyboardScale(GdkEventKey const &event, int dir)
+{
+ if (empty()) return false;
+
+ // TODO should the saved rotation center or the current center be used?
+ Geom::Rect bound = (size() == 1 ? *bounds() : *pointwiseBounds());
+ double maxext = bound.maxExtent();
+ if (Geom::are_near(maxext, 0)) return false;
+ Geom::Point center = _handles->rotationCenter().position();
+
+ double length_change;
+ if (held_alt(event)) {
+ // Scale by "one pixel". It means shrink/grow 1px for the larger dimension
+ // of the bounding box.
+ length_change = 1.0 / _desktop->current_zoom() * dir;
+ } else {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ length_change = prefs->getDoubleLimited("/options/defaultscale/value", 2, 1, 1000);
+ length_change *= dir;
+ }
+ double scale = (maxext + length_change) / maxext;
+
+ Geom::Matrix m = Geom::Translate(-center) * Geom::Scale(scale) * Geom::Translate(center);
+ transform(m);
+ signal_commit.emit(COMMIT_KEYBOARD_SCALE_UNIFORM);
+ return true;
+}
+
+bool ControlPointSelection::_keyboardFlip(Geom::Dim2 d)
+{
+ if (empty()) return false;
+
+ Geom::Scale scale_transform(1, 1);
+ if (d == Geom::X) {
+ scale_transform = Geom::Scale(-1, 1);
+ } else {
+ scale_transform = Geom::Scale(1, -1);
+ }
+
+ SelectableControlPoint *scp =
+ dynamic_cast<SelectableControlPoint*>(ControlPoint::mouseovered_point);
+ Geom::Point center = scp ? scp->position() : _handles->rotationCenter().position();
+
+ Geom::Matrix m = Geom::Translate(-center) * scale_transform * Geom::Translate(center);
+ transform(m);
+ signal_commit.emit(d == Geom::X ? COMMIT_FLIP_X : COMMIT_FLIP_Y);
+ return true;
+}
+
+void ControlPointSelection::_commitTransform(CommitEvent ce)
+{
+ _updateTransformHandles(true);
+ signal_commit.emit(ce);
+}
+
+bool ControlPointSelection::event(GdkEvent *event)
+{
+ // implement generic event handling that should apply for all control point selections here;
+ // for example, keyboard moves and transformations. This way this functionality doesn't need
+ // to be duplicated in many places
+ // Later split out so that it can be reused in object selection
+
+ switch (event->type) {
+ case GDK_KEY_PRESS:
+ // do not handle key events if the selection is empty
+ if (empty()) break;
+
+ switch(shortcut_key(event->key)) {
+ // moves
+ case GDK_Up:
+ case GDK_KP_Up:
+ case GDK_KP_8:
+ return _keyboardMove(event->key, Geom::Point(0, 1));
+ case GDK_Down:
+ case GDK_KP_Down:
+ case GDK_KP_2:
+ return _keyboardMove(event->key, Geom::Point(0, -1));
+ case GDK_Right:
+ case GDK_KP_Right:
+ case GDK_KP_6:
+ return _keyboardMove(event->key, Geom::Point(1, 0));
+ case GDK_Left:
+ case GDK_KP_Left:
+ case GDK_KP_4:
+ return _keyboardMove(event->key, Geom::Point(-1, 0));
+
+ // rotates
+ case GDK_bracketleft:
+ return _keyboardRotate(event->key, 1);
+ case GDK_bracketright:
+ return _keyboardRotate(event->key, -1);
+
+ // scaling
+ case GDK_less:
+ case GDK_comma:
+ return _keyboardScale(event->key, -1);
+ case GDK_greater:
+ case GDK_period:
+ return _keyboardScale(event->key, 1);
+
+ // TODO: skewing
+
+ // flipping
+ // NOTE: H is horizontal flip, while Shift+H switches transform handle mode!
+ case GDK_h:
+ case GDK_H:
+ if (held_shift(event->key)) {
+ // TODO make a method for mode switching
+ if (_handles->mode() == TransformHandleSet::MODE_SCALE) {
+ _handles->setMode(TransformHandleSet::MODE_ROTATE_SKEW);
+ if (size() == 1) _handles->rotationCenter().setVisible(false);
+ } else {
+ _handles->setMode(TransformHandleSet::MODE_SCALE);
+ }
+ return true;
+ }
+ // any modifiers except shift should cause no action
+ if (held_any_modifiers(event->key)) break;
+ return _keyboardFlip(Geom::X);
+ case GDK_v:
+ case GDK_V:
+ if (held_any_modifiers(event->key)) break;
+ return _keyboardFlip(Geom::Y);
+ default: break;
+ }
+ break;
+ default: break;
+ }
+ return false;
+}
+
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/control-point-selection.h b/src/ui/tool/control-point-selection.h
new file mode 100644
index 000000000..38df5c7e5
--- /dev/null
+++ b/src/ui/tool/control-point-selection.h
@@ -0,0 +1,151 @@
+/** @file
+ * Node selection - stores a set of nodes and applies transformations
+ * to them
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_NODE_SELECTION_H
+#define SEEN_UI_TOOL_NODE_SELECTION_H
+
+#include <memory>
+#include <tr1/unordered_map>
+#include <tr1/unordered_set>
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <boost/optional.hpp>
+#include <sigc++/sigc++.h>
+#include <2geom/forward.h>
+#include <2geom/point.h>
+#include "display/display-forward.h"
+#include "util/accumulators.h"
+#include "util/hash.h"
+#include "ui/tool/commit-events.h"
+#include "ui/tool/manipulator.h"
+
+namespace std { using namespace tr1; }
+
+class SPDesktop;
+
+namespace Inkscape {
+namespace UI {
+
+class TransformHandleSet;
+class SelectableControlPoint;
+
+class ControlPointSelection : public Manipulator {
+public:
+ ControlPointSelection(SPDesktop *d, SPCanvasGroup *th_group);
+ ~ControlPointSelection();
+ typedef std::list<sigc::connection> connlist_type;
+ typedef std::unordered_map< SelectableControlPoint *,
+ boost::shared_ptr<connlist_type> > map_type;
+ typedef std::unordered_set< SelectableControlPoint * > set_type;
+ typedef set_type Set; // convenience alias
+
+ typedef map_type::iterator iterator;
+ typedef map_type::const_iterator const_iterator;
+ typedef map_type::size_type size_type;
+ typedef SelectableControlPoint *value_type;
+ typedef SelectableControlPoint *key_type;
+
+ // size
+ bool empty() { return _points.empty(); }
+ size_type size() { return _points.size(); }
+
+ // iterators
+ iterator begin() { return _points.begin(); }
+ const_iterator begin() const { return _points.begin(); }
+ iterator end() { return _points.end(); }
+ const_iterator end() const { return _points.end(); }
+
+ // insert
+ std::pair<iterator, bool> insert(const value_type& x);
+ template <class InputIterator>
+ void insert(InputIterator first, InputIterator last) {
+ for (; first != last; ++first) {
+ insert(*first);
+ }
+ }
+
+ // erase
+ void clear();
+ void erase(iterator pos);
+ size_type erase(const key_type& k);
+ void erase(iterator first, iterator last);
+
+ // find
+ iterator find(const key_type &k) { return _points.find(k); }
+
+ // Sometimes it is very useful to keep a list of all selectable points.
+ set_type const &allPoints() const { return _all_points; }
+ set_type &allPoints() { return _all_points; }
+ // ...for example in these methods. Another useful case is snapping.
+ void selectAll();
+ void selectArea(Geom::Rect const &);
+ void invertSelection();
+ void spatialGrow(SelectableControlPoint *origin, int dir);
+
+ virtual bool event(GdkEvent *);
+
+ void transform(Geom::Matrix const &m);
+ void align(Geom::Dim2 d);
+ void distribute(Geom::Dim2 d);
+
+ Geom::OptRect pointwiseBounds();
+ Geom::OptRect bounds();
+
+ void showTransformHandles(bool v, bool one_node);
+ // the two methods below do not modify the state; they are for use in manipulators
+ // that need to temporarily hide the handles
+ void hideTransformHandles();
+ void restoreTransformHandles();
+
+ // TODO this is really only applicable to nodes... maybe derive a NodeSelection?
+ void setSculpting(bool v) { _sculpt_enabled = v; }
+
+ sigc::signal<void> signal_update;
+ sigc::signal<void, SelectableControlPoint *, bool> signal_point_changed;
+ sigc::signal<void, CommitEvent> signal_commit;
+private:
+ void _selectionGrabbed(SelectableControlPoint *, GdkEventMotion *);
+ void _selectionDragged(Geom::Point const &, Geom::Point &, GdkEventMotion *);
+ void _selectionUngrabbed();
+ void _updateTransformHandles(bool preserve_center);
+ bool _keyboardMove(GdkEventKey const &, Geom::Point const &);
+ bool _keyboardRotate(GdkEventKey const &, int);
+ bool _keyboardScale(GdkEventKey const &, int);
+ bool _keyboardFlip(Geom::Dim2);
+ void _keyboardTransform(Geom::Matrix const &);
+ void _commitTransform(CommitEvent ce);
+ map_type _points;
+ set_type _all_points;
+ boost::optional<double> _rot_radius;
+ TransformHandleSet *_handles;
+ SelectableControlPoint *_grabbed_point;
+ unsigned _dragging : 1;
+ unsigned _handles_visible : 1;
+ unsigned _one_node_handles : 1;
+ unsigned _sculpt_enabled : 1;
+ unsigned _sculpting : 1;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/control-point.cpp b/src/ui/tool/control-point.cpp
new file mode 100644
index 000000000..73dd0d19e
--- /dev/null
+++ b/src/ui/tool/control-point.cpp
@@ -0,0 +1,630 @@
+/** @file
+ * Desktop-bound visual control object - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <iostream>
+#include <gdkmm.h>
+#include <gtkmm.h>
+#include <2geom/point.h>
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "display/snap-indicator.h"
+#include "event-context.h"
+#include "message-context.h"
+#include "preferences.h"
+#include "ui/tool/control-point.h"
+#include "ui/tool/event-utils.h"
+
+namespace Inkscape {
+namespace UI {
+
+// class and member documentation goes here...
+
+/**
+ * @class ControlPoint
+ * @brief Draggable point, the workhorse of on-canvas editing.
+ *
+ * Control points (formerly known as knots) are graphical representations of some significant
+ * point in the drawing. The drawing can be changed by dragging the point and the things that are
+ * attached to it with the mouse. Example things that could be edited with draggable points
+ * are gradient stops, the place where text is attached to a path, text kerns, nodes and handles
+ * in a path, and many more. Control points use signals heavily - <b>read the libsigc++
+ * tutorial on the wiki</b> before using this class.</b>
+ *
+ * @par Control point signals
+ * @par
+ * The control point has several signals which allow you to react to things that happen to it.
+ * The most important singals are the grabbed, dragged, ungrabbed and moved signals.
+ * When a drag happens, the order of emission is as follows:
+ * - <tt>signal_grabbed</tt>
+ * - <tt>signal_dragged</tt>
+ * - <tt>signal_dragged</tt>
+ * - <tt>signal_dragged</tt>
+ * - ...
+ * - <tt>signal_dragged</tt>
+ * - <tt>signal_ungrabbed</tt>
+ *
+ * The control point can also respond to clicks and double clicks. On a double click,
+ * <tt>signal_clicked</tt> is emitted, followed by <tt>signal_doubleclicked</tt>.
+ *
+ * A few signal usage hints if you can't be bothered to read the tutorial:
+ * - If you want some other object or a global function to react to signals of a control point
+ * from some other object, and you want to access the control point that emitted the signal
+ * in the handler, use <tt>sigc::bind</tt> like this:
+ * @code
+ * void handle_clicked_signal(ControlPoint *point, int button);
+ * point->signal_clicked.connect(
+ * sigc::bind<0>( sigc::ptr_fun(handle_clicked_signal),
+ * point ));
+ * @endcode
+ * - You can ignore unneeded parameters using sigc::hide.
+ * - If you want to get rid of the handlers added by constructors in superclasses,
+ * use the <tt>clear()</tt> method: @code signal_clicked.clear(); @endcode
+ * - To connect at the front of the slot list instead of at the end, use:
+ * @code
+ * signal_clicked.slots().push_front(
+ * sigc::mem_fun(*this, &FunkyPoint::_clickedHandler));
+ * @endcode
+ * - Note that calling <tt>slots()</tt> does not copy anything. You can disconnect
+ * and reorder slots by manipulating the elements of the slot list. The returned object is
+ * of type @verbatim (signal type)::slot_list @endverbatim.
+ *
+ * @par Which method to override?
+ * @par
+ * You might wonder which hook to use when you want to do things when the point is relocated.
+ * Here are some tips:
+ * - If the point is used to edit an object, override the move() method.
+ * - If the point can usually be dragged wherever you like but can optionally be constrained
+ * to axes or the like, add a handler for <tt>signal_dragged</tt> that modifies its new
+ * position argument.
+ * - If the point has additional canvas items tied to it (like handle lines), override
+ * the setPosition() method.
+ */
+
+/**
+ * @var ControlPoint::signal_dragged
+ * Emitted while dragging, but before moving the knot to new position.
+ * Old position will always be the same as position() - there are two parameters
+ * only for convenience.
+ * - First parameter: old position, always equal to position()
+ * - Second parameter: new position (after drag). This is passed as a non-const reference,
+ * so you can change it from the handler - that's how constrained dragging is implemented.
+ * - Third parameter: motion event
+ */
+
+/**
+ * @var ControlPoint::signal_clicked
+ * Emitted when the control point is clicked, at mouse button release. The parameter contains
+ * the event that caused the signal to be emitted. Your signal handler should return true
+ * if the click had some effect. If it did nothing, return false. Improperly handling this signal
+ * can cause the context menu not to appear when a control point is right-clicked.
+ */
+
+/**
+ * @var ControlPoint::signal_doubleclicked
+ * Emitted when the control point is doubleclicked, at mouse button release. The parameter
+ * contains the event that caused the signal to be emitted. Your signal handler should return true
+ * if the double click had some effect. If it did nothing, return false.
+ */
+
+/**
+ * @var ControlPoint::signal_grabbed
+ * Emitted when the control point is grabbed and a drag starts. The parameter contains
+ * the causing event. Return true to prevent further processing. Because all control points
+ * handle drag tolerance, <tt>signal_dragged</tt> will be emitted immediately after this signal
+ * to move the point to its new position.
+ */
+
+/**
+ * @var ControlPoint::signal_ungrabbed
+ * Emitted when the control point finishes a drag. The parameter contains the event which
+ * caused the signal, but it can be NULL if the grab was broken.
+ */
+
+/**
+ * @enum ControlPoint::State
+ * Enumeration representing the possible states of the control point, used to determine
+ * its appearance.
+ * @var ControlPoint::STATE_NORMAL
+ * Normal state
+ * @var ControlPoint::STATE_MOUSEOVER
+ * Mouse is hovering over the control point
+ * @var ControlPoint::STATE_CLICKED
+ * First mouse button pressed over the control point
+ */
+
+// Default colors for control points
+static ControlPoint::ColorSet default_color_set = {
+ {0xffffff00, 0x01000000}, // normal fill, stroke
+ {0xff0000ff, 0x01000000}, // mouseover fill, stroke
+ {0x0000ffff, 0x01000000} // clicked fill, stroke
+};
+
+/** Holds the currently mouseovered control point. */
+ControlPoint *ControlPoint::mouseovered_point = 0;
+
+/** Emitted when the mouseovered point changes. The parameter is the new mouseovered point.
+ * When a point ceases to be mouseovered, the parameter will be NULL. */
+sigc::signal<void, ControlPoint*> ControlPoint::signal_mouseover_change;
+
+/** Stores the window point over which the cursor was during the last mouse button press */
+Geom::Point ControlPoint::_drag_event_origin(Geom::infinity(), Geom::infinity());
+
+/** Stores the desktop point from which the last drag was initiated */
+Geom::Point ControlPoint::_drag_origin(Geom::infinity(), Geom::infinity());
+
+/** Events which should be captured when a handle is being dragged. */
+int const ControlPoint::_grab_event_mask = (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_KEY_PRESS_MASK |
+ GDK_KEY_RELEASE_MASK);
+
+bool ControlPoint::_drag_initiated = false;
+bool ControlPoint::_event_grab = false;
+
+/** A color set which you can use to create an invisible control that can still receive events.
+ * @relates ControlPoint */
+ControlPoint::ColorSet invisible_cset = {
+ {0x00000000, 0x00000000},
+ {0x00000000, 0x00000000},
+ {0x00000000, 0x00000000}
+};
+
+/**
+ * Create a regular control point.
+ * Derive to have constructors with a reasonable number of parameters.
+ *
+ * @param d Desktop for this control
+ * @param initial_pos Initial position of the control point in desktop coordinates
+ * @param anchor Where is the control point rendered relative to its desktop coordinates
+ * @param shape Shape of the control point: square, diamond, circle...
+ * @param size Pixel size of the visual representation
+ * @param cset Colors of the point
+ * @param group The canvas group the point's canvas item should be created in
+ */
+ControlPoint::ControlPoint(SPDesktop *d, Geom::Point const &initial_pos,
+ Gtk::AnchorType anchor, SPCtrlShapeType shape,
+ unsigned int size, ColorSet *cset, SPCanvasGroup *group)
+ : _desktop (d)
+ , _canvas_item (NULL)
+ , _cset (cset ? cset : &default_color_set)
+ , _state (STATE_NORMAL)
+ , _position (initial_pos)
+{
+ _canvas_item = sp_canvas_item_new(
+ group ? group : sp_desktop_controls (_desktop), SP_TYPE_CTRL,
+ "anchor", (GtkAnchorType) anchor, "size", (gdouble) size, "shape", shape,
+ "filled", TRUE, "fill_color", _cset->normal.fill,
+ "stroked", TRUE, "stroke_color", _cset->normal.stroke,
+ "mode", SP_CTRL_MODE_XOR, NULL);
+ _commonInit();
+}
+
+/**
+ * Create a control point with a pixbuf-based visual representation.
+ *
+ * @param d Desktop for this control
+ * @param initial_pos Initial position of the control point in desktop coordinates
+ * @param anchor Where is the control point rendered relative to its desktop coordinates
+ * @param pixbuf Pixbuf to be used as the visual representation
+ * @param cset Colors of the point
+ * @param group The canvas group the point's canvas item should be created in
+ */
+ControlPoint::ControlPoint(SPDesktop *d, Geom::Point const &initial_pos,
+ Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pixbuf,
+ ColorSet *cset, SPCanvasGroup *group)
+ : _desktop (d)
+ , _canvas_item (NULL)
+ , _cset(cset ? cset : &default_color_set)
+ , _position (initial_pos)
+{
+ _canvas_item = sp_canvas_item_new(
+ group ? group : sp_desktop_controls(_desktop), SP_TYPE_CTRL,
+ "anchor", (GtkAnchorType) anchor, "size", (gdouble) pixbuf->get_width(),
+ "shape", SP_CTRL_SHAPE_BITMAP, "pixbuf", pixbuf->gobj(),
+ "filled", TRUE, "fill_color", _cset->normal.fill,
+ "stroked", TRUE, "stroke_color", _cset->normal.stroke,
+ "mode", SP_CTRL_MODE_XOR, NULL);
+ _commonInit();
+}
+
+ControlPoint::~ControlPoint()
+{
+ // avoid storing invalid points in mouseovered_point
+ if (this == mouseovered_point) {
+ _clearMouseover();
+ }
+
+ g_signal_handler_disconnect(G_OBJECT(_canvas_item), _event_handler_connection);
+ //sp_canvas_item_hide(_canvas_item);
+ gtk_object_destroy(_canvas_item);
+}
+
+void ControlPoint::_commonInit()
+{
+ _event_handler_connection = g_signal_connect(G_OBJECT(_canvas_item), "event",
+ G_CALLBACK(_event_handler), this);
+ SP_CTRL(_canvas_item)->moveto(_position);
+}
+
+/** Relocate the control point without side effects.
+ * Overload this method only if there is an additional graphical representation
+ * that must be updated (like the lines that connect handles to nodes). If you override it,
+ * you must also call the superclass implementation of the method.
+ * @todo Investigate whether this method should be protected */
+void ControlPoint::setPosition(Geom::Point const &pos)
+{
+ _position = pos;
+ SP_CTRL(_canvas_item)->moveto(pos);
+}
+
+/** Move the control point to new position with side effects.
+ * This is called after each drag. Override this method if only some positions make sense
+ * for a control point (like a point that must always be on a path and can't modify it),
+ * or when moving a control point changes the positions of other points. */
+void ControlPoint::move(Geom::Point const &pos)
+{
+ setPosition(pos);
+}
+
+/** Apply an arbitrary affine transformation to a control point. This is used
+ * by ControlPointSelection, and is important for things like nodes with handles.
+ * The default implementation simply moves the point according to the transform. */
+void ControlPoint::transform(Geom::Matrix const &m) {
+ move(position() * m);
+}
+
+bool ControlPoint::visible() const
+{
+ return sp_canvas_item_is_visible(_canvas_item);
+}
+
+/** Set the visibility of the control point. An invisible point is not drawn on the canvas
+ * and cannot receive any events. If you want to have an invisible point that can respond
+ * to events, use <tt>invisible_cset</tt> as its color set. */
+void ControlPoint::setVisible(bool v)
+{
+ if (v) sp_canvas_item_show(_canvas_item);
+ else sp_canvas_item_hide(_canvas_item);
+}
+
+Glib::ustring ControlPoint::format_tip(char const *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ char *dyntip = g_strdup_vprintf(format, args);
+ va_end(args);
+ Glib::ustring ret = dyntip;
+ g_free(dyntip);
+ return ret;
+}
+
+unsigned int ControlPoint::_size() const
+{
+ double ret;
+ g_object_get(_canvas_item, "size", &ret, NULL);
+ return static_cast<unsigned int>(ret);
+}
+
+SPCtrlShapeType ControlPoint::_shape() const
+{
+ SPCtrlShapeType ret;
+ g_object_get(_canvas_item, "shape", &ret, NULL);
+ return ret;
+}
+
+GtkAnchorType ControlPoint::_anchor() const
+{
+ GtkAnchorType ret;
+ g_object_get(_canvas_item, "anchor", &ret, NULL);
+ return ret;
+}
+
+Glib::RefPtr<Gdk::Pixbuf> ControlPoint::_pixbuf()
+{
+ GdkPixbuf *ret;
+ g_object_get(_canvas_item, "pixbuf", &ret, NULL);
+ return Glib::wrap(ret);
+}
+
+// Same for setters.
+
+void ControlPoint::_setSize(unsigned int size)
+{
+ g_object_set(_canvas_item, "size", (gdouble) size, NULL);
+}
+
+void ControlPoint::_setShape(SPCtrlShapeType shape)
+{
+ g_object_set(_canvas_item, "shape", shape, NULL);
+}
+
+void ControlPoint::_setAnchor(GtkAnchorType anchor)
+{
+ g_object_set(_canvas_item, "anchor", anchor, NULL);
+}
+
+void ControlPoint::_setPixbuf(Glib::RefPtr<Gdk::Pixbuf> p)
+{
+ g_object_set(_canvas_item, "pixbuf", Glib::unwrap(p), NULL);
+}
+
+// re-routes events into the virtual function
+int ControlPoint::_event_handler(SPCanvasItem *item, GdkEvent *event, ControlPoint *point)
+{
+ return point->_eventHandler(event) ? TRUE : FALSE;
+}
+
+// main event callback, which emits all other callbacks.
+bool ControlPoint::_eventHandler(GdkEvent *event)
+{
+ // NOTE the static variables below are shared for all points!
+
+ // offset from the pointer hotspot to the center of the grabbed knot in desktop coords
+ static Geom::Point pointer_offset;
+ // number of last doubleclicked button, to be
+ static unsigned next_release_doubleclick = 0;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int drag_tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ switch(event->type)
+ {
+ case GDK_2BUTTON_PRESS:
+ // store the button number for next release
+ next_release_doubleclick = event->button.button;
+ return true;
+
+ case GDK_BUTTON_PRESS:
+ next_release_doubleclick = 0;
+ if (event->button.button == 1) {
+ // mouse click. internally, start dragging, but do not emit signals
+ // or change position until drag tolerance is exceeded.
+ _drag_event_origin[Geom::X] = event->button.x;
+ _drag_event_origin[Geom::Y] = event->button.y;
+ pointer_offset = _position - _desktop->w2d(_drag_event_origin);
+ _drag_initiated = false;
+ // route all events to this handler
+ sp_canvas_item_grab(_canvas_item, _grab_event_mask, NULL, event->button.time);
+ _event_grab = true;
+ _setState(STATE_CLICKED);
+ }
+ return true;
+
+ case GDK_MOTION_NOTIFY:
+ if (held_button<1>(event->motion) && !_desktop->event_context->space_panning) {
+ _desktop->snapindicator->remove_snaptarget();
+ bool transferred = false;
+ if (!_drag_initiated) {
+ bool t = fabs(event->motion.x - _drag_event_origin[Geom::X]) <= drag_tolerance &&
+ fabs(event->motion.y - _drag_event_origin[Geom::Y]) <= drag_tolerance;
+ if (t) return true;
+
+ // if we are here, it means the tolerance was just exceeded.
+ next_release_doubleclick = 0;
+ _drag_origin = _position;
+ transferred = signal_grabbed.emit(&event->motion);
+ // _drag_initiated might change during the above signal emission
+ if (!_drag_initiated) {
+ // this guarantees smooth redraws while dragging
+ sp_canvas_force_full_redraw_after_interruptions(_desktop->canvas, 5);
+ _drag_initiated = true;
+ }
+ }
+ if (!transferred) {
+ // dragging in progress
+ Geom::Point new_pos = _desktop->w2d(event_point(event->motion)) + pointer_offset;
+
+ // the new position is passed by reference and can be changed in the handlers.
+ signal_dragged.emit(_position, new_pos, &event->motion);
+ move(new_pos);
+ _updateDragTip(&event->motion); // update dragging tip after moving to new position
+
+ _desktop->scroll_to_point(new_pos);
+ _desktop->set_coordinate_status(_position);
+ sp_event_context_snap_delay_handler(_desktop->event_context, NULL,
+ reinterpret_cast<SPKnot*>(this), &event->motion,
+ DelayedSnapEvent::CONTROL_POINT_HANDLER);
+ }
+ return true;
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ if (!_event_grab) break;
+
+ // TODO I think snapping on release is wrong, or at least counter-intuitive.
+ sp_event_context_snap_watchdog_callback(_desktop->event_context->_delayed_snap_event);
+ sp_event_context_discard_delayed_snap_event(_desktop->event_context);
+ _desktop->snapindicator->remove_snaptarget();
+
+ sp_canvas_item_ungrab(_canvas_item, event->button.time);
+ _setMouseover(this, event->button.state);
+ _event_grab = false;
+
+ if (_drag_initiated) {
+ sp_canvas_end_forced_full_redraws(_desktop->canvas);
+ }
+
+ if (event->button.button == next_release_doubleclick) {
+ _drag_initiated = false;
+ return signal_doubleclicked.emit(&event->button);
+ }
+ if (event->button.button == 1) {
+ if (_drag_initiated) {
+ // it is the end of a drag
+ signal_ungrabbed.emit(&event->button);
+ _drag_initiated = false;
+ return true;
+ } else {
+ // it is the end of a click
+ return signal_clicked.emit(&event->button);
+ }
+ }
+ _drag_initiated = false;
+ break;
+
+ case GDK_ENTER_NOTIFY:
+ _setMouseover(this, event->crossing.state);
+ return true;
+ case GDK_LEAVE_NOTIFY:
+ _clearMouseover();
+ return true;
+
+ case GDK_GRAB_BROKEN:
+ if (!event->grab_broken.keyboard && _event_grab) {
+ {
+ signal_ungrabbed.emit(0);
+ if (_drag_initiated)
+ sp_canvas_end_forced_full_redraws(_desktop->canvas);
+ }
+ _setState(STATE_NORMAL);
+ _event_grab = false;
+ _drag_initiated = false;
+ return true;
+ }
+ break;
+
+ // update tips on modifier state change
+ case GDK_KEY_PRESS:
+ case GDK_KEY_RELEASE:
+ if (mouseovered_point != this) return false;
+ if (_drag_initiated) {
+ return true; // this prevents the tool from overwriting the drag tip
+ } else {
+ unsigned state = state_after_event(event);
+ if (state != event->key.state) {
+ // we need to return true if there was a tip available, otherwise the tool's
+ // handler will process this event and set the tool's message, overwriting
+ // the point's message
+ return _updateTip(state);
+ }
+ }
+ break;
+
+ default: break;
+ }
+
+ return false;
+}
+
+void ControlPoint::_setMouseover(ControlPoint *p, unsigned state)
+{
+ bool visible = p->visible();
+ if (visible) { // invisible points shouldn't get mouseovered
+ p->_setState(STATE_MOUSEOVER);
+ }
+ p->_updateTip(state);
+
+ if (visible && mouseovered_point != p) {
+ mouseovered_point = p;
+ signal_mouseover_change.emit(mouseovered_point);
+ }
+}
+
+bool ControlPoint::_updateTip(unsigned state)
+{
+ Glib::ustring tip = _getTip(state);
+ if (!tip.empty()) {
+ _desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE,
+ tip.data());
+ return true;
+ } else {
+ _desktop->event_context->defaultMessageContext()->clear();
+ return false;
+ }
+}
+
+bool ControlPoint::_updateDragTip(GdkEventMotion *event)
+{
+ if (!_hasDragTips()) return false;
+ Glib::ustring tip = _getDragTip(event);
+ if (!tip.empty()) {
+ _desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE,
+ tip.data());
+ return true;
+ } else {
+ _desktop->event_context->defaultMessageContext()->clear();
+ return false;
+ }
+}
+
+void ControlPoint::_clearMouseover()
+{
+ if (mouseovered_point) {
+ mouseovered_point->_desktop->event_context->defaultMessageContext()->clear();
+ mouseovered_point->_setState(STATE_NORMAL);
+ mouseovered_point = 0;
+ signal_mouseover_change.emit(mouseovered_point);
+ }
+}
+
+/** Transfer the grab to another point. This method allows one to create a draggable point
+ * that should be dragged instead of the one that received the grabbed signal.
+ * This is used to implement dragging out handles in the new node tool, for example.
+ *
+ * This method will NOT emit the ungrab signal of @c prev_point, because this would complicate
+ * using it with selectable control points. If you use this method while dragging, you must emit
+ * the ungrab signal yourself.
+ *
+ * Note that this will break horribly if you try to transfer grab between points in different
+ * desktops, which doesn't make much sense anyway. */
+void ControlPoint::transferGrab(ControlPoint *prev_point, GdkEventMotion *event)
+{
+ if (!_event_grab) return;
+
+ signal_grabbed.emit(event);
+ sp_canvas_item_ungrab(prev_point->_canvas_item, event->time);
+ sp_canvas_item_grab(_canvas_item, _grab_event_mask, NULL, event->time);
+
+ if (!_drag_initiated) {
+ sp_canvas_force_full_redraw_after_interruptions(_desktop->canvas, 5);
+ _drag_initiated = true;
+ }
+
+ prev_point->_setState(STATE_NORMAL);
+ _setMouseover(this, event->state);
+}
+
+/**
+ * @brief Change the state of the knot
+ * Alters the appearance of the knot to match one of the states: normal, mouseover
+ * or clicked.
+ */
+void ControlPoint::_setState(State state)
+{
+ ColorEntry current = {0, 0};
+ switch(state) {
+ case STATE_NORMAL:
+ current = _cset->normal; break;
+ case STATE_MOUSEOVER:
+ current = _cset->mouseover; break;
+ case STATE_CLICKED:
+ current = _cset->clicked; break;
+ };
+ _setColors(current);
+ _state = state;
+}
+void ControlPoint::_setColors(ColorEntry colors)
+{
+ g_object_set(_canvas_item, "fill_color", colors.fill, "stroke_color", colors.stroke, NULL);
+}
+
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/control-point.h b/src/ui/tool/control-point.h
new file mode 100644
index 000000000..4997c5ef4
--- /dev/null
+++ b/src/ui/tool/control-point.h
@@ -0,0 +1,169 @@
+/** @file
+ * Desktop-bound visual control object
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_CONTROL_POINT_H
+#define SEEN_UI_TOOL_CONTROL_POINT_H
+
+#include <boost/utility.hpp>
+#include <sigc++/sigc++.h>
+#include <gdkmm.h>
+#include <gtkmm.h>
+#include <2geom/point.h>
+
+#include "display/display-forward.h"
+#include "forward.h"
+#include "util/accumulators.h"
+#include "display/sodipodi-ctrl.h"
+
+namespace Inkscape {
+namespace UI {
+
+// most of the documentation is in the .cpp file
+
+class ControlPoint : boost::noncopyable, public sigc::trackable {
+public:
+ typedef Inkscape::Util::ReverseInterruptible RInt;
+ typedef Inkscape::Util::Interruptible Int;
+ // these have to be public, because GCC doesn't allow protected types in constructors,
+ // even if the constructors are protected themselves.
+ struct ColorEntry {
+ guint32 fill;
+ guint32 stroke;
+ };
+ struct ColorSet {
+ ColorEntry normal;
+ ColorEntry mouseover;
+ ColorEntry clicked;
+ };
+ enum State {
+ STATE_NORMAL,
+ STATE_MOUSEOVER,
+ STATE_CLICKED
+ };
+
+ virtual ~ControlPoint();
+
+ /// @name Adjust the position of the control point
+ /// @{
+ /** Current position of the control point. */
+ Geom::Point const &position() const { return _position; }
+ operator Geom::Point const &() { return _position; }
+ virtual void move(Geom::Point const &pos);
+ virtual void setPosition(Geom::Point const &pos);
+ virtual void transform(Geom::Matrix const &m);
+ /// @}
+
+ /// @name Toggle the point's visibility
+ /// @{
+ bool visible() const;
+ virtual void setVisible(bool v);
+ /// @}
+
+ /// @name Transfer grab from another event handler
+ /// @{
+ void transferGrab(ControlPoint *from, GdkEventMotion *event);
+ /// @}
+
+ /// @name Receive notifications about control point events
+ /// @{
+ sigc::signal<void, Geom::Point const &, Geom::Point &, GdkEventMotion*> signal_dragged;
+ sigc::signal<bool, GdkEventButton*>::accumulated<RInt> signal_clicked;
+ sigc::signal<bool, GdkEventButton*>::accumulated<RInt> signal_doubleclicked;
+ sigc::signal<bool, GdkEventMotion*>::accumulated<Int> signal_grabbed;
+ sigc::signal<void, GdkEventButton*> signal_ungrabbed;
+ /// @}
+
+ /// @name Inspect the state of the control point
+ /// @{
+ State state() { return _state; }
+ bool mouseovered() { return this == mouseovered_point; }
+ /// @}
+
+ static ControlPoint *mouseovered_point;
+ static sigc::signal<void, ControlPoint*> signal_mouseover_change;
+ static Glib::ustring format_tip(char const *format, ...) G_GNUC_PRINTF(1,2);
+
+ // temporarily public, until snapping is refactored a little
+ virtual bool _eventHandler(GdkEvent *event);
+
+protected:
+ ControlPoint(SPDesktop *d, Geom::Point const &initial_pos, Gtk::AnchorType anchor,
+ SPCtrlShapeType shape, unsigned int size, ColorSet *cset = 0, SPCanvasGroup *group = 0);
+ ControlPoint(SPDesktop *d, Geom::Point const &initial_pos, Gtk::AnchorType anchor,
+ Glib::RefPtr<Gdk::Pixbuf> pixbuf, ColorSet *cset = 0, SPCanvasGroup *group = 0);
+
+ /// @name Manipulate the control point's appearance in subclasses
+ /// @{
+ virtual void _setState(State state);
+ void _setColors(ColorEntry c);
+
+ unsigned int _size() const;
+ SPCtrlShapeType _shape() const;
+ GtkAnchorType _anchor() const;
+ Glib::RefPtr<Gdk::Pixbuf> _pixbuf();
+
+ void _setSize(unsigned int size);
+ void _setShape(SPCtrlShapeType shape);
+ void _setAnchor(GtkAnchorType anchor);
+ void _setPixbuf(Glib::RefPtr<Gdk::Pixbuf>);
+ /// @}
+
+ virtual Glib::ustring _getTip(unsigned state) { return ""; }
+ virtual Glib::ustring _getDragTip(GdkEventMotion *event) { return ""; }
+ virtual bool _hasDragTips() { return false; }
+
+ SPDesktop *const _desktop; ///< The desktop this control point resides on.
+ SPCanvasItem * _canvas_item; ///< Visual representation of the control point.
+ ColorSet *_cset; ///< Colors used to represent the point
+ State _state;
+
+ static int const _grab_event_mask;
+ static Geom::Point const &_last_click_event_point() { return _drag_event_origin; }
+ static Geom::Point const &_last_drag_origin() { return _drag_origin; }
+
+private:
+ ControlPoint(ControlPoint const &other);
+ void operator=(ControlPoint const &other);
+
+ static int _event_handler(SPCanvasItem *item, GdkEvent *event, ControlPoint *point);
+ static void _setMouseover(ControlPoint *, unsigned state);
+ static void _clearMouseover();
+ bool _updateTip(unsigned state);
+ bool _updateDragTip(GdkEventMotion *event);
+ void _setDefaultColors();
+ void _commonInit();
+
+ Geom::Point _position; ///< Current position in desktop coordinates
+ gulong _event_handler_connection;
+
+ static Geom::Point _drag_event_origin;
+ static Geom::Point _drag_origin;
+ static bool _event_grab;
+ static bool _drag_initiated;
+};
+
+extern ControlPoint::ColorSet invisible_cset;
+
+
+} // namespace UI
+} // namespace Inkscape
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/curve-drag-point.cpp b/src/ui/tool/curve-drag-point.cpp
new file mode 100644
index 000000000..182362259
--- /dev/null
+++ b/src/ui/tool/curve-drag-point.cpp
@@ -0,0 +1,186 @@
+/** @file
+ * Control point that is dragged during path drag
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glib/gi18n.h>
+#include <2geom/bezier-curve.h>
+#include "desktop.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/curve-drag-point.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tool/path-manipulator.h"
+#include "ui/tool/node.h"
+
+namespace Inkscape {
+namespace UI {
+
+/**
+ * @class CurveDragPoint
+ * An invisible point used to drag curves. This point is used by PathManipulator to allow editing
+ * of path segments by dragging them. It is defined in a separate file so that the node tool
+ * can check if the mouseovered control point is a curve drag point and update the cursor
+ * accordingly, without the need to drag in the full PathManipulator header.
+ */
+
+// This point should be invisible to the user - use the invisible_cset from control-point.h
+// TODO make some methods from path-manipulator.cpp public so that this point doesn't have
+// to be declared as a friend
+
+bool CurveDragPoint::_drags_stroke = false;
+
+CurveDragPoint::CurveDragPoint(PathManipulator &pm)
+ : ControlPoint(pm._multi_path_manipulator._path_data.node_data.desktop, Geom::Point(),
+ Gtk::ANCHOR_CENTER, SP_CTRL_SHAPE_CIRCLE, 1.0, &invisible_cset,
+ pm._multi_path_manipulator._path_data.dragpoint_group)
+ , _pm(pm)
+{
+ setVisible(false);
+ signal_grabbed.connect(
+ sigc::bind_return(
+ sigc::mem_fun(*this, &CurveDragPoint::_grabbedHandler),
+ false));
+ signal_dragged.connect(
+ sigc::hide(
+ sigc::mem_fun(*this, &CurveDragPoint::_draggedHandler)));
+ signal_ungrabbed.connect(
+ sigc::hide(
+ sigc::mem_fun(*this, &CurveDragPoint::_ungrabbedHandler)));
+ signal_clicked.connect(
+ sigc::mem_fun(*this, &CurveDragPoint::_clickedHandler));
+ signal_doubleclicked.connect(
+ sigc::mem_fun(*this, &CurveDragPoint::_doubleclickedHandler));
+}
+
+void CurveDragPoint::_grabbedHandler(GdkEventMotion *event)
+{
+ _pm._selection.hideTransformHandles();
+ NodeList::iterator second = first.next();
+
+ // move the handles to 1/3 the length of the segment for line segments
+ if (first->front()->isDegenerate() && second->back()->isDegenerate()) {
+
+ // delta is a vector equal 1/3 of distance from first to second
+ Geom::Point delta = (second->position() - first->position()) / 3.0;
+ first->front()->move(first->front()->position() + delta);
+ second->back()->move(second->back()->position() - delta);
+
+ signal_update.emit();
+ }
+}
+
+void CurveDragPoint::_draggedHandler(Geom::Point const &old_pos, Geom::Point const &new_pos)
+{
+ if (_drags_stroke) {
+ // TODO
+ } else {
+ NodeList::iterator second = first.next();
+ // Magic Bezier Drag Equations follow!
+ // "weight" describes how the influence of the drag should be distributed
+ // among the handles; 0 = front handle only, 1 = back handle only.
+ double weight, t = _t;
+ if (t <= 1.0 / 6.0) weight = 0;
+ else if (t <= 0.5) weight = (pow((6 * t - 1) / 2.0, 3)) / 2;
+ else if (t <= 5.0 / 6.0) weight = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
+ else weight = 1;
+
+ Geom::Point delta = new_pos - old_pos;
+ Geom::Point offset0 = ((1-weight)/(3*t*(1-t)*(1-t))) * delta;
+ Geom::Point offset1 = (weight/(3*t*t*(1-t))) * delta;
+
+ first->front()->move(first->front()->position() + offset0);
+ second->back()->move(second->back()->position() + offset1);
+ }
+
+ signal_update.emit();
+}
+
+void CurveDragPoint::_ungrabbedHandler()
+{
+ _pm._updateDragPoint(_desktop->d2w(position()));
+ _pm._commit(_("Drag curve"));
+ _pm._selection.restoreTransformHandles();
+}
+
+bool CurveDragPoint::_clickedHandler(GdkEventButton *event)
+{
+ // This check is probably redundant
+ if (!first || event->button != 1) return false;
+ // the next iterator can be invalid if we click very near the end of path
+ NodeList::iterator second = first.next();
+ if (!second) return false;
+
+ if (held_shift(*event)) {
+ // if both nodes of the segment are selected, deselect;
+ // otherwise add to selection
+ if (first->selected() && second->selected()) {
+ _pm._selection.erase(first.ptr());
+ _pm._selection.erase(second.ptr());
+ } else {
+ _pm._selection.insert(first.ptr());
+ _pm._selection.insert(second.ptr());
+ }
+ } else {
+ // without Shift, take selection
+ _pm._selection.clear();
+ _pm._selection.insert(first.ptr());
+ _pm._selection.insert(second.ptr());
+ }
+ return true;
+}
+
+bool CurveDragPoint::_doubleclickedHandler(GdkEventButton *event)
+{
+ if (event->button != 1 || !first || !first.next()) return false;
+
+ // The purpose of this call is to make way for the just created node.
+ // Otherwise clicks on the new node would only work after the user moves the mouse a bit.
+ // PathManipulator will restore visibility when necessary.
+ setVisible(false);
+ NodeList::iterator inserted = _pm.subdivideSegment(first, _t);
+ _pm._selection.clear();
+ _pm._selection.insert(inserted.ptr());
+
+ signal_update.emit();
+ _pm._commit(_("Add node"));
+ return true;
+}
+
+Glib::ustring CurveDragPoint::_getTip(unsigned state)
+{
+ if (!first || !first.next()) return NULL;
+ bool linear = first->front()->isDegenerate() && first.next()->back()->isDegenerate();
+ if (state_held_shift(state)) {
+ return C_("Path segment statusbar tip",
+ "<b>Shift:</b> click to toggle segment selection");
+ }
+ if (linear) {
+ return C_("Path segment statusbar tip",
+ "<b>Linear segment:</b> drag to convert to a Bezier segment, "
+ "doubleclick to insert node, click to select this segment");
+ } else {
+ return C_("Path segment statusbar tip",
+ "<b>Bezier segment:</b> drag to shape the segment, doubleclick to insert node, "
+ "click to select this segment");
+ }
+}
+
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/curve-drag-point.h b/src/ui/tool/curve-drag-point.h
new file mode 100644
index 000000000..c9f32f709
--- /dev/null
+++ b/src/ui/tool/curve-drag-point.h
@@ -0,0 +1,60 @@
+/** @file
+ * Control point that is dragged during path drag
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_CURVE_DRAG_POINT_H
+#define SEEN_UI_TOOL_CURVE_DRAG_POINT_H
+
+#include "ui/tool/control-point.h"
+#include "ui/tool/node.h"
+
+class SPDesktop;
+namespace Inkscape {
+namespace UI {
+
+class PathManipulator;
+struct PathSharedData;
+
+class CurveDragPoint : public ControlPoint {
+public:
+ CurveDragPoint(PathManipulator &pm);
+ void setSize(double sz) { _setSize(sz); }
+ void setTimeValue(double t) { _t = t; }
+ void setIterator(NodeList::iterator i) { first = i; }
+ sigc::signal<void> signal_update;
+protected:
+ virtual Glib::ustring _getTip(unsigned state);
+private:
+ void _grabbedHandler(GdkEventMotion *);
+ void _draggedHandler(Geom::Point const &, Geom::Point const &);
+ bool _clickedHandler(GdkEventButton *);
+ bool _doubleclickedHandler(GdkEventButton *);
+ void _ungrabbedHandler();
+ double _t;
+ PathManipulator &_pm;
+ NodeList::iterator first;
+ static bool _drags_stroke;
+ static Geom::Point _stroke_drag_origin;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/event-utils.cpp b/src/ui/tool/event-utils.cpp
new file mode 100644
index 000000000..6912897da
--- /dev/null
+++ b/src/ui/tool/event-utils.cpp
@@ -0,0 +1,113 @@
+/** @file
+ * Collection of shorthands to deal with GDK events.
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include "ui/tool/event-utils.h"
+
+namespace Inkscape {
+namespace UI {
+
+
+guint shortcut_key(GdkEventKey const &event)
+{
+ guint shortcut_key = 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*/,
+ &shortcut_key, NULL, NULL, NULL);
+ return shortcut_key;
+}
+
+unsigned consume_same_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;
+}
+
+/** Returns the modifier state valid after this event. Use this when you process events
+ * that change the modifier state. Currently handles only Shift, Ctrl, Alt. */
+unsigned state_after_event(GdkEvent *event)
+{
+ unsigned state = 0;
+ switch (event->type) {
+ case GDK_KEY_PRESS:
+ state = event->key.state;
+ switch(shortcut_key(event->key)) {
+ case GDK_Shift_L:
+ case GDK_Shift_R:
+ state |= GDK_SHIFT_MASK;
+ break;
+ case GDK_Control_L:
+ case GDK_Control_R:
+ state |= GDK_CONTROL_MASK;
+ break;
+ case GDK_Alt_L:
+ case GDK_Alt_R:
+ state |= GDK_MOD1_MASK;
+ break;
+ default: break;
+ }
+ break;
+ case GDK_KEY_RELEASE:
+ state = event->key.state;
+ switch(shortcut_key(event->key)) {
+ case GDK_Shift_L:
+ case GDK_Shift_R:
+ state &= ~GDK_SHIFT_MASK;
+ break;
+ case GDK_Control_L:
+ case GDK_Control_R:
+ state &= ~GDK_CONTROL_MASK;
+ break;
+ case GDK_Alt_L:
+ case GDK_Alt_R:
+ state &= ~GDK_MOD1_MASK;
+ break;
+ default: break;
+ }
+ break;
+ default: break;
+ }
+ return state;
+}
+
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/event-utils.h b/src/ui/tool/event-utils.h
new file mode 100644
index 000000000..74907d61c
--- /dev/null
+++ b/src/ui/tool/event-utils.h
@@ -0,0 +1,129 @@
+/** @file
+ * Collection of shorthands to deal with GDK events.
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_EVENT_UTILS_H
+#define SEEN_UI_TOOL_EVENT_UTILS_H
+
+#include <gdk/gdk.h>
+#include <2geom/point.h>
+
+namespace Inkscape {
+namespace UI {
+
+inline bool state_held_shift(unsigned state) {
+ return state & GDK_SHIFT_MASK;
+}
+inline bool state_held_control(unsigned state) {
+ return state & GDK_CONTROL_MASK;
+}
+inline bool state_held_alt(unsigned state) {
+ return state & GDK_MOD1_MASK;
+}
+inline bool state_held_only_shift(unsigned state) {
+ return (state & GDK_SHIFT_MASK) && !(state & (GDK_CONTROL_MASK | GDK_MOD1_MASK));
+}
+inline bool state_held_only_control(unsigned state) {
+ return (state & GDK_CONTROL_MASK) && !(state & (GDK_SHIFT_MASK | GDK_MOD1_MASK));
+}
+inline bool state_held_only_alt(unsigned state) {
+ return (state & GDK_MOD1_MASK) && !(state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK));
+}
+inline bool state_held_any_modifiers(unsigned state) {
+ return state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK);
+}
+inline bool state_held_no_modifiers(unsigned state) {
+ return !state_held_any_modifiers(state);
+}
+template <unsigned button>
+inline bool state_held_button(unsigned state) {
+ return (button == 0 || button > 5) ? false : state & (GDK_BUTTON1_MASK << (button-1));
+}
+
+
+/** Checks whether Shift was held when the event was generated. */
+template <typename E>
+inline bool held_shift(E const &event) {
+ return state_held_shift(event.state);
+}
+
+/** Checks whether Control was held when the event was generated. */
+template <typename E>
+inline bool held_control(E const &event) {
+ return state_held_control(event.state);
+}
+
+/** Checks whether Alt was held when the event was generated. */
+template <typename E>
+inline bool held_alt(E const &event) {
+ return state_held_alt(event.state);
+}
+
+/** True if from the set of Ctrl, Shift and Alt only Ctrl was held when the event
+ * was generated. */
+template <typename E>
+inline bool held_only_control(E const &event) {
+ return state_held_only_control(event.state);
+}
+
+/** True if from the set of Ctrl, Shift and Alt only Shift was held when the event
+ * was generated. */
+template <typename E>
+inline bool held_only_shift(E const &event) {
+ return state_held_only_shift(event.state);
+}
+
+/** True if from the set of Ctrl, Shift and Alt only Alt was held when the event
+ * was generated. */
+template <typename E>
+inline bool held_only_alt(E const &event) {
+ return state_held_only_alt(event.state);
+}
+
+template <typename E>
+inline bool held_no_modifiers(E const &event) {
+ return state_held_no_modifiers(event.state);
+}
+
+template <typename E>
+inline bool held_any_modifiers(E const &event) {
+ return state_held_any_modifiers(event.state);
+}
+
+template <typename E>
+inline Geom::Point event_point(E const &event) {
+ return Geom::Point(event.x, event.y);
+}
+
+/** Use like this:
+ * @code if (held_button<2>(event->motion)) { ... @endcode */
+template <unsigned button, typename E>
+inline bool held_button(E const &event) {
+ return state_held_button<button>(event.state);
+}
+
+guint shortcut_key(GdkEventKey const &event);
+unsigned consume_same_key_events(guint keyval, gint mask);
+unsigned state_after_event(GdkEvent *event);
+
+} // namespace UI
+} // namespace Inkscape
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/manipulator.cpp b/src/ui/tool/manipulator.cpp
new file mode 100644
index 000000000..b532fcab4
--- /dev/null
+++ b/src/ui/tool/manipulator.cpp
@@ -0,0 +1,89 @@
+/** @file
+ * Manipulator base class and manipulator group - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/tool/manipulator.h"
+#include "ui/tool/node.h"
+
+namespace Inkscape {
+namespace UI {
+
+/*
+void Manipulator::_grabEvents()
+{
+ if (_group) _group->_grabEvents(boost::shared_ptr<Manipulator>(this));
+}
+void Manipulator::_ungrabEvents()
+{
+ if (_group) _group->_ungrabEvents(boost::shared_ptr<Manipulator>(this));
+}
+
+ManipulatorGroup::ManipulatorGroup(SPDesktop *d) :
+ _desktop(d)
+{
+}
+ManipulatorGroup::~ManipulatorGroup()
+{
+}
+
+void ManipulatorGroup::_grabEvents(boost::shared_ptr<Manipulator> m)
+{
+ if (!_grab) _grab = m;
+}
+void ManipulatorGroup::_ungrabEvents(boost::shared_ptr<Manipulator> m)
+{
+ if (_grab == m) _grab.reset();
+}
+
+void ManipulatorGroup::add(boost::shared_ptr<Manipulator> m)
+{
+ m->_group = this;
+ push_back(m);
+}
+void ManipulatorGroup::remove(boost::shared_ptr<Manipulator> m)
+{
+ for (std::list<boost::shared_ptr<Manipulator> >::iterator i = begin(); i != end(); ++i) {
+ if ((*i) == m) {
+ erase(i);
+ break;
+ }
+ }
+ m->_group = 0;
+}
+
+void ManipulatorGroup::clear()
+{
+ std::list<boost::shared_ptr<Manipulator> >::clear();
+}
+
+bool ManipulatorGroup::event(GdkEvent *event)
+{
+ if (_grab) {
+ return _grab->event(event);
+ }
+
+ for (std::list<boost::shared_ptr<Manipulator> >::iterator i = begin(); i != end(); ++i) {
+ if ((*i)->event(event) || _grab) return true;
+ }
+ return false;
+}*/
+
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/manipulator.h b/src/ui/tool/manipulator.h
new file mode 100644
index 000000000..799dad0d3
--- /dev/null
+++ b/src/ui/tool/manipulator.h
@@ -0,0 +1,165 @@
+/** @file
+ * Manipulator - edits something on-canvas
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_MANIPULATOR_H
+#define SEEN_UI_TOOL_MANIPULATOR_H
+
+#include <set>
+#include <map>
+#include <sigc++/sigc++.h>
+#include <glib.h>
+#include <gdk/gdk.h>
+#include <boost/shared_ptr.hpp>
+
+class SPDesktop;
+namespace Inkscape {
+namespace UI {
+
+class ManipulatorGroup;
+class ControlPointSelection;
+
+/**
+ * @brief Tool component that processes events and does something in response to them.
+ * Note: this class is probably redundant.
+ */
+class Manipulator {
+friend class ManipulatorGroup;
+public:
+ Manipulator(SPDesktop *d)
+ : _desktop(d)
+ {}
+ virtual ~Manipulator() {}
+
+ /// Handle input event. Returns true if handled.
+ virtual bool event(GdkEvent *)=0;
+protected:
+ SPDesktop *const _desktop;
+};
+
+/**
+ * @brief Tool component that edits something on the canvas using selectable control points.
+ * Note: this class is probably redundant.
+ */
+class PointManipulator : public Manipulator, public sigc::trackable {
+public:
+ PointManipulator(SPDesktop *d, ControlPointSelection &sel)
+ : Manipulator(d)
+ , _selection(sel)
+ {}
+protected:
+ ControlPointSelection &_selection;
+};
+
+/** Manipulator that aggregates several manipulators of the same type.
+ * The order of invoking events on the member manipulators is undefined.
+ * To make this class more useful, derive from it and add actions that can be performed
+ * on all manipulators in the set.
+ *
+ * This is not used at the moment and is probably useless. */
+template <typename T>
+class MultiManipulator : public PointManipulator {
+public:
+ //typedef typename T::ItemType ItemType;
+ typedef typename std::pair<void*, boost::shared_ptr<T> > MapPair;
+ typedef typename std::map<void*, boost::shared_ptr<T> > MapType;
+
+ MultiManipulator(SPDesktop *d, ControlPointSelection &sel)
+ : PointManipulator(d, sel)
+ {}
+ void addItem(void *item) {
+ boost::shared_ptr<T> m(_createManipulator(item));
+ _mmap.insert(MapPair(item, m));
+ }
+ void removeItem(void *item) {
+ _mmap.erase(item);
+ }
+ void clear() {
+ _mmap.clear();
+ }
+ bool contains(void *item) {
+ return _mmap.find(item) != _mmap.end();
+ }
+ bool empty() {
+ return _mmap.empty();
+ }
+ void setItems(GSList const *list) {
+ std::set<void*> to_remove;
+ for (typename MapType::iterator mi = _mmap.begin(); mi != _mmap.end(); ++mi) {
+ to_remove.insert(mi->first);
+ }
+ for (GSList *i = const_cast<GSList*>(list); i; i = i->next) {
+ if (_isItemType(i->data)) {
+ // erase returns the number of items removed
+ // if nothing was removed, it means this item did not have a manipulator - add it
+ if (!to_remove.erase(i->data)) addItem(i->data);
+ }
+ }
+ typedef typename std::set<void*>::iterator RmIter;
+ for (RmIter ri = to_remove.begin(); ri != to_remove.end(); ++ri) {
+ removeItem(*ri);
+ }
+ }
+
+ /** Invoke a method on all managed manipulators.
+ * Example:
+ * @code m.invokeForAll(&SomeManipulator::someMethod); @endcode
+ */
+ template <typename R>
+ void invokeForAll(R (T::*method)()) {
+ for (typename MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)();
+ }
+ }
+ template <typename R, typename A>
+ void invokeForAll(R (T::*method)(A), A a) {
+ for (typename MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)(a);
+ }
+ }
+ template <typename R, typename A>
+ void invokeForAll(R (T::*method)(A const &), A const &a) {
+ for (typename MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)(a);
+ }
+ }
+ template <typename R, typename A, typename B>
+ void invokeForAll(R (T::*method)(A,B), A a, B b) {
+ for (typename MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)(a, b);
+ }
+ }
+
+ virtual bool event(GdkEvent *event) {
+ for (typename MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ if ((*i).second->event(event)) return true;
+ }
+ return false;
+ }
+protected:
+ virtual T *_createManipulator(void *item) = 0;
+ virtual bool _isItemType(void *item) = 0;
+ MapType _mmap;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/multi-path-manipulator.cpp b/src/ui/tool/multi-path-manipulator.cpp
new file mode 100644
index 000000000..3ae7e4d24
--- /dev/null
+++ b/src/ui/tool/multi-path-manipulator.cpp
@@ -0,0 +1,598 @@
+/** @file
+ * Path manipulator - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <tr1/unordered_set>
+#include <boost/shared_ptr.hpp>
+#include <glib.h>
+#include <glibmm/i18n.h>
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "document.h"
+#include "live_effects/lpeobject.h"
+#include "message-stack.h"
+#include "preferences.h"
+#include "sp-path.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/node.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tool/path-manipulator.h"
+
+namespace std { using namespace tr1; }
+
+namespace Inkscape {
+namespace UI {
+
+namespace {
+typedef std::pair<NodeList::iterator, NodeList::iterator> IterPair;
+typedef std::vector<IterPair> IterPairList;
+typedef std::unordered_set<NodeList::iterator> IterSet;
+typedef std::multimap<double, IterPair> DistanceMap;
+typedef std::pair<double, IterPair> DistanceMapItem;
+
+/** Find pairs of selected endnodes suitable for joining. */
+void find_join_iterators(ControlPointSelection &sel, IterPairList &pairs)
+{
+ IterSet join_iters;
+ DistanceMap dists;
+
+ // find all endnodes in selection
+ for (ControlPointSelection::iterator i = sel.begin(); i != sel.end(); ++i) {
+ Node *node = dynamic_cast<Node*>(i->first);
+ if (!node) continue;
+ NodeList::iterator iter = NodeList::get_iterator(node);
+ if (!iter.next() || !iter.prev()) join_iters.insert(iter);
+ }
+
+ if (join_iters.size() < 2) return;
+
+ // Below we find the closest pairs. The algorithm is O(N^3).
+ // We can go down to O(N^2 log N) by using O(N^2) memory, by putting all pairs
+ // with their distances in a multimap (not worth it IMO).
+ while (join_iters.size() >= 2) {
+ double closest = DBL_MAX;
+ IterPair closest_pair;
+ for (IterSet::iterator i = join_iters.begin(); i != join_iters.end(); ++i) {
+ for (IterSet::iterator j = join_iters.begin(); j != i; ++j) {
+ double dist = Geom::distance(**i, **j);
+ if (dist < closest) {
+ closest = dist;
+ closest_pair = std::make_pair(*i, *j);
+ }
+ }
+ }
+ pairs.push_back(closest_pair);
+ join_iters.erase(closest_pair.first);
+ join_iters.erase(closest_pair.second);
+ }
+}
+
+/** After this function, first should be at the end of path and second at the beginnning.
+ * @returns True if the nodes are in the same subpath */
+bool prepare_join(IterPair &join_iters)
+{
+ if (&NodeList::get(join_iters.first) == &NodeList::get(join_iters.second)) {
+ if (join_iters.first.next()) // if first is begin, swap the iterators
+ std::swap(join_iters.first, join_iters.second);
+ return true;
+ }
+
+ NodeList &sp_first = NodeList::get(join_iters.first);
+ NodeList &sp_second = NodeList::get(join_iters.second);
+ if (join_iters.first.next()) { // first is begin
+ if (join_iters.second.next()) { // second is begin
+ sp_first.reverse();
+ } else { // second is end
+ std::swap(join_iters.first, join_iters.second);
+ }
+ } else { // first is end
+ if (join_iters.second.next()) { // second is begin
+ // do nothing
+ } else { // second is end
+ sp_second.reverse();
+ }
+ }
+ return false;
+}
+} // anonymous namespace
+
+
+MultiPathManipulator::MultiPathManipulator(PathSharedData &data, sigc::connection &chg)
+ : PointManipulator(data.node_data.desktop, *data.node_data.selection)
+ , _path_data(data)
+ , _changed(chg)
+{
+ _selection.signal_commit.connect(
+ sigc::mem_fun(*this, &MultiPathManipulator::_commit));
+ _selection.signal_point_changed.connect(
+ sigc::hide( sigc::hide(
+ signal_coords_changed.make_slot())));
+}
+
+MultiPathManipulator::~MultiPathManipulator()
+{
+ _mmap.clear();
+}
+
+/** Remove empty manipulators. */
+void MultiPathManipulator::cleanup()
+{
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ) {
+ if (i->second->empty()) _mmap.erase(i++);
+ else ++i;
+ }
+}
+
+/** @brief Change the set of items to edit.
+ *
+ * This method attempts to preserve as much of the state as possible. */
+void MultiPathManipulator::setItems(std::set<ShapeRecord> const &s)
+{
+ std::set<ShapeRecord> shapes(s);
+
+ // iterate over currently edited items, modifying / removing them as necessary
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end();) {
+ std::set<ShapeRecord>::iterator si = shapes.find(i->first);
+ if (si == shapes.end()) {
+ // This item is no longer supposed to be edited - remove its manipulator
+ _mmap.erase(i++);
+ } else {
+ ShapeRecord const &sr = i->first;
+ ShapeRecord const &sr_new = *si;
+ // if the shape record differs, replace the key only and modify other values
+ if (sr.edit_transform != sr_new.edit_transform ||
+ sr.role != sr_new.role)
+ {
+ boost::shared_ptr<PathManipulator> hold(i->second);
+ if (sr.edit_transform != sr_new.edit_transform)
+ hold->setControlsTransform(sr_new.edit_transform);
+ if (sr.role != sr_new.role) {
+ //hold->setOutlineColor(_getOutlineColor(sr_new.role));
+ }
+ _mmap.erase(sr);
+ _mmap.insert(std::make_pair(sr_new, hold));
+ }
+ shapes.erase(si); // remove the processed record
+ ++i;
+ }
+ }
+
+ // add newly selected items
+ for (std::set<ShapeRecord>::iterator i = shapes.begin(); i != shapes.end(); ++i) {
+ ShapeRecord const &r = *i;
+ if (!SP_IS_PATH(r.item) && !IS_LIVEPATHEFFECT(r.item)) continue;
+ boost::shared_ptr<PathManipulator> newpm(new PathManipulator(*this, (SPPath*) r.item,
+ r.edit_transform, _getOutlineColor(r.role), r.lpe_key));
+ newpm->showHandles(_show_handles);
+ // always show outlines for clips and masks
+ newpm->showOutline(_show_outline || r.role != SHAPE_ROLE_NORMAL);
+ newpm->showPathDirection(_show_path_direction);
+ _mmap.insert(std::make_pair(r, newpm));
+ }
+}
+
+void MultiPathManipulator::selectSubpaths()
+{
+ if (_selection.empty()) {
+ _selection.selectAll();
+ } else {
+ invokeForAll(&PathManipulator::selectSubpaths);
+ }
+}
+
+void MultiPathManipulator::shiftSelection(int dir)
+{
+ invokeForAll(&PathManipulator::shiftSelection, dir);
+}
+
+void MultiPathManipulator::invertSelectionInSubpaths()
+{
+ invokeForAll(&PathManipulator::invertSelectionInSubpaths);
+}
+
+void MultiPathManipulator::setNodeType(NodeType type)
+{
+ if (_selection.empty()) return;
+ for (ControlPointSelection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ Node *node = dynamic_cast<Node*>(i->first);
+ if (node) node->setType(type);
+ }
+ _done(_("Change node type"));
+}
+
+void MultiPathManipulator::setSegmentType(SegmentType type)
+{
+ if (_selection.empty()) return;
+ invokeForAll(&PathManipulator::setSegmentType, type);
+ if (type == SEGMENT_STRAIGHT) {
+ _done(_("Straighten segments"));
+ } else {
+ _done(_("Make segments curves"));
+ }
+}
+
+void MultiPathManipulator::insertNodes()
+{
+ invokeForAll(&PathManipulator::insertNodes);
+ _done(_("Add nodes"));
+}
+
+void MultiPathManipulator::joinNodes()
+{
+ invokeForAll(&PathManipulator::hideDragPoint);
+ // Node join has two parts. In the first one we join two subpaths by fusing endpoints
+ // into one. In the second we fuse nodes in each subpath.
+ IterPairList joins;
+ NodeList::iterator preserve_pos;
+ Node *mouseover_node = dynamic_cast<Node*>(ControlPoint::mouseovered_point);
+ if (mouseover_node) {
+ preserve_pos = NodeList::get_iterator(mouseover_node);
+ }
+ find_join_iterators(_selection, joins);
+
+ for (IterPairList::iterator i = joins.begin(); i != joins.end(); ++i) {
+ bool same_path = prepare_join(*i);
+ bool mouseover = true;
+ NodeList &sp_first = NodeList::get(i->first);
+ NodeList &sp_second = NodeList::get(i->second);
+ i->first->setType(NODE_CUSP, false);
+
+ Geom::Point joined_pos, pos_front, pos_back;
+ pos_front = *i->second->front();
+ pos_back = *i->first->back();
+ if (i->first == preserve_pos) {
+ joined_pos = *i->first;
+ } else if (i->second == preserve_pos) {
+ joined_pos = *i->second;
+ } else {
+ joined_pos = Geom::middle_point(pos_back, pos_front);
+ mouseover = false;
+ }
+
+ // if the handles aren't degenerate, don't move them
+ i->first->move(joined_pos);
+ Node *joined_node = i->first.ptr();
+ if (!i->second->front()->isDegenerate()) {
+ joined_node->front()->setPosition(pos_front);
+ }
+ if (!i->first->back()->isDegenerate()) {
+ joined_node->back()->setPosition(pos_back);
+ }
+ if (mouseover) {
+ // Second node could be mouseovered, but it will be deleted, so we must change
+ // the preserve_pos iterator to the first node.
+ preserve_pos = i->first;
+ }
+ sp_second.erase(i->second);
+
+ if (same_path) {
+ sp_first.setClosed(true);
+ } else {
+ sp_first.splice(sp_first.end(), sp_second);
+ sp_second.kill();
+ }
+ _selection.insert(i->first.ptr());
+ }
+
+ if (joins.empty()) {
+ // Second part replaces contiguous selections of nodes with single nodes
+ invokeForAll(&PathManipulator::weldNodes, preserve_pos);
+ }
+
+ _doneWithCleanup(_("Join nodes"));
+}
+
+void MultiPathManipulator::breakNodes()
+{
+ if (_selection.empty()) return;
+ invokeForAll(&PathManipulator::breakNodes);
+ _done(_("Break nodes"));
+}
+
+void MultiPathManipulator::deleteNodes(bool keep_shape)
+{
+ if (_selection.empty()) return;
+ invokeForAll(&PathManipulator::deleteNodes, keep_shape);
+ _doneWithCleanup(_("Delete nodes"));
+}
+
+/** Join selected endpoints to create segments. */
+void MultiPathManipulator::joinSegments()
+{
+ IterPairList joins;
+ find_join_iterators(_selection, joins);
+
+ for (IterPairList::iterator i = joins.begin(); i != joins.end(); ++i) {
+ bool same_path = prepare_join(*i);
+ NodeList &sp_first = NodeList::get(i->first);
+ NodeList &sp_second = NodeList::get(i->second);
+ i->first->setType(NODE_CUSP, false);
+ i->second->setType(NODE_CUSP, false);
+ if (same_path) {
+ sp_first.setClosed(true);
+ } else {
+ sp_first.splice(sp_first.end(), sp_second);
+ sp_second.kill();
+ }
+ }
+
+ if (joins.empty()) {
+ invokeForAll(&PathManipulator::weldSegments);
+ }
+ _doneWithCleanup("Join segments");
+}
+
+void MultiPathManipulator::deleteSegments()
+{
+ if (_selection.empty()) return;
+ invokeForAll(&PathManipulator::deleteSegments);
+ _doneWithCleanup("Delete segments");
+}
+
+void MultiPathManipulator::alignNodes(Geom::Dim2 d)
+{
+ _selection.align(d);
+ if (d == Geom::X) {
+ _done("Align nodes to a horizontal line");
+ } else {
+ _done("Align nodes to a vertical line");
+ }
+}
+
+void MultiPathManipulator::distributeNodes(Geom::Dim2 d)
+{
+ _selection.distribute(d);
+ if (d == Geom::X) {
+ _done("Distrubute nodes horizontally");
+ } else {
+ _done("Distribute nodes vertically");
+ }
+}
+
+void MultiPathManipulator::reverseSubpaths()
+{
+ invokeForAll(&PathManipulator::reverseSubpaths);
+ _done("Reverse selected subpaths");
+}
+
+void MultiPathManipulator::move(Geom::Point const &delta)
+{
+ _selection.transform(Geom::Translate(delta));
+ _done("Move nodes");
+}
+
+void MultiPathManipulator::showOutline(bool show)
+{
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ // always show outlines for clipping paths and masks
+ i->second->showOutline(show || i->first.role != SHAPE_ROLE_NORMAL);
+ }
+ _show_outline = show;
+}
+
+void MultiPathManipulator::showHandles(bool show)
+{
+ invokeForAll(&PathManipulator::showHandles, show);
+ _show_handles = show;
+}
+
+void MultiPathManipulator::showPathDirection(bool show)
+{
+ invokeForAll(&PathManipulator::showPathDirection, show);
+ _show_path_direction = show;
+}
+
+void MultiPathManipulator::updateOutlineColors()
+{
+ //for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ // i->second->setOutlineColor(_getOutlineColor(i->first.role));
+ //}
+}
+
+bool MultiPathManipulator::event(GdkEvent *event)
+{
+ switch (event->type) {
+ case GDK_KEY_PRESS:
+ switch (shortcut_key(event->key)) {
+ case GDK_Insert:
+ case GDK_KP_Insert:
+ insertNodes();
+ return true;
+ case GDK_i:
+ case GDK_I:
+ if (held_only_shift(event->key)) {
+ insertNodes();
+ return true;
+ }
+ break;
+ case GDK_j:
+ case GDK_J:
+ if (held_only_shift(event->key)) {
+ joinNodes();
+ return true;
+ }
+ if (held_only_alt(event->key)) {
+ joinSegments();
+ return true;
+ }
+ break;
+ case GDK_b:
+ case GDK_B:
+ if (held_only_shift(event->key)) {
+ breakNodes();
+ return true;
+ }
+ break;
+ case GDK_Delete:
+ case GDK_KP_Delete:
+ case GDK_BackSpace:
+ if (held_shift(event->key)) break;
+ if (held_alt(event->key)) {
+ deleteSegments();
+ } else {
+ deleteNodes(!held_control(event->key));
+ }
+ return true;
+ case GDK_c:
+ case GDK_C:
+ if (held_only_shift(event->key)) {
+ setNodeType(NODE_CUSP);
+ return true;
+ }
+ break;
+ case GDK_s:
+ case GDK_S:
+ if (held_only_shift(event->key)) {
+ setNodeType(NODE_SMOOTH);
+ return true;
+ }
+ break;
+ case GDK_a:
+ case GDK_A:
+ if (held_only_shift(event->key)) {
+ setNodeType(NODE_AUTO);
+ return true;
+ }
+ break;
+ case GDK_y:
+ case GDK_Y:
+ if (held_only_shift(event->key)) {
+ setNodeType(NODE_SYMMETRIC);
+ return true;
+ }
+ break;
+ case GDK_r:
+ case GDK_R:
+ if (held_only_shift(event->key)) {
+ reverseSubpaths();
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ default: break;
+ }
+
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ if (i->second->event(event)) return true;
+ }
+ return false;
+}
+
+/** Commit changes to XML and add undo stack entry based on the action that was done. Invoked
+ * by sub-manipulators, for example TransformHandleSet and ControlPointSelection. */
+void MultiPathManipulator::_commit(CommitEvent cps)
+{
+ gchar const *reason = NULL;
+ gchar const *key = NULL;
+ switch(cps) {
+ case COMMIT_MOUSE_MOVE:
+ reason = _("Move nodes");
+ break;
+ case COMMIT_KEYBOARD_MOVE_X:
+ reason = _("Move nodes horizontally");
+ key = "node:move:x";
+ break;
+ case COMMIT_KEYBOARD_MOVE_Y:
+ reason = _("Move nodes vertically");
+ key = "node:move:y";
+ break;
+ case COMMIT_MOUSE_ROTATE:
+ reason = _("Rotate nodes");
+ break;
+ case COMMIT_KEYBOARD_ROTATE:
+ reason = _("Rotate nodes");
+ key = "node:rotate";
+ break;
+ case COMMIT_MOUSE_SCALE_UNIFORM:
+ reason = _("Scale nodes uniformly");
+ break;
+ case COMMIT_MOUSE_SCALE:
+ reason = _("Scale nodes");
+ break;
+ case COMMIT_KEYBOARD_SCALE_UNIFORM:
+ reason = _("Scale nodes uniformly");
+ key = "node:scale:uniform";
+ break;
+ case COMMIT_KEYBOARD_SCALE_X:
+ reason = _("Scale nodes horizontally");
+ key = "node:scale:x";
+ break;
+ case COMMIT_KEYBOARD_SCALE_Y:
+ reason = _("Scale nodes vertically");
+ key = "node:scale:y";
+ break;
+ case COMMIT_FLIP_X:
+ reason = _("Flip nodes horizontally");
+ break;
+ case COMMIT_FLIP_Y:
+ reason = _("Flip nodes vertically");
+ break;
+ default: return;
+ }
+
+ _selection.signal_update.emit();
+ invokeForAll(&PathManipulator::writeXML);
+ if (key) {
+ sp_document_maybe_done(sp_desktop_document(_desktop), key, SP_VERB_CONTEXT_NODE, reason);
+ } else {
+ sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason);
+ }
+ signal_coords_changed.emit();
+}
+
+/** Commits changes to XML and adds undo stack entry. */
+void MultiPathManipulator::_done(gchar const *reason) {
+ invokeForAll(&PathManipulator::update);
+ invokeForAll(&PathManipulator::writeXML);
+ sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason);
+ signal_coords_changed.emit();
+}
+
+/** Commits changes to XML, adds undo stack entry and removes empty manipulators. */
+void MultiPathManipulator::_doneWithCleanup(gchar const *reason) {
+ _changed.block();
+ _done(reason);
+ cleanup();
+ _changed.unblock();
+}
+
+/** Get an outline color based on the shape's role (normal, mask, LPE parameter, etc.). */
+guint32 MultiPathManipulator::_getOutlineColor(ShapeRole role)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ switch(role) {
+ case SHAPE_ROLE_CLIPPING_PATH:
+ return prefs->getColor("/tools/nodes/clipping_path_color", 0x00ff00ff);
+ case SHAPE_ROLE_MASK:
+ return prefs->getColor("/tools/nodes/mask_color", 0x0000ffff);
+ case SHAPE_ROLE_LPE_PARAM:
+ return prefs->getColor("/tools/nodes/lpe_param_color", 0x009000ff);
+ case SHAPE_ROLE_NORMAL:
+ default:
+ return prefs->getColor("/tools/nodes/outline_color", 0xff0000ff);
+ }
+}
+
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/multi-path-manipulator.h b/src/ui/tool/multi-path-manipulator.h
new file mode 100644
index 000000000..151e153ec
--- /dev/null
+++ b/src/ui/tool/multi-path-manipulator.h
@@ -0,0 +1,132 @@
+/** @file
+ * Multi path manipulator - a tool component that edits multiple paths at once
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_MULTI_PATH_MANIPULATOR_H
+#define SEEN_UI_TOOL_MULTI_PATH_MANIPULATOR_H
+
+#include <sigc++/connection.h>
+#include "display/display-forward.h"
+#include "forward.h"
+#include "ui/tool/commit-events.h"
+#include "ui/tool/manipulator.h"
+#include "ui/tool/node.h"
+#include "ui/tool/node-types.h"
+#include "ui/tool/shape-record.h"
+
+struct SPCanvasGroup;
+
+namespace Inkscape {
+namespace UI {
+
+class PathManipulator;
+class MultiPathManipulator;
+struct PathSharedData;
+
+/**
+ * Manipulator that manages multiple path manipulators active at the same time.
+ */
+class MultiPathManipulator : public PointManipulator {
+public:
+ MultiPathManipulator(PathSharedData &data, sigc::connection &chg);
+ virtual ~MultiPathManipulator();
+ virtual bool event(GdkEvent *event);
+
+ bool empty() { return _mmap.empty(); }
+ unsigned size() { return _mmap.empty(); }
+ void setItems(std::set<ShapeRecord> const &);
+ void clear() { _mmap.clear(); }
+ void cleanup();
+
+ void selectSubpaths();
+ void shiftSelection(int dir);
+ void invertSelectionInSubpaths();
+
+ void setNodeType(NodeType t);
+ void setSegmentType(SegmentType t);
+
+ void insertNodes();
+ void joinNodes();
+ void breakNodes();
+ void deleteNodes(bool keep_shape = true);
+ void joinSegments();
+ void deleteSegments();
+ void alignNodes(Geom::Dim2 d);
+ void distributeNodes(Geom::Dim2 d);
+ void reverseSubpaths();
+ void move(Geom::Point const &delta);
+
+ void showOutline(bool show);
+ void showHandles(bool show);
+ void showPathDirection(bool show);
+ void updateOutlineColors();
+
+ sigc::signal<void> signal_coords_changed; /// Emitted whenever the coordinates
+ /// shown in the status bar need updating
+private:
+ typedef std::pair<ShapeRecord, boost::shared_ptr<PathManipulator> > MapPair;
+ typedef std::map<ShapeRecord, boost::shared_ptr<PathManipulator> > MapType;
+
+ template <typename R>
+ void invokeForAll(R (PathManipulator::*method)()) {
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)();
+ }
+ }
+ template <typename R, typename A>
+ void invokeForAll(R (PathManipulator::*method)(A), A a) {
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)(a);
+ }
+ }
+ template <typename R, typename A>
+ void invokeForAll(R (PathManipulator::*method)(A const &), A const &a) {
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)(a);
+ }
+ }
+ template <typename R, typename A, typename B>
+ void invokeForAll(R (PathManipulator::*method)(A,B), A a, B b) {
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)(a, b);
+ }
+ }
+
+ void _commit(CommitEvent cps);
+ void _done(gchar const *);
+ void _doneWithCleanup(gchar const *);
+ guint32 _getOutlineColor(ShapeRole role);
+
+ MapType _mmap;
+public:
+ PathSharedData const &_path_data;
+private:
+ sigc::connection &_changed;
+ bool _show_handles;
+ bool _show_outline;
+ bool _show_path_direction;
+
+ friend class PathManipulator;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/node-tool.cpp b/src/ui/tool/node-tool.cpp
new file mode 100644
index 000000000..c1ba3394e
--- /dev/null
+++ b/src/ui/tool/node-tool.cpp
@@ -0,0 +1,616 @@
+/** @file
+ * @brief New node tool - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include "desktop.h"
+#include "desktop-handles.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 "ui/tool/node-tool.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/curve-drag-point.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"
+
+/** @struct InkNodeTool
+ *
+ * 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 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.
+ */
+
+namespace {
+SPCanvasGroup *create_control_group(SPDesktop *d);
+void ink_node_tool_class_init(InkNodeToolClass *klass);
+void ink_node_tool_init(InkNodeTool *node_context);
+void ink_node_tool_dispose(GObject *object);
+
+void ink_node_tool_setup(SPEventContext *ec);
+gint ink_node_tool_root_handler(SPEventContext *event_context, GdkEvent *event);
+gint ink_node_tool_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
+void ink_node_tool_set(SPEventContext *ec, Inkscape::Preferences::Entry *value);
+
+void ink_node_tool_update_tip(InkNodeTool *nt, GdkEvent *event);
+void ink_node_tool_selection_changed(InkNodeTool *nt, Inkscape::Selection *sel);
+void ink_node_tool_select_area(InkNodeTool *nt, Geom::Rect const &, GdkEventButton *);
+void ink_node_tool_select_point(InkNodeTool *nt, Geom::Point const &, GdkEventButton *);
+void ink_node_tool_mouseover_changed(InkNodeTool *nt, Inkscape::UI::ControlPoint *p);
+} // anonymous namespace
+
+GType ink_node_tool_get_type()
+{
+ static GType type = 0;
+ if (!type) {
+ GTypeInfo info = {
+ sizeof(InkNodeToolClass),
+ NULL, NULL,
+ (GClassInitFunc) ink_node_tool_class_init,
+ NULL, NULL,
+ sizeof(InkNodeTool),
+ 4,
+ (GInstanceInitFunc) ink_node_tool_init,
+ NULL, /* value_table */
+ };
+ type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "InkNodeTool", &info, (GTypeFlags)0);
+ }
+ return type;
+}
+
+namespace {
+
+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)
+{
+ gtk_object_destroy(GTK_OBJECT(g));
+}
+
+void ink_node_tool_class_init(InkNodeToolClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
+
+ object_class->dispose = ink_node_tool_dispose;
+
+ event_context_class->setup = ink_node_tool_setup;
+ event_context_class->set = ink_node_tool_set;
+ event_context_class->root_handler = ink_node_tool_root_handler;
+ event_context_class->item_handler = ink_node_tool_item_handler;
+}
+
+void ink_node_tool_init(InkNodeTool *nt)
+{
+ SPEventContext *event_context = SP_EVENT_CONTEXT(nt);
+
+ event_context->cursor_shape = cursor_node_xpm;
+ event_context->hot_x = 1;
+ event_context->hot_y = 1;
+
+ new (&nt->_selection_changed_connection) sigc::connection();
+ new (&nt->_selection_modified_connection) sigc::connection();
+ new (&nt->_mouseover_changed_connection) sigc::connection();
+ //new (&nt->_mgroup) Inkscape::UI::ManipulatorGroup(nt->desktop);
+ new (&nt->_selected_nodes) CSelPtr();
+ new (&nt->_multipath) MultiPathPtr();
+ new (&nt->_selector) SelectorPtr();
+ new (&nt->_path_data) PathSharedDataPtr();
+}
+
+void ink_node_tool_dispose(GObject *object)
+{
+ InkNodeTool *nt = INK_NODE_TOOL(object);
+
+ nt->enableGrDrag(false);
+
+ nt->_selection_changed_connection.disconnect();
+ nt->_selection_modified_connection.disconnect();
+ nt->_mouseover_changed_connection.disconnect();
+ nt->_multipath.~MultiPathPtr();
+ nt->_selected_nodes.~CSelPtr();
+ nt->_selector.~SelectorPtr();
+
+ Inkscape::UI::PathSharedData &data = *nt->_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(nt->_transform_handle_group);
+
+ nt->_path_data.~PathSharedDataPtr();
+ nt->_selection_changed_connection.~connection();
+ nt->_selection_modified_connection.~connection();
+ nt->_mouseover_changed_connection.~connection();
+
+ if (nt->_node_message_context) {
+ delete nt->_node_message_context;
+ }
+ if (nt->shape_editor) {
+ nt->shape_editor->unset_item(SH_KNOTHOLDER);
+ delete nt->shape_editor;
+ }
+
+ G_OBJECT_CLASS(g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL)))->dispose(object);
+}
+
+void ink_node_tool_setup(SPEventContext *ec)
+{
+ InkNodeTool *nt = INK_NODE_TOOL(ec);
+
+ SPEventContextClass *parent = (SPEventContextClass *) g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL));
+ if (parent->setup) parent->setup(ec);
+
+ nt->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
+
+ nt->_path_data.reset(new Inkscape::UI::PathSharedData());
+ Inkscape::UI::PathSharedData &data = *nt->_path_data;
+ data.node_data.desktop = nt->desktop;
+
+ // selector has to be created here, so that its hidden control point is on the bottom
+ nt->_selector.reset(new Inkscape::UI::Selector(nt->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(nt->desktop);
+ data.node_data.handle_line_group = create_control_group(nt->desktop);
+ data.dragpoint_group = create_control_group(nt->desktop);
+ nt->_transform_handle_group = create_control_group(nt->desktop);
+ data.node_data.node_group = create_control_group(nt->desktop);
+ data.node_data.handle_group = create_control_group(nt->desktop);
+
+ Inkscape::Selection *selection = sp_desktop_selection (ec->desktop);
+ nt->_selection_changed_connection.disconnect();
+ nt->_selection_changed_connection =
+ selection->connectChanged(
+ sigc::bind<0>(
+ sigc::ptr_fun(&ink_node_tool_selection_changed),
+ nt));
+ /*nt->_selection_modified_connection.disconnect();
+ nt->_selection_modified_connection =
+ selection->connectModified(
+ sigc::hide(sigc::bind<0>(
+ sigc::ptr_fun(&ink_node_tool_selection_modified),
+ nt)));*/
+ nt->_mouseover_changed_connection.disconnect();
+ nt->_mouseover_changed_connection =
+ Inkscape::UI::ControlPoint::signal_mouseover_change.connect(
+ sigc::bind<0>(
+ sigc::ptr_fun(&ink_node_tool_mouseover_changed),
+ nt));
+
+ nt->_selected_nodes.reset(
+ new Inkscape::UI::ControlPointSelection(nt->desktop, nt->_transform_handle_group));
+ data.node_data.selection = nt->_selected_nodes.get();
+ nt->_multipath.reset(new Inkscape::UI::MultiPathManipulator(data,
+ nt->_selection_changed_connection));
+
+ nt->_selector->signal_point.connect(
+ sigc::bind<0>(
+ sigc::ptr_fun(&ink_node_tool_select_point),
+ nt));
+ nt->_selector->signal_area.connect(
+ sigc::bind<0>(
+ sigc::ptr_fun(&ink_node_tool_select_area),
+ nt));
+
+ nt->_multipath->signal_coords_changed.connect(
+ sigc::bind(
+ sigc::mem_fun(*nt->desktop, &SPDesktop::emitToolSubselectionChanged),
+ (void*) 0));
+ nt->_selected_nodes->signal_point_changed.connect(
+ sigc::hide( sigc::hide(
+ sigc::bind(
+ sigc::bind(
+ sigc::ptr_fun(ink_node_tool_update_tip),
+ (GdkEvent*)0),
+ nt))));
+
+ nt->cursor_drag = false;
+ nt->show_transform_handles = true;
+ nt->single_node_transform_handles = false;
+ nt->flash_tempitem = NULL;
+ nt->flashed_item = NULL;
+ // TODO remove this!
+ nt->shape_editor = new ShapeEditor(nt->desktop);
+
+ // read prefs before adding items to selection to prevent momentarily showing the outline
+ sp_event_context_read(nt, "show_handles");
+ sp_event_context_read(nt, "show_outline");
+ sp_event_context_read(nt, "show_path_direction");
+ sp_event_context_read(nt, "show_transform_handles");
+ sp_event_context_read(nt, "single_node_transform_handles");
+ sp_event_context_read(nt, "edit_clipping_paths");
+ sp_event_context_read(nt, "edit_masks");
+
+ ink_node_tool_selection_changed(nt, selection);
+ ink_node_tool_update_tip(nt, NULL);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/nodes/selcue")) {
+ ec->enableSelectionCue();
+ }
+ if (prefs->getBool("/tools/nodes/gradientdrag")) {
+ ec->enableGrDrag();
+ }
+
+ nt->desktop->emitToolSubselectionChanged(NULL); // sets the coord entry fields to inactive
+}
+
+void ink_node_tool_set(SPEventContext *ec, Inkscape::Preferences::Entry *value)
+{
+ InkNodeTool *nt = INK_NODE_TOOL(ec);
+ Glib::ustring entry_name = value->getEntryName();
+
+ if (entry_name == "show_handles") {
+ nt->_multipath->showHandles(value->getBool(true));
+ } else if (entry_name == "show_outline") {
+ nt->show_outline = value->getBool();
+ nt->_multipath->showOutline(nt->show_outline);
+ } else if (entry_name == "show_path_direction") {
+ nt->show_path_direction = value->getBool();
+ nt->_multipath->showPathDirection(nt->show_path_direction);
+ } else if (entry_name == "show_transform_handles") {
+ nt->show_transform_handles = value->getBool(true);
+ nt->_selected_nodes->showTransformHandles(
+ nt->show_transform_handles, nt->single_node_transform_handles);
+ } else if (entry_name == "single_node_transform_handles") {
+ nt->single_node_transform_handles = value->getBool();
+ nt->_selected_nodes->showTransformHandles(
+ nt->show_transform_handles, nt->single_node_transform_handles);
+ } else if (entry_name == "edit_clipping_paths") {
+ nt->edit_clipping_paths = value->getBool();
+ ink_node_tool_selection_changed(nt, nt->desktop->selection);
+ } else if (entry_name == "edit_masks") {
+ nt->edit_masks = value->getBool();
+ ink_node_tool_selection_changed(nt, nt->desktop->selection);
+ } else {
+ SPEventContextClass *parent_class =
+ (SPEventContextClass *) g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL));
+ if (parent_class->set)
+ parent_class->set(ec, value);
+ }
+}
+
+/** Recursively collect ShapeRecords */
+void gather_items(InkNodeTool *nt, SPItem *base, SPObject *obj, Inkscape::UI::ShapeRole role,
+ std::set<Inkscape::UI::ShapeRecord> &s)
+{
+ using namespace Inkscape::UI;
+ if (!obj) return;
+
+ if (SP_IS_PATH(obj) && obj->repr->attribute("inkscape:original-d") != NULL) {
+ ShapeRecord r;
+ r.item = static_cast<SPItem*>(obj);
+ r.edit_transform = Geom::identity(); // TODO wrong?
+ r.role = SHAPE_ROLE_LPE_PARAM;
+ 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 ? sp_item_i2doc_affine(base) : 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);
+ }
+ }
+ }
+}
+
+struct IsPath {
+ bool operator()(SPItem *i) const { return SP_IS_PATH(i); }
+};
+
+void ink_node_tool_selection_changed(InkNodeTool *nt, 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(nt, NULL, static_cast<SPItem*>(obj), SHAPE_ROLE_NORMAL, shapes);
+ }
+ }
+
+ // ugly hack: set the first editable non-path item for knotholder
+ // maybe use multiple ShapeEditors for now, to allow editing many shapes at once?
+ bool something_set = false;
+ for (std::set<ShapeRecord>::iterator i = shapes.begin(); i != shapes.end(); ++i) {
+ ShapeRecord const &r = *i;
+ if (SP_IS_SHAPE(r.item) && !SP_IS_PATH(r.item)) {
+ nt->shape_editor->set_item(r.item, SH_KNOTHOLDER);
+ something_set = true;
+ break;
+ }
+ }
+ if (!something_set) {
+ nt->shape_editor->unset_item(SH_KNOTHOLDER);
+ }
+
+ nt->_multipath->setItems(shapes);
+ ink_node_tool_update_tip(nt, NULL);
+ nt->desktop->updateNow();
+}
+
+gint ink_node_tool_root_handler(SPEventContext *event_context, 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
+
+ SPDesktop *desktop = event_context->desktop;
+ Inkscape::Selection *selection = desktop->selection;
+ InkNodeTool *nt = static_cast<InkNodeTool*>(event_context);
+ static Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (nt->_multipath->event(event)) return true;
+ if (nt->_selector->event(event)) return true;
+ if (nt->_selected_nodes->event(event)) return true;
+
+ switch (event->type)
+ {
+ case GDK_MOTION_NOTIFY:
+ // create outline
+ if (prefs->getBool("/tools/nodes/pathflash_enabled")) {
+ SPItem *over_item = sp_event_context_find_item (desktop, event_point(event->button),
+ FALSE, TRUE);
+ if (over_item == nt->flashed_item) break;
+ if (!prefs->getBool("/tools/nodes/pathflash_selected") && selection->includes(over_item)) break;
+ if (nt->flash_tempitem) {
+ desktop->remove_temporary_canvasitem(nt->flash_tempitem);
+ nt->flash_tempitem = NULL;
+ nt->flashed_item = NULL;
+ }
+ if (!SP_IS_PATH(over_item)) break; // for now, handle only paths
+
+ nt->flashed_item = over_item;
+ SPCurve *c = sp_path_get_curve_for_edit(SP_PATH(over_item));
+ c->transform(sp_item_i2d_affine(over_item));
+ 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);
+ nt->flash_tempitem = desktop->add_temporary_canvasitem(flash,
+ prefs->getInt("/tools/nodes/pathflash_timeout", 500));
+ c->unref();
+ }
+ return true;
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval(&event->key))
+ {
+ case GDK_Escape: // deselect everything
+ if (nt->_selected_nodes->empty()) {
+ selection->clear();
+ } else {
+ nt->_selected_nodes->clear();
+ }
+ ink_node_tool_update_tip(nt, event);
+ return TRUE;
+ case GDK_a:
+ if (held_control(event->key)) {
+ if (held_alt(event->key)) {
+ nt->_selected_nodes->selectAll();
+ } else {
+ // select all nodes in subpaths that have something selected
+ // if nothing is selected, select everything
+ nt->_multipath->selectSubpaths();
+ }
+ ink_node_tool_update_tip(nt, event);
+ return TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+ ink_node_tool_update_tip(nt, event);
+ break;
+ case GDK_KEY_RELEASE:
+ ink_node_tool_update_tip(nt, event);
+ break;
+ default: break;
+ }
+
+ SPEventContextClass *parent_class = (SPEventContextClass *) g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL));
+ if (parent_class->root_handler)
+ return parent_class->root_handler(event_context, event);
+ return FALSE;
+}
+
+void ink_node_tool_update_tip(InkNodeTool *nt, 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)) {
+ nt->_node_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"));
+ return;
+ }
+ }
+ unsigned sz = nt->_selected_nodes->size();
+ if (sz != 0) {
+ char *dyntip = g_strdup_printf(C_("Node tool tip",
+ "Selected <b>%d nodes</b>. Drag to select nodes, click to select a single object "
+ "or unselect all objects"), sz);
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, dyntip);
+ g_free(dyntip);
+ } else if (nt->_multipath->empty()) {
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE,
+ C_("Node tool tip", "Drag or click to select objects to edit"));
+ } else {
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE,
+ C_("Node tool tip", "Drag to select nodes, click to select an object "
+ "or clear the selection"));
+ }
+}
+
+gint ink_node_tool_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
+{
+ SPEventContextClass *parent_class =
+ (SPEventContextClass *) g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL));
+ if (parent_class->item_handler)
+ return parent_class->item_handler(event_context, item, event);
+ return FALSE;
+}
+
+void ink_node_tool_select_area(InkNodeTool *nt, Geom::Rect const &sel, GdkEventButton *event)
+{
+ using namespace Inkscape::UI;
+ if (nt->_multipath->empty()) {
+ // if multipath is empty, select rubberbanded items rather than nodes
+ Inkscape::Selection *selection = nt->desktop->selection;
+ GSList *items = sp_document_items_in_box(
+ sp_desktop_document(nt->desktop), nt->desktop->dkey, sel);
+ selection->setList(items);
+ g_slist_free(items);
+ } else {
+ nt->_selected_nodes->selectArea(sel);
+ }
+}
+void ink_node_tool_select_point(InkNodeTool *nt, 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 = nt->desktop->selection;
+
+ SPItem *item_clicked = sp_event_context_find_item (nt->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 (!(event->state & GDK_SHIFT_MASK)) {
+ selection->clear();
+ }
+ return;
+ }
+ if (held_shift(*event)) {
+ selection->toggle(item_clicked);
+ } else {
+ selection->set(item_clicked);
+ }
+ nt->desktop->updateNow();
+}
+
+void ink_node_tool_mouseover_changed(InkNodeTool *nt, Inkscape::UI::ControlPoint *p)
+{
+ using Inkscape::UI::CurveDragPoint;
+ CurveDragPoint *cdp = dynamic_cast<CurveDragPoint*>(p);
+ if (cdp && !nt->cursor_drag) {
+ nt->cursor_shape = cursor_node_d_xpm;
+ nt->hot_x = 1;
+ nt->hot_y = 1;
+ sp_event_context_update_cursor(nt);
+ nt->cursor_drag = true;
+ } else if (!cdp && nt->cursor_drag) {
+ nt->cursor_shape = cursor_node_xpm;
+ nt->hot_x = 1;
+ nt->hot_y = 1;
+ sp_event_context_update_cursor(nt);
+ nt->cursor_drag = false;
+ }
+}
+
+} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/node-tool.h b/src/ui/tool/node-tool.h
new file mode 100644
index 000000000..65b16ff72
--- /dev/null
+++ b/src/ui/tool/node-tool.h
@@ -0,0 +1,85 @@
+/** @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 <memory>
+#include <glib.h>
+#include <sigc++/sigc++.h>
+#include "event-context.h"
+#include "forward.h"
+#include "display/display-forward.h"
+#include "ui/tool/node-types.h"
+
+#define INK_TYPE_NODE_TOOL (ink_node_tool_get_type ())
+#define INK_NODE_TOOL(obj) (GTK_CHECK_CAST ((obj), INK_TYPE_NODE_TOOL, InkNodeTool))
+#define INK_NODE_TOOL_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), INK_TYPE_NODE_TOOL, InkNodeToolClass))
+#define INK_IS_NODE_TOOL(obj) (GTK_CHECK_TYPE ((obj), INK_TYPE_NODE_TOOL))
+#define INK_IS_NODE_TOOL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), INK_TYPE_NODE_TOOL))
+
+class InkNodeTool;
+class InkNodeToolClass;
+
+namespace Inkscape {
+namespace UI {
+class MultiPathManipulator;
+class ControlPointSelection;
+class Selector;
+struct PathSharedData;
+}
+}
+
+typedef std::auto_ptr<Inkscape::UI::MultiPathManipulator> MultiPathPtr;
+typedef std::auto_ptr<Inkscape::UI::ControlPointSelection> CSelPtr;
+typedef std::auto_ptr<Inkscape::UI::Selector> SelectorPtr;
+typedef std::auto_ptr<Inkscape::UI::PathSharedData> PathSharedDataPtr;
+
+struct InkNodeTool : public SPEventContext
+{
+ sigc::connection _selection_changed_connection;
+ sigc::connection _mouseover_changed_connection;
+ sigc::connection _selection_modified_connection;
+ Inkscape::MessageContext *_node_message_context;
+ SPItem *flashed_item;
+ Inkscape::Display::TemporaryItem *flash_tempitem;
+ CSelPtr _selected_nodes;
+ MultiPathPtr _multipath;
+ SelectorPtr _selector;
+ PathSharedDataPtr _path_data;
+ SPCanvasGroup *_transform_handle_group;
+
+ unsigned cursor_drag : 1;
+ unsigned show_outline : 1;
+ unsigned show_path_direction : 1;
+ unsigned show_transform_handles : 1;
+ unsigned single_node_transform_handles : 1;
+ unsigned edit_clipping_paths : 1;
+ unsigned edit_masks : 1;
+};
+
+struct InkNodeToolClass {
+ SPEventContextClass parent_class;
+};
+
+GType ink_node_tool_get_type (void);
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/node-types.h b/src/ui/tool/node-types.h
new file mode 100644
index 000000000..80eaf4fa7
--- /dev/null
+++ b/src/ui/tool/node-types.h
@@ -0,0 +1,48 @@
+/** @file
+ * Node types and other small enums.
+ * This file exists to reduce the number of includes pulled in by toolbox.cpp.
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_NODE_TYPES_H
+#define SEEN_UI_TOOL_NODE_TYPES_H
+
+namespace Inkscape {
+namespace UI {
+
+/** Types of nodes supported in the node tool. */
+enum NodeType {
+ NODE_CUSP, ///< Cusp node - no handle constraints
+ NODE_SMOOTH, ///< Smooth node - handles must be colinear
+ NODE_AUTO, ///< Auto node - handles adjusted automatically based on neighboring nodes
+ NODE_SYMMETRIC, ///< Symmetric node - handles must be colinear and of equal length
+ NODE_LAST_REAL_TYPE, ///< Last real type of node - used for ctrl+click on a node
+ NODE_PICK_BEST = 100 ///< Select type based on handle positions
+};
+
+/** Types of segments supported in the node tool. */
+enum SegmentType {
+ SEGMENT_STRAIGHT, ///< Straight linear segment
+ SEGMENT_CUBIC_BEZIER ///< Bezier curve with two control points
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/node.cpp b/src/ui/tool/node.cpp
new file mode 100644
index 000000000..303c0fb75
--- /dev/null
+++ b/src/ui/tool/node.cpp
@@ -0,0 +1,1204 @@
+/** @file
+ * Editable node - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <iostream>
+#include <stdexcept>
+#include <boost/utility.hpp>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <2geom/bezier-utils.h>
+#include <2geom/transforms.h>
+
+#include "display/sp-ctrlline.h"
+#include "display/sp-canvas.h"
+#include "display/sp-canvas-util.h"
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "preferences.h"
+#include "snap.h"
+#include "snap-preferences.h"
+#include "sp-metrics.h"
+#include "sp-namedview.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tool/node.h"
+#include "ui/tool/path-manipulator.h"
+
+namespace Inkscape {
+namespace UI {
+
+static SelectableControlPoint::ColorSet node_colors = {
+ {
+ {0xbfbfbf00, 0x000000ff}, // normal fill, stroke
+ {0xff000000, 0x000000ff}, // mouseover fill, stroke
+ {0xff000000, 0x000000ff} // clicked fill, stroke
+ },
+ {0x0000ffff, 0x000000ff}, // normal fill, stroke when selected
+ {0xff000000, 0x000000ff}, // mouseover fill, stroke when selected
+ {0xff000000, 0x000000ff} // clicked fill, stroke when selected
+};
+
+static ControlPoint::ColorSet handle_colors = {
+ {0xffffffff, 0x000000ff}, // normal fill, stroke
+ {0xff000000, 0x000000ff}, // mouseover fill, stroke
+ {0xff000000, 0x000000ff} // clicked fill, stroke
+};
+
+std::ostream &operator<<(std::ostream &out, NodeType type)
+{
+ switch(type) {
+ case NODE_CUSP: out << 'c'; break;
+ case NODE_SMOOTH: out << 's'; break;
+ case NODE_AUTO: out << 'a'; break;
+ case NODE_SYMMETRIC: out << 'z'; break;
+ default: out << 'b'; break;
+ }
+ return out;
+}
+
+/** Computes an unit vector of the direction from first to second control point */
+static Geom::Point direction(Geom::Point const &first, Geom::Point const &second) {
+ return Geom::unit_vector(second - first);
+}
+
+/**
+ * @class Handle
+ * @brief Control point of a cubic Bezier curve in a path.
+ *
+ * Handle keeps the node type invariant only for the opposite handle of the same node.
+ * Keeping the invariant on node moves is left to the %Node class.
+ */
+
+double Handle::_saved_length = 0.0;
+bool Handle::_drag_out = false;
+
+Handle::Handle(NodeSharedData const &data, Geom::Point const &initial_pos, Node *parent)
+ : ControlPoint(data.desktop, initial_pos, Gtk::ANCHOR_CENTER, SP_CTRL_SHAPE_CIRCLE, 7.0,
+ &handle_colors, data.handle_group)
+ , _parent(parent)
+ , _degenerate(true)
+{
+ _cset = &handle_colors;
+ _handle_line = sp_canvas_item_new(data.handle_line_group, SP_TYPE_CTRLLINE, NULL);
+ setVisible(false);
+ signal_grabbed.connect(
+ sigc::bind_return(
+ sigc::hide(
+ sigc::mem_fun(*this, &Handle::_grabbedHandler)),
+ false));
+ signal_dragged.connect(
+ sigc::hide<0>(
+ sigc::mem_fun(*this, &Handle::_draggedHandler)));
+ signal_ungrabbed.connect(
+ sigc::hide(sigc::mem_fun(*this, &Handle::_ungrabbedHandler)));
+}
+Handle::~Handle()
+{
+ sp_canvas_item_hide(_handle_line);
+ gtk_object_destroy(GTK_OBJECT(_handle_line));
+}
+
+void Handle::setVisible(bool v)
+{
+ ControlPoint::setVisible(v);
+ if (v) sp_canvas_item_show(_handle_line);
+ else sp_canvas_item_hide(_handle_line);
+}
+
+void Handle::move(Geom::Point const &new_pos)
+{
+ Handle *other, *towards, *towards_second;
+ Node *node_towards; // node in direction of this handle
+ Node *node_away; // node in the opposite direction
+ if (this == &_parent->_front) {
+ other = &_parent->_back;
+ node_towards = _parent->_next();
+ node_away = _parent->_prev();
+ towards = node_towards ? &node_towards->_back : 0;
+ towards_second = node_towards ? &node_towards->_front : 0;
+ } else {
+ other = &_parent->_front;
+ node_towards = _parent->_prev();
+ node_away = _parent->_next();
+ towards = node_towards ? &node_towards->_front : 0;
+ towards_second = node_towards ? &node_towards->_back : 0;
+ }
+
+ if (Geom::are_near(new_pos, _parent->position())) {
+ // The handle becomes degenerate. If the segment between it and the node
+ // in its direction becomes linear and there are smooth nodes
+ // at its ends, make their handles colinear with the segment
+ if (towards && towards->isDegenerate()) {
+ if (node_towards->type() == NODE_SMOOTH) {
+ towards_second->setDirection(*_parent, *node_towards);
+ }
+ if (_parent->type() == NODE_SMOOTH) {
+ other->setDirection(*node_towards, *_parent);
+ }
+ }
+ setPosition(new_pos);
+ return;
+ }
+
+ if (_parent->type() == NODE_SMOOTH && Node::_is_line_segment(_parent, node_away)) {
+ // restrict movement to the line joining the nodes
+ Geom::Point direction = _parent->position() - node_away->position();
+ Geom::Point delta = new_pos - _parent->position();
+ // project the relative position on the direction line
+ Geom::Point new_delta = (Geom::dot(delta, direction)
+ / Geom::L2sq(direction)) * direction;
+ setRelativePos(new_delta);
+ return;
+ }
+
+ switch (_parent->type()) {
+ case NODE_AUTO:
+ _parent->setType(NODE_SMOOTH, false);
+ // fall through - auto nodes degrade into smooth nodes
+ case NODE_SMOOTH: {
+ /* for smooth nodes, we need to rotate the other handle so that it's colinear
+ * with the dragged one while conserving length. */
+ other->setDirection(new_pos, *_parent);
+ } break;
+ case NODE_SYMMETRIC:
+ // for symmetric nodes, place the other handle on the opposite side
+ other->setRelativePos(-(new_pos - _parent->position()));
+ break;
+ default: break;
+ }
+
+ setPosition(new_pos);
+}
+
+void Handle::setPosition(Geom::Point const &p)
+{
+ ControlPoint::setPosition(p);
+ sp_ctrlline_set_coords(SP_CTRLLINE(_handle_line), _parent->position(), position());
+
+ // update degeneration info and visibility
+ if (Geom::are_near(position(), _parent->position()))
+ _degenerate = true;
+ else _degenerate = false;
+ if (_parent->_handles_shown && _parent->visible() && !_degenerate) {
+ setVisible(true);
+ } else {
+ setVisible(false);
+ }
+ // If both handles become degenerate, convert to parent cusp node
+ if (_parent->isDegenerate()) {
+ _parent->setType(NODE_CUSP, false);
+ }
+}
+
+void Handle::setLength(double len)
+{
+ if (isDegenerate()) return;
+ Geom::Point dir = Geom::unit_vector(relativePos());
+ setRelativePos(dir * len);
+}
+
+void Handle::retract()
+{
+ setPosition(_parent->position());
+}
+
+void Handle::setDirection(Geom::Point const &from, Geom::Point const &to)
+{
+ setDirection(to - from);
+}
+
+void Handle::setDirection(Geom::Point const &dir)
+{
+ Geom::Point unitdir = Geom::unit_vector(dir);
+ setRelativePos(unitdir * length());
+}
+
+char const *Handle::handle_type_to_localized_string(NodeType type)
+{
+ switch(type) {
+ case NODE_CUSP: return _("Cusp node handle");
+ case NODE_SMOOTH: return _("Smooth node handle");
+ case NODE_SYMMETRIC: return _("Symmetric node handle");
+ case NODE_AUTO: return _("Auto-smooth node handle");
+ default: return "";
+ }
+}
+
+void Handle::_grabbedHandler()
+{
+ _saved_length = _drag_out ? 0 : length();
+}
+
+void Handle::_draggedHandler(Geom::Point &new_pos, GdkEventMotion *event)
+{
+ Geom::Point parent_pos = _parent->position();
+ // with Alt, preserve length
+ if (held_alt(*event)) {
+ new_pos = parent_pos + Geom::unit_vector(new_pos - parent_pos) * _saved_length;
+ }
+ // with Ctrl, constrain to M_PI/rotationsnapsperpi increments.
+ if (held_control(*event)) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int snaps = 2 * prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
+ Geom::Point origin = _last_drag_origin();
+ Geom::Point rel_origin = origin - parent_pos;
+ new_pos = parent_pos + Geom::constrain_angle(Geom::Point(0,0), new_pos - parent_pos, snaps,
+ _drag_out ? Geom::Point(1,0) : Geom::unit_vector(rel_origin));
+ }
+ signal_update.emit();
+}
+
+void Handle::_ungrabbedHandler()
+{
+ // hide the handle if it's less than dragtolerance away from the node
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int drag_tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ Geom::Point dist = _desktop->d2w(_parent->position()) - _desktop->d2w(position());
+ if (dist.length() <= drag_tolerance) {
+ move(_parent->position());
+ }
+ _drag_out = false;
+}
+
+static double snap_increment_degrees() {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
+ return 180.0 / snaps;
+}
+
+Glib::ustring Handle::_getTip(unsigned state)
+{
+ if (state_held_alt(state)) {
+ if (state_held_control(state)) {
+ return format_tip(C_("Path handle tip",
+ "<b>Ctrl+Alt</b>: preserve length and snap rotation angle to %f° increments"),
+ snap_increment_degrees());
+ } else {
+ return C_("Path handle tip",
+ "<b>Alt:</b> preserve handle length while dragging");
+ }
+ } else {
+ if (state_held_control(state)) {
+ return format_tip(C_("Path handle tip",
+ "<b>Ctrl:</b> snap rotation angle to %f° increments, click to retract"),
+ snap_increment_degrees());
+ }
+ }
+ switch (_parent->type()) {
+ case NODE_AUTO:
+ return C_("Path handle tip",
+ "<b>Auto node handle:</b> drag to convert to smooth node");
+ default:
+ return format_tip(C_("Path handle tip", "<b>%s:</b> drag to shape the curve"),
+ handle_type_to_localized_string(_parent->type()));
+ }
+}
+
+Glib::ustring Handle::_getDragTip(GdkEventMotion *event)
+{
+ Geom::Point dist = position() - _last_drag_origin();
+ // report angle in mathematical convention
+ double angle = Geom::angle_between(Geom::Point(-1,0), position() - _parent->position());
+ angle += M_PI; // angle is (-M_PI...M_PI] - offset by +pi and scale to 0...360
+ angle *= 360.0 / (2 * M_PI);
+ GString *x = SP_PX_TO_METRIC_STRING(dist[Geom::X], _desktop->namedview->getDefaultMetric());
+ GString *y = SP_PX_TO_METRIC_STRING(dist[Geom::Y], _desktop->namedview->getDefaultMetric());
+ GString *len = SP_PX_TO_METRIC_STRING(length(), _desktop->namedview->getDefaultMetric());
+ Glib::ustring ret = format_tip(C_("Path handle tip",
+ "Move by %s, %s; angle %.2f°, length %s"), x->str, y->str, angle, len->str);
+ g_string_free(x, TRUE);
+ g_string_free(y, TRUE);
+ g_string_free(len, TRUE);
+ return ret;
+}
+
+/**
+ * @class Node
+ * @brief Curve endpoint in an editable path.
+ *
+ * The method move() keeps node type invariants during translations.
+ */
+
+Node::Node(NodeSharedData const &data, Geom::Point const &initial_pos)
+ : SelectableControlPoint(data.desktop, initial_pos, Gtk::ANCHOR_CENTER,
+ SP_CTRL_SHAPE_DIAMOND, 9.0, *data.selection, &node_colors, data.node_group)
+ , _front(data, initial_pos, this)
+ , _back(data, initial_pos, this)
+ , _type(NODE_CUSP)
+ , _handles_shown(false)
+{
+ // NOTE we do not set type here, because the handles are still degenerate
+ // connect to own grabbed signal - dragging out handles
+ signal_grabbed.connect(
+ sigc::mem_fun(*this, &Node::_grabbedHandler));
+ signal_dragged.connect( sigc::hide<0>(
+ sigc::mem_fun(*this, &Node::_draggedHandler)));
+}
+
+// NOTE: not using iterators won't make this much quicker because iterators can be 100% inlined.
+Node *Node::_next()
+{
+ NodeList::iterator n = NodeList::get_iterator(this).next();
+ if (n) return n.ptr();
+ return NULL;
+}
+Node *Node::_prev()
+{
+ NodeList::iterator p = NodeList::get_iterator(this).prev();
+ if (p) return p.ptr();
+ return NULL;
+}
+
+void Node::move(Geom::Point const &new_pos)
+{
+ // move handles when the node moves.
+ Geom::Point old_pos = position();
+ Geom::Point delta = new_pos - position();
+ setPosition(new_pos);
+ _front.setPosition(_front.position() + delta);
+ _back.setPosition(_back.position() + delta);
+
+ // if the node has a smooth handle after a line segment, it should be kept colinear
+ // with the segment
+ _fixNeighbors(old_pos, new_pos);
+}
+
+void Node::transform(Geom::Matrix const &m)
+{
+ Geom::Point old_pos = position();
+ setPosition(position() * m);
+ _front.setPosition(_front.position() * m);
+ _back.setPosition(_back.position() * m);
+
+ /* Affine transforms keep handle invariants for smooth and symmetric nodes,
+ * but smooth nodes at ends of linear segments and auto nodes need special treatment */
+ _fixNeighbors(old_pos, position());
+}
+
+Geom::Rect Node::bounds()
+{
+ Geom::Rect b(position(), position());
+ b.expandTo(_front.position());
+ b.expandTo(_back.position());
+ return b;
+}
+
+void Node::_fixNeighbors(Geom::Point const &old_pos, Geom::Point const &new_pos)
+{
+ /* This method restores handle invariants for neighboring nodes,
+ * and invariants that are based on positions of those nodes for this one. */
+
+ /* Fix auto handles */
+ if (_type == NODE_AUTO) _updateAutoHandles();
+ if (old_pos != new_pos) {
+ if (_next() && _next()->_type == NODE_AUTO) _next()->_updateAutoHandles();
+ if (_prev() && _prev()->_type == NODE_AUTO) _prev()->_updateAutoHandles();
+ }
+
+ /* Fix smooth handles at the ends of linear segments.
+ * Rotate the appropriate handle to be colinear with the segment.
+ * If there is a smooth node at the other end of the segment, rotate it too. */
+ Handle *handle, *other_handle;
+ Node *other;
+ if (_is_line_segment(this, _next())) {
+ handle = &_back;
+ other = _next();
+ other_handle = &_next()->_front;
+ } else if (_is_line_segment(_prev(), this)) {
+ handle = &_front;
+ other = _prev();
+ other_handle = &_prev()->_back;
+ } else return;
+
+ if (_type == NODE_SMOOTH && !handle->isDegenerate()) {
+ handle->setDirection(other->position(), new_pos);
+ }
+ // also update the handle on the other end of the segment
+ if (other->_type == NODE_SMOOTH && !other_handle->isDegenerate()) {
+ other_handle->setDirection(new_pos, other->position());
+ }
+}
+
+void Node::_updateAutoHandles()
+{
+ // Recompute the position of automatic handles.
+ // For endnodes, retract both handles. (It's only possible to create an end auto node
+ // through the XML editor.)
+ if (isEndNode()) {
+ _front.retract();
+ _back.retract();
+ return;
+ }
+
+ // Auto nodes automaticaly adjust their handles to give an appearance of smoothness,
+ // no matter what their surroundings are.
+ Geom::Point vec_next = _next()->position() - position();
+ Geom::Point vec_prev = _prev()->position() - position();
+ double len_next = vec_next.length(), len_prev = vec_prev.length();
+ if (len_next > 0 && len_prev > 0) {
+ // "dir" is an unit vector perpendicular to the bisector of the angle created
+ // by the previous node, this auto node and the next node.
+ Geom::Point dir = Geom::unit_vector((len_prev / len_next) * vec_next - vec_prev);
+ // Handle lengths are equal to 1/3 of the distance from the adjacent node.
+ _back.setRelativePos(-dir * (len_prev / 3));
+ _front.setRelativePos(dir * (len_next / 3));
+ } else {
+ // If any of the adjacent nodes coincides, retract both handles.
+ _front.retract();
+ _back.retract();
+ }
+}
+
+void Node::showHandles(bool v)
+{
+ _handles_shown = v;
+ if (!_front.isDegenerate()) _front.setVisible(v);
+ if (!_back.isDegenerate()) _back.setVisible(v);
+}
+
+/** Sets the node type and optionally restores the invariants associated with the given type.
+ * @param type The type to set
+ * @param update_handles Whether to restore invariants associated with the given type.
+ * Passing false is useful e.g. wen initially creating the path,
+ * and when making cusp nodes during some node algorithms.
+ * Pass true when used in response to an UI node type button.
+ */
+void Node::setType(NodeType type, bool update_handles)
+{
+ if (type == NODE_PICK_BEST) {
+ pickBestType();
+ updateState(); // The size of the control might have changed
+ return;
+ }
+
+ // if update_handles is true, adjust handle positions to match the node type
+ // handle degenerate handles appropriately
+ if (update_handles) {
+ switch (type) {
+ case NODE_CUSP:
+ // if the existing type is also NODE_CUSP, retract handles
+ if (_type == NODE_CUSP) {
+ _front.retract();
+ _back.retract();
+ }
+ break;
+ case NODE_AUTO:
+ // auto handles make no sense for endnodes
+ if (isEndNode()) return;
+ _updateAutoHandles();
+ break;
+ case NODE_SMOOTH: {
+ // rotate handles to be colinear
+ // for degenerate nodes set positions like auto handles
+ bool prev_line = _is_line_segment(_prev(), this);
+ bool next_line = _is_line_segment(this, _next());
+ if (isDegenerate()) {
+ _updateAutoHandles();
+ } else if (_front.isDegenerate()) {
+ // if the front handle is degenerate and this...next is a line segment,
+ // make back colinear; otherwise pull out the other handle
+ // to 1/3 of distance to prev
+ if (next_line) {
+ _back.setDirection(*_next(), *this);
+ } else if (_prev()) {
+ Geom::Point dir = direction(_back, *this);
+ _front.setRelativePos((_prev()->position() - position()).length() / 3 * dir);
+ }
+ } else if (_back.isDegenerate()) {
+ if (prev_line) {
+ _front.setDirection(*_prev(), *this);
+ } else if (_next()) {
+ Geom::Point dir = direction(_front, *this);
+ _back.setRelativePos((_next()->position() - position()).length() / 3 * dir);
+ }
+ } else {
+ // both handles are extended. make colinear while keeping length
+ // first make back colinear with the vector front ---> back,
+ // then make front colinear with back ---> node
+ // (not back ---> front because back's position was changed in the first call)
+ _back.setDirection(_front, _back);
+ _front.setDirection(_back, *this);
+ }
+ } break;
+ case NODE_SYMMETRIC:
+ if (isEndNode()) return; // symmetric handles make no sense for endnodes
+ if (isDegenerate()) {
+ // similar to auto handles but set the same length for both
+ Geom::Point vec_next = _next()->position() - position();
+ Geom::Point vec_prev = _prev()->position() - position();
+ double len_next = vec_next.length(), len_prev = vec_prev.length();
+ double len = (len_next + len_prev) / 6; // take 1/3 of average
+ if (len == 0) return;
+
+ Geom::Point dir = Geom::unit_vector((len_prev / len_next) * vec_next - vec_prev);
+ _back.setRelativePos(-dir * len);
+ _front.setRelativePos(dir * len);
+ } else {
+ // Both handles are extended. Compute average length, use direction from
+ // back handle to front handle. This also works correctly for degenerates
+ double len = (_front.length() + _back.length()) / 2;
+ Geom::Point dir = direction(_back, _front);
+ _front.setRelativePos(dir * len);
+ _back.setRelativePos(-dir * len);
+ }
+ break;
+ default: break;
+ }
+ }
+ _type = type;
+ _setShape(_node_type_to_shape(type));
+ updateState();
+}
+
+/** Pick the best type for this node, based on the position of its handles.
+ * This is what assigns types to nodes created using the pen tool. */
+void Node::pickBestType()
+{
+ _type = NODE_CUSP;
+ bool front_degen = _front.isDegenerate();
+ bool back_degen = _back.isDegenerate();
+ bool both_degen = front_degen && back_degen;
+ bool neither_degen = !front_degen && !back_degen;
+ do {
+ // if both handles are degenerate, do nothing
+ if (both_degen) break;
+ // if neither are degenerate, check their respective positions
+ if (neither_degen) {
+ Geom::Point front_delta = _front.position() - position();
+ Geom::Point back_delta = _back.position() - position();
+ // for now do not automatically make nodes symmetric, it can be annoying
+ /*if (Geom::are_near(front_delta, -back_delta)) {
+ _type = NODE_SYMMETRIC;
+ break;
+ }*/
+ if (Geom::are_near(Geom::unit_vector(front_delta),
+ Geom::unit_vector(-back_delta)))
+ {
+ _type = NODE_SMOOTH;
+ break;
+ }
+ }
+ // check whether the handle aligns with the previous line segment.
+ // we know that if front is degenerate, back isn't, because
+ // both_degen was false
+ if (front_degen && _next() && _next()->_back.isDegenerate()) {
+ Geom::Point segment_delta = Geom::unit_vector(_next()->position() - position());
+ Geom::Point handle_delta = Geom::unit_vector(_back.position() - position());
+ if (Geom::are_near(segment_delta, -handle_delta)) {
+ _type = NODE_SMOOTH;
+ break;
+ }
+ } else if (back_degen && _prev() && _prev()->_front.isDegenerate()) {
+ Geom::Point segment_delta = Geom::unit_vector(_prev()->position() - position());
+ Geom::Point handle_delta = Geom::unit_vector(_front.position() - position());
+ if (Geom::are_near(segment_delta, -handle_delta)) {
+ _type = NODE_SMOOTH;
+ break;
+ }
+ }
+ } while (false);
+ _setShape(_node_type_to_shape(_type));
+ updateState();
+}
+
+bool Node::isEndNode()
+{
+ return !_prev() || !_next();
+}
+
+/** Move the node to the bottom of its canvas group. Useful for node break, to ensure that
+ * the selected nodes are above the unselected ones. */
+void Node::sink()
+{
+ sp_canvas_item_move_to_z(_canvas_item, 0);
+}
+
+NodeType Node::parse_nodetype(char x)
+{
+ switch (x) {
+ case 'a': return NODE_AUTO;
+ case 'c': return NODE_CUSP;
+ case 's': return NODE_SMOOTH;
+ case 'z': return NODE_SYMMETRIC;
+ default: return NODE_PICK_BEST;
+ }
+}
+
+/** Customized event handler to catch scroll events needed for selection grow/shrink. */
+bool Node::_eventHandler(GdkEvent *event)
+{
+ static NodeList::iterator origin;
+ static int dir;
+
+ switch (event->type)
+ {
+ case GDK_SCROLL:
+ if (event->scroll.direction == GDK_SCROLL_UP) {
+ dir = 1;
+ } else if (event->scroll.direction == GDK_SCROLL_DOWN) {
+ dir = -1;
+ } else break;
+ if (held_control(event->scroll)) {
+ _selection.spatialGrow(this, dir);
+ } else {
+ _linearGrow(dir);
+ }
+ return true;
+ default:
+ break;
+ }
+ return ControlPoint::_eventHandler(event);
+}
+
+// TODO Move this to 2Geom!
+static double bezier_length (Geom::Point a0, Geom::Point a1, Geom::Point a2, Geom::Point a3)
+{
+ double lower = Geom::distance(a0, a3);
+ double upper = Geom::distance(a0, a1) + Geom::distance(a1, a2) + Geom::distance(a2, a3);
+
+ if (upper - lower < Geom::EPSILON) return (lower + upper)/2;
+
+ Geom::Point // Casteljau subdivision
+ b0 = a0,
+ c0 = a3,
+ b1 = 0.5*(a0 + a1),
+ t0 = 0.5*(a1 + a2),
+ c1 = 0.5*(a2 + a3),
+ b2 = 0.5*(b1 + t0),
+ c2 = 0.5*(t0 + c1),
+ b3 = 0.5*(b2 + c2); // == c3
+ return bezier_length(b0, b1, b2, b3) + bezier_length(b3, c2, c1, c0);
+}
+
+/** Select or deselect a node in this node's subpath based on its path distance from this node.
+ * @param dir If negative, shrink selection by one node; if positive, grow by one node */
+void Node::_linearGrow(int dir)
+{
+ // Interestingly, we do not need any help from PathManipulator when doing linear grow.
+ // First handle the trivial case of growing over an unselected node.
+ if (!selected() && dir > 0) {
+ _selection.insert(this);
+ return;
+ }
+
+ NodeList::iterator this_iter = NodeList::get_iterator(this);
+ NodeList::iterator fwd = this_iter, rev = this_iter;
+ double distance_back = 0, distance_front = 0;
+
+ // Linear grow is simple. We find the first unselected nodes in each direction
+ // and compare the linear distances to them.
+ if (dir > 0) {
+ if (!selected()) {
+ _selection.insert(this);
+ return;
+ }
+
+ // find first unselected nodes on both sides
+ while (fwd && fwd->selected()) {
+ NodeList::iterator n = fwd.next();
+ distance_front += bezier_length(*fwd, fwd->_front, n->_back, *n);
+ fwd = n;
+ if (fwd == this_iter)
+ // there is no unselected node in this cyclic subpath
+ return;
+ }
+ // do the same for the second direction. Do not check for equality with
+ // this node, because there is at least one unselected node in the subpath,
+ // so we are guaranteed to stop.
+ while (rev && rev->selected()) {
+ NodeList::iterator p = rev.prev();
+ distance_back += bezier_length(*rev, rev->_back, p->_front, *p);
+ rev = p;
+ }
+
+ NodeList::iterator t; // node to select
+ if (fwd && rev) {
+ if (distance_front <= distance_back) t = fwd;
+ else t = rev;
+ } else {
+ if (fwd) t = fwd;
+ if (rev) t = rev;
+ }
+ if (t) _selection.insert(t.ptr());
+
+ // Linear shrink is more complicated. We need to find the farthest selected node.
+ // This means we have to check the entire subpath. We go in the direction in which
+ // the distance we traveled is lower. We do this until we run out of nodes (ends of path)
+ // or the two iterators meet. On the way, we store the last selected node and its distance
+ // in each direction (if any). At the end, we choose the one that is farther and deselect it.
+ } else {
+ // both iterators that store last selected nodes are initially empty
+ NodeList::iterator last_fwd, last_rev;
+ double last_distance_back = 0, last_distance_front = 0;
+
+ while (rev || fwd) {
+ if (fwd && (!rev || distance_front <= distance_back)) {
+ if (fwd->selected()) {
+ last_fwd = fwd;
+ last_distance_front = distance_front;
+ }
+ NodeList::iterator n = fwd.next();
+ if (n) distance_front += bezier_length(*fwd, fwd->_front, n->_back, *n);
+ fwd = n;
+ } else if (rev && (!fwd || distance_front > distance_back)) {
+ if (rev->selected()) {
+ last_rev = rev;
+ last_distance_back = distance_back;
+ }
+ NodeList::iterator p = rev.prev();
+ if (p) distance_back += bezier_length(*rev, rev->_back, p->_front, *p);
+ rev = p;
+ }
+ // Check whether we walked the entire cyclic subpath.
+ // This is initially true because both iterators start from this node,
+ // so this check cannot go in the while condition.
+ // When this happens, we need to check the last node, pointed to by the iterators.
+ if (fwd && fwd == rev) {
+ if (!fwd->selected()) break;
+ NodeList::iterator fwdp = fwd.prev(), revn = rev.next();
+ double df = distance_front + bezier_length(*fwdp, fwdp->_front, fwd->_back, *fwd);
+ double db = distance_back + bezier_length(*revn, revn->_back, rev->_front, *rev);
+ if (df > db) {
+ last_fwd = fwd;
+ last_distance_front = df;
+ } else {
+ last_rev = rev;
+ last_distance_back = db;
+ }
+ break;
+ }
+ }
+
+ NodeList::iterator t;
+ if (last_fwd && last_rev) {
+ if (last_distance_front >= last_distance_back) t = last_fwd;
+ else t = last_rev;
+ } else {
+ if (last_fwd) t = last_fwd;
+ if (last_rev) t = last_rev;
+ }
+ if (t) _selection.erase(t.ptr());
+ }
+}
+
+void Node::_setState(State state)
+{
+ // change node size to match type and selection state
+ switch (_type) {
+ case NODE_AUTO:
+ case NODE_CUSP:
+ if (selected()) _setSize(11);
+ else _setSize(9);
+ break;
+ default:
+ if(selected()) _setSize(9);
+ else _setSize(7);
+ break;
+ }
+ SelectableControlPoint::_setState(state);
+}
+
+bool Node::_grabbedHandler(GdkEventMotion *event)
+{
+ // Dragging out handles with Shift + drag on a node.
+ if (!held_shift(*event)) return false;
+
+ Handle *h;
+ Geom::Point evp = event_point(*event);
+ Geom::Point rel_evp = evp - _last_click_event_point();
+
+ // This should work even if dragtolerance is zero and evp coincides with node position.
+ double angle_next = HUGE_VAL;
+ double angle_prev = HUGE_VAL;
+ bool has_degenerate = false;
+ // determine which handle to drag out based on degeneration and the direction of drag
+ if (_front.isDegenerate() && _next()) {
+ Geom::Point next_relpos = _desktop->d2w(_next()->position())
+ - _desktop->d2w(position());
+ angle_next = fabs(Geom::angle_between(rel_evp, next_relpos));
+ has_degenerate = true;
+ }
+ if (_back.isDegenerate() && _prev()) {
+ Geom::Point prev_relpos = _desktop->d2w(_prev()->position())
+ - _desktop->d2w(position());
+ angle_prev = fabs(Geom::angle_between(rel_evp, prev_relpos));
+ has_degenerate = true;
+ }
+ if (!has_degenerate) return false;
+ h = angle_next < angle_prev ? &_front : &_back;
+
+ h->setPosition(_desktop->w2d(evp));
+ h->setVisible(true);
+ h->transferGrab(this, event);
+ Handle::_drag_out = true;
+ return true;
+}
+
+void Node::_draggedHandler(Geom::Point &new_pos, GdkEventMotion *event)
+{
+ // For a note on how snapping is implemented in Inkscape, see snap.h.
+ SnapManager &sm = _desktop->namedview->snap_manager;
+ Inkscape::SnapPreferences::PointType t = Inkscape::SnapPreferences::SNAPPOINT_NODE;
+ bool snap = sm.someSnapperMightSnap();
+ std::vector<Inkscape::SnapCandidatePoint> unselected;
+ if (snap) {
+ /* setup
+ * TODO We are doing this every time a snap happens. It should once be done only once
+ * per drag - maybe in the grabbed handler?
+ * TODO Unselected nodes vector must be valid during the snap run, because it is not
+ * copied. Fix this in snap.h and snap.cpp, then the above.
+ * TODO Snapping to unselected segments of selected paths doesn't work yet. */
+
+ // Build the list of unselected nodes.
+ typedef ControlPointSelection::Set Set;
+ Set nodes = _selection.allPoints();
+ for (Set::iterator i = nodes.begin(); i != nodes.end(); ++i) {
+ if (!(*i)->selected()) {
+ Node *n = static_cast<Node*>(*i);
+ Inkscape::SnapCandidatePoint p(n->position(), n->_snapSourceType(), n->_snapTargetType());
+ unselected.push_back(p);
+ }
+ }
+ sm.setupIgnoreSelection(_desktop, true, &unselected);
+ }
+
+ if (held_control(*event)) {
+ Geom::Point origin = _last_drag_origin();
+ if (held_alt(*event)) {
+ // with Ctrl+Alt, constrain to handle lines
+ // project the new position onto a handle line that is closer
+ Inkscape::Snapper::ConstraintLine line_front(origin, _front.relativePos());
+ Inkscape::Snapper::ConstraintLine line_back(origin, _back.relativePos());
+
+ // TODO: combine these two branches by modifying snap.h / snap.cpp
+ if (snap) {
+ Inkscape::SnappedPoint fp, bp;
+ fp = sm.constrainedSnap(t, Inkscape::SnapCandidatePoint(position(), _snapSourceType()), line_front);
+ bp = sm.constrainedSnap(t, Inkscape::SnapCandidatePoint(position(), _snapSourceType()), line_back);
+
+ if (fp.isOtherSnapBetter(bp, false)) {
+ bp.getPoint(new_pos);
+ } else {
+ fp.getPoint(new_pos);
+ }
+ } else {
+ Geom::Point p_front = line_front.projection(new_pos);
+ Geom::Point p_back = line_back.projection(new_pos);
+ if (Geom::distance(new_pos, p_front) < Geom::distance(new_pos, p_back)) {
+ new_pos = p_front;
+ } else {
+ new_pos = p_back;
+ }
+ }
+ } else {
+ // with Ctrl, constrain to axes
+ // TODO combine the two branches
+ if (snap) {
+ Inkscape::SnappedPoint fp, bp;
+ Inkscape::Snapper::ConstraintLine line_x(origin, Geom::Point(1, 0));
+ Inkscape::Snapper::ConstraintLine line_y(origin, Geom::Point(0, 1));
+ fp = sm.constrainedSnap(t, Inkscape::SnapCandidatePoint(position(), _snapSourceType()), line_x);
+ bp = sm.constrainedSnap(t, Inkscape::SnapCandidatePoint(position(), _snapSourceType()), line_y);
+
+ if (fp.isOtherSnapBetter(bp, false)) {
+ fp = bp;
+ }
+ fp.getPoint(new_pos);
+ } else {
+ Geom::Point origin = _last_drag_origin();
+ Geom::Point delta = new_pos - origin;
+ Geom::Dim2 d = (fabs(delta[Geom::X]) < fabs(delta[Geom::Y])) ? Geom::X : Geom::Y;
+ new_pos[d] = origin[d];
+ }
+ }
+ } else if (snap) {
+ sm.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, new_pos, _snapSourceType());
+ }
+}
+
+Inkscape::SnapSourceType Node::_snapSourceType()
+{
+ if (_type == NODE_SMOOTH || _type == NODE_AUTO)
+ return SNAPSOURCE_NODE_SMOOTH;
+ return SNAPSOURCE_NODE_CUSP;
+}
+Inkscape::SnapTargetType Node::_snapTargetType()
+{
+ if (_type == NODE_SMOOTH || _type == NODE_AUTO)
+ return SNAPTARGET_NODE_SMOOTH;
+ return SNAPTARGET_NODE_CUSP;
+}
+
+Glib::ustring Node::_getTip(unsigned state)
+{
+ if (state_held_shift(state)) {
+ if ((_next() && _front.isDegenerate()) || (_prev() && _back.isDegenerate())) {
+ if (state_held_control(state)) {
+ return format_tip(C_("Path node tip",
+ "<b>Shift+Ctrl:</b> drag out a handle and snap its angle "
+ "to %f° increments"), snap_increment_degrees());
+ }
+ return C_("Path node tip",
+ "<b>Shift:</b> drag out a handle, click to toggle selection");
+ }
+ return C_("Path node tip", "<b>Shift:</b> click to toggle selection");
+ }
+
+ if (state_held_control(state)) {
+ if (state_held_alt(state)) {
+ return C_("Path node tip", "<b>Ctrl+Alt:</b> move along handle lines");
+ }
+ return C_("Path node tip",
+ "<b>Ctrl:</b> move along axes, click to change node type");
+ }
+
+ // assemble tip from node name
+ char const *nodetype = node_type_to_localized_string(_type);
+ return format_tip(C_("Path node tip",
+ "<b>%s:</b> drag to shape the path, click to select this node"), nodetype);
+}
+
+Glib::ustring Node::_getDragTip(GdkEventMotion *event)
+{
+ Geom::Point dist = position() - _last_drag_origin();
+ GString *x = SP_PX_TO_METRIC_STRING(dist[Geom::X], _desktop->namedview->getDefaultMetric());
+ GString *y = SP_PX_TO_METRIC_STRING(dist[Geom::Y], _desktop->namedview->getDefaultMetric());
+ Glib::ustring ret = format_tip(C_("Path node tip", "Move by %s, %s"),
+ x->str, y->str);
+ g_string_free(x, TRUE);
+ g_string_free(y, TRUE);
+ return ret;
+}
+
+char const *Node::node_type_to_localized_string(NodeType type)
+{
+ switch (type) {
+ case NODE_CUSP: return _("Cusp node");
+ case NODE_SMOOTH: return _("Smooth node");
+ case NODE_SYMMETRIC: return _("Symmetric node");
+ case NODE_AUTO: return _("Auto-smooth node");
+ default: return "";
+ }
+}
+
+/** Determine whether two nodes are joined by a linear segment. */
+bool Node::_is_line_segment(Node *first, Node *second)
+{
+ if (!first || !second) return false;
+ if (first->_next() == second)
+ return first->_front.isDegenerate() && second->_back.isDegenerate();
+ if (second->_next() == first)
+ return second->_front.isDegenerate() && first->_back.isDegenerate();
+ return false;
+}
+
+SPCtrlShapeType Node::_node_type_to_shape(NodeType type)
+{
+ switch(type) {
+ case NODE_CUSP: return SP_CTRL_SHAPE_DIAMOND;
+ case NODE_SMOOTH: return SP_CTRL_SHAPE_SQUARE;
+ case NODE_AUTO: return SP_CTRL_SHAPE_CIRCLE;
+ case NODE_SYMMETRIC: return SP_CTRL_SHAPE_SQUARE;
+ default: return SP_CTRL_SHAPE_DIAMOND;
+ }
+}
+
+
+/**
+ * @class NodeList
+ * @brief An editable list of nodes representing a subpath.
+ *
+ * It can optionally be cyclic to represent a closed path.
+ * The list has iterators that act like plain node iterators, but can also be used
+ * to obtain shared pointers to nodes.
+ */
+
+NodeList::NodeList(SubpathList &splist)
+ : _list(splist)
+ , _closed(false)
+{
+ this->list = this;
+ this->next = this;
+ this->prev = this;
+}
+
+NodeList::~NodeList()
+{
+ clear();
+}
+
+bool NodeList::empty()
+{
+ return next == this;
+}
+
+NodeList::size_type NodeList::size()
+{
+ size_type sz = 0;
+ for (ListNode *ln = next; ln != this; ln = ln->next) ++sz;
+ return sz;
+}
+
+bool NodeList::closed()
+{
+ return _closed;
+}
+
+/** A subpath is degenerate if it has no segments - either one node in an open path
+ * or no nodes in a closed path */
+bool NodeList::degenerate()
+{
+ return closed() ? empty() : ++begin() == end();
+}
+
+NodeList::iterator NodeList::before(double t, double *fracpart)
+{
+ double intpart;
+ *fracpart = std::modf(t, &intpart);
+ int index = intpart;
+
+ iterator ret = begin();
+ std::advance(ret, index);
+ return ret;
+}
+
+// insert a node before i
+NodeList::iterator NodeList::insert(iterator i, Node *x)
+{
+ ListNode *ins = i._node;
+ x->next = ins;
+ x->prev = ins->prev;
+ ins->prev->next = x;
+ ins->prev = x;
+ x->ListNode::list = this;
+ _list.signal_insert_node.emit(x);
+ return iterator(x);
+}
+
+void NodeList::splice(iterator pos, NodeList &list)
+{
+ splice(pos, list, list.begin(), list.end());
+}
+
+void NodeList::splice(iterator pos, NodeList &list, iterator i)
+{
+ NodeList::iterator j = i;
+ ++j;
+ splice(pos, list, i, j);
+}
+
+void NodeList::splice(iterator pos, NodeList &list, iterator first, iterator last)
+{
+ ListNode *ins_beg = first._node, *ins_end = last._node, *at = pos._node;
+ for (ListNode *ln = ins_beg; ln != ins_end; ln = ln->next) {
+ list._list.signal_remove_node.emit(static_cast<Node*>(ln));
+ ln->list = this;
+ _list.signal_insert_node.emit(static_cast<Node*>(ln));
+ }
+ ins_beg->prev->next = ins_end;
+ ins_end->prev->next = at;
+ at->prev->next = ins_beg;
+
+ ListNode *atprev = at->prev;
+ at->prev = ins_end->prev;
+ ins_end->prev = ins_beg->prev;
+ ins_beg->prev = atprev;
+}
+
+void NodeList::shift(int n)
+{
+ // 1. make the list perfectly cyclic
+ next->prev = prev;
+ prev->next = next;
+ // 2. find new begin
+ ListNode *new_begin = next;
+ if (n > 0) {
+ for (; n > 0; --n) new_begin = new_begin->next;
+ } else {
+ for (; n < 0; ++n) new_begin = new_begin->prev;
+ }
+ // 3. relink begin to list
+ next = new_begin;
+ prev = new_begin->prev;
+ new_begin->prev->next = this;
+ new_begin->prev = this;
+}
+
+void NodeList::reverse()
+{
+ for (ListNode *ln = next; ln != this; ln = ln->prev) {
+ std::swap(ln->next, ln->prev);
+ Node *node = static_cast<Node*>(ln);
+ Geom::Point save_pos = node->front()->position();
+ node->front()->setPosition(node->back()->position());
+ node->back()->setPosition(save_pos);
+ }
+ std::swap(next, prev);
+}
+
+void NodeList::clear()
+{
+ for (iterator i = begin(); i != end();) erase (i++);
+}
+
+NodeList::iterator NodeList::erase(iterator i)
+{
+ // some gymnastics are required to ensure that the node is valid when deleted;
+ // otherwise the code that updates handle visibility will break
+ Node *rm = static_cast<Node*>(i._node);
+ ListNode *rmnext = rm->next, *rmprev = rm->prev;
+ ++i;
+ _list.signal_remove_node.emit(rm);
+ delete rm;
+ rmprev->next = rmnext;
+ rmnext->prev = rmprev;
+ return i;
+}
+
+// TODO this method is very ugly!
+// converting SubpathList to an intrusive list might allow us to get rid of it
+void NodeList::kill()
+{
+ for (SubpathList::iterator i = _list.begin(); i != _list.end(); ++i) {
+ if (i->get() == this) {
+ _list.erase(i);
+ return;
+ }
+ }
+}
+
+NodeList &NodeList::get(Node *n) {
+ return *(n->list());
+}
+NodeList &NodeList::get(iterator const &i) {
+ return *(i._node->list);
+}
+
+
+/**
+ * @class SubpathList
+ * @brief Editable path composed of one or more subpaths
+ */
+
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/node.h b/src/ui/tool/node.h
new file mode 100644
index 000000000..d822d854f
--- /dev/null
+++ b/src/ui/tool/node.h
@@ -0,0 +1,398 @@
+/** @file
+ * Editable node and associated data structures.
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_NODE_H
+#define SEEN_UI_TOOL_NODE_H
+
+#include <iterator>
+#include <iosfwd>
+#include <stdexcept>
+#include <tr1/functional>
+#include <boost/utility.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/optional.hpp>
+#include <boost/operators.hpp>
+#include "snapped-point.h"
+#include "ui/tool/selectable-control-point.h"
+#include "ui/tool/node-types.h"
+
+
+namespace Inkscape {
+namespace UI {
+template <typename> class NodeIterator;
+}
+}
+
+namespace std {
+namespace tr1 {
+template <typename N> struct hash< Inkscape::UI::NodeIterator<N> >;
+}
+}
+
+namespace Inkscape {
+namespace UI {
+
+class PathManipulator;
+class MultiPathManipulator;
+
+class Node;
+class Handle;
+class NodeList;
+class SubpathList;
+template <typename> class NodeIterator;
+
+std::ostream &operator<<(std::ostream &, NodeType);
+
+/*
+template <typename T>
+struct ListMember {
+ T *next;
+ T *prev;
+};
+struct SubpathMember : public ListMember<NodeListMember> {
+ Subpath *list;
+};
+struct SubpathListMember : public ListMember<SubpathListMember> {
+ SubpathList *list;
+};
+*/
+
+struct ListNode {
+ ListNode *next;
+ ListNode *prev;
+ NodeList *list;
+};
+
+struct NodeSharedData {
+ SPDesktop *desktop;
+ ControlPointSelection *selection;
+ SPCanvasGroup *node_group;
+ SPCanvasGroup *handle_group;
+ SPCanvasGroup *handle_line_group;
+};
+
+class Handle : public ControlPoint {
+public:
+ virtual ~Handle();
+ inline Geom::Point relativePos();
+ inline double length();
+ bool isDegenerate() { return _degenerate; }
+
+ virtual void setVisible(bool);
+ virtual void move(Geom::Point const &p);
+
+ virtual void setPosition(Geom::Point const &p);
+ inline void setRelativePos(Geom::Point const &p);
+ void setLength(double len);
+ void retract();
+ void setDirection(Geom::Point const &from, Geom::Point const &to);
+ void setDirection(Geom::Point const &dir);
+ Node *parent() { return _parent; }
+
+ static char const *handle_type_to_localized_string(NodeType type);
+ sigc::signal<void> signal_update;
+protected:
+ Handle(NodeSharedData const &data, Geom::Point const &initial_pos, Node *parent);
+ virtual Glib::ustring _getTip(unsigned state);
+ virtual Glib::ustring _getDragTip(GdkEventMotion *event);
+ virtual bool _hasDragTips() { return true; }
+private:
+ void _grabbedHandler();
+ void _draggedHandler(Geom::Point &, GdkEventMotion *);
+ void _ungrabbedHandler();
+ Node *_parent; // the handle's lifetime does not extend beyond that of the parent node,
+ // so a naked pointer is OK and allows setting it during Node's construction
+ SPCanvasItem *_handle_line;
+ bool _degenerate; // this is used often internally so it makes sense to cache this
+
+ static double _saved_length;
+ static bool _drag_out;
+ friend class Node;
+};
+
+class Node : ListNode, public SelectableControlPoint {
+public:
+ Node(NodeSharedData const &data, Geom::Point const &pos);
+ virtual void move(Geom::Point const &p);
+ virtual void transform(Geom::Matrix const &m);
+ virtual Geom::Rect bounds();
+
+ NodeType type() { return _type; }
+ void setType(NodeType type, bool update_handles = true);
+ void showHandles(bool v);
+ void pickBestType(); // automatically determine the type from handle positions
+ bool isDegenerate() { return _front.isDegenerate() && _back.isDegenerate(); }
+ bool isEndNode();
+ Handle *front() { return &_front; }
+ Handle *back() { return &_back; }
+ static NodeType parse_nodetype(char x);
+ NodeList *list() { return static_cast<ListNode*>(this)->list; }
+ void sink();
+
+ static char const *node_type_to_localized_string(NodeType type);
+ // temporarily public
+ virtual bool _eventHandler(GdkEvent *event);
+protected:
+ virtual void _setState(State state);
+ virtual Glib::ustring _getTip(unsigned state);
+ virtual Glib::ustring _getDragTip(GdkEventMotion *event);
+ virtual bool _hasDragTips() { return true; }
+private:
+ Node(Node const &);
+ bool _grabbedHandler(GdkEventMotion *);
+ void _draggedHandler(Geom::Point &, GdkEventMotion *);
+ void _fixNeighbors(Geom::Point const &old_pos, Geom::Point const &new_pos);
+ void _updateAutoHandles();
+ void _linearGrow(int dir);
+ Node *_next();
+ Node *_prev();
+ Inkscape::SnapSourceType _snapSourceType();
+ Inkscape::SnapTargetType _snapTargetType();
+ static SPCtrlShapeType _node_type_to_shape(NodeType type);
+ static bool _is_line_segment(Node *first, Node *second);
+
+ // Handles are always present, but are not visible if they coincide with the node
+ // (are degenerate). A segment that has both handles degenerate is always treated
+ // as a line segment
+ Handle _front; ///< Node handle in the backward direction of the path
+ Handle _back; ///< Node handle in the forward direction of the path
+ NodeType _type; ///< Type of node - cusp, smooth...
+ bool _handles_shown;
+ friend class Handle;
+ friend class NodeList;
+ friend class NodeIterator<Node>;
+ friend class NodeIterator<Node const>;
+};
+
+/// Iterator for editable nodes
+/** Use this class for all operations that require some knowledge about the node's
+ * neighbors. It works like a bidirectional iterator.
+ *
+ * Because paths can be cyclic, node iterators have two different ways to
+ * increment and decrement them. Nodes can be iterated over either in the
+ * sequence order, which always has a beginning and an end, or in the path order,
+ * which can be cyclic (moving to the next node never yields the end iterator).
+ *
+ * When @a i is a node iterator, then:
+ * - <code>++i</code> moves the iterator to the next node in sequence order;
+ * - <code>--i</code> moves the iterator to the previous node in sequence order;
+ * - <code>i.next()</code> returns the next node with wrap-around if the path is cyclic;
+ * - <code>i.prev()</code> returns the previous node with wrap-around if the path is cyclic.
+ *
+ * next() and prev() do not change their iterator. They can return the end iterator
+ * if the path is open.
+ *
+ * Unlike most other iterators, you can check whether a node iterator is invalid
+ * (is an end iterator) without having access to the iterator's container.
+ * Simply use <code>if (i) { ...</code>
+ * */
+template <typename N>
+class NodeIterator
+ : public boost::bidirectional_iterator_helper<NodeIterator<N>, N, std::ptrdiff_t,
+ N *, N &>
+{
+public:
+ typedef NodeIterator self;
+ NodeIterator()
+ : _node(0)
+ {}
+ // default copy, default assign
+
+ self &operator++() {
+ _node = _node->next;
+ return *this;
+ }
+ self &operator--() {
+ _node = _node->prev;
+ return *this;
+ }
+ bool operator==(self const &other) const { return _node == other._node; }
+ N &operator*() const { return *static_cast<N*>(_node); }
+ inline operator bool() const; // define after NodeList
+ /// Get a pointer to the underlying node. Equivalent to <code>&*i</code>.
+ N *get_pointer() const { return static_cast<N*>(_node); }
+ /// @see get_pointer()
+ N *ptr() const { return static_cast<N*>(_node); }
+
+ self next() const;
+ self prev() const;
+private:
+ NodeIterator(ListNode const *n)
+ : _node(const_cast<ListNode*>(n))
+ {}
+ ListNode *_node;
+ friend class NodeList;
+ friend class std::tr1::hash<self>;
+};
+
+class NodeList : ListNode, boost::noncopyable, public boost::enable_shared_from_this<NodeList> {
+public:
+ typedef std::size_t size_type;
+ typedef Node &reference;
+ typedef Node const &const_reference;
+ typedef Node *pointer;
+ typedef Node const *const_pointer;
+ typedef Node value_type;
+ typedef NodeIterator<value_type> iterator;
+ typedef NodeIterator<value_type const> const_iterator;
+ typedef std::reverse_iterator<iterator> reverse_iterator;
+ typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
+
+ // TODO Lame. Make this private and make SubpathList a factory
+ NodeList(SubpathList &_list);
+ ~NodeList();
+
+ // iterators
+ iterator begin() { return iterator(next); }
+ iterator end() { return iterator(this); }
+ const_iterator begin() const { return const_iterator(next); }
+ const_iterator end() const { return const_iterator(this); }
+ reverse_iterator rbegin() { return reverse_iterator(end()); }
+ reverse_iterator rend() { return reverse_iterator(begin()); }
+ const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); }
+ const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }
+
+ // size
+ bool empty();
+ size_type size();
+
+ // extra node-specific methods
+ bool closed();
+ bool degenerate();
+ void setClosed(bool c) { _closed = c; }
+ iterator before(double t, double *fracpart = NULL);
+ const_iterator before(double t, double *fracpart = NULL) const {
+ return const_iterator(before(t, fracpart)._node);
+ }
+
+ // list operations
+ iterator insert(iterator pos, Node *x);
+ template <class InputIterator>
+ void insert(iterator pos, InputIterator first, InputIterator last) {
+ for (; first != last; ++first) insert(pos, *first);
+ }
+ void splice(iterator pos, NodeList &list);
+ void splice(iterator pos, NodeList &list, iterator i);
+ void splice(iterator pos, NodeList &list, iterator first, iterator last);
+ void reverse();
+ void shift(int n);
+ void push_front(Node *x) { insert(begin(), x); }
+ void pop_front() { erase(begin()); }
+ void push_back(Node *x) { insert(end(), x); }
+ void pop_back() { erase(--end()); }
+ void clear();
+ iterator erase(iterator pos);
+ iterator erase(iterator first, iterator last) {
+ NodeList::iterator ret = first;
+ while (first != last) ret = erase(first++);
+ return ret;
+ }
+
+ // member access - undefined results when the list is empty
+ Node &front() { return *static_cast<Node*>(next); }
+ Node &back() { return *static_cast<Node*>(prev); }
+
+ // HACK remove this subpath from its path. This will be removed later.
+ void kill();
+
+ static iterator get_iterator(Node *n) { return iterator(n); }
+ static const_iterator get_iterator(Node const *n) { return const_iterator(n); }
+ static NodeList &get(Node *n);
+ static NodeList &get(iterator const &i);
+private:
+ // no copy or assign
+ NodeList(NodeList const &);
+ void operator=(NodeList const &);
+
+ SubpathList &_list;
+ bool _closed;
+
+ friend class Node;
+ friend class Handle; // required to access handle and handle line groups
+ friend class NodeIterator<Node>;
+ friend class NodeIterator<Node const>;
+};
+
+/** List of node lists. Represents an editable path. */
+class SubpathList : public std::list< boost::shared_ptr<NodeList> > {
+public:
+ typedef std::list< boost::shared_ptr<NodeList> > list_type;
+
+ SubpathList(PathManipulator &pm) : _path_manipulator(pm) {}
+
+ sigc::signal<void, Node *> signal_insert_node;
+ sigc::signal<void, Node *> signal_remove_node;
+private:
+ list_type _nodelists;
+ PathManipulator &_path_manipulator;
+ friend class NodeList;
+ friend class Node;
+ friend class Handle;
+};
+
+
+
+// define inline Handle funcs after definition of Node
+inline Geom::Point Handle::relativePos() {
+ return position() - _parent->position();
+}
+inline void Handle::setRelativePos(Geom::Point const &p) {
+ setPosition(_parent->position() + p);
+}
+inline double Handle::length() {
+ return relativePos().length();
+}
+
+// definitions for node iterator
+template <typename N>
+NodeIterator<N>::operator bool() const {
+ return _node && static_cast<ListNode*>(_node->list) != _node;
+}
+template <typename N>
+NodeIterator<N> NodeIterator<N>::next() const {
+ NodeIterator<N> ret(*this);
+ ++ret;
+ if (!ret && _node->list->closed()) ++ret;
+ return ret;
+}
+template <typename N>
+NodeIterator<N> NodeIterator<N>::prev() const {
+ NodeIterator<N> ret(*this);
+ --ret;
+ if (!ret && _node->list->closed()) --ret;
+ return ret;
+}
+
+} // namespace UI
+} // namespace Inkscape
+
+namespace std {
+namespace tr1 {
+template <typename N>
+struct hash< Inkscape::UI::NodeIterator<N> > : public unary_function<Inkscape::UI::NodeIterator<N>, size_t> {
+ size_t operator()(Inkscape::UI::NodeIterator<N> const &ni) const {
+ return reinterpret_cast<size_t>(ni._node);
+ }
+};
+}
+}
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/path-manipulator.cpp b/src/ui/tool/path-manipulator.cpp
new file mode 100644
index 000000000..9889eb787
--- /dev/null
+++ b/src/ui/tool/path-manipulator.cpp
@@ -0,0 +1,1318 @@
+/** @file
+ * Path manipulator - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <string>
+#include <sstream>
+#include <deque>
+#include <stdexcept>
+#include <boost/shared_ptr.hpp>
+#include <2geom/bezier-curve.h>
+#include <2geom/bezier-utils.h>
+#include <2geom/svg-path.h>
+#include <glibmm.h>
+#include <glibmm/i18n.h>
+#include "ui/tool/path-manipulator.h"
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "display/sp-canvas.h"
+#include "display/sp-canvas-util.h"
+#include "display/curve.h"
+#include "display/canvas-bpath.h"
+#include "document.h"
+#include "live_effects/effect.h"
+#include "live_effects/lpeobject.h"
+#include "live_effects/parameter/path.h"
+#include "sp-path.h"
+#include "helper/geom.h"
+#include "preferences.h"
+#include "style.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/curve-drag-point.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "xml/node.h"
+#include "xml/node-observer.h"
+
+namespace Inkscape {
+namespace UI {
+
+namespace {
+/// Types of path changes that we must react to.
+enum PathChange {
+ PATH_CHANGE_D,
+ PATH_CHANGE_TRANSFORM
+};
+
+} // anonymous namespace
+
+/**
+ * Notifies the path manipulator when something changes the path being edited
+ * (e.g. undo / redo)
+ */
+class PathManipulatorObserver : public Inkscape::XML::NodeObserver {
+public:
+ PathManipulatorObserver(PathManipulator *p) : _pm(p), _blocked(false) {}
+ virtual void notifyAttributeChanged(Inkscape::XML::Node &, GQuark attr,
+ Util::ptr_shared<char>, Util::ptr_shared<char>)
+ {
+ // do nothing if blocked
+ if (_blocked) return;
+
+ GQuark path_d = g_quark_from_static_string("d");
+ GQuark path_transform = g_quark_from_static_string("transform");
+ GQuark lpe_quark = _pm->_lpe_key.empty() ? 0 : g_quark_from_string(_pm->_lpe_key.data());
+
+ // only react to "d" (path data) and "transform" attribute changes
+ if (attr == lpe_quark || attr == path_d) {
+ _pm->_externalChange(PATH_CHANGE_D);
+ } else if (attr == path_transform) {
+ _pm->_externalChange(PATH_CHANGE_TRANSFORM);
+ }
+ }
+ void block() { _blocked = true; }
+ void unblock() { _blocked = false; }
+private:
+ PathManipulator *_pm;
+ bool _blocked;
+};
+
+void build_segment(Geom::PathBuilder &, Node *, Node *);
+
+PathManipulator::PathManipulator(MultiPathManipulator &mpm, SPPath *path,
+ Geom::Matrix const &et, guint32 outline_color, Glib::ustring lpe_key)
+ : PointManipulator(mpm._path_data.node_data.desktop, *mpm._path_data.node_data.selection)
+ , _subpaths(*this)
+ , _multi_path_manipulator(mpm)
+ , _path(path)
+ , _spcurve(NULL)
+ , _dragpoint(new CurveDragPoint(*this))
+ , _observer(new PathManipulatorObserver(this))
+ , _edit_transform(et)
+ , _show_handles(true)
+ , _show_outline(false)
+ , _lpe_key(lpe_key)
+{
+ /* Because curve drag point is always created first, it does not cover nodes */
+ if (_lpe_key.empty()) {
+ _i2d_transform = sp_item_i2d_affine(SP_ITEM(path));
+ } else {
+ _i2d_transform = Geom::identity();
+ }
+ _d2i_transform = _i2d_transform.inverse();
+ _dragpoint->setVisible(false);
+
+ _getGeometry();
+
+ _outline = sp_canvas_bpath_new(_multi_path_manipulator._path_data.outline_group, NULL);
+ sp_canvas_item_hide(_outline);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(_outline), outline_color, 1.0,
+ SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(_outline), 0, SP_WIND_RULE_NONZERO);
+
+ _subpaths.signal_insert_node.connect(
+ sigc::mem_fun(*this, &PathManipulator::_attachNodeHandlers));
+ _subpaths.signal_remove_node.connect(
+ sigc::mem_fun(*this, &PathManipulator::_removeNodeHandlers));
+ _selection.signal_update.connect(
+ sigc::mem_fun(*this, &PathManipulator::update));
+ _selection.signal_point_changed.connect(
+ sigc::mem_fun(*this, &PathManipulator::_selectionChanged));
+ _dragpoint->signal_update.connect(
+ sigc::mem_fun(*this, &PathManipulator::update));
+ _desktop->signal_zoom_changed.connect(
+ sigc::hide( sigc::mem_fun(*this, &PathManipulator::_updateOutlineOnZoomChange)));
+
+ _createControlPointsFromGeometry();
+
+ _path->repr->addObserver(*_observer);
+}
+
+PathManipulator::~PathManipulator()
+{
+ delete _dragpoint;
+ if (_path) _path->repr->removeObserver(*_observer);
+ delete _observer;
+ gtk_object_destroy(_outline);
+ if (_spcurve) _spcurve->unref();
+ clear();
+}
+
+/** Handle motion events to update the position of the curve drag point. */
+bool PathManipulator::event(GdkEvent *event)
+{
+ if (empty()) return false;
+
+ switch (event->type)
+ {
+ case GDK_MOTION_NOTIFY:
+ _updateDragPoint(event_point(event->motion));
+ break;
+ default: break;
+ }
+ return false;
+}
+
+/** Check whether the manipulator has any nodes. */
+bool PathManipulator::empty() {
+ return !_path || _subpaths.empty();
+}
+
+/** Update the display and the outline of the path. */
+void PathManipulator::update()
+{
+ _createGeometryFromControlPoints();
+}
+
+/** Store the changes to the path in XML. */
+void PathManipulator::writeXML()
+{
+ if (!_path) return;
+ _observer->block();
+ if (!empty()) {
+ SP_OBJECT(_path)->updateRepr();
+ _getXMLNode()->setAttribute(_nodetypesKey().data(), _createTypeString().data());
+ } else {
+ // this manipulator will have to be destroyed right after this call
+ _getXMLNode()->removeObserver(*_observer);
+ sp_object_ref(_path);
+ _path->deleteObject(true, true);
+ sp_object_unref(_path);
+ _path = 0;
+ }
+ _observer->unblock();
+}
+
+/** Remove all nodes from the path. */
+void PathManipulator::clear()
+{
+ // no longer necessary since nodes remove themselves from selection on destruction
+ //_removeNodesFromSelection();
+ _subpaths.clear();
+}
+
+/** Select all nodes in subpaths that have something selected. */
+void PathManipulator::selectSubpaths()
+{
+ for (std::list<SubpathPtr>::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ NodeList::iterator sp_start = (*i)->begin(), sp_end = (*i)->end();
+ for (NodeList::iterator j = sp_start; j != sp_end; ++j) {
+ if (j->selected()) {
+ // if at least one of the nodes from this subpath is selected,
+ // select all nodes from this subpath
+ for (NodeList::iterator ins = sp_start; ins != sp_end; ++ins)
+ _selection.insert(ins.ptr());
+ continue;
+ }
+ }
+ }
+}
+
+/** Move the selection forward or backward by one node in each subpath, based on the sign
+ * of the parameter. */
+void PathManipulator::shiftSelection(int dir)
+{
+ if (dir == 0) return;
+ // We cannot do any tricks here, like iterating in different directions based on
+ // the sign and only setting the selection of nodes behind us, because it would break
+ // for closed paths.
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ std::deque<bool> sels; // I hope this is specialized for bools!
+ unsigned num = 0;
+
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ sels.push_back(j->selected());
+ _selection.erase(j.ptr());
+ ++num;
+ }
+ if (num == 0) continue; // should never happen!
+
+ num = 0;
+ // In closed subpath, shift the selection cyclically. In an open one,
+ // let the selection 'slide into nothing' at ends.
+ if (dir > 0) {
+ if ((*i)->closed()) {
+ bool last = sels.back();
+ sels.pop_back();
+ sels.push_front(last);
+ } else {
+ sels.push_front(false);
+ }
+ } else {
+ if ((*i)->closed()) {
+ bool first = sels.front();
+ sels.pop_front();
+ sels.push_back(first);
+ } else {
+ sels.push_back(false);
+ num = 1;
+ }
+ }
+
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ if (sels[num]) _selection.insert(j.ptr());
+ ++num;
+ }
+ }
+}
+
+/** Invert selection in the selected subpaths. */
+void PathManipulator::invertSelectionInSubpaths()
+{
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ if (j->selected()) {
+ // found selected node - invert selection in this subpath
+ for (NodeList::iterator k = (*i)->begin(); k != (*i)->end(); ++k) {
+ if (k->selected()) _selection.erase(k.ptr());
+ else _selection.insert(k.ptr());
+ }
+ // next subpath
+ break;
+ }
+ }
+ }
+}
+
+/** Insert a new node in the middle of each selected segment. */
+void PathManipulator::insertNodes()
+{
+ if (!_num_selected) return;
+
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ NodeList::iterator k = j.next();
+ if (k && j->selected() && k->selected()) {
+ j = subdivideSegment(j, 0.5);
+ _selection.insert(j.ptr());
+ }
+ }
+ }
+}
+
+/** Replace contiguous selections of nodes in each subpath with one node. */
+void PathManipulator::weldNodes(NodeList::iterator preserve_pos)
+{
+ if (!_num_selected) return;
+ _dragpoint->setVisible(false);
+
+ bool pos_valid = preserve_pos;
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ SubpathPtr sp = *i;
+ unsigned num_selected = 0, num_unselected = 0;
+ for (NodeList::iterator j = sp->begin(); j != sp->end(); ++j) {
+ if (j->selected()) ++num_selected;
+ else ++num_unselected;
+ }
+ if (num_selected < 2) continue;
+ if (num_unselected == 0) {
+ // if all nodes in a subpath are selected, the operation doesn't make much sense
+ continue;
+ }
+
+ // Start from unselected node in closed paths, so that we don't start in the middle
+ // of a selection
+ NodeList::iterator sel_beg = sp->begin(), sel_end;
+ if (sp->closed()) {
+ while (sel_beg->selected()) ++sel_beg;
+ }
+
+ // Work loop
+ while (num_selected > 0) {
+ // Find selected node
+ while (sel_beg && !sel_beg->selected()) sel_beg = sel_beg.next();
+ if (!sel_beg) throw std::logic_error("Join nodes: end of open path reached, "
+ "but there are still nodes to process!");
+
+ // note: this is initialized to zero, because the loop below counts sel_beg as well
+ // the loop conditions are simpler that way
+ unsigned num_points = 0;
+ bool use_pos = false;
+ Geom::Point back_pos, front_pos;
+ back_pos = *sel_beg->back();
+
+ for (sel_end = sel_beg; sel_end && sel_end->selected(); sel_end = sel_end.next()) {
+ ++num_points;
+ front_pos = *sel_end->front();
+ if (pos_valid && sel_end == preserve_pos) use_pos = true;
+ }
+ if (num_points > 1) {
+ Geom::Point joined_pos;
+ if (use_pos) {
+ joined_pos = preserve_pos->position();
+ pos_valid = false;
+ } else {
+ joined_pos = Geom::middle_point(back_pos, front_pos);
+ }
+ sel_beg->setType(NODE_CUSP, false);
+ sel_beg->move(joined_pos);
+ // do not move handles if they aren't degenerate
+ if (!sel_beg->back()->isDegenerate()) {
+ sel_beg->back()->setPosition(back_pos);
+ }
+ if (!sel_end.prev()->front()->isDegenerate()) {
+ sel_beg->front()->setPosition(front_pos);
+ }
+ sel_beg = sel_beg.next();
+ while (sel_beg != sel_end) {
+ NodeList::iterator next = sel_beg.next();
+ sp->erase(sel_beg);
+ sel_beg = next;
+ --num_selected;
+ }
+ }
+ --num_selected; // for the joined node or single selected node
+ }
+ }
+}
+
+/** Remove nodes in the middle of selected segments. */
+void PathManipulator::weldSegments()
+{
+ if (!_num_selected) return;
+ _dragpoint->setVisible(false);
+
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ SubpathPtr sp = *i;
+ unsigned num_selected = 0, num_unselected = 0;
+ for (NodeList::iterator j = sp->begin(); j != sp->end(); ++j) {
+ if (j->selected()) ++num_selected;
+ else ++num_unselected;
+ }
+ if (num_selected < 3) continue;
+ if (num_unselected == 0 && sp->closed()) {
+ // if all nodes in a closed subpath are selected, the operation doesn't make much sense
+ continue;
+ }
+
+ // Start from unselected node in closed paths, so that we don't start in the middle
+ // of a selection
+ NodeList::iterator sel_beg = sp->begin(), sel_end;
+ if (sp->closed()) {
+ while (sel_beg->selected()) ++sel_beg;
+ }
+
+ // Work loop
+ while (num_selected > 0) {
+ // Find selected node
+ while (sel_beg && !sel_beg->selected()) sel_beg = sel_beg.next();
+ if (!sel_beg) throw std::logic_error("Join nodes: end of open path reached, "
+ "but there are still nodes to process!");
+
+ // note: this is initialized to zero, because the loop below counts sel_beg as well
+ // the loop conditions are simpler that way
+ unsigned num_points = 0;
+
+ // find the end of selected segment
+ for (sel_end = sel_beg; sel_end && sel_end->selected(); sel_end = sel_end.next()) {
+ ++num_points;
+ }
+ if (num_points > 2) {
+ // remove nodes in the middle
+ sel_beg = sel_beg.next();
+ while (sel_beg != sel_end.prev()) {
+ NodeList::iterator next = sel_beg.next();
+ sp->erase(sel_beg);
+ sel_beg = next;
+ }
+ sel_beg = sel_end;
+ }
+ num_selected -= num_points;
+ }
+ }
+}
+
+/** Break the subpath at selected nodes. It also works for single node closed paths. */
+void PathManipulator::breakNodes()
+{
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ SubpathPtr sp = *i;
+ NodeList::iterator cur = sp->begin(), end = sp->end();
+ if (!sp->closed()) {
+ // Each open path must have at least two nodes so no checks are required.
+ // For 2-node open paths, cur == end
+ ++cur;
+ --end;
+ }
+ for (; cur != end; ++cur) {
+ if (!cur->selected()) continue;
+ SubpathPtr ins;
+ bool becomes_open = false;
+
+ if (sp->closed()) {
+ // Move the node to break at to the beginning of path
+ if (cur != sp->begin())
+ sp->splice(sp->begin(), *sp, cur, sp->end());
+ sp->setClosed(false);
+ ins = sp;
+ becomes_open = true;
+ } else {
+ SubpathPtr new_sp(new NodeList(_subpaths));
+ new_sp->splice(new_sp->end(), *sp, sp->begin(), cur);
+ _subpaths.insert(i, new_sp);
+ ins = new_sp;
+ }
+
+ Node *n = new Node(_multi_path_manipulator._path_data.node_data, cur->position());
+ ins->insert(ins->end(), n);
+ cur->setType(NODE_CUSP, false);
+ n->back()->setRelativePos(cur->back()->relativePos());
+ cur->back()->retract();
+ n->sink();
+
+ if (becomes_open) {
+ cur = sp->begin(); // this will be increased to ++sp->begin()
+ end = --sp->end();
+ }
+ }
+ }
+}
+
+/** Delete selected nodes in the path, optionally substituting deleted segments with bezier curves
+ * in a way that attempts to preserve the original shape of the curve. */
+void PathManipulator::deleteNodes(bool keep_shape)
+{
+ if (!_num_selected) return;
+ hideDragPoint();
+
+ unsigned const samples_per_segment = 10;
+ double const t_step = 1.0 / samples_per_segment;
+
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end();) {
+ SubpathPtr sp = *i;
+
+ // If there are less than 2 unselected nodes in an open subpath or no unselected nodes
+ // in a closed one, delete entire subpath.
+ unsigned num_unselected = 0, num_selected = 0;
+ for (NodeList::iterator j = sp->begin(); j != sp->end(); ++j) {
+ if (j->selected()) ++num_selected;
+ else ++num_unselected;
+ }
+ if (num_selected == 0) {
+ ++i;
+ continue;
+ }
+ if (sp->closed() ? (num_unselected < 1) : (num_unselected < 2)) {
+ _subpaths.erase(i++);
+ continue;
+ }
+
+ // In closed paths, start from an unselected node - otherwise we might start in the middle
+ // of a selected stretch and the resulting bezier fit would be suboptimal
+ NodeList::iterator sel_beg = sp->begin(), sel_end;
+ if (sp->closed()) {
+ while (sel_beg->selected()) ++sel_beg;
+ }
+ sel_end = sel_beg;
+
+ while (num_selected > 0) {
+ while (!sel_beg->selected()) sel_beg = sel_beg.next();
+ sel_end = sel_beg;
+ unsigned del_len = 0;
+ while (sel_end && sel_end->selected()) {
+ ++del_len;
+ sel_end = sel_end.next();
+ }
+
+ // set surrounding node types to cusp if:
+ // 1. keep_shape is on, or
+ // 2. we are deleting at the end or beginning of an open path
+ // if !sel_end then sel_beg.prev() must be valid, otherwise the entire subpath
+ // would be deleted before we get here
+ if ((keep_shape || !sel_end) && sel_beg.prev()) sel_beg.prev()->setType(NODE_CUSP, false);
+ if ((keep_shape || !sel_beg.prev()) && sel_end) sel_end->setType(NODE_CUSP, false);
+
+ if (keep_shape && sel_beg.prev() && sel_end) {
+ // Fill fit data
+ unsigned num_samples = (del_len + 1) * samples_per_segment + 1;
+ Geom::Point *bezier_data = new Geom::Point[num_samples];
+ Geom::Point result[4];
+ unsigned seg = 0;
+
+ for (NodeList::iterator cur = sel_beg.prev(); cur != sel_end; cur = cur.next()) {
+ Geom::CubicBezier bc(*cur, *cur->front(), *cur.next(), *cur.next()->back());
+ for (unsigned s = 0; s < samples_per_segment; ++s) {
+ bezier_data[seg * samples_per_segment + s] = bc.pointAt(t_step * s);
+ }
+ ++seg;
+ }
+ // Fill last point
+ bezier_data[num_samples - 1] = sel_end->position();
+ // Compute replacement bezier curve
+ // TODO the fitting algorithm sucks - rewrite it to be awesome
+ bezier_fit_cubic(result, bezier_data, num_samples, 0.5);
+ delete[] bezier_data;
+
+ sel_beg.prev()->front()->setPosition(result[1]);
+ sel_end->back()->setPosition(result[2]);
+ }
+ // We cannot simply use sp->erase(sel_beg, sel_end), because it would break
+ // for cases when the selected stretch crosses the beginning of the path
+ while (sel_beg != sel_end) {
+ NodeList::iterator next = sel_beg.next();
+ sp->erase(sel_beg);
+ sel_beg = next;
+ }
+ num_selected -= del_len;
+ }
+ ++i;
+ }
+}
+
+/** Removes selected segments */
+void PathManipulator::deleteSegments()
+{
+ if (_num_selected == 0) return;
+ hideDragPoint();
+
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end();) {
+ SubpathPtr sp = *i;
+ bool has_unselected = false;
+ unsigned num_selected = 0;
+ for (NodeList::iterator j = sp->begin(); j != sp->end(); ++j) {
+ if (j->selected()) {
+ ++num_selected;
+ } else {
+ has_unselected = true;
+ }
+ }
+ if (!has_unselected) {
+ _subpaths.erase(i++);
+ continue;
+ }
+
+ NodeList::iterator sel_beg = sp->begin();
+ if (sp->closed()) {
+ while (sel_beg && sel_beg->selected()) ++sel_beg;
+ }
+ while (num_selected > 0) {
+ if (!sel_beg->selected()) {
+ sel_beg = sel_beg.next();
+ continue;
+ }
+ NodeList::iterator sel_end = sel_beg;
+ unsigned num_points = 0;
+ while (sel_end && sel_end->selected()) {
+ sel_end = sel_end.next();
+ ++num_points;
+ }
+ if (num_points >= 2) {
+ // Retract end handles
+ sel_end.prev()->setType(NODE_CUSP, false);
+ sel_end.prev()->back()->retract();
+ sel_beg->setType(NODE_CUSP, false);
+ sel_beg->front()->retract();
+ if (sp->closed()) {
+ // In closed paths, relocate the beginning of the path to the last selected
+ // node and then unclose it. Remove the nodes from the first selected node
+ // to the new end of path.
+ if (sel_end.prev() != sp->begin())
+ sp->splice(sp->begin(), *sp, sel_end.prev(), sp->end());
+ sp->setClosed(false);
+ sp->erase(sel_beg.next(), sp->end());
+ } else {
+ // for open paths:
+ // 1. At end or beginning, delete including the node on the end or beginning
+ // 2. In the middle, delete only inner nodes
+ if (sel_beg == sp->begin()) {
+ sp->erase(sp->begin(), sel_end.prev());
+ } else if (sel_end == sp->end()) {
+ sp->erase(sel_beg.next(), sp->end());
+ } else {
+ SubpathPtr new_sp(new NodeList(_subpaths));
+ new_sp->splice(new_sp->end(), *sp, sp->begin(), sel_beg.next());
+ _subpaths.insert(i, new_sp);
+ if (sel_end.prev())
+ sp->erase(sp->begin(), sel_end.prev());
+ }
+ }
+ }
+ sel_beg = sel_end;
+ num_selected -= num_points;
+ }
+ ++i;
+ }
+}
+
+/** Reverse the subpaths that have anything selected. */
+void PathManipulator::reverseSubpaths()
+{
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ if (j->selected()) {
+ (*i)->reverse();
+ break; // continue with the next subpath
+ }
+ }
+ }
+}
+
+/** Make selected segments curves / lines. */
+void PathManipulator::setSegmentType(SegmentType type)
+{
+ if (!_num_selected) return;
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ NodeList::iterator k = j.next();
+ if (!(k && j->selected() && k->selected())) continue;
+ switch (type) {
+ case SEGMENT_STRAIGHT:
+ if (j->front()->isDegenerate() && k->back()->isDegenerate())
+ break;
+ j->front()->move(*j);
+ k->back()->move(*k);
+ break;
+ case SEGMENT_CUBIC_BEZIER:
+ if (!j->front()->isDegenerate() || !k->back()->isDegenerate())
+ break;
+ j->front()->move(j->position() + (k->position() - j->position()) / 3);
+ k->back()->move(k->position() + (j->position() - k->position()) / 3);
+ break;
+ }
+ }
+ }
+}
+
+/** Set the visibility of handles. */
+void PathManipulator::showHandles(bool show)
+{
+ if (show == _show_handles) return;
+ if (show) {
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ if (!j->selected()) continue;
+ j->showHandles(true);
+ if (j.prev()) j.prev()->showHandles(true);
+ if (j.next()) j.next()->showHandles(true);
+ }
+ }
+ } else {
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ j->showHandles(false);
+ }
+ }
+ }
+ _show_handles = show;
+}
+
+/** Set the visibility of outline. */
+void PathManipulator::showOutline(bool show)
+{
+ if (show == _show_outline) return;
+ _show_outline = show;
+ _updateOutline();
+}
+
+void PathManipulator::showPathDirection(bool show)
+{
+ if (show == _show_path_direction) return;
+ _show_path_direction = show;
+ _updateOutline();
+}
+
+void PathManipulator::setControlsTransform(Geom::Matrix const &tnew)
+{
+ Geom::Matrix delta = _i2d_transform.inverse() * _edit_transform.inverse() * tnew * _i2d_transform;
+ _edit_transform = tnew;
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ j->transform(delta);
+ }
+ }
+ _createGeometryFromControlPoints();
+}
+
+/** Hide the curve drag point until the next motion event. */
+void PathManipulator::hideDragPoint()
+{
+ _dragpoint->setVisible(false);
+ _dragpoint->setIterator(NodeList::iterator());
+}
+
+/** Insert a node in the segment beginning with the supplied iterator,
+ * at the given time value */
+NodeList::iterator PathManipulator::subdivideSegment(NodeList::iterator first, double t)
+{
+ if (!first) throw std::invalid_argument("Subdivide after invalid iterator");
+ NodeList &list = NodeList::get(first);
+ NodeList::iterator second = first.next();
+ if (!second) throw std::invalid_argument("Subdivide after last node in open path");
+
+ // We need to insert the segment after 'first'. We can't simply use 'second'
+ // as the point of insertion, because when 'first' is the last node of closed path,
+ // the new node will be inserted as the first node instead.
+ NodeList::iterator insert_at = first;
+ ++insert_at;
+
+ NodeList::iterator inserted;
+ if (first->front()->isDegenerate() && second->back()->isDegenerate()) {
+ // for a line segment, insert a cusp node
+ Node *n = new Node(_multi_path_manipulator._path_data.node_data,
+ Geom::lerp(t, first->position(), second->position()));
+ n->setType(NODE_CUSP, false);
+ inserted = list.insert(insert_at, n);
+ } else {
+ // build bezier curve and subdivide
+ Geom::CubicBezier temp(first->position(), first->front()->position(),
+ second->back()->position(), second->position());
+ std::pair<Geom::CubicBezier, Geom::CubicBezier> div = temp.subdivide(t);
+ std::vector<Geom::Point> seg1 = div.first.points(), seg2 = div.second.points();
+
+ // set new handle positions
+ Node *n = new Node(_multi_path_manipulator._path_data.node_data, seg2[0]);
+ n->back()->setPosition(seg1[2]);
+ n->front()->setPosition(seg2[1]);
+ n->setType(NODE_SMOOTH, false);
+ inserted = list.insert(insert_at, n);
+
+ first->front()->move(seg1[1]);
+ second->back()->move(seg2[2]);
+ }
+ return inserted;
+}
+
+/** Find the node that is closest/farthest from the origin
+ * @param origin Point of reference
+ * @param search_selected Consider selected nodes
+ * @param search_unselected Consider unselected nodes
+ * @param closest If true, return closest node, if false, return farthest
+ * @return The matching node, or an empty iterator if none found
+ */
+NodeList::iterator PathManipulator::extremeNode(NodeList::iterator origin, bool search_selected,
+ bool search_unselected, bool closest)
+{
+ NodeList::iterator match;
+ double extr_dist = closest ? HUGE_VAL : -HUGE_VAL;
+ if (_num_selected == 0 && !search_unselected) return match;
+
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ if(j->selected()) {
+ if (!search_selected) continue;
+ } else {
+ if (!search_unselected) continue;
+ }
+ double dist = Geom::distance(*j, *origin);
+ bool cond = closest ? (dist < extr_dist) : (dist > extr_dist);
+ if (cond) {
+ match = j;
+ extr_dist = dist;
+ }
+ }
+ }
+ return match;
+}
+
+/** Called by the XML observer when something else than us modifies the path. */
+void PathManipulator::_externalChange(unsigned type)
+{
+ switch (type) {
+ case PATH_CHANGE_D: {
+ _getGeometry();
+
+ // ugly: stored offsets of selected nodes in a vector
+ // vector<bool> should be specialized so that it takes only 1 bit per value
+ std::vector<bool> selpos;
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ selpos.push_back(j->selected());
+ }
+ }
+ unsigned size = selpos.size(), curpos = 0;
+
+ _createControlPointsFromGeometry();
+
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ if (curpos >= size) goto end_restore;
+ if (selpos[curpos]) _selection.insert(j.ptr());
+ ++curpos;
+ }
+ }
+ end_restore:
+
+ _updateOutline();
+ } break;
+ case PATH_CHANGE_TRANSFORM: {
+ Geom::Matrix i2d_change = _d2i_transform;
+ _i2d_transform = sp_item_i2d_affine(SP_ITEM(_path));
+ _d2i_transform = _i2d_transform.inverse();
+ i2d_change *= _i2d_transform;
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ j->transform(i2d_change);
+ }
+ }
+ _updateOutline();
+ } break;
+ default: break;
+ }
+}
+
+/** Create nodes and handles based on the XML of the edited path. */
+void PathManipulator::_createControlPointsFromGeometry()
+{
+ clear();
+
+ // sanitize pathvector and store it in SPCurve,
+ // so that _updateDragPoint doesn't crash on paths with naked movetos
+ Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers(_spcurve->get_pathvector());
+ for (Geom::PathVector::iterator i = pathv.begin(); i != pathv.end(); ) {
+ if (i->empty()) pathv.erase(i++);
+ else ++i;
+ }
+ _spcurve->set_pathvector(pathv);
+
+ pathv *= (_edit_transform * _i2d_transform);
+
+ // in this loop, we know that there are no zero-segment subpaths
+ for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
+ // prepare new subpath
+ SubpathPtr subpath(new NodeList(_subpaths));
+ _subpaths.push_back(subpath);
+
+ Node *previous_node = new Node(_multi_path_manipulator._path_data.node_data, pit->initialPoint());
+ subpath->push_back(previous_node);
+ Geom::Curve const &cseg = pit->back_closed();
+ bool fuse_ends = pit->closed()
+ && Geom::are_near(cseg.initialPoint(), cseg.finalPoint());
+
+ for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_open(); ++cit) {
+ Geom::Point pos = cit->finalPoint();
+ Node *current_node;
+ // if the closing segment is degenerate and the path is closed, we need to move
+ // the handle of the first node instead of creating a new one
+ if (fuse_ends && cit == --(pit->end_open())) {
+ current_node = subpath->begin().get_pointer();
+ } else {
+ /* regardless of segment type, create a new node at the end
+ * of this segment (unless this is the last segment of a closed path
+ * with a degenerate closing segment */
+ current_node = new Node(_multi_path_manipulator._path_data.node_data, pos);
+ subpath->push_back(current_node);
+ }
+ // if this is a bezier segment, move handles appropriately
+ if (Geom::CubicBezier const *cubic_bezier =
+ dynamic_cast<Geom::CubicBezier const*>(&*cit))
+ {
+ std::vector<Geom::Point> points = cubic_bezier->points();
+
+ previous_node->front()->setPosition(points[1]);
+ current_node ->back() ->setPosition(points[2]);
+ }
+ previous_node = current_node;
+ }
+ // If the path is closed, make the list cyclic
+ if (pit->closed()) subpath->setClosed(true);
+ }
+
+ // we need to set the nodetypes after all the handles are in place,
+ // so that pickBestType works correctly
+ // TODO maybe migrate to inkscape:node-types?
+ gchar const *nts_raw = _path ? _path->repr->attribute(_nodetypesKey().data()) : 0;
+ std::string nodetype_string = nts_raw ? nts_raw : "";
+ /* Calculate the needed length of the nodetype string.
+ * For closed paths, the entry is duplicated for the starting node,
+ * so we can just use the count of segments including the closing one
+ * to include the extra end node. */
+ std::string::size_type nodetype_len = 0;
+ for (Geom::PathVector::const_iterator i = pathv.begin(); i != pathv.end(); ++i) {
+ if (i->empty()) continue;
+ nodetype_len += i->size_closed();
+ }
+ /* pad the string to required length with a bogus value.
+ * 'b' and any other letter not recognized by the parser causes the best fit to be set
+ * as the node type */
+ if (nodetype_len > nodetype_string.size()) {
+ nodetype_string.append(nodetype_len - nodetype_string.size(), 'b');
+ }
+ std::string::iterator tsi = nodetype_string.begin();
+ for (std::list<SubpathPtr>::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ j->setType(Node::parse_nodetype(*tsi++), false);
+ }
+ if ((*i)->closed()) {
+ // STUPIDITY ALERT: it seems we need to use the duplicate type symbol instead of
+ // the first one to remain backward compatible.
+ (*i)->begin()->setType(Node::parse_nodetype(*tsi++), false);
+ }
+ }
+}
+
+/** Construct the geometric representation of nodes and handles, update the outline
+ * and display */
+void PathManipulator::_createGeometryFromControlPoints()
+{
+ Geom::PathBuilder builder;
+ for (std::list<SubpathPtr>::iterator spi = _subpaths.begin(); spi != _subpaths.end(); ) {
+ SubpathPtr subpath = *spi;
+ if (subpath->empty()) {
+ _subpaths.erase(spi++);
+ continue;
+ }
+ NodeList::iterator prev = subpath->begin();
+ builder.moveTo(prev->position());
+
+ for (NodeList::iterator i = ++subpath->begin(); i != subpath->end(); ++i) {
+ build_segment(builder, prev.ptr(), i.ptr());
+ prev = i;
+ }
+ if (subpath->closed()) {
+ // Here we link the last and first node if the path is closed.
+ // If the last segment is Bezier, we add it.
+ if (!prev->front()->isDegenerate() || !subpath->begin()->back()->isDegenerate()) {
+ build_segment(builder, prev.ptr(), subpath->begin().ptr());
+ }
+ // if that segment is linear, we just call closePath().
+ builder.closePath();
+ }
+ ++spi;
+ }
+ builder.finish();
+ _spcurve->set_pathvector(builder.peek() * (_edit_transform * _i2d_transform).inverse());
+ _updateOutline();
+ _setGeometry();
+}
+
+/** Build one segment of the geometric representation.
+ * @relates PathManipulator */
+void build_segment(Geom::PathBuilder &builder, Node *prev_node, Node *cur_node)
+{
+ if (cur_node->back()->isDegenerate() && prev_node->front()->isDegenerate())
+ {
+ // NOTE: It seems like the renderer cannot correctly handle vline / hline segments,
+ // and trying to display a path using them results in funny artifacts.
+ builder.lineTo(cur_node->position());
+ } else {
+ // this is a bezier segment
+ builder.curveTo(
+ prev_node->front()->position(),
+ cur_node->back()->position(),
+ cur_node->position());
+ }
+}
+
+/** Construct a node type string to store in the sodipodi:nodetypes attribute. */
+std::string PathManipulator::_createTypeString()
+{
+ // precondition: no single-node subpaths
+ std::stringstream tstr;
+ for (std::list<SubpathPtr>::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ tstr << j->type();
+ }
+ // nodestring format peculiarity: first node is counted twice for closed paths
+ if ((*i)->closed()) tstr << (*i)->begin()->type();
+ }
+ return tstr.str();
+}
+
+/** Update the path outline. */
+void PathManipulator::_updateOutline()
+{
+ if (!_show_outline) {
+ sp_canvas_item_hide(_outline);
+ return;
+ }
+
+ Geom::PathVector pv = _spcurve->get_pathvector();
+ pv *= (_edit_transform * _i2d_transform);
+ // This SPCurve thing has to be killed with extreme prejudice
+ SPCurve *_hc = new SPCurve();
+ if (_show_path_direction) {
+ // To show the direction, we append additional subpaths which consist of a single
+ // linear segment that starts at the time value of 0.5 and extends for 10 pixels
+ // at an angle 150 degrees from the unit tangent. This creates the appearance
+ // of little 'harpoons' that show the direction of the subpaths.
+ Geom::PathVector arrows;
+ for (Geom::PathVector::iterator i = pv.begin(); i != pv.end(); ++i) {
+ Geom::Path &path = *i;
+ for (Geom::Path::const_iterator j = path.begin(); j != path.end_default(); ++j) {
+ Geom::Point at = j->pointAt(0.5);
+ Geom::Point ut = j->unitTangentAt(0.5);
+ // rotate the point
+ ut *= Geom::Rotate(150.0 / 180.0 * M_PI);
+ Geom::Point arrow_end = _desktop->w2d(
+ _desktop->d2w(at) + Geom::unit_vector(_desktop->d2w(ut)) * 10.0);
+
+ Geom::Path arrow(at);
+ arrow.appendNew<Geom::LineSegment>(arrow_end);
+ arrows.push_back(arrow);
+ }
+ }
+ pv.insert(pv.end(), arrows.begin(), arrows.end());
+ }
+ _hc->set_pathvector(pv);
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(_outline), _hc);
+ sp_canvas_item_show(_outline);
+ _hc->unref();
+}
+
+/** Retrieve the geometry of the edited object from the object tree */
+void PathManipulator::_getGeometry()
+{
+ using namespace Inkscape::LivePathEffect;
+ if (!_lpe_key.empty()) {
+ Effect *lpe = LIVEPATHEFFECT(_path)->get_lpe();
+ if (lpe) {
+ PathParam *pathparam = dynamic_cast<PathParam *>(lpe->getParameter(_lpe_key.data()));
+ if (!_spcurve)
+ _spcurve = new SPCurve(pathparam->get_pathvector());
+ else
+ _spcurve->set_pathvector(pathparam->get_pathvector());
+ }
+ } else {
+ if (_spcurve) _spcurve->unref();
+ _spcurve = sp_path_get_curve_for_edit(_path);
+ }
+}
+
+/** Set the geometry of the edited object in the object tree, but do not commit to XML */
+void PathManipulator::_setGeometry()
+{
+ using namespace Inkscape::LivePathEffect;
+ if (empty()) return;
+
+ if (!_lpe_key.empty()) {
+ // copied from nodepath.cpp
+ // NOTE: if we are editing an LPE param, _path is not actually an SPPath, it is
+ // a LivePathEffectObject. (mad laughter)
+ Effect *lpe = LIVEPATHEFFECT(_path)->get_lpe();
+ if (lpe) {
+ PathParam *pathparam = dynamic_cast<PathParam *>(lpe->getParameter(_lpe_key.data()));
+ pathparam->set_new_value(_spcurve->get_pathvector(), false);
+ LIVEPATHEFFECT(_path)->requestModified(SP_OBJECT_MODIFIED_FLAG);
+ }
+ } else {
+ if (_path->repr->attribute("inkscape:original-d"))
+ sp_path_set_original_curve(_path, _spcurve, true, false);
+ else
+ sp_shape_set_curve(SP_SHAPE(_path), _spcurve, false);
+ }
+}
+
+/** Figure out in what attribute to store the nodetype string. */
+Glib::ustring PathManipulator::_nodetypesKey()
+{
+ if (_lpe_key.empty()) return "sodipodi:nodetypes";
+ return _lpe_key + "-nodetypes";
+}
+
+/** Return the XML node we are editing.
+ * This method is wrong but necessary at the moment. */
+Inkscape::XML::Node *PathManipulator::_getXMLNode()
+{
+ if (_lpe_key.empty()) return _path->repr;
+ return LIVEPATHEFFECT(_path)->repr;
+}
+
+void PathManipulator::_attachNodeHandlers(Node *node)
+{
+ Handle *handles[2] = { node->front(), node->back() };
+ for (int i = 0; i < 2; ++i) {
+ handles[i]->signal_update.connect(
+ sigc::mem_fun(*this, &PathManipulator::update));
+ handles[i]->signal_ungrabbed.connect(
+ sigc::hide(
+ sigc::mem_fun(*this, &PathManipulator::_handleUngrabbed)));
+ handles[i]->signal_grabbed.connect(
+ sigc::bind_return(
+ sigc::hide(
+ sigc::mem_fun(*this, &PathManipulator::_handleGrabbed)),
+ false));
+ handles[i]->signal_clicked.connect(
+ sigc::bind<0>(
+ sigc::mem_fun(*this, &PathManipulator::_handleClicked),
+ handles[i]));
+ }
+ node->signal_clicked.connect(
+ sigc::bind<0>(
+ sigc::mem_fun(*this, &PathManipulator::_nodeClicked),
+ node));
+}
+void PathManipulator::_removeNodeHandlers(Node *node)
+{
+ // It is safe to assume that nobody else connected to handles' signals after us,
+ // so we pop our slots from the back. This preserves existing connections
+ // created by Node and Handle constructors.
+ Handle *handles[2] = { node->front(), node->back() };
+ for (int i = 0; i < 2; ++i) {
+ handles[i]->signal_update.slots().pop_back();
+ handles[i]->signal_grabbed.slots().pop_back();
+ handles[i]->signal_ungrabbed.slots().pop_back();
+ handles[i]->signal_clicked.slots().pop_back();
+ }
+ // Same for this one: CPS only connects to grab, drag, and ungrab
+ node->signal_clicked.slots().pop_back();
+}
+
+bool PathManipulator::_nodeClicked(Node *n, GdkEventButton *event)
+{
+ // cycle between node types on ctrl+click
+ if (event->button != 1 || !held_control(*event)) return false;
+ if (n->isEndNode()) {
+ if (n->type() == NODE_CUSP) {
+ n->setType(NODE_SMOOTH);
+ } else {
+ n->setType(NODE_CUSP);
+ }
+ } else {
+ n->setType(static_cast<NodeType>((n->type() + 1) % NODE_LAST_REAL_TYPE));
+ }
+ update();
+ _commit(_("Cycle node type"));
+ return true;
+}
+
+void PathManipulator::_handleGrabbed()
+{
+ _selection.hideTransformHandles();
+}
+
+void PathManipulator::_handleUngrabbed()
+{
+ _selection.restoreTransformHandles();
+ _commit(_("Drag handle"));
+}
+
+bool PathManipulator::_handleClicked(Handle *h, GdkEventButton *event)
+{
+ // retracting by Ctrl+click
+ if (event->button == 1 && held_control(*event)) {
+ h->move(h->parent()->position());
+ update();
+ _commit(_("Retract handle"));
+ return true;
+ }
+ return false;
+}
+
+void PathManipulator::_selectionChanged(SelectableControlPoint *p, bool selected)
+{
+ // don't do anything if we do not show handles
+ if (!_show_handles) return;
+
+ // only do something if a node changed selection state
+ Node *node = dynamic_cast<Node*>(p);
+ if (!node) return;
+
+ // update handle display
+ NodeList::iterator iters[5];
+ iters[2] = NodeList::get_iterator(node);
+ iters[1] = iters[2].prev();
+ iters[3] = iters[2].next();
+ if (selected) {
+ // selection - show handles on this node and adjacent ones
+ node->showHandles(true);
+ if (iters[1]) iters[1]->showHandles(true);
+ if (iters[3]) iters[3]->showHandles(true);
+ } else {
+ /* Deselection is more complex.
+ * The change might affect 3 nodes - this one and two adjacent.
+ * If the node and both its neighbors are deselected, hide handles.
+ * Otherwise, leave as is. */
+ if (iters[1]) iters[0] = iters[1].prev();
+ if (iters[3]) iters[4] = iters[3].next();
+ bool nodesel[5];
+ for (int i = 0; i < 5; ++i) {
+ nodesel[i] = iters[i] && iters[i]->selected();
+ }
+ for (int i = 1; i < 4; ++i) {
+ if (iters[i] && !nodesel[i-1] && !nodesel[i] && !nodesel[i+1]) {
+ iters[i]->showHandles(false);
+ }
+ }
+ }
+
+ if (selected) ++_num_selected;
+ else --_num_selected;
+}
+
+/** Removes all nodes belonging to this manipulator from the control pont selection */
+void PathManipulator::_removeNodesFromSelection()
+{
+ // remove this manipulator's nodes from selection
+ for (std::list<SubpathPtr>::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ _selection.erase(j.get_pointer());
+ }
+ }
+}
+
+/** Update the XML representation and put the specified annotation on the undo stack */
+void PathManipulator::_commit(Glib::ustring const &annotation)
+{
+ writeXML();
+ sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, annotation.data());
+}
+
+/** Update the position of the curve drag point such that it is over the nearest
+ * point of the path. */
+void PathManipulator::_updateDragPoint(Geom::Point const &evp)
+{
+ // TODO find a way to make this faster (no transform required)
+ Geom::PathVector pv = _spcurve->get_pathvector() * (_edit_transform * _i2d_transform);
+ boost::optional<Geom::PathVectorPosition> pvp
+ = Geom::nearestPoint(pv, _desktop->w2d(evp));
+ if (!pvp) return;
+ Geom::Point nearest_point = _desktop->d2w(pv.at(pvp->path_nr).pointAt(pvp->t));
+
+ double fracpart;
+ std::list<SubpathPtr>::iterator spi = _subpaths.begin();
+ for (unsigned i = 0; i < pvp->path_nr; ++i, ++spi) {}
+ NodeList::iterator first = (*spi)->before(pvp->t, &fracpart);
+
+ double stroke_tolerance = _getStrokeTolerance();
+ if (Geom::distance(evp, nearest_point) < stroke_tolerance) {
+ _dragpoint->setVisible(true);
+ _dragpoint->setPosition(_desktop->w2d(nearest_point));
+ _dragpoint->setSize(2 * stroke_tolerance);
+ _dragpoint->setTimeValue(fracpart);
+ _dragpoint->setIterator(first);
+ } else {
+ _dragpoint->setVisible(false);
+ }
+}
+
+/// This is called on zoom change to update the direction arrows
+void PathManipulator::_updateOutlineOnZoomChange()
+{
+ if (_show_path_direction) _updateOutline();
+}
+
+/** Compute the radius from the edge of the path where clicks chould initiate a curve drag
+ * or segment selection, in window coordinates. */
+double PathManipulator::_getStrokeTolerance()
+{
+ /* Stroke event tolerance is equal to half the stroke's width plus the global
+ * drag tolerance setting. */
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double ret = prefs->getIntLimited("/options/dragtolerance/value", 2, 0, 100);
+ if (_path && !SP_OBJECT_STYLE(_path)->stroke.isNone()) {
+ ret += SP_OBJECT_STYLE(_path)->stroke_width.computed * 0.5
+ * (_edit_transform * _i2d_transform).descrim() // scale to desktop coords
+ * _desktop->current_zoom(); // == _d2w.descrim() - scale to window coords
+ }
+ return ret;
+}
+
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/path-manipulator.h b/src/ui/tool/path-manipulator.h
new file mode 100644
index 000000000..99e183b45
--- /dev/null
+++ b/src/ui/tool/path-manipulator.h
@@ -0,0 +1,154 @@
+/** @file
+ * Path manipulator - a component that edits a single path on-canvas
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_PATH_MANIPULATOR_H
+#define SEEN_UI_TOOL_PATH_MANIPULATOR_H
+
+#include <string>
+#include <memory>
+#include <2geom/pathvector.h>
+#include <2geom/matrix.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include "display/display-forward.h"
+#include "forward.h"
+#include "ui/tool/node.h"
+#include "ui/tool/manipulator.h"
+
+struct SPCanvasItem;
+
+namespace Inkscape {
+namespace XML { class Node; }
+
+namespace UI {
+
+class PathManipulator;
+class ControlPointSelection;
+class PathManipulatorObserver;
+class CurveDragPoint;
+class PathCanvasGroups;
+class MultiPathManipulator;
+class Node;
+
+struct PathSharedData {
+ NodeSharedData node_data;
+ SPCanvasGroup *outline_group;
+ SPCanvasGroup *dragpoint_group;
+};
+
+/**
+ * Manipulator that edits a single path using nodes with handles.
+ * Currently only cubic bezier and linear segments are supported, but this might change
+ * some time in the future.
+ */
+class PathManipulator : public PointManipulator {
+public:
+ typedef SPPath *ItemType;
+
+ PathManipulator(MultiPathManipulator &mpm, SPPath *path, Geom::Matrix const &edit_trans,
+ guint32 outline_color, Glib::ustring lpe_key);
+ ~PathManipulator();
+ virtual bool event(GdkEvent *);
+
+ bool empty();
+ void writeXML();
+ void update(); // update display, but don't commit
+ void clear(); // remove all nodes from manipulator
+ SPPath *item() { return _path; }
+
+ void selectSubpaths();
+ void shiftSelection(int dir);
+ void invertSelectionInSubpaths();
+
+ void insertNodes();
+ void weldNodes(NodeList::iterator preserve_pos = NodeList::iterator());
+ void weldSegments();
+ void breakNodes();
+ void deleteNodes(bool keep_shape = true);
+ void deleteSegments();
+ void reverseSubpaths();
+ void setSegmentType(SegmentType);
+
+ void showOutline(bool show);
+ void showHandles(bool show);
+ void showPathDirection(bool show);
+ void setControlsTransform(Geom::Matrix const &);
+ void hideDragPoint();
+
+ NodeList::iterator subdivideSegment(NodeList::iterator after, double t);
+ NodeList::iterator extremeNode(NodeList::iterator origin, bool search_selected,
+ bool search_unselected, bool closest);
+
+ static bool is_item_type(void *item);
+private:
+ typedef NodeList Subpath;
+ typedef boost::shared_ptr<NodeList> SubpathPtr;
+
+ void _createControlPointsFromGeometry();
+ void _createGeometryFromControlPoints();
+ std::string _createTypeString();
+ void _updateOutline();
+ //void _setOutline(Geom::PathVector const &);
+ void _getGeometry();
+ void _setGeometry();
+ Glib::ustring _nodetypesKey();
+ Inkscape::XML::Node *_getXMLNode();
+
+ void _attachNodeHandlers(Node *n);
+ void _removeNodeHandlers(Node *n);
+
+ void _selectionChanged(SelectableControlPoint *p, bool selected);
+ bool _nodeClicked(Node *, GdkEventButton *);
+ void _handleGrabbed();
+ bool _handleClicked(Handle *, GdkEventButton *);
+ void _handleUngrabbed();
+ void _externalChange(unsigned type);
+ void _removeNodesFromSelection();
+ void _commit(Glib::ustring const &annotation);
+ void _updateDragPoint(Geom::Point const &);
+ void _updateOutlineOnZoomChange();
+ double _getStrokeTolerance();
+
+ SubpathList _subpaths;
+ MultiPathManipulator &_multi_path_manipulator;
+ SPPath *_path;
+ SPCurve *_spcurve; // in item coordinates
+ SPCanvasItem *_outline;
+ CurveDragPoint *_dragpoint; // an invisible control point hoverng over curve
+ PathManipulatorObserver *_observer;
+ Geom::Matrix _d2i_transform; ///< desktop-to-item transform
+ Geom::Matrix _i2d_transform; ///< item-to-desktop transform, inverse of _d2i_transform
+ Geom::Matrix _edit_transform; ///< additional transform to apply to editing controls
+ unsigned _num_selected; ///< number of selected nodes
+ bool _show_handles;
+ bool _show_outline;
+ bool _show_path_direction;
+ Glib::ustring _lpe_key;
+
+ friend class PathManipulatorObserver;
+ friend class CurveDragPoint;
+ friend class Node;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/selectable-control-point.cpp b/src/ui/tool/selectable-control-point.cpp
new file mode 100644
index 000000000..5b9aa4fc8
--- /dev/null
+++ b/src/ui/tool/selectable-control-point.cpp
@@ -0,0 +1,137 @@
+/** @file
+ * Desktop-bound selectable control object - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/selectable-control-point.h"
+
+namespace Inkscape {
+namespace UI {
+
+static SelectableControlPoint::ColorSet default_scp_color_set = {
+ {
+ {0xffffff00, 0x01000000}, // normal fill, stroke
+ {0xff0000ff, 0x01000000}, // mouseover fill, stroke
+ {0x0000ffff, 0x01000000} // clicked fill, stroke
+ },
+ {0x0000ffff, 0x000000ff}, // normal fill, stroke when selected
+ {0xff000000, 0x000000ff}, // mouseover fill, stroke when selected
+ {0xff000000, 0x000000ff} // clicked fill, stroke when selected
+};
+
+SelectableControlPoint::SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos,
+ Gtk::AnchorType anchor, SPCtrlShapeType shape, unsigned int size,
+ ControlPointSelection &sel, ColorSet *cset, SPCanvasGroup *group)
+ : ControlPoint (d, initial_pos, anchor, shape, size,
+ cset ? reinterpret_cast<ControlPoint::ColorSet*>(cset)
+ : reinterpret_cast<ControlPoint::ColorSet*>(&default_scp_color_set), group)
+ , _selection (sel)
+{
+ _connectHandlers();
+}
+SelectableControlPoint::SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos,
+ Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pixbuf,
+ ControlPointSelection &sel, ColorSet *cset, SPCanvasGroup *group)
+ : ControlPoint (d, initial_pos, anchor, pixbuf,
+ cset ? reinterpret_cast<ControlPoint::ColorSet*>(cset)
+ : reinterpret_cast<ControlPoint::ColorSet*>(&default_scp_color_set), group)
+ , _selection (sel)
+{
+ _connectHandlers();
+}
+
+SelectableControlPoint::~SelectableControlPoint()
+{
+ _selection.erase(this);
+ _selection.allPoints().erase(this);
+}
+
+void SelectableControlPoint::_connectHandlers()
+{
+ _selection.allPoints().insert(this);
+ signal_clicked.connect(
+ sigc::mem_fun(*this, &SelectableControlPoint::_clickedHandler));
+ signal_grabbed.connect(
+ sigc::bind_return(
+ sigc::mem_fun(*this, &SelectableControlPoint::_grabbedHandler),
+ false));
+}
+
+void SelectableControlPoint::_grabbedHandler(GdkEventMotion *event)
+{
+ // if a point is dragged while not selected, it should select itself
+ if (!selected()) {
+ _takeSelection();
+ // HACK!!! invoke the last slot for signal_grabbed (it will be the callback registered
+ // by ControlPointSelection when adding to selection).
+ signal_grabbed.slots().back()(event);
+ }
+}
+bool SelectableControlPoint::_clickedHandler(GdkEventButton *event)
+{
+ if (event->button != 1) return false;
+ if (held_shift(*event)) {
+ if (selected()) {
+ _selection.erase(this);
+ } else {
+ _selection.insert(this);
+ }
+ } else {
+ _takeSelection();
+ }
+ return true;
+}
+
+void SelectableControlPoint::_takeSelection()
+{
+ _selection.clear();
+ _selection.insert(this);
+}
+
+bool SelectableControlPoint::selected() const
+{
+ SelectableControlPoint *p = const_cast<SelectableControlPoint*>(this);
+ return _selection.find(p) != _selection.end();
+}
+
+void SelectableControlPoint::_setState(State state)
+{
+ if (!selected()) {
+ ControlPoint::_setState(state);
+ return;
+ }
+
+ ColorSet *cset = reinterpret_cast<ColorSet*>(_cset);
+ ColorEntry current = {0, 0};
+ switch (state) {
+ case STATE_NORMAL:
+ current = cset->selected_normal; break;
+ case STATE_MOUSEOVER:
+ current = cset->selected_mouseover; break;
+ case STATE_CLICKED:
+ current = cset->selected_clicked; break;
+ }
+ _setColors(current);
+ _state = state;
+}
+
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/selectable-control-point.h b/src/ui/tool/selectable-control-point.h
new file mode 100644
index 000000000..87e415258
--- /dev/null
+++ b/src/ui/tool/selectable-control-point.h
@@ -0,0 +1,71 @@
+/** @file
+ * Desktop-bound selectable control object
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_SELECTABLE_CONTROL_POINT_H
+#define SEEN_UI_TOOL_SELECTABLE_CONTROL_POINT_H
+
+#include <boost/enable_shared_from_this.hpp>
+#include "ui/tool/control-point.h"
+
+namespace Inkscape {
+namespace UI {
+
+class ControlPointSelection;
+
+class SelectableControlPoint : public ControlPoint {
+public:
+ struct ColorSet {
+ ControlPoint::ColorSet cpset;
+ ColorEntry selected_normal;
+ ColorEntry selected_mouseover;
+ ColorEntry selected_clicked;
+ };
+
+ ~SelectableControlPoint();
+ bool selected() const;
+ void updateState() const { const_cast<SelectableControlPoint*>(this)->_setState(_state); }
+ virtual Geom::Rect bounds() {
+ return Geom::Rect(position(), position());
+ }
+protected:
+ SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos,
+ Gtk::AnchorType anchor, SPCtrlShapeType shape,
+ unsigned int size, ControlPointSelection &sel, ColorSet *cset = 0,
+ SPCanvasGroup *group = 0);
+ SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos,
+ Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pixbuf,
+ ControlPointSelection &sel, ColorSet *cset = 0, SPCanvasGroup *group = 0);
+
+ virtual void _setState(State state);
+
+ ControlPointSelection &_selection;
+private:
+ void _connectHandlers();
+ void _takeSelection();
+
+ bool _clickedHandler(GdkEventButton *);
+ void _grabbedHandler(GdkEventMotion *);
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/selector.cpp b/src/ui/tool/selector.cpp
new file mode 100644
index 000000000..f95c9e064
--- /dev/null
+++ b/src/ui/tool/selector.cpp
@@ -0,0 +1,133 @@
+/** @file
+ * Selector component (click and rubberband)
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "display/sodipodi-ctrlrect.h"
+#include "event-context.h"
+#include "preferences.h"
+#include "ui/tool/control-point.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/selector.h"
+
+namespace Inkscape {
+namespace UI {
+
+/** A hidden control point used for rubberbanding and selection.
+ * It uses a clever hack: the canvas item is hidden and only receives events when fed */
+class SelectorPoint : public ControlPoint {
+public:
+ SelectorPoint(SPDesktop *d, SPCanvasGroup *group, Selector *s)
+ : ControlPoint(d, Geom::Point(0,0), Gtk::ANCHOR_CENTER, SP_CTRL_SHAPE_SQUARE,
+ 1, &invisible_cset, group)
+ , _selector(s)
+ , _cancel(false)
+ {
+ setVisible(false);
+ _rubber = static_cast<CtrlRect*>(sp_canvas_item_new(sp_desktop_controls(_desktop),
+ SP_TYPE_CTRLRECT, NULL));
+ sp_canvas_item_hide(_rubber);
+
+ signal_clicked.connect(sigc::mem_fun(*this, &SelectorPoint::_clicked));
+ signal_grabbed.connect(
+ sigc::bind_return(
+ sigc::hide(
+ sigc::mem_fun(*this, &SelectorPoint::_grabbed)),
+ false));
+ signal_dragged.connect(
+ sigc::hide<0>( sigc::hide(
+ sigc::mem_fun(*this, &SelectorPoint::_dragged))));
+ signal_ungrabbed.connect(sigc::mem_fun(*this, &SelectorPoint::_ungrabbed));
+ }
+ ~SelectorPoint() {
+ gtk_object_destroy(_rubber);
+ }
+ SPDesktop *desktop() { return _desktop; }
+ bool event(GdkEvent *e) {
+ return _eventHandler(e);
+ }
+
+protected:
+ virtual bool _eventHandler(GdkEvent *event) {
+ if (event->type == GDK_KEY_PRESS && shortcut_key(event->key) == GDK_Escape &&
+ sp_canvas_item_is_visible(_rubber))
+ {
+ _cancel = true;
+ sp_canvas_item_hide(_rubber);
+ return true;
+ }
+ return ControlPoint::_eventHandler(event);
+ }
+
+private:
+ bool _clicked(GdkEventButton *event) {
+ if (event->button != 1) return false;
+ _selector->signal_point.emit(position(), event);
+ return true;
+ }
+ void _grabbed() {
+ _cancel = false;
+ _start = position();
+ sp_canvas_item_show(_rubber);
+ }
+ void _dragged(Geom::Point &new_pos) {
+ if (_cancel) return;
+ Geom::Rect sel(_start, new_pos);
+ _rubber->setRectangle(sel);
+ }
+ void _ungrabbed(GdkEventButton *event) {
+ if (_cancel) return;
+ sp_canvas_item_hide(_rubber);
+ Geom::Rect sel(_start, position());
+ _selector->signal_area.emit(sel, event);
+ }
+ CtrlRect *_rubber;
+ Selector *_selector;
+ Geom::Point _start;
+ bool _cancel;
+};
+
+
+Selector::Selector(SPDesktop *d)
+ : Manipulator(d)
+ , _dragger(new SelectorPoint(d, sp_desktop_controls(d), this))
+{
+ _dragger->setVisible(false);
+}
+
+Selector::~Selector()
+{
+ delete _dragger;
+}
+
+bool Selector::event(GdkEvent *event)
+{
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ _dragger->setPosition(_desktop->w2d(event_point(event->motion)));
+ break;
+ default: break;
+ }
+ return _dragger->event(event);
+}
+
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/selector.h b/src/ui/tool/selector.h
new file mode 100644
index 000000000..f7c00ea71
--- /dev/null
+++ b/src/ui/tool/selector.h
@@ -0,0 +1,59 @@
+/** @file
+ * Selector component (click and rubberband)
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_SELECTOR_H
+#define SEEN_UI_TOOL_SELECTOR_H
+
+#include <memory>
+#include <gdk/gdk.h>
+#include <2geom/rect.h>
+#include "display/display-forward.h"
+#include "ui/tool/manipulator.h"
+
+class SPDesktop;
+class CtrlRect;
+
+namespace Inkscape {
+namespace UI {
+
+class SelectorPoint;
+
+class Selector : public Manipulator {
+public:
+ Selector(SPDesktop *d);
+ virtual ~Selector();
+ virtual bool event(GdkEvent *);
+
+ sigc::signal<void, Geom::Rect const &, GdkEventButton*> signal_area;
+ sigc::signal<void, Geom::Point const &, GdkEventButton*> signal_point;
+private:
+ SelectorPoint *_dragger;
+ Geom::Point _start;
+ CtrlRect *_rubber;
+ gulong _connection;
+ bool _cancel;
+ friend class SelectorPoint;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/shape-record.h b/src/ui/tool/shape-record.h
new file mode 100644
index 000000000..fdb49ac36
--- /dev/null
+++ b/src/ui/tool/shape-record.h
@@ -0,0 +1,57 @@
+/** @file
+ * Structures that store data needed for shape editing which are not contained
+ * directly in the XML node
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_SHAPE_RECORD_H
+#define SEEN_UI_TOOL_SHAPE_RECORD_H
+
+#include <glibmm/ustring.h>
+#include <boost/operators.hpp>
+#include <2geom/matrix.h>
+
+class SPItem;
+namespace Inkscape {
+namespace UI {
+
+/** Role of the shape in the drawing - affects outline display and color */
+enum ShapeRole {
+ SHAPE_ROLE_NORMAL,
+ SHAPE_ROLE_CLIPPING_PATH,
+ SHAPE_ROLE_MASK,
+ SHAPE_ROLE_LPE_PARAM // implies edit_original set to true in ShapeRecord
+};
+
+struct ShapeRecord :
+ public boost::totally_ordered<ShapeRecord>
+{
+ SPItem *item; // SP node for the edited shape
+ Geom::Matrix edit_transform; // how to transform controls - used for clipping paths and masks
+ ShapeRole role;
+ Glib::ustring lpe_key; // name of LPE shape param being edited
+
+ inline bool operator==(ShapeRecord const &o) const { return item == o.item; }
+ inline bool operator<(ShapeRecord const &o) const { return item < o.item; }
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/transform-handle-set.cpp b/src/ui/tool/transform-handle-set.cpp
new file mode 100644
index 000000000..cf8907299
--- /dev/null
+++ b/src/ui/tool/transform-handle-set.cpp
@@ -0,0 +1,644 @@
+/** @file
+ * Affine transform handles component
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <math.h>
+#include <algorithm>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <2geom/transforms.h>
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "display/sodipodi-ctrlrect.h"
+#include "preferences.h"
+#include "ui/tool/commit-events.h"
+#include "ui/tool/control-point.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/transform-handle-set.h"
+
+// FIXME BRAIN DAMAGE WARNING: this is a global variable in select-context.cpp
+// It should be moved to a header
+extern GdkPixbuf *handles[];
+GType sp_select_context_get_type();
+
+namespace Inkscape {
+namespace UI {
+
+namespace {
+Gtk::AnchorType corner_to_anchor(unsigned c) {
+ switch (c % 4) {
+ case 0: return Gtk::ANCHOR_NE;
+ case 1: return Gtk::ANCHOR_NW;
+ case 2: return Gtk::ANCHOR_SW;
+ default: return Gtk::ANCHOR_SE;
+ }
+}
+Gtk::AnchorType side_to_anchor(unsigned s) {
+ switch (s % 4) {
+ case 0: return Gtk::ANCHOR_N;
+ case 1: return Gtk::ANCHOR_W;
+ case 2: return Gtk::ANCHOR_S;
+ default: return Gtk::ANCHOR_E;
+ }
+}
+
+// TODO move those two functions into a common place
+double snap_angle(double a) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
+ double unit_angle = M_PI / snaps;
+ return CLAMP(unit_angle * round(a / unit_angle), -M_PI, M_PI);
+}
+double snap_increment_degrees() {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
+ return 180.0 / snaps;
+}
+
+ControlPoint::ColorSet thandle_cset = {
+ {0x000000ff, 0x000000ff},
+ {0x00ff6600, 0x000000ff},
+ {0x00ff6600, 0x000000ff}
+};
+
+ControlPoint::ColorSet center_cset = {
+ {0x00000000, 0x000000ff},
+ {0x00000000, 0xff0000b0},
+ {0x00000000, 0xff0000b0}
+};
+} // anonymous namespace
+
+/** Base class for node transform handles to simplify implementation */
+class TransformHandle : public ControlPoint {
+public:
+ TransformHandle(TransformHandleSet &th, Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pb)
+ : ControlPoint(th._desktop, Geom::Point(), anchor, pb, &thandle_cset,
+ th._transform_handle_group)
+ , _th(th)
+ {
+ setVisible(false);
+ signal_grabbed.connect(
+ sigc::bind_return(
+ sigc::hide(
+ sigc::mem_fun(*this, &TransformHandle::_grabbedHandler)),
+ false));
+ signal_dragged.connect(
+ sigc::hide<0>(
+ sigc::mem_fun(*this, &TransformHandle::_draggedHandler)));
+ signal_ungrabbed.connect(
+ sigc::hide(
+ sigc::mem_fun(*this, &TransformHandle::_ungrabbedHandler)));
+ }
+protected:
+ virtual void startTransform() {}
+ virtual void endTransform() {}
+ virtual Geom::Matrix computeTransform(Geom::Point const &pos, GdkEventMotion *event) = 0;
+ virtual CommitEvent getCommitEvent() = 0;
+
+ Geom::Matrix _last_transform;
+ Geom::Point _origin;
+ TransformHandleSet &_th;
+private:
+ void _grabbedHandler() {
+ _origin = position();
+ _last_transform.setIdentity();
+ startTransform();
+
+ _th._setActiveHandle(this);
+ _cset = &invisible_cset;
+ _setState(_state);
+ }
+ void _draggedHandler(Geom::Point &new_pos, GdkEventMotion *event)
+ {
+ Geom::Matrix t = computeTransform(new_pos, event);
+ // protect against degeneracies
+ if (t.isSingular()) return;
+ Geom::Matrix incr = _last_transform.inverse() * t;
+ if (incr.isSingular()) return;
+ _th.signal_transform.emit(incr);
+ _last_transform = t;
+ }
+ void _ungrabbedHandler() {
+ _th._clearActiveHandle();
+ _cset = &thandle_cset;
+ _setState(_state);
+ endTransform();
+ _th.signal_commit.emit(getCommitEvent());
+ }
+};
+
+class ScaleHandle : public TransformHandle {
+public:
+ ScaleHandle(TransformHandleSet &th, Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pb)
+ : TransformHandle(th, anchor, pb)
+ {}
+protected:
+ virtual Glib::ustring _getTip(unsigned state) {
+ if (state_held_control(state)) {
+ if (state_held_shift(state)) {
+ return C_("Transform handle tip",
+ "<b>Shift+Ctrl:</b> scale uniformly about the rotation center");
+ }
+ return C_("Transform handle tip", "<b>Ctrl:</b> scale uniformly");
+ }
+ if (state_held_shift(state)) {
+ if (state_held_alt(state)) {
+ return C_("Transform handle tip",
+ "<b>Shift+Alt:</b> scale using an integer ratio about the rotation center");
+ }
+ return C_("Transform handle tip", "<b>Shift:</b> scale from the rotation center");
+ }
+ if (state_held_alt(state)) {
+ return C_("Transform handle tip", "<b>Alt:</b> scale using an integer ratio");
+ }
+ return C_("Transform handle tip", "<b>Scale handle:</b> drag to scale the selection");
+ }
+ virtual Glib::ustring _getDragTip(GdkEventMotion *event) {
+ return format_tip(C_("Transform handle tip",
+ "Scale by %.2f%% x %.2f%%"), _last_scale_x * 100, _last_scale_y * 100);
+ }
+ virtual bool _hasDragTips() { return true; }
+
+ static double _last_scale_x, _last_scale_y;
+};
+double ScaleHandle::_last_scale_x = 1.0;
+double ScaleHandle::_last_scale_y = 1.0;
+
+/// Corner scaling handle for node transforms
+class ScaleCornerHandle : public ScaleHandle {
+public:
+ ScaleCornerHandle(TransformHandleSet &th, unsigned corner)
+ : ScaleHandle(th, corner_to_anchor(corner), _corner_to_pixbuf(corner))
+ , _corner(corner)
+ {}
+protected:
+ virtual void startTransform() {
+ _sc_center = _th.rotationCenter();
+ _sc_opposite = _th.bounds().corner(_corner + 2);
+ _last_scale_x = _last_scale_y = 1.0;
+ }
+ virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) {
+ Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite;
+ Geom::Point vold = _origin - scc, vnew = new_pos - scc;
+ // avoid exploding the selection
+ if (Geom::are_near(vold[Geom::X], 0) || Geom::are_near(vold[Geom::Y], 0))
+ return Geom::identity();
+
+ double scale[2] = { vnew[Geom::X] / vold[Geom::X], vnew[Geom::Y] / vold[Geom::Y] };
+ if (held_alt(*event)) {
+ for (unsigned i = 0; i < 2; ++i) {
+ if (scale[i] >= 1.0) scale[i] = round(scale[i]);
+ else scale[i] = 1.0 / round(1.0 / scale[i]);
+ }
+ } else if (held_control(*event)) {
+ scale[0] = scale[1] = std::min(scale[0], scale[1]);
+ }
+ _last_scale_x = scale[0];
+ _last_scale_y = scale[1];
+ Geom::Matrix t = Geom::Translate(-scc)
+ * Geom::Scale(scale[0], scale[1])
+ * Geom::Translate(scc);
+ return t;
+ }
+ virtual CommitEvent getCommitEvent() {
+ return _last_transform.isUniformScale()
+ ? COMMIT_MOUSE_SCALE_UNIFORM
+ : COMMIT_MOUSE_SCALE;
+ }
+private:
+ static Glib::RefPtr<Gdk::Pixbuf> _corner_to_pixbuf(unsigned c) {
+ sp_select_context_get_type();
+ switch (c % 2) {
+ case 0: return Glib::wrap(handles[1], true);
+ default: return Glib::wrap(handles[0], true);
+ }
+ }
+ Geom::Point _sc_center;
+ Geom::Point _sc_opposite;
+ unsigned _corner;
+};
+
+/// Side scaling handle for node transforms
+class ScaleSideHandle : public ScaleHandle {
+public:
+ ScaleSideHandle(TransformHandleSet &th, unsigned side)
+ : ScaleHandle(th, side_to_anchor(side), _side_to_pixbuf(side))
+ , _side(side)
+ {}
+protected:
+ virtual void startTransform() {
+ _sc_center = _th.rotationCenter();
+ Geom::Rect b = _th.bounds();
+ _sc_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3));
+ _last_scale_x = _last_scale_y = 1.0;
+ }
+ virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) {
+ Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite;
+ Geom::Point vs;
+ Geom::Dim2 d1 = static_cast<Geom::Dim2>((_side + 1) % 2);
+ Geom::Dim2 d2 = static_cast<Geom::Dim2>(_side % 2);
+
+ // avoid exploding the selection
+ if (Geom::are_near(scc[d1], _origin[d1]))
+ return Geom::identity();
+
+ vs[d1] = (new_pos - scc)[d1] / (_origin - scc)[d1];
+ if (held_alt(*event)) {
+ if (vs[d1] >= 1.0) vs[d1] = round(vs[d1]);
+ else vs[d1] = 1.0 / round(1.0 / vs[d1]);
+ }
+ vs[d2] = held_control(*event) ? vs[d1] : 1.0;
+
+ _last_scale_x = vs[Geom::X];
+ _last_scale_y = vs[Geom::Y];
+ Geom::Matrix t = Geom::Translate(-scc)
+ * Geom::Scale(vs)
+ * Geom::Translate(scc);
+ return t;
+ }
+ virtual CommitEvent getCommitEvent() {
+ return _last_transform.isUniformScale()
+ ? COMMIT_MOUSE_SCALE_UNIFORM
+ : COMMIT_MOUSE_SCALE;
+ }
+private:
+ static Glib::RefPtr<Gdk::Pixbuf> _side_to_pixbuf(unsigned c) {
+ sp_select_context_get_type();
+ switch (c % 2) {
+ case 0: return Glib::wrap(handles[3], true);
+ default: return Glib::wrap(handles[2], true);
+ }
+ }
+ Geom::Point _sc_center;
+ Geom::Point _sc_opposite;
+ unsigned _side;
+};
+
+/// Rotation handle for node transforms
+class RotateHandle : public TransformHandle {
+public:
+ RotateHandle(TransformHandleSet &th, unsigned corner)
+ : TransformHandle(th, corner_to_anchor(corner), _corner_to_pixbuf(corner))
+ , _corner(corner)
+ {}
+protected:
+ virtual void startTransform() {
+ _rot_center = _th.rotationCenter();
+ _rot_opposite = _th.bounds().corner(_corner + 2);
+ _last_angle = 0;
+ }
+ virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event)
+ {
+ Geom::Point rotc = held_shift(*event) ? _rot_opposite : _rot_center;
+ double angle = Geom::angle_between(_origin - rotc, new_pos - rotc);
+ if (held_control(*event)) {
+ angle = snap_angle(angle);
+ }
+ _last_angle = angle;
+ Geom::Matrix t = Geom::Translate(-rotc)
+ * Geom::Rotate(angle)
+ * Geom::Translate(rotc);
+ return t;
+ }
+ virtual CommitEvent getCommitEvent() { return COMMIT_MOUSE_ROTATE; }
+ virtual Glib::ustring _getTip(unsigned state) {
+ if (state_held_shift(state)) {
+ if (state_held_control(state)) {
+ return format_tip(C_("Transform handle tip",
+ "<b>Shift+Ctrl:</b> rotate around the opposite corner and snap "
+ "angle to %f° increments"), snap_increment_degrees());
+ }
+ return C_("Transform handle tip", "<b>Shift:</b> rotate around the opposite corner");
+ }
+ if (state_held_control(state)) {
+ return format_tip(C_("Transform handle tip",
+ "<b>Ctrl:</b> snap angle to %f° increments"), snap_increment_degrees());
+ }
+ return C_("Transform handle tip", "<b>Rotation handle:</b> drag to rotate "
+ "the selection around the rotation center");
+ }
+ virtual Glib::ustring _getDragTip(GdkEventMotion *event) {
+ return format_tip(C_("Transform handle tip", "Rotate by %.2f°"),
+ _last_angle * 360.0);
+ }
+ virtual bool _hasDragTips() { return true; }
+private:
+ static Glib::RefPtr<Gdk::Pixbuf> _corner_to_pixbuf(unsigned c) {
+ sp_select_context_get_type();
+ switch (c % 4) {
+ case 0: return Glib::wrap(handles[10], true);
+ case 1: return Glib::wrap(handles[8], true);
+ case 2: return Glib::wrap(handles[6], true);
+ default: return Glib::wrap(handles[4], true);
+ }
+ }
+ Geom::Point _rot_center;
+ Geom::Point _rot_opposite;
+ unsigned _corner;
+ static double _last_angle;
+};
+double RotateHandle::_last_angle = 0;
+
+class SkewHandle : public TransformHandle {
+public:
+ SkewHandle(TransformHandleSet &th, unsigned side)
+ : TransformHandle(th, side_to_anchor(side), _side_to_pixbuf(side))
+ , _side(side)
+ {}
+protected:
+ virtual void startTransform() {
+ _skew_center = _th.rotationCenter();
+ Geom::Rect b = _th.bounds();
+ _skew_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3));
+ _last_angle = 0;
+ _last_horizontal = _side % 2;
+ }
+ virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event)
+ {
+ Geom::Point scc = held_shift(*event) ? _skew_center : _skew_opposite;
+ // d1 and d2 are reversed with respect to ScaleSideHandle
+ Geom::Dim2 d1 = static_cast<Geom::Dim2>(_side % 2);
+ Geom::Dim2 d2 = static_cast<Geom::Dim2>((_side + 1) % 2);
+ Geom::Point proj, scale(1.0, 1.0);
+
+ // Skew handles allow scaling up to integer multiples of the original size
+ // in the second direction; prevent explosions
+ // TODO should the scaling part be only active with Alt?
+ if (!Geom::are_near(_origin[d2], scc[d2])) {
+ scale[d2] = (new_pos - scc)[d2] / (_origin - scc)[d2];
+ }
+
+ if (scale[d2] < 1.0) {
+ scale[d2] = copysign(1.0, scale[d2]);
+ } else {
+ scale[d2] = floor(scale[d2]);
+ }
+
+ // Calculate skew angle. The angle is calculated with regards to the point obtained
+ // by projecting the handle position on the relevant side of the bounding box.
+ // This avoids degeneracies when moving the skew angle over the rotation center
+ proj[d1] = new_pos[d1];
+ proj[d2] = scc[d2] + (_origin[d2] - scc[d2]) * scale[d2];
+ double angle = 0;
+ if (!Geom::are_near(proj[d2], scc[d2]))
+ angle = Geom::angle_between(_origin - scc, proj - scc);
+ if (held_control(*event)) angle = snap_angle(angle);
+
+ // skew matrix has the from [[1, k],[0, 1]] for horizontal skew
+ // and [[1,0],[k,1]] for vertical skew.
+ Geom::Matrix skew = Geom::identity();
+ // correct the sign of the tangent
+ skew[d2 + 1] = (d1 == Geom::X ? -1.0 : 1.0) * tan(angle);
+
+ _last_angle = angle;
+ Geom::Matrix t = Geom::Translate(-scc)
+ * Geom::Scale(scale) * skew
+ * Geom::Translate(scc);
+ return t;
+ }
+ virtual CommitEvent getCommitEvent() {
+ return _side % 2
+ ? COMMIT_MOUSE_SKEW_Y
+ : COMMIT_MOUSE_SKEW_X;
+ }
+ virtual Glib::ustring _getTip(unsigned state) {
+ if (state_held_shift(state)) {
+ if (state_held_control(state)) {
+ return format_tip(C_("Transform handle tip",
+ "<b>Shift+Ctrl:</b> skew about the rotation center with snapping "
+ "to %f° increments"), snap_increment_degrees());
+ }
+ return C_("Transform handle tip", "<b>Shift:</b> skew about the rotation center");
+ }
+ if (state_held_control(state)) {
+ return format_tip(C_("Transform handle tip",
+ "<b>Ctrl:</b> snap skew angle to %f° increments"), snap_increment_degrees());
+ }
+ return C_("Transform handle tip",
+ "<b>Skew handle:</b> drag to skew (shear) selection about "
+ "the opposite handle");
+ }
+ virtual Glib::ustring _getDragTip(GdkEventMotion *event) {
+ if (_last_horizontal) {
+ return format_tip(C_("Transform handle tip", "Skew horizontally by %.2f°"),
+ _last_angle * 360.0);
+ } else {
+ return format_tip(C_("Transform handle tip", "Skew vertically by %.2f°"),
+ _last_angle * 360.0);
+ }
+ }
+ virtual bool _hasDragTips() { return true; }
+private:
+ static Glib::RefPtr<Gdk::Pixbuf> _side_to_pixbuf(unsigned s) {
+ sp_select_context_get_type();
+ switch (s % 4) {
+ case 0: return Glib::wrap(handles[9], true);
+ case 1: return Glib::wrap(handles[7], true);
+ case 2: return Glib::wrap(handles[5], true);
+ default: return Glib::wrap(handles[11], true);
+ }
+ }
+ Geom::Point _skew_center;
+ Geom::Point _skew_opposite;
+ unsigned _side;
+ static bool _last_horizontal;
+ static double _last_angle;
+};
+bool SkewHandle::_last_horizontal = false;
+double SkewHandle::_last_angle = 0;
+
+class RotationCenter : public ControlPoint {
+public:
+ RotationCenter(TransformHandleSet &th)
+ : ControlPoint(th._desktop, Geom::Point(), Gtk::ANCHOR_CENTER, _get_pixbuf(),
+ &center_cset, th._transform_handle_group)
+ , _th(th)
+ {
+ setVisible(false);
+ }
+protected:
+ virtual Glib::ustring _getTip(unsigned state) {
+ return C_("Transform handle tip",
+ "<b>Rotation center:</b> drag to change the origin of transforms");
+ }
+private:
+ static Glib::RefPtr<Gdk::Pixbuf> _get_pixbuf() {
+ sp_select_context_get_type();
+ return Glib::wrap(handles[12], true);
+ }
+ TransformHandleSet &_th;
+};
+
+TransformHandleSet::TransformHandleSet(SPDesktop *d, SPCanvasGroup *th_group)
+ : Manipulator(d)
+ , _active(0)
+ , _transform_handle_group(th_group)
+ , _mode(MODE_SCALE)
+ , _in_transform(false)
+ , _visible(true)
+{
+ _trans_outline = static_cast<CtrlRect*>(sp_canvas_item_new(sp_desktop_controls(_desktop),
+ SP_TYPE_CTRLRECT, NULL));
+ sp_canvas_item_hide(_trans_outline);
+ _trans_outline->setDashed(true);
+
+ for (unsigned i = 0; i < 4; ++i) {
+ _scale_corners[i] = new ScaleCornerHandle(*this, i);
+ _scale_sides[i] = new ScaleSideHandle(*this, i);
+ _rot_corners[i] = new RotateHandle(*this, i);
+ _skew_sides[i] = new SkewHandle(*this, i);
+ }
+ _center = new RotationCenter(*this);
+ // when transforming, update rotation center position
+ signal_transform.connect(sigc::mem_fun(*_center, &RotationCenter::transform));
+}
+
+TransformHandleSet::~TransformHandleSet()
+{
+ for (unsigned i = 0; i < 17; ++i) {
+ delete _handles[i];
+ }
+}
+
+/** Sets the mode of transform handles (scale or rotate). */
+void TransformHandleSet::setMode(Mode m)
+{
+ _mode = m;
+ _updateVisibility(_visible);
+}
+
+Geom::Rect TransformHandleSet::bounds()
+{
+ return Geom::Rect(*_scale_corners[0], *_scale_corners[2]);
+}
+
+ControlPoint &TransformHandleSet::rotationCenter()
+{
+ return *_center;
+}
+
+void TransformHandleSet::setVisible(bool v)
+{
+ if (_visible != v) {
+ _visible = v;
+ _updateVisibility(_visible);
+ }
+}
+
+void TransformHandleSet::setBounds(Geom::Rect const &r, bool preserve_center)
+{
+ if (_in_transform) {
+ _trans_outline->setRectangle(r);
+ } else {
+ for (unsigned i = 0; i < 4; ++i) {
+ _scale_corners[i]->move(r.corner(i));
+ _scale_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1)));
+ _rot_corners[i]->move(r.corner(i));
+ _skew_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1)));
+ }
+ if (!preserve_center) _center->move(r.midpoint());
+ if (_visible) _updateVisibility(true);
+ }
+}
+
+bool TransformHandleSet::event(GdkEvent*)
+{
+ return false;
+}
+
+void TransformHandleSet::_emitTransform(Geom::Matrix const &t)
+{
+ signal_transform.emit(t);
+ _center->transform(t);
+}
+
+void TransformHandleSet::_setActiveHandle(ControlPoint *th)
+{
+ _active = th;
+ if (_in_transform)
+ throw std::logic_error("Transform initiated when another transform in progress");
+ _in_transform = true;
+ // hide all handles except the active one
+ _updateVisibility(false);
+ sp_canvas_item_show(_trans_outline);
+}
+
+void TransformHandleSet::_clearActiveHandle()
+{
+ // This can only be called from handles, so they had to be visible before _setActiveHandle
+ sp_canvas_item_hide(_trans_outline);
+ _active = 0;
+ _in_transform = false;
+ _updateVisibility(_visible);
+}
+
+/** Update the visibility of transformation handles according to settings and the dimensions
+ * of the bounding box. It hides the handles that would have no effect or lead to
+ * discontinuities. Additionally, side handles for which there is no space are not shown. */
+void TransformHandleSet::_updateVisibility(bool v)
+{
+ if (v) {
+ Geom::Rect b = bounds();
+ Geom::Point handle_size(
+ gdk_pixbuf_get_width(handles[0]) / _desktop->current_zoom(),
+ gdk_pixbuf_get_height(handles[0]) / _desktop->current_zoom());
+ Geom::Point bp = b.dimensions();
+
+ // do not scale when the bounding rectangle has zero width or height
+ bool show_scale = (_mode == MODE_SCALE) && !Geom::are_near(b.minExtent(), 0);
+ // do not rotate if the bounding rectangle is degenerate
+ bool show_rotate = (_mode == MODE_ROTATE_SKEW) && !Geom::are_near(b.maxExtent(), 0);
+ bool show_scale_side[2], show_skew[2];
+
+ // show sides if:
+ // a) there is enough space between corner handles, or
+ // b) corner handles are not shown, but side handles make sense
+ // this affects horizontal and vertical scale handles; skew handles never
+ // make sense if rotate handles are not shown
+ for (unsigned i = 0; i < 2; ++i) {
+ Geom::Dim2 d = static_cast<Geom::Dim2>(i);
+ Geom::Dim2 otherd = static_cast<Geom::Dim2>((i+1)%2);
+ show_scale_side[i] = (_mode == MODE_SCALE);
+ show_scale_side[i] &= (show_scale ? bp[d] >= handle_size[d]
+ : !Geom::are_near(bp[otherd], 0));
+ show_skew[i] = (show_rotate && bp[d] >= handle_size[d]
+ && !Geom::are_near(bp[otherd], 0));
+ }
+ for (unsigned i = 0; i < 4; ++i) {
+ _scale_corners[i]->setVisible(show_scale);
+ _rot_corners[i]->setVisible(show_rotate);
+ _scale_sides[i]->setVisible(show_scale_side[i%2]);
+ _skew_sides[i]->setVisible(show_skew[i%2]);
+ }
+ // show rotation center if there is enough space (?)
+ _center->setVisible(show_rotate /*&& bp[Geom::X] > handle_size[Geom::X]
+ && bp[Geom::Y] > handle_size[Geom::Y]*/);
+ } else {
+ for (unsigned i = 0; i < 17; ++i) {
+ if (_handles[i] != _active)
+ _handles[i]->setVisible(false);
+ }
+ }
+
+}
+
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/transform-handle-set.h b/src/ui/tool/transform-handle-set.h
new file mode 100644
index 000000000..48ad3af51
--- /dev/null
+++ b/src/ui/tool/transform-handle-set.h
@@ -0,0 +1,96 @@
+/** @file
+ * Affine transform handles component
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_TRANSFORM_HANDLE_SET_H
+#define SEEN_UI_TOOL_TRANSFORM_HANDLE_SET_H
+
+#include <memory>
+#include <gdk/gdk.h>
+#include <2geom/forward.h>
+#include "display/display-forward.h"
+#include "ui/tool/commit-events.h"
+#include "ui/tool/manipulator.h"
+
+class SPDesktop;
+class CtrlRect; // this is not present in display-forward.h!
+namespace Inkscape {
+namespace UI {
+
+//class TransformHandle;
+class RotateHandle;
+class SkewHandle;
+class ScaleCornerHandle;
+class ScaleSideHandle;
+class RotationCenter;
+
+class TransformHandleSet : public Manipulator {
+public:
+ enum Mode {
+ MODE_SCALE,
+ MODE_ROTATE_SKEW
+ };
+
+ TransformHandleSet(SPDesktop *d, SPCanvasGroup *th_group);
+ virtual ~TransformHandleSet();
+ virtual bool event(GdkEvent *);
+
+ bool visible() { return _visible; }
+ Mode mode() { return _mode; }
+ Geom::Rect bounds();
+ void setVisible(bool v);
+ void setMode(Mode);
+ void setBounds(Geom::Rect const &, bool preserve_center = false);
+
+ bool transforming() { return _in_transform; }
+ ControlPoint &rotationCenter();
+
+ sigc::signal<void, Geom::Matrix const &> signal_transform;
+ sigc::signal<void, CommitEvent> signal_commit;
+private:
+ void _emitTransform(Geom::Matrix const &);
+ void _setActiveHandle(ControlPoint *h);
+ void _clearActiveHandle();
+ void _updateVisibility(bool v);
+ union {
+ ControlPoint *_handles[17];
+ struct {
+ ScaleCornerHandle *_scale_corners[4];
+ ScaleSideHandle *_scale_sides[4];
+ RotateHandle *_rot_corners[4];
+ SkewHandle *_skew_sides[4];
+ RotationCenter *_center;
+ };
+ };
+ ControlPoint *_active;
+ SPCanvasGroup *_transform_handle_group;
+ CtrlRect *_trans_outline;
+ Mode _mode;
+ bool _in_transform;
+ bool _visible;
+ bool _rot_center_visible;
+ friend class TransformHandle;
+ friend class RotationCenter;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/util/accumulators.h b/src/util/accumulators.h
new file mode 100644
index 000000000..c627786b1
--- /dev/null
+++ b/src/util/accumulators.h
@@ -0,0 +1,113 @@
+/** @file
+ * Frequently used accumulators for use with libsigc++
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UTIL_ACCUMULATORS_H
+#define SEEN_UTIL_ACCUMULATORS_H
+
+#include <iterator>
+
+namespace Inkscape {
+namespace Util {
+
+/**
+ * Accumulator which evaluates slots in reverse connection order.
+ * The slot that was connected last is evaluated first.
+ */
+struct Reverse {
+ typedef void result_type;
+ template <typename T_iterator>
+ result_type operator()(T_iterator first, T_iterator last) const {
+ while (first != last) *(--last);
+ }
+};
+
+/**
+ * Accumulator type for interruptible signals. Slots return a boolean value; emission
+ * is stopped when true is returned from a slot.
+ */
+struct Interruptible {
+ typedef bool result_type;
+ template <typename T_iterator>
+ result_type operator()(T_iterator first, T_iterator last) const {
+ for (; first != last; ++first)
+ if (*first) return true;
+ return false;
+ }
+};
+
+/**
+ * Same as Interruptible, but the slots are called in reverse order of connection,
+ * e.g. the slot that was connected last is evaluated first.
+ */
+struct ReverseInterruptible {
+ typedef bool result_type;
+ template <typename T_iterator>
+ result_type operator()(T_iterator first, T_iterator last) const {
+ while (first != last) {
+ if (*(--last)) return true;
+ }
+ return false;
+ }
+};
+
+/**
+ * The template parameter specifies how many slots from the beginning of the list
+ * should be evaluated after other slots. Useful for signals which invoke other signals
+ * once complete. Undefined results if the signal does not have at least @c num_chained
+ * slots before first emission.
+ *
+ * For example, if template param = 3, the execution order is as follows:
+ * @verbatim
+ 8. 1. 2. 3. 4. 5. 6. 7.
+ S1 S2 S3 S4 S5 S6 S7 S8 @endverbatim
+ */
+template <unsigned num_chained = 1>
+struct Chained {
+ typedef void result_type;
+ template <typename T_iterator>
+ result_type operator()(T_iterator first, T_iterator last) const {
+ T_iterator save_first = first;
+ // ARGH, iterator_traits is not defined for slot iterators!
+ //std::advance(first, num_chained);
+ for (unsigned i = 0; i < num_chained && first != last; ++i) ++first;
+ for (; first != last; ++first) *first;
+ for (unsigned i = 0; i < num_chained && save_first != last; ++i, ++save_first)
+ *save_first;
+ }
+};
+
+/**
+ * Executes a logical OR on the results from slots.
+ */
+struct LogicalOr {
+ typedef bool result_type;
+ template <typename T_iterator>
+ result_type operator()(T_iterator first, T_iterator last) const {
+ bool ret = false;
+ for (; first != last; ++first) ret |= *first;
+ return ret;
+ }
+};
+
+} // namespace Util
+} // namespace Inkscape
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/util/hash.h b/src/util/hash.h
new file mode 100644
index 000000000..d5abeff5a
--- /dev/null
+++ b/src/util/hash.h
@@ -0,0 +1,41 @@
+/** @file
+ * Hash function for various things
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UTIL_HASH_H
+#define SEEN_UTIL_HASH_H
+
+#include <boost/shared_ptr.hpp>
+
+namespace std {
+namespace tr1 {
+
+/** Hash function for Boost shared pointers */
+template <typename T>
+struct hash< boost::shared_ptr<T> > : public std::unary_function<boost::shared_ptr<T>, size_t> {
+ size_t operator()(boost::shared_ptr<T> p) const {
+ return reinterpret_cast<size_t>(p.get());
+ }
+};
+
+} // namespace tr1
+} // namespace std
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/verbs.cpp b/src/verbs.cpp
index 6f86c3cce..37f4da4d6 100644
--- a/src/verbs.cpp
+++ b/src/verbs.cpp
@@ -58,7 +58,6 @@
#include "layer-fns.h"
#include "layer-manager.h"
#include "message-stack.h"
-#include "node-context.h"
#include "path-chemistry.h"
#include "preferences.h"
#include "select-context.h"
@@ -80,6 +79,9 @@
#include "ui/dialog/layers.h"
#include "ui/dialog/swatches.h"
#include "ui/icon-names.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tool/node-tool.h"
//#ifdef WITH_INKBOARD
//#include "jabber_whiteboard/session-manager.h"
@@ -919,28 +921,32 @@ EditVerb::perform(SPAction *action, void *data, void */*pdata*/)
break;
case SP_VERB_EDIT_SELECT_ALL:
if (tools_isactive(dt, TOOLS_NODES)) {
- ec->shape_editor->select_all_from_subpath(false);
+ InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context);
+ nt->_multipath->selectSubpaths();
} else {
sp_edit_select_all(dt);
}
break;
case SP_VERB_EDIT_INVERT:
if (tools_isactive(dt, TOOLS_NODES)) {
- ec->shape_editor->select_all_from_subpath(true);
+ InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context);
+ nt->_multipath->invertSelectionInSubpaths();
} else {
sp_edit_invert(dt);
}
break;
case SP_VERB_EDIT_SELECT_ALL_IN_ALL_LAYERS:
if (tools_isactive(dt, TOOLS_NODES)) {
- ec->shape_editor->select_all(false);
+ InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context);
+ nt->_selected_nodes->selectAll();
} else {
sp_edit_select_all_in_all_layers(dt);
}
break;
case SP_VERB_EDIT_INVERT_IN_ALL_LAYERS:
if (tools_isactive(dt, TOOLS_NODES)) {
- ec->shape_editor->select_all(true);
+ InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context);
+ nt->_selected_nodes->invertSelection();
} else {
sp_edit_invert_in_all_layers(dt);
}
@@ -948,7 +954,8 @@ EditVerb::perform(SPAction *action, void *data, void */*pdata*/)
case SP_VERB_EDIT_SELECT_NEXT:
if (tools_isactive(dt, TOOLS_NODES)) {
- ec->shape_editor->select_next();
+ InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context);
+ nt->_multipath->shiftSelection(1);
} else if (tools_isactive(dt, TOOLS_GRADIENT)
&& ec->_grdrag->isNonEmpty()) {
sp_gradient_context_select_next (ec);
@@ -958,7 +965,8 @@ EditVerb::perform(SPAction *action, void *data, void */*pdata*/)
break;
case SP_VERB_EDIT_SELECT_PREV:
if (tools_isactive(dt, TOOLS_NODES)) {
- ec->shape_editor->select_prev();
+ InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context);
+ nt->_multipath->shiftSelection(-1);
} else if (tools_isactive(dt, TOOLS_GRADIENT)
&& ec->_grdrag->isNonEmpty()) {
sp_gradient_context_select_prev (ec);
@@ -969,7 +977,8 @@ EditVerb::perform(SPAction *action, void *data, void */*pdata*/)
case SP_VERB_EDIT_DESELECT:
if (tools_isactive(dt, TOOLS_NODES)) {
- ec->shape_editor->deselect();
+ InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context);
+ nt->_selected_nodes->clear();
} else {
sp_desktop_selection(dt)->clear();
}
@@ -1086,7 +1095,13 @@ SelectionVerb::perform(SPAction *action, void *data, void */*pdata*/)
sp_selected_path_simplify(dt);
break;
case SP_VERB_SELECTION_REVERSE:
- sp_selected_path_reverse(dt);
+ // TODO make this a virtual method of event context!
+ if (tools_isactive(dt, TOOLS_NODES)) {
+ InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context);
+ nt->_multipath->reverseSubpaths();
+ } else {
+ sp_selected_path_reverse(dt);
+ }
break;
case SP_VERB_SELECTION_TRACE:
inkscape_dialogs_unhide();
@@ -1365,41 +1380,12 @@ ObjectVerb::perform( SPAction *action, void *data, void */*pdata*/ )
flowtext_to_text();
break;
case SP_VERB_OBJECT_FLIP_HORIZONTAL:
- // When working with the node tool ...
- if (tools_isactive(dt, TOOLS_NODES)) {
- Inkscape::NodePath::Node *active_node = Inkscape::NodePath::Path::active_node;
-
- // ... and one of the nodes is currently mouseovered ...
- if (active_node) {
-
- // ... flip the selected nodes about that node
- ec->shape_editor->flip(Geom::X, active_node->pos);
- } else {
-
- // ... or else about the center of their bounding box.
- ec->shape_editor->flip(Geom::X);
- }
-
- // When working with the selector tool, flip the selection about its rotation center
- // (if it is visible) or about the center of the bounding box.
- } else {
- sp_selection_scale_relative(sel, center, Geom::Scale(-1.0, 1.0));
- }
+ sp_selection_scale_relative(sel, center, Geom::Scale(-1.0, 1.0));
sp_document_done(sp_desktop_document(dt), SP_VERB_OBJECT_FLIP_HORIZONTAL,
_("Flip horizontally"));
break;
case SP_VERB_OBJECT_FLIP_VERTICAL:
- // The behaviour is analogous to flipping horizontally
- if (tools_isactive(dt, TOOLS_NODES)) {
- Inkscape::NodePath::Node *active_node = Inkscape::NodePath::Path::active_node;
- if (active_node) {
- ec->shape_editor->flip(Geom::Y, active_node->pos);
- } else {
- ec->shape_editor->flip(Geom::Y);
- }
- } else {
- sp_selection_scale_relative(sel, center, Geom::Scale(1.0, -1.0));
- }
+ sp_selection_scale_relative(sel, center, Geom::Scale(1.0, -1.0));
sp_document_done(sp_desktop_document(dt), SP_VERB_OBJECT_FLIP_VERTICAL,
_("Flip vertically"));
break;
diff --git a/src/widgets/toolbox.cpp b/src/widgets/toolbox.cpp
index 8b8ea1a01..00f83cdbd 100644
--- a/src/widgets/toolbox.cpp
+++ b/src/widgets/toolbox.cpp
@@ -9,7 +9,7 @@
* Frank Felfe <innerspace@iname.com>
* John Cliff <simarilius@yahoo.com>
* David Turner <novalis@gnu.org>
- * Josh Andler <scislac@users.sf.net>
+ * Josh Andler <scislac@scislac.com>
* Jon A. Cruz <jon@joncruz.org>
* Maximilian Albert <maximilian.albert@gmail.com>
*
@@ -65,7 +65,6 @@
#include "../live_effects/lpe-line_segment.h"
#include "../lpe-tool-context.h"
#include "../mod360.h"
-#include "../node-context.h"
#include "../pen-context.h"
#include "../preferences.h"
#include "../selection-chemistry.h"
@@ -89,6 +88,9 @@
#include "../spray-context.h"
#include "../ui/dialog/calligraphic-profile-rename.h"
#include "../ui/icon-names.h"
+#include "../ui/tool/control-point-selection.h"
+#include "../ui/tool/node-tool.h"
+#include "../ui/tool/multi-path-manipulator.h"
#include "../ui/widget/style-swatch.h"
#include "../verbs.h"
#include "../widgets/button.h"
@@ -171,7 +173,7 @@ static struct {
sp_verb_t doubleclick_verb;
} const tools[] = {
{ "SPSelectContext", "select_tool", SP_VERB_CONTEXT_SELECT, SP_VERB_CONTEXT_SELECT_PREFS},
- { "SPNodeContext", "node_tool", SP_VERB_CONTEXT_NODE, SP_VERB_CONTEXT_NODE_PREFS },
+ { "InkNodeTool", "node_tool", SP_VERB_CONTEXT_NODE, SP_VERB_CONTEXT_NODE_PREFS },
{ "SPTweakContext", "tweak_tool", SP_VERB_CONTEXT_TWEAK, SP_VERB_CONTEXT_TWEAK_PREFS },
{ "SPSprayContext", "spray_tool", SP_VERB_CONTEXT_SPRAY, SP_VERB_CONTEXT_SPRAY_PREFS },
{ "SPZoomContext", "zoom_tool", SP_VERB_CONTEXT_ZOOM, SP_VERB_CONTEXT_ZOOM_PREFS },
@@ -205,7 +207,7 @@ static struct {
} const aux_toolboxes[] = {
{ "SPSelectContext", "select_toolbox", 0, sp_select_toolbox_prep, "SelectToolbar",
SP_VERB_INVALID, 0, 0},
- { "SPNodeContext", "node_toolbox", 0, sp_node_toolbox_prep, "NodeToolbar",
+ { "InkNodeTool", "node_toolbox", 0, sp_node_toolbox_prep, "NodeToolbar",
SP_VERB_INVALID, 0, 0},
{ "SPTweakContext", "tweak_toolbox", 0, sp_tweak_toolbox_prep, "TweakToolbar",
SP_VERB_CONTEXT_TWEAK_PREFS, "/tools/tweak", N_("Color/opacity used for color tweaking")},
@@ -1015,135 +1017,127 @@ static EgeAdjustmentAction * create_adjustment_action( gchar const *name,
//# node editing callbacks
//####################################
-/**
- * FIXME: Returns current shape_editor in context. // later eliminate this function at all!
- */
-static ShapeEditor *get_current_shape_editor()
+/** Temporary hack: Returns the node tool in the active desktop.
+ * Will go away during tool refactoring. */
+static InkNodeTool *get_node_tool()
{
- if (!SP_ACTIVE_DESKTOP) {
- return NULL;
- }
-
- SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
-
- if (!SP_IS_NODE_CONTEXT(event_context)) {
- return NULL;
- }
-
- return event_context->shape_editor;
+ if (!SP_ACTIVE_DESKTOP) return NULL;
+ SPEventContext *ec = SP_ACTIVE_DESKTOP->event_context;
+ if (!INK_IS_NODE_TOOL(ec)) return NULL;
+ return static_cast<InkNodeTool*>(ec);
}
void
sp_node_path_edit_add(void)
{
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor) shape_editor->add_node();
+ InkNodeTool *nt = get_node_tool();
+ if (nt) nt->_multipath->insertNodes();
}
void
sp_node_path_edit_delete(void)
{
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor) shape_editor->delete_nodes_preserving_shape();
+ InkNodeTool *nt = get_node_tool();
+ if (nt) nt->_multipath->deleteNodes();
}
void
sp_node_path_edit_delete_segment(void)
{
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor) shape_editor->delete_segment();
+ InkNodeTool *nt = get_node_tool();
+ if (nt) nt->_multipath->deleteSegments();
}
void
sp_node_path_edit_break(void)
{
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor) shape_editor->break_at_nodes();
+ InkNodeTool *nt = get_node_tool();
+ if (nt) nt->_multipath->breakNodes();
}
void
sp_node_path_edit_join(void)
{
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor) shape_editor->join_nodes();
+ InkNodeTool *nt = get_node_tool();
+ if (nt) nt->_multipath->joinNodes();
}
void
sp_node_path_edit_join_segment(void)
{
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor) shape_editor->join_segments();
+ InkNodeTool *nt = get_node_tool();
+ if (nt) nt->_multipath->joinSegments();
}
void
sp_node_path_edit_toline(void)
{
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor) shape_editor->set_type_of_segments(NR_LINETO);
+ InkNodeTool *nt = get_node_tool();
+ if (nt) nt->_multipath->setSegmentType(Inkscape::UI::SEGMENT_STRAIGHT);
}
void
sp_node_path_edit_tocurve(void)
{
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor) shape_editor->set_type_of_segments(NR_CURVETO);
+ InkNodeTool *nt = get_node_tool();
+ if (nt) nt->_multipath->setSegmentType(Inkscape::UI::SEGMENT_CUBIC_BEZIER);
}
void
sp_node_path_edit_cusp(void)
{
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor) shape_editor->set_node_type(Inkscape::NodePath::NODE_CUSP);
+ InkNodeTool *nt = get_node_tool();
+ if (nt) nt->_multipath->setNodeType(Inkscape::UI::NODE_CUSP);
}
void
sp_node_path_edit_smooth(void)
{
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor) shape_editor->set_node_type(Inkscape::NodePath::NODE_SMOOTH);
+ InkNodeTool *nt = get_node_tool();
+ if (nt) nt->_multipath->setNodeType(Inkscape::UI::NODE_SMOOTH);
}
void
sp_node_path_edit_symmetrical(void)
{
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor) shape_editor->set_node_type(Inkscape::NodePath::NODE_SYMM);
+ InkNodeTool *nt = get_node_tool();
+ if (nt) nt->_multipath->setNodeType(Inkscape::UI::NODE_SYMMETRIC);
}
void
sp_node_path_edit_auto(void)
{
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor) shape_editor->set_node_type(Inkscape::NodePath::NODE_AUTO);
+ InkNodeTool *nt = get_node_tool();
+ if (nt) nt->_multipath->setNodeType(Inkscape::UI::NODE_AUTO);
}
static void toggle_show_handles (GtkToggleAction *act, gpointer /*data*/) {
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
bool show = gtk_toggle_action_get_active( act );
prefs->setBool("/tools/nodes/show_handles", show);
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor) shape_editor->show_handles(show);
}
static void toggle_show_helperpath (GtkToggleAction *act, gpointer /*data*/) {
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
bool show = gtk_toggle_action_get_active( act );
- prefs->setBool("/tools/nodes/show_helperpath", show);
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor) shape_editor->show_helperpath(show);
+ prefs->setBool("/tools/nodes/show_outline", show);
}
void sp_node_path_edit_nextLPEparam (GtkAction */*act*/, gpointer data) {
sp_selection_next_patheffect_param( reinterpret_cast<SPDesktop*>(data) );
}
-void sp_node_path_edit_clippath (GtkAction */*act*/, gpointer data) {
- sp_selection_edit_clip_or_mask( reinterpret_cast<SPDesktop*>(data), true);
+void toggle_edit_clip (GtkToggleAction *act, gpointer data) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool edit = gtk_toggle_action_get_active( act );
+ prefs->setBool("/tools/nodes/edit_clipping_paths", edit);
}
-void sp_node_path_edit_maskpath (GtkAction */*act*/, gpointer data) {
- sp_selection_edit_clip_or_mask( reinterpret_cast<SPDesktop*>(data), false);
+void toggle_edit_mask (GtkToggleAction *act, gpointer data) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool edit = gtk_toggle_action_get_active( act );
+ prefs->setBool("/tools/nodes/edit_masks", edit);
}
/* is called when the node selection is modified */
@@ -1166,54 +1160,29 @@ sp_node_toolbox_coord_changed(gpointer /*shape_editor*/, GObject *tbl)
UnitTracker* tracker = reinterpret_cast<UnitTracker*>( g_object_get_data( tbl, "tracker" ) );
SPUnit const *unit = tracker->getActiveUnit();
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor && shape_editor->has_nodepath()) {
- Inkscape::NodePath::Path *nodepath = shape_editor->get_nodepath();
- int n_selected = 0;
- if (nodepath) {
- n_selected = nodepath->numSelected();
- }
-
- if (n_selected == 0) {
- gtk_action_set_sensitive(xact, FALSE);
- gtk_action_set_sensitive(yact, FALSE);
- } else {
- gtk_action_set_sensitive(xact, TRUE);
- gtk_action_set_sensitive(yact, TRUE);
- Geom::Coord oldx = sp_units_get_pixels(gtk_adjustment_get_value(xadj), *unit);
- Geom::Coord oldy = sp_units_get_pixels(gtk_adjustment_get_value(xadj), *unit);
-
- if (n_selected == 1) {
- Geom::Point sel_node = nodepath->singleSelectedCoords();
- if (oldx != sel_node[Geom::X] || oldy != sel_node[Geom::Y]) {
- gtk_adjustment_set_value(xadj, sp_pixels_get_units(sel_node[Geom::X], *unit));
- gtk_adjustment_set_value(yadj, sp_pixels_get_units(sel_node[Geom::Y], *unit));
- }
- } else {
- boost::optional<Geom::Coord> x = sp_node_selected_common_coord(nodepath, Geom::X);
- boost::optional<Geom::Coord> y = sp_node_selected_common_coord(nodepath, Geom::Y);
- if ((x && ((*x) != oldx)) || (y && ((*y) != oldy))) {
- /* Note: Currently x and y will always have a value, even if the coordinates of the
- selected nodes don't coincide (in this case we use the coordinates of the center
- of the bounding box). So the entries are never set to zero. */
- // FIXME: Maybe we should clear the entry if several nodes are selected
- // instead of providing a kind of average value
- gtk_adjustment_set_value(xadj, sp_pixels_get_units(x ? (*x) : 0.0, *unit));
- gtk_adjustment_set_value(yadj, sp_pixels_get_units(y ? (*y) : 0.0, *unit));
- }
- }
- }
- } else {
- // no shape-editor or nodepath yet (when we just switched to the tool); coord entries must be inactive
+ InkNodeTool *nt = get_node_tool();
+ if (!nt || nt->_selected_nodes->empty()) {
+ // no path selected
gtk_action_set_sensitive(xact, FALSE);
gtk_action_set_sensitive(yact, FALSE);
+ } else {
+ gtk_action_set_sensitive(xact, TRUE);
+ gtk_action_set_sensitive(yact, TRUE);
+ Geom::Coord oldx = sp_units_get_pixels(gtk_adjustment_get_value(xadj), *unit);
+ Geom::Coord oldy = sp_units_get_pixels(gtk_adjustment_get_value(xadj), *unit);
+ Geom::Point mid = nt->_selected_nodes->pointwiseBounds()->midpoint();
+
+ if (oldx != mid[Geom::X])
+ gtk_adjustment_set_value(xadj, sp_pixels_get_units(mid[Geom::X], *unit));
+ if (oldy != mid[Geom::Y])
+ gtk_adjustment_set_value(yadj, sp_pixels_get_units(mid[Geom::Y], *unit));
}
g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) );
}
static void
-sp_node_path_value_changed(GtkAdjustment *adj, GObject *tbl, gchar const *value_name)
+sp_node_path_value_changed(GtkAdjustment *adj, GObject *tbl, Geom::Dim2 d)
{
SPDesktop *desktop = (SPDesktop *) g_object_get_data( tbl, "desktop" );
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
@@ -1222,7 +1191,8 @@ sp_node_path_value_changed(GtkAdjustment *adj, GObject *tbl, gchar const *value_
SPUnit const *unit = tracker->getActiveUnit();
if (sp_document_get_undo_sensitive(sp_desktop_document(desktop))) {
- prefs->setDouble(Glib::ustring("/tools/nodes/") + value_name, sp_units_get_pixels(adj->value, *unit));
+ prefs->setDouble(Glib::ustring("/tools/nodes/") + (d == Geom::X ? "x" : "y"),
+ sp_units_get_pixels(adj->value, *unit));
}
// quit if run by the attr_changed listener
@@ -1233,15 +1203,13 @@ sp_node_path_value_changed(GtkAdjustment *adj, GObject *tbl, gchar const *value_
// in turn, prevent listener from responding
g_object_set_data( tbl, "freeze", GINT_TO_POINTER(TRUE));
- ShapeEditor *shape_editor = get_current_shape_editor();
- if (shape_editor && shape_editor->has_nodepath()) {
+ InkNodeTool *nt = get_node_tool();
+ if (nt && !nt->_selected_nodes->empty()) {
double val = sp_units_get_pixels(gtk_adjustment_get_value(adj), *unit);
- if (!strcmp(value_name, "x")) {
- sp_node_selected_move_absolute(shape_editor->get_nodepath(), val, Geom::X);
- }
- if (!strcmp(value_name, "y")) {
- sp_node_selected_move_absolute(shape_editor->get_nodepath(), val, Geom::Y);
- }
+ double oldval = nt->_selected_nodes->pointwiseBounds()->midpoint()[d];
+ Geom::Point delta(0,0);
+ delta[d] = val - oldval;
+ nt->_multipath->move(delta);
}
g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) );
@@ -1250,13 +1218,13 @@ sp_node_path_value_changed(GtkAdjustment *adj, GObject *tbl, gchar const *value_
static void
sp_node_path_x_value_changed(GtkAdjustment *adj, GObject *tbl)
{
- sp_node_path_value_changed(adj, tbl, "x");
+ sp_node_path_value_changed(adj, tbl, Geom::X);
}
static void
sp_node_path_y_value_changed(GtkAdjustment *adj, GObject *tbl)
{
- sp_node_path_value_changed(adj, tbl, "y");
+ sp_node_path_value_changed(adj, tbl, Geom::Y);
}
void
@@ -1275,26 +1243,6 @@ sp_node_toolbox_sel_changed (Inkscape::Selection *selection, GObject *tbl)
gtk_action_set_sensitive(w, FALSE);
}
}
-
- {
- GtkAction* w = GTK_ACTION( g_object_get_data( tbl, "nodes_clippathedit" ) );
- SPItem *item = selection->singleItem();
- if (item && item->clip_ref && item->clip_ref->getObject()) {
- gtk_action_set_sensitive(w, TRUE);
- } else {
- gtk_action_set_sensitive(w, FALSE);
- }
- }
-
- {
- GtkAction* w = GTK_ACTION( g_object_get_data( tbl, "nodes_maskedit" ) );
- SPItem *item = selection->singleItem();
- if (item && item->mask_ref && item->mask_ref->getObject()) {
- gtk_action_set_sensitive(w, TRUE);
- } else {
- gtk_action_set_sensitive(w, FALSE);
- }
- }
}
void
@@ -1342,8 +1290,8 @@ static void sp_node_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions
{
InkAction* inky = ink_action_new( "NodeJoinAction",
- _("Join endnodes"),
- _("Join selected endnodes"),
+ _("Join nodes"),
+ _("Join selected nodes"),
INKSCAPE_ICON_NODE_JOIN,
secondarySize );
g_object_set( inky, "short_label", _("Join"), NULL );
@@ -1461,7 +1409,7 @@ static void sp_node_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions
Inkscape::ICON_SIZE_DECORATION );
gtk_action_group_add_action( mainActions, GTK_ACTION( act ) );
g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_show_helperpath), desktop );
- gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(act), prefs->getBool("/tools/nodes/show_helperpath", false) );
+ gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(act), prefs->getBool("/tools/nodes/show_outline", false) );
}
{
@@ -1476,25 +1424,25 @@ static void sp_node_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions
}
{
- InkAction* inky = ink_action_new( "ObjectEditClipPathAction",
- _("Edit clipping path"),
- _("Edit the clipping path of the object"),
+ InkToggleAction* inky = ink_toggle_action_new( "ObjectEditClipPathAction",
+ _("Edit clipping paths"),
+ _("Show editing controls for clipping paths of selected objects"),
INKSCAPE_ICON_PATH_CLIP_EDIT,
Inkscape::ICON_SIZE_DECORATION );
- g_signal_connect_after( G_OBJECT(inky), "activate", G_CALLBACK(sp_node_path_edit_clippath), desktop );
gtk_action_group_add_action( mainActions, GTK_ACTION(inky) );
- g_object_set_data( holder, "nodes_clippathedit", inky);
+ g_signal_connect_after( G_OBJECT(inky), "toggled", G_CALLBACK(toggle_edit_clip), desktop );
+ gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(inky), prefs->getBool("/tools/nodes/edit_clipping_paths") );
}
{
- InkAction* inky = ink_action_new( "ObjectEditMaskPathAction",
- _("Edit mask path"),
- _("Edit the mask of the object"),
+ InkToggleAction* inky = ink_toggle_action_new( "ObjectEditMaskPathAction",
+ _("Edit masks"),
+ _("Show editing controls for masks of selected objects"),
INKSCAPE_ICON_PATH_MASK_EDIT,
Inkscape::ICON_SIZE_DECORATION );
- g_signal_connect_after( G_OBJECT(inky), "activate", G_CALLBACK(sp_node_path_edit_maskpath), desktop );
gtk_action_group_add_action( mainActions, GTK_ACTION(inky) );
- g_object_set_data( holder, "nodes_maskedit", inky);
+ g_signal_connect_after( G_OBJECT(inky), "toggled", G_CALLBACK(toggle_edit_mask), desktop );
+ gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(inky), prefs->getBool("/tools/nodes/edit_masks") );
}
/* X coord of selected node(s) */
@@ -6749,10 +6697,10 @@ sp_text_toolbox_family_keypress (GtkWidget */*w*/, GdkEventKey *event, GObject *
case GDK_Return:
// unfreeze and update, which will defocus
g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) );
- sp_text_toolbox_family_changed (NULL, tbl);
+ sp_text_toolbox_family_changed (NULL, tbl);
return TRUE; // I consumed the event
break;
- case GDK_Escape:
+ case GDK_Escape:
// defocus
gtk_widget_grab_focus (GTK_WIDGET(desktop->canvas));
return TRUE; // I consumed the event
@@ -7028,10 +6976,10 @@ void sp_text_toolbox_family_popnotify(GtkComboBox *widget,
}
// update
- sp_text_toolbox_family_changed (NULL, tbl);
+ sp_text_toolbox_family_changed (NULL, tbl);
break;
}
- }
+ }
}
}
@@ -7069,7 +7017,7 @@ GtkWidget *sp_text_toolbox_new (SPDesktop *desktop)
g_signal_connect (G_OBJECT (font_sel->gobj()), "key-press-event", G_CALLBACK(sp_text_toolbox_family_list_keypress), tbl);
cbe_add_completion(font_sel->gobj(), G_OBJECT(tbl));
-
+
gtk_toolbar_append_widget( tbl, (GtkWidget*) font_sel->gobj(), "", "");
g_object_set_data (G_OBJECT (tbl), "family-entry-combo", font_sel);
@@ -7082,7 +7030,7 @@ GtkWidget *sp_text_toolbox_new (SPDesktop *desktop)
g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (sp_text_toolbox_family_changed), tbl);
g_signal_connect (G_OBJECT (font_sel->gobj()), "changed", G_CALLBACK (sp_text_toolbox_family_changed), tbl);
- g_signal_connect (G_OBJECT (font_sel->gobj()), "notify::popup-shown",
+ g_signal_connect (G_OBJECT (font_sel->gobj()), "notify::popup-shown",
G_CALLBACK (sp_text_toolbox_family_popnotify), tbl);
g_signal_connect (G_OBJECT (entry), "key-press-event", G_CALLBACK(sp_text_toolbox_family_keypress), tbl);
g_signal_connect (G_OBJECT (entry), "focus-in-event", G_CALLBACK (sp_text_toolbox_entry_focus_in), tbl);
@@ -7405,7 +7353,7 @@ static void connector_spacing_changed(GtkAdjustment *adj, GObject* tbl)
if ( !repr->attribute("inkscape:connector-spacing") &&
( adj->value == defaultConnSpacing )) {
- // Don't need to update the repr if the attribute doesn't
+ // Don't need to update the repr if the attribute doesn't
// exist and it is being set to the default value -- as will
// happen at startup.
return;