Code

Split SPCanvasItem and SPCanvasGroup to individual .h files. Removed forward header.
[inkscape.git] / src / connector-context.cpp
1 /**
2  * Connector creation tool
3  *
4  * Authors:
5  *   Michael Wybrow <mjwybrow@users.sourceforge.net>
6  *   Abhishek Sharma
7  *   Jon A. Cruz <jon@joncruz.org>
8  *
9  * Copyright (C) 2005-2008  Michael Wybrow
10  * Copyright (C) 2009  Monash University
11  * Copyright (C) 2010  authors
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  *
15  * TODO:
16  *  o  Show a visual indicator for objects with the 'avoid' property set.
17  *  o  Allow user to change a object between a path and connector through
18  *     the interface.
19  *  o  Create an interface for setting markers (arrow heads).
20  *  o  Better distinguish between paths and connectors to prevent problems
21  *     in the node tool and paths accidentally being turned into connectors
22  *     in the connector tool.  Perhaps have a way to convert between.
23  *  o  Only call libavoid's updateEndPoint as required.  Currently we do it
24  *     for both endpoints, even if only one is moving.
25  *  o  Allow user-placeable connection points.
26  *  o  Deal sanely with connectors with both endpoints attached to the
27  *     same connection point, and drawing of connectors attaching
28  *     overlapping shapes (currently tries to adjust connector to be
29  *     outside both bounding boxes).
30  *  o  Fix many special cases related to connectors updating,
31  *     e.g., copying a couple of shapes and a connector that are
32  *           attached to each other.
33  *     e.g., detach connector when it is moved or transformed in
34  *           one of the other contexts.
35  *  o  Cope with shapes whose ids change when they have attached
36  *     connectors.
37  *  o  During dragging motion, gobble up to and use the final motion event.
38  *     Gobbling away all duplicates after the current can occasionally result
39  *     in the path lagging behind the mouse cursor if it is no longer being
40  *     dragged.
41  *  o  Fix up libavoid's representation after undo actions.  It doesn't see
42  *     any transform signals and hence doesn't know shapes have moved back to
43  *     there earlier positions.
44  *  o  Decide whether drawing/editing mode should be an Inkscape preference
45  *     or the connector tool should always start in drawing mode.
46  *  o  Correct the problem with switching to the select tool when pressing
47  *     space bar (there are moments when it refuses to do so).
48  *
49  * ----------------------------------------------------------------------------
50  *
51  * mjwybrow's observations on acracan's Summer of Code connector work:
52  *
53  *  -  GUI comments:
54  *
55  *      -  Buttons for adding and removing user-specified connection
56  *      points should probably have "+" and "-" symbols on them so they
57  *      are consistent with the similar buttons for the node tool.
58  *      -  Controls on the connector tool be should be reordered logically,
59  *      possibly as follows:
60  *
61  *      *Connector*: [Polyline-radio-button] [Orthgonal-radio-button]
62  *        [Curvature-control] | *Shape*: [Avoid-button] [Dont-avoid-button]
63  *        [Spacing-control] | *Connection pts*: [Edit-mode] [Add-pt] [Rm-pt]
64  *
65  *      I think that the network layout controls be moved to the
66  *      Align and Distribute dialog (there is already the layout button
67  *      there, but no options are exposed).
68  *
69  *      I think that the style change between polyline and orthogonal
70  *      would be much clearer with two buttons (radio behaviour -- just
71  *      one is true).
72  *
73  *      The other tools show a label change from "New:" to "Change:"
74  *      depending on whether an object is selected.  We could consider
75  *      this but there may not be space.
76  *
77  *      The Add-pt and Rm-pt buttons should be greyed out (inactive) if
78  *      we are not in connection point editing mode.  And probably also
79  *      if there is no shape selected, i.e. at the times they have no
80  *      effect when clicked.
81  *
82  *      Likewise for the avoid/ignore shapes buttons.  These should be
83  *      inactive when a shape is not selected in the connector context.
84  *
85  *  -  When creating/editing connection points:
86  *
87  *      -  Strange things can happen if you have connectors selected, or
88  *      try rerouting connectors by dragging their endpoints when in
89  *      connection point editing mode.
90  *
91  *      -  Possibly the selected shape's connection points should always
92  *      be shown (i.e., have knots) when in editing mode.
93  *
94  *      -  It is a little strange to be able to place connection points
95  *      competely outside shapes.  Especially when you later can't draw
96  *      connectors to them since the knots are only visible when you
97  *      are over the shape.  I think that you should only be able to
98  *      place connection points inside or on the boundary of the shape
99  *      itself.
100  *
101  *      -  The intended ability to place a new point at the current cursor
102  *      position by pressing RETURN does not seem to work.
103  *
104  *      -  The Status bar tooltip should change to reflect editing mode
105  *      and tell the user about RETURN and how to use the tool.
106  *
107  *  -  Connection points general:
108  *
109  *      -  Connection points that were inside the shape can end up outside
110  *      after a rotation is applied to the shape in the select tool.
111  *      It doesn't seem like the correct transform is being applied to
112  *      these, or it is being applied at the wrong time.  I'd expect
113  *      connection points to rotate with the shape, and stay at the
114  *      same position "on the shape"
115  *
116  *      -  I was able to make the connectors attached to a shape fall off
117  *      the shape after scaling it.  Not sure the exact cause, but may
118  *      require more investigation/debugging.
119  *
120  *      -  The user-defined connection points should be either absolute
121  *      (as the current ones are) or defined as a percentage of the
122  *      shape.  These would be based on a toggle setting on the
123  *      toolbar, and they would be placed in exactly the same way by
124  *      the user.  The only difference would be that they would be
125  *      store as percentage positions in the SVG connection-points
126  *      property and that they would update/move automatically if the
127  *      object was resized or scaled.
128  *
129  *      -  Thinking more, I think you always want to store and think about
130  *      the positions of connection points to be pre-transform, but
131  *      obviously the shape transform is applied to them.  That way,
132  *      they will rotate and scale automatically with the shape, when
133  *      the shape transform is altered.  The Percentage version would
134  *      compute their position from the pre-transform dimensions and
135  *      then have the transform applied to them, for example.
136  *
137  *      -  The connection points in the test_connection_points.svg file
138  *      seem to follow the shape when it is moved, but connection
139  *      points I add to new shapes, do not follow the shape, either
140  *      when the shape is just moved or transformed.  There is
141  *      something wrong here.  What exactly should the behaviour be
142  *      currently?
143  *
144  *      -  I see that connection points are specified at absolute canvas
145  *      positions.  I really think that they should be specified in
146  *      shape coordinated relative to the shapes.  There may be
147  *      transforms applied to layers and the canvas which would make
148  *      specifying them quite difficult.  I'd expect a position of 0, 0
149  *      to be on the shape in question or very close to it, for example.
150  *
151  */
155 #include <gdk/gdkkeysyms.h>
156 #include <string>
157 #include <cstring>
159 #include "connector-context.h"
160 #include "pixmaps/cursor-connector.xpm"
161 #include "pixmaps/cursor-node.xpm"
162 //#include "pixmaps/cursor-node-m.xpm"
163 //#include "pixmaps/cursor-node-d.xpm"
164 #include "xml/node-event-vector.h"
165 #include "xml/repr.h"
166 #include "svg/svg.h"
167 #include "desktop.h"
168 #include "desktop-style.h"
169 #include "desktop-handles.h"
170 #include "document.h"
171 #include "message-context.h"
172 #include "message-stack.h"
173 #include "selection.h"
174 #include "inkscape.h"
175 #include "preferences.h"
176 #include "sp-path.h"
177 #include "display/sp-canvas.h"
178 #include "display/canvas-bpath.h"
179 #include "display/sodipodi-ctrl.h"
180 #include <glibmm/i18n.h>
181 #include <glibmm/stringutils.h>
182 #include "snap.h"
183 #include "knot.h"
184 #include "sp-conn-end.h"
185 #include "sp-conn-end-pair.h"
186 #include "conn-avoid-ref.h"
187 #include "libavoid/vertices.h"
188 #include "libavoid/router.h"
189 #include "context-fns.h"
190 #include "sp-namedview.h"
191 #include "sp-text.h"
192 #include "sp-flowtext.h"
193 #include "display/curve.h"
195 using Inkscape::DocumentUndo;
197 static void sp_connector_context_class_init(SPConnectorContextClass *klass);
198 static void sp_connector_context_init(SPConnectorContext *conn_context);
199 static void sp_connector_context_dispose(GObject *object);
201 static void sp_connector_context_setup(SPEventContext *ec);
202 static void sp_connector_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val);
203 static void sp_connector_context_finish(SPEventContext *ec);
204 static gint sp_connector_context_root_handler(SPEventContext *ec, GdkEvent *event);
205 static gint sp_connector_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
207 // Stuff borrowed from DrawContext
208 static void spcc_connector_set_initial_point(SPConnectorContext *cc, Geom::Point const p);
209 static void spcc_connector_set_subsequent_point(SPConnectorContext *cc, Geom::Point const p);
210 static void spcc_connector_finish_segment(SPConnectorContext *cc, Geom::Point p);
211 static void spcc_reset_colors(SPConnectorContext *cc);
212 static void spcc_connector_finish(SPConnectorContext *cc);
213 static void spcc_concat_colors_and_flush(SPConnectorContext *cc);
214 static void spcc_flush_white(SPConnectorContext *cc, SPCurve *gc);
216 // Context event handlers
217 static gint connector_handle_button_press(SPConnectorContext *const cc, GdkEventButton const &bevent);
218 static gint connector_handle_motion_notify(SPConnectorContext *const cc, GdkEventMotion const &mevent);
219 static gint connector_handle_button_release(SPConnectorContext *const cc, GdkEventButton const &revent);
220 static gint connector_handle_key_press(SPConnectorContext *const cc, guint const keyval);
222 static void cc_active_shape_add_knot(SPDesktop* desktop, SPItem* item, ConnectionPointMap &cphandles, ConnectionPoint& cp);
223 static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item);
224 static void cc_clear_active_shape(SPConnectorContext *cc);
225 static void cc_set_active_conn(SPConnectorContext *cc, SPItem *item);
226 static void cc_clear_active_conn(SPConnectorContext *cc);
227 static bool conn_pt_handle_test(SPConnectorContext *cc, Geom::Point& p, gchar **href, gchar **cpid);
228 static void cc_select_handle(SPKnot* knot);
229 static void cc_deselect_handle(SPKnot* knot);
230 static bool cc_item_is_shape(SPItem *item);
231 static void cc_selection_changed(Inkscape::Selection *selection, gpointer data);
232 static void cc_connector_rerouting_finish(SPConnectorContext *const cc,
233         Geom::Point *const p);
235 static void shape_event_attr_deleted(Inkscape::XML::Node *repr,
236         Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data);
237 static void shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
238         gchar const *old_value, gchar const *new_value, bool is_interactive,
239         gpointer data);
242 static char* cc_knot_tips[] = { _("<b>Connection point</b>: click or drag to create a new connector"),
243                            _("<b>Connection point</b>: click to select, drag to move") };
245 /*static Geom::Point connector_drag_origin_w(0, 0);
246 static bool connector_within_tolerance = false;*/
247 static SPEventContextClass *parent_class;
250 static Inkscape::XML::NodeEventVector shape_repr_events = {
251     NULL, /* child_added */
252     NULL, /* child_added */
253     shape_event_attr_changed,
254     NULL, /* content_changed */
255     NULL  /* order_changed */
256 };
258 static Inkscape::XML::NodeEventVector layer_repr_events = {
259     NULL, /* child_added */
260     shape_event_attr_deleted,
261     NULL, /* child_added */
262     NULL, /* content_changed */
263     NULL  /* order_changed */
264 };
267 GType
268 sp_connector_context_get_type(void)
270     static GType type = 0;
271     if (!type) {
272         GTypeInfo info = {
273             sizeof(SPConnectorContextClass),
274             NULL, NULL,
275             (GClassInitFunc) sp_connector_context_class_init,
276             NULL, NULL,
277             sizeof(SPConnectorContext),
278             4,
279             (GInstanceInitFunc) sp_connector_context_init,
280             NULL,   /* value_table */
281         };
282         type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPConnectorContext", &info, (GTypeFlags)0);
283     }
284     return type;
287 static void
288 sp_connector_context_class_init(SPConnectorContextClass *klass)
290     GObjectClass *object_class;
291     SPEventContextClass *event_context_class;
293     object_class = (GObjectClass *) klass;
294     event_context_class = (SPEventContextClass *) klass;
296     parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
298     object_class->dispose = sp_connector_context_dispose;
300     event_context_class->setup = sp_connector_context_setup;
301     event_context_class->set = sp_connector_context_set;
302     event_context_class->finish = sp_connector_context_finish;
303     event_context_class->root_handler = sp_connector_context_root_handler;
304     event_context_class->item_handler = sp_connector_context_item_handler;
308 static void
309 sp_connector_context_init(SPConnectorContext *cc)
311     SPEventContext *ec = SP_EVENT_CONTEXT(cc);
313     ec->cursor_shape = cursor_connector_xpm;
314     ec->hot_x = 1;
315     ec->hot_y = 1;
316     ec->xp = 0;
317     ec->yp = 0;
319     cc->mode = SP_CONNECTOR_CONTEXT_DRAWING_MODE;
320     cc->knot_tip = 0;
322     cc->red_color = 0xff00007f;
324     cc->newconn = NULL;
325     cc->newConnRef = NULL;
326     cc->curvature = 0.0;
328     cc->sel_changed_connection = sigc::connection();
330     cc->active_shape = NULL;
331     cc->active_shape_repr = NULL;
332     cc->active_shape_layer_repr = NULL;
334     cc->active_conn = NULL;
335     cc->active_conn_repr = NULL;
337     cc->active_handle = NULL;
339     cc->selected_handle = NULL;
341     cc->clickeditem = NULL;
342     cc->clickedhandle = NULL;
344     new (&cc->connpthandles) ConnectionPointMap();
346     for (int i = 0; i < 2; ++i) {
347         cc->endpt_handle[i] = NULL;
348         cc->endpt_handler_id[i] = 0;
349     }
350     cc->shref = NULL;
351     cc->scpid = NULL;
352     cc->ehref = NULL;
353     cc->ecpid = NULL;
354     cc->npoints = 0;
355     cc->state = SP_CONNECTOR_CONTEXT_IDLE;
359 static void
360 sp_connector_context_dispose(GObject *object)
362     SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(object);
364     cc->sel_changed_connection.disconnect();
366     if (!cc->connpthandles.empty()) {
367         for (ConnectionPointMap::iterator it = cc->connpthandles.begin();
368                 it != cc->connpthandles.end(); ++it) {
369             g_object_unref(it->first);
370         }
371         cc->connpthandles.clear();
372     }
373     cc->connpthandles.~ConnectionPointMap();
374     for (int i = 0; i < 2; ++i) {
375         if (cc->endpt_handle[1]) {
376             g_object_unref(cc->endpt_handle[i]);
377             cc->endpt_handle[i] = NULL;
378         }
379     }
380     if (cc->shref) {
381         g_free(cc->shref);
382         cc->shref = NULL;
383     }
384     if (cc->scpid) {
385         g_free(cc->scpid);
386         cc->scpid = NULL;
387     }
388     if (cc->ehref) {
389         g_free(cc->shref);
390         cc->shref = NULL;
391     }
392     if (cc->ecpid) {
393         g_free(cc->scpid);
394         cc->scpid = NULL;
395     }
396     g_assert( cc->newConnRef == NULL );
398     G_OBJECT_CLASS(parent_class)->dispose(object);
402 static void
403 sp_connector_context_setup(SPEventContext *ec)
405     SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec);
406     SPDesktop *dt = ec->desktop;
408     if (((SPEventContextClass *) parent_class)->setup) {
409         ((SPEventContextClass *) parent_class)->setup(ec);
410     }
412     cc->selection = sp_desktop_selection(dt);
414     cc->sel_changed_connection.disconnect();
415     cc->sel_changed_connection = cc->selection->connectChanged(
416             sigc::bind(sigc::ptr_fun(&cc_selection_changed),
417             (gpointer) cc));
419     /* Create red bpath */
420     cc->red_bpath = sp_canvas_bpath_new(sp_desktop_sketch(ec->desktop), NULL);
421     sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cc->red_bpath), cc->red_color,
422             1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
423     sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cc->red_bpath), 0x00000000,
424             SP_WIND_RULE_NONZERO);
425     /* Create red curve */
426     cc->red_curve = new SPCurve();
428     /* Create green curve */
429     cc->green_curve = new SPCurve();
431     // Notice the initial selection.
432     cc_selection_changed(cc->selection, (gpointer) cc);
434     cc->within_tolerance = false;
436     sp_event_context_read(ec, "curvature");
437     sp_event_context_read(ec, "orthogonal");
438     sp_event_context_read(ec, "mode");
439     cc->knot_tip = cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE ? cc_knot_tips[0] : cc_knot_tips[1];
440     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
441     if (prefs->getBool("/tools/connector/selcue", 0)) {
442         ec->enableSelectionCue();
443     }
445     // Make sure we see all enter events for canvas items,
446     // even if a mouse button is depressed.
447     dt->canvas->gen_all_enter_events = true;
451 static void
452 sp_connector_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val)
454     SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec);
456     /* fixme: Proper error handling for non-numeric data.  Use a locale-independent function like
457      * g_ascii_strtod (or a thin wrapper that does the right thing for invalid values inf/nan). */
458     Glib::ustring name = val->getEntryName();
459     if ( name == "curvature" ) {
460         cc->curvature = val->getDoubleLimited(); // prevents NaN and +/-Inf from messing up
461     }
462     else if ( name == "orthogonal" ) {
463         cc->isOrthogonal = val->getBool();
464     }
465     else if ( name == "mode")
466     {
467         sp_connector_context_switch_mode(ec, val->getBool() ? SP_CONNECTOR_CONTEXT_EDITING_MODE : SP_CONNECTOR_CONTEXT_DRAWING_MODE);
468     }
471 void sp_connector_context_switch_mode(SPEventContext* ec, unsigned int newMode)
473     SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec);
475     cc->mode = newMode;
476     if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE )
477     {
478         ec->cursor_shape = cursor_connector_xpm;
479         cc->knot_tip = cc_knot_tips[0];
480         if (cc->selected_handle)
481             cc_deselect_handle( cc->selected_handle );
482         cc->selected_handle = NULL;
483         // Show all default connection points
485     }
486     else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE )
487     {
488         ec->cursor_shape = cursor_node_xpm;
489         cc->knot_tip = cc_knot_tips[1];
490 /*            if (cc->active_shape)
491         {
492             cc->selection->set( SP_OBJECT( cc->active_shape ) );
493         }
494         else
495         {
496             SPItem* item = cc->selection->singleItem();
497             if ( item )
498             {
499                 cc_set_active_shape(cc, item);
500                 cc->selection->set( SP_OBJECT( item ) );
501             }
502         }*/
503     }
504     sp_event_context_update_cursor(ec);
509 static void
510 sp_connector_context_finish(SPEventContext *ec)
512     SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec);
514     spcc_connector_finish(cc);
515     cc->state = SP_CONNECTOR_CONTEXT_IDLE;
517     if (((SPEventContextClass *) parent_class)->finish) {
518         ((SPEventContextClass *) parent_class)->finish(ec);
519     }
521     if (cc->selection) {
522         cc->selection = NULL;
523     }
524     cc_clear_active_shape(cc);
525     cc_clear_active_conn(cc);
527     // Restore the default event generating behaviour.
528     SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(ec);
529     desktop->canvas->gen_all_enter_events = false;
533 //-----------------------------------------------------------------------------
536 static void
537 cc_clear_active_shape(SPConnectorContext *cc)
539     if (cc->active_shape == NULL) {
540         return;
541     }
542     g_assert( cc->active_shape_repr );
543     g_assert( cc->active_shape_layer_repr );
545     cc->active_shape = NULL;
547     if (cc->active_shape_repr) {
548         sp_repr_remove_listener_by_data(cc->active_shape_repr, cc);
549         Inkscape::GC::release(cc->active_shape_repr);
550         cc->active_shape_repr = NULL;
552         sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc);
553         Inkscape::GC::release(cc->active_shape_layer_repr);
554         cc->active_shape_layer_repr = NULL;
555     }
557     // Hide the connection points if they exist.
558     if (cc->connpthandles.size()) {
559         for (ConnectionPointMap::iterator it = cc->connpthandles.begin();
560                 it != cc->connpthandles.end(); ++it) {
561             sp_knot_hide(it->first);
562         }
563     }
567 static void
568 cc_clear_active_conn(SPConnectorContext *cc)
570     if (cc->active_conn == NULL) {
571         return;
572     }
573     g_assert( cc->active_conn_repr );
575     cc->active_conn = NULL;
577     if (cc->active_conn_repr) {
578         sp_repr_remove_listener_by_data(cc->active_conn_repr, cc);
579         Inkscape::GC::release(cc->active_conn_repr);
580         cc->active_conn_repr = NULL;
581     }
583     // Hide the endpoint handles.
584     for (int i = 0; i < 2; ++i) {
585         if (cc->endpt_handle[i]) {
586             sp_knot_hide(cc->endpt_handle[i]);
587         }
588     }
592 static bool
593 conn_pt_handle_test(SPConnectorContext *cc, Geom::Point& p, gchar **href, gchar **cpid)
595     // TODO: this will need to change when there are more connection
596     //       points available for each shape.
598     if (cc->active_handle && (cc->connpthandles.find(cc->active_handle) != cc->connpthandles.end()))
599     {
600         p = cc->active_handle->pos;
601         const ConnectionPoint& cp = cc->connpthandles[cc->active_handle];
602         *href = g_strdup_printf("#%s", cc->active_shape->getId());
603         *cpid = g_strdup_printf("%c%d", cp.type == ConnPointDefault ? 'd' : 'u' , cp.id);
604         return true;
605     }
606     *href = NULL;
607     *cpid = NULL;
608     return false;
611 static void
612 cc_select_handle(SPKnot* knot)
614     knot->setShape(SP_KNOT_SHAPE_SQUARE);
615     knot->setSize(10);
616     knot->setAnchor(GTK_ANCHOR_CENTER);
617     knot->setFill(0x0000ffff, 0x0000ffff, 0x0000ffff);
618     sp_knot_update_ctrl(knot);
621 static void
622 cc_deselect_handle(SPKnot* knot)
624     knot->setShape(SP_KNOT_SHAPE_SQUARE);
625     knot->setSize(8);
626     knot->setAnchor(GTK_ANCHOR_CENTER);
627     knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff);
628     sp_knot_update_ctrl(knot);
631 static gint
632 sp_connector_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
634     gint ret = FALSE;
636     SPDesktop *desktop = event_context->desktop;
638     SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(event_context);
640     Geom::Point p(event->button.x, event->button.y);
642     switch (event->type) {
643         case GDK_BUTTON_RELEASE:
644             if (event->button.button == 1 && !event_context->space_panning) {
645                 if ((cc->state == SP_CONNECTOR_CONTEXT_DRAGGING) &&
646                         (event_context->within_tolerance))
647                 {
648                     spcc_reset_colors(cc);
649                     cc->state = SP_CONNECTOR_CONTEXT_IDLE;
650                 }
651                 if (cc->state != SP_CONNECTOR_CONTEXT_IDLE) {
652                     // Doing something else like rerouting.
653                     break;
654                 }
655                 // find out clicked item, honoring Alt
656                 SPItem *item = sp_event_context_find_item(desktop,
657                         p, event->button.state & GDK_MOD1_MASK, FALSE);
659                 if (event->button.state & GDK_SHIFT_MASK) {
660                     cc->selection->toggle(item);
661                 } else {
662                     cc->selection->set(item);
663                     if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE && cc->selected_handle )
664                     {
665                         cc_deselect_handle( cc->selected_handle );
666                         cc->selected_handle = NULL;
667                     }
668                     /* When selecting a new item,
669                        do not allow showing connection points
670                        on connectors. (yet?)
671                     */
672                     if ( item != cc->active_shape && !cc_item_is_connector( item ) )
673                         cc_set_active_shape( cc, item );
674                 }
675                 ret = TRUE;
677             }
678             break;
679         case GDK_ENTER_NOTIFY:
680         {
681             if (cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE || (cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE && !cc->selected_handle))
682             {
683                 if (cc_item_is_shape(item)) {
685                     // I don't really understand what the above does,
686                     // so I commented it.
687                     // This is a shape, so show connection point(s).
688     /*                if (!(cc->active_shape)
689                             // Don't show handle for another handle.
690     //                         || (cc->connpthandles.find((SPKnot*) item) != cc->connpthandles.end())
691                         )
692                     {
693                         cc_set_active_shape(cc, item);
694                     }*/
695                     cc_set_active_shape(cc, item);
696                 }
697                 ret = TRUE;
698             }
699             break;
700         }
701         default:
702             break;
703     }
705     return ret;
709 gint
710 sp_connector_context_root_handler(SPEventContext *ec, GdkEvent *event)
712     SPConnectorContext *const cc = SP_CONNECTOR_CONTEXT(ec);
714     gint ret = FALSE;
716     switch (event->type) {
717         case GDK_BUTTON_PRESS:
718             ret = connector_handle_button_press(cc, event->button);
719             break;
721         case GDK_MOTION_NOTIFY:
722             ret = connector_handle_motion_notify(cc, event->motion);
723             break;
725         case GDK_BUTTON_RELEASE:
726             ret = connector_handle_button_release(cc, event->button);
727             break;
728         case GDK_KEY_PRESS:
729             ret = connector_handle_key_press(cc, get_group0_keyval (&event->key));
730             break;
732         default:
733             break;
734     }
736     if (!ret) {
737         gint (*const parent_root_handler)(SPEventContext *, GdkEvent *)
738             = ((SPEventContextClass *) parent_class)->root_handler;
739         if (parent_root_handler) {
740             ret = parent_root_handler(ec, event);
741         }
742     }
744     return ret;
748 static gint
749 connector_handle_button_press(SPConnectorContext *const cc, GdkEventButton const &bevent)
751     Geom::Point const event_w(bevent.x, bevent.y);
752     /* Find desktop coordinates */
753     Geom::Point p = cc->desktop->w2d(event_w);
754     SPEventContext *event_context = SP_EVENT_CONTEXT(cc);
756     gint ret = FALSE;
757     if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE )
758     {
759         if ( bevent.button == 1 && !event_context->space_panning ) {
761             SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
763             if (Inkscape::have_viable_layer(desktop, cc->_message_context) == false) {
764                 return TRUE;
765             }
767             Geom::Point const event_w(bevent.x,
768                                     bevent.y);
769 //             connector_drag_origin_w = event_w;
770             cc->xp = bevent.x;
771             cc->yp = bevent.y;
772             cc->within_tolerance = true;
774             Geom::Point const event_dt = cc->desktop->w2d(event_w);
776             SnapManager &m = cc->desktop->namedview->snap_manager;
778             switch (cc->state) {
779                 case SP_CONNECTOR_CONTEXT_STOP:
780                     /* This is allowed, if we just canceled curve */
781                 case SP_CONNECTOR_CONTEXT_IDLE:
782                 {
783                     if ( cc->npoints == 0 ) {
784                         cc_clear_active_conn(cc);
786                         SP_EVENT_CONTEXT_DESKTOP(cc)->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new connector"));
788                         /* Set start anchor */
789                         /* Create green anchor */
790                         Geom::Point p = event_dt;
792                         // Test whether we clicked on a connection point
793                         bool found = conn_pt_handle_test(cc, p, &cc->shref, &cc->scpid);
795                         if (!found) {
796                             // This is the first point, so just snap it to the grid
797                             // as there's no other points to go off.
798                             m.setup(cc->desktop);
799                             m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
800                             m.unSetup();
801                         }
802                         spcc_connector_set_initial_point(cc, p);
804                     }
805                     cc->state = SP_CONNECTOR_CONTEXT_DRAGGING;
806                     ret = TRUE;
807                     break;
808                 }
809                 case SP_CONNECTOR_CONTEXT_DRAGGING:
810                 {
811                     // This is the second click of a connector creation.
812                     m.setup(cc->desktop);
813                     m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
814                     m.unSetup();
816                     spcc_connector_set_subsequent_point(cc, p);
817                     spcc_connector_finish_segment(cc, p);
818                     // Test whether we clicked on a connection point
819                     /*bool found = */conn_pt_handle_test(cc, p, &cc->ehref, &cc->ecpid);
820                     if (cc->npoints != 0) {
821                         spcc_connector_finish(cc);
822                     }
823                     cc_set_active_conn(cc, cc->newconn);
824                     cc->state = SP_CONNECTOR_CONTEXT_IDLE;
825                     ret = TRUE;
826                     break;
827                 }
828                 case SP_CONNECTOR_CONTEXT_CLOSE:
829                 {
830                     g_warning("Button down in CLOSE state");
831                     break;
832                 }
833                 default:
834                     break;
835             }
836         } else if (bevent.button == 3) {
837             if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) {
838                 // A context menu is going to be triggered here,
839                 // so end the rerouting operation.
840                 cc_connector_rerouting_finish(cc, &p);
842                 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
844                 // Don't set ret to TRUE, so we drop through to the
845                 // parent handler which will open the context menu.
846             }
847             else if (cc->npoints != 0) {
848                 spcc_connector_finish(cc);
849                 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
850                 ret = TRUE;
851             }
852         }
853     }
854     else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE )
855     {
856         if ( bevent.button == 1 && !event_context->space_panning )
857         {
858             // Initialize variables in case of dragging
860             SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
862             if (Inkscape::have_viable_layer(desktop, cc->_message_context) == false) {
863                 return TRUE;
864             }
866             cc->xp = bevent.x;
867             cc->yp = bevent.y;
868             cc->within_tolerance = true;
870             ConnectionPointMap::iterator const& active_knot_it = cc->connpthandles.find( cc->active_handle );
872             switch (cc->state)
873             {
874                 case SP_CONNECTOR_CONTEXT_IDLE:
875                     if ( active_knot_it != cc->connpthandles.end() )
876                     {
877                         // We do not allow selecting and, thereby, moving default knots
878                         if ( active_knot_it->second.type != ConnPointDefault)
879                         {
880                             if (cc->selected_handle != cc->active_handle)
881                             {
882                                 if ( cc->selected_handle )
883                                     cc_deselect_handle( cc->selected_handle );
884                                 cc->selected_handle = cc->active_handle;
885                                 cc_select_handle( cc->selected_handle );
886                             }
887                         }
888                         else
889                             // Just ignore the default connection point
890                             return FALSE;
891                     }
892                     else
893                         if ( cc->selected_handle )
894                         {
895                             cc_deselect_handle( cc->selected_handle );
896                             cc->selected_handle = NULL;
897                         }
899                     if ( cc->selected_handle )
900                     {
901                         cc->state = SP_CONNECTOR_CONTEXT_DRAGGING;
902                         cc->selection->set( SP_OBJECT( cc->active_shape ) );
903                     }
905                     ret = TRUE;
906                     break;
907                 // Dragging valid because of the way we create
908                 // new connection points.
909                 case SP_CONNECTOR_CONTEXT_DRAGGING:
910                     // Do nothing.
911                     ret = TRUE;
912                     break;
913             }
914         }
915     }
916     return ret;
920 static gint
921 connector_handle_motion_notify(SPConnectorContext *const cc, GdkEventMotion const &mevent)
923     gint ret = FALSE;
924     SPEventContext *event_context = SP_EVENT_CONTEXT(cc);
925     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
927     if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) {
928         // allow middle-button scrolling
929         return FALSE;
930     }
932     Geom::Point const event_w(mevent.x, mevent.y);
934     if (cc->within_tolerance) {
935         cc->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
936         if ( ( abs( (gint) mevent.x - cc->xp ) < cc->tolerance ) &&
937              ( abs( (gint) mevent.y - cc->yp ) < cc->tolerance ) ) {
938             return FALSE;   // Do not drag if we're within tolerance from origin.
939         }
940     }
941     // Once the user has moved farther than tolerance from the original location
942     // (indicating they intend to move the object, not click), then always process
943     // the motion notify coordinates as given (no snapping back to origin)
944     cc->within_tolerance = false;
946     SPDesktop *const dt = cc->desktop;
948     /* Find desktop coordinates */
949     Geom::Point p = dt->w2d(event_w);
951     if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE )
952     {
953         SnapManager &m = dt->namedview->snap_manager;
955         switch (cc->state) {
956             case SP_CONNECTOR_CONTEXT_DRAGGING:
957             {
958                 gobble_motion_events(mevent.state);
959                 // This is movement during a connector creation.
960                 if ( cc->npoints > 0 ) {
961                     m.setup(dt);
962                     m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
963                     m.unSetup();
964                     cc->selection->clear();
965                     spcc_connector_set_subsequent_point(cc, p);
966                     ret = TRUE;
967                 }
968                 break;
969             }
970             case SP_CONNECTOR_CONTEXT_REROUTING:
971             {
972                 gobble_motion_events(GDK_BUTTON1_MASK);
973                 g_assert( SP_IS_PATH(cc->clickeditem));
975                 m.setup(dt);
976                 m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
977                 m.unSetup();
979                 // Update the hidden path
980                 Geom::Matrix i2d = (cc->clickeditem)->i2d_affine();
981                 Geom::Matrix d2i = i2d.inverse();
982                 SPPath *path = SP_PATH(cc->clickeditem);
983                 SPCurve *curve = path->original_curve ? path->original_curve : path->curve;
984                 if (cc->clickedhandle == cc->endpt_handle[0]) {
985                     Geom::Point o = cc->endpt_handle[1]->pos;
986                     curve->stretch_endpoints(p * d2i, o * d2i);
987                 }
988                 else {
989                     Geom::Point o = cc->endpt_handle[0]->pos;
990                     curve->stretch_endpoints(o * d2i, p * d2i);
991                 }
992                 sp_conn_reroute_path_immediate(path);
994                 // Copy this to the temporary visible path
995                 cc->red_curve = path->original_curve ?
996                         path->original_curve->copy() : path->curve->copy();
997                 cc->red_curve->transform(i2d);
999                 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
1000                 ret = TRUE;
1001                 break;
1002             }
1003             case SP_CONNECTOR_CONTEXT_STOP:
1004                 /* This is perfectly valid */
1005                 break;
1006             default:
1007                 if (!sp_event_context_knot_mouseover(cc)) {
1008                     m.setup(dt);
1009                     m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_OTHER_HANDLE));
1010                     m.unSetup();
1011                 }
1012                 break;
1013         }
1014     }
1015     else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE )
1016     {
1017         switch ( cc->state )
1018         {
1019             case SP_CONNECTOR_CONTEXT_DRAGGING:
1020                 sp_knot_set_position(cc->selected_handle, p, 0);
1021                 ret = TRUE;
1022                 break;
1023             case SP_CONNECTOR_CONTEXT_NEWCONNPOINT:
1024                 sp_knot_set_position(cc->selected_handle, p, 0);
1025                 ret = TRUE;
1026                 break;
1027         }
1028     }
1030     return ret;
1034 static gint
1035 connector_handle_button_release(SPConnectorContext *const cc, GdkEventButton const &revent)
1037     gint ret = FALSE;
1038     SPEventContext *event_context = SP_EVENT_CONTEXT(cc);
1039     if ( revent.button == 1 && !event_context->space_panning ) {
1041         SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1042         SPDocument *doc = sp_desktop_document(desktop);
1044         SnapManager &m = desktop->namedview->snap_manager;
1046         Geom::Point const event_w(revent.x, revent.y);
1048         /* Find desktop coordinates */
1049         Geom::Point p = cc->desktop->w2d(event_w);
1050         if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE )
1051         {
1052             switch (cc->state) {
1053                 //case SP_CONNECTOR_CONTEXT_POINT:
1054                 case SP_CONNECTOR_CONTEXT_DRAGGING:
1055                 {
1056                     m.setup(desktop);
1057                     m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
1058                     m.unSetup();
1060                     if (cc->within_tolerance)
1061                     {
1062                         spcc_connector_finish_segment(cc, p);
1063                         return TRUE;
1064                     }
1065                     // Connector has been created via a drag, end it now.
1066                     spcc_connector_set_subsequent_point(cc, p);
1067                     spcc_connector_finish_segment(cc, p);
1068                     // Test whether we clicked on a connection point
1069                     /*bool found = */conn_pt_handle_test(cc, p, &cc->ehref, &cc->ecpid);
1070                     if (cc->npoints != 0) {
1071                         spcc_connector_finish(cc);
1072                     }
1073                     cc_set_active_conn(cc, cc->newconn);
1074                     cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1075                     break;
1076                 }
1077                 case SP_CONNECTOR_CONTEXT_REROUTING:
1078                 {
1079                     m.setup(desktop);
1080                     m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
1081                     m.unSetup();
1082                     cc_connector_rerouting_finish(cc, &p);
1084                     doc->ensureUpToDate();
1085                     cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1086                     return TRUE;
1087                     break;
1088                 }
1089                 case SP_CONNECTOR_CONTEXT_STOP:
1090                     /* This is allowed, if we just cancelled curve */
1091                     break;
1092                 default:
1093                     break;
1094             }
1095             ret = TRUE;
1096         }
1097         else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE )
1098         {
1099             switch ( cc->state )
1100             {
1101                 case SP_CONNECTOR_CONTEXT_DRAGGING:
1103                     if (!cc->within_tolerance)
1104                     {
1105                         m.setup(desktop);
1106                         m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
1107                         m.unSetup();
1108                         sp_knot_set_position(cc->selected_handle, p, 0);
1109                         ConnectionPoint& cp = cc->connpthandles[cc->selected_handle];
1110                         cp.pos = p * (cc->active_shape)->dt2i_affine();
1111                         cc->active_shape->avoidRef->updateConnectionPoint(cp);
1112                     }
1114                     cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1115                     ret = TRUE;
1116                     break;
1119                 case SP_CONNECTOR_CONTEXT_NEWCONNPOINT:
1120                     m.setup(desktop);
1121                     m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
1122                     m.unSetup();
1124                     sp_knot_set_position(cc->selected_handle, p, 0);
1126                     ConnectionPoint cp;
1127                     cp.type = ConnPointUserDefined;
1128                     cp.pos = p * (cc->active_shape)->dt2i_affine();
1129                     cp.dir = Avoid::ConnDirAll;
1130                     g_object_unref(cc->selected_handle);
1131                     cc->active_shape->avoidRef->addConnectionPoint(cp);
1132                     doc->ensureUpToDate();
1133                     for (ConnectionPointMap::iterator it = cc->connpthandles.begin(); it != cc->connpthandles.end(); ++it)
1134                         if (it->second.type == ConnPointUserDefined && it->second.id == cp.id)
1135                         {
1136                             cc->selected_handle = it->first;
1137                             break;
1138                         }
1139                     cc_select_handle( cc->selected_handle );
1140                     cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1141                     ret = TRUE;
1142                     break;
1143             }
1144         }
1145     }
1148     return ret;
1152 static gint
1153 connector_handle_key_press(SPConnectorContext *const cc, guint const keyval)
1155     gint ret = FALSE;
1156     /* fixme: */
1157     if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE )
1158     {
1159         switch (keyval) {
1160             case GDK_Return:
1161             case GDK_KP_Enter:
1162                 if (cc->npoints != 0) {
1163                     spcc_connector_finish(cc);
1164                     cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1165                     ret = TRUE;
1166                 }
1167                 break;
1168             case GDK_Escape:
1169                 if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) {
1171                     SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1172                     SPDocument *doc = sp_desktop_document(desktop);
1174                     cc_connector_rerouting_finish(cc, NULL);
1176                     DocumentUndo::undo(doc);
1178                     cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1179                     desktop->messageStack()->flash( Inkscape::NORMAL_MESSAGE,
1180                             _("Connector endpoint drag cancelled."));
1181                     ret = TRUE;
1182                 }
1183                 else if (cc->npoints != 0) {
1184                     // if drawing, cancel, otherwise pass it up for deselecting
1185                     cc->state = SP_CONNECTOR_CONTEXT_STOP;
1186                     spcc_reset_colors(cc);
1187                     ret = TRUE;
1188                 }
1189                 break;
1190             default:
1191                 break;
1192         }
1193     }
1194     else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE )
1195     {
1196         switch ( cc->state )
1197         {
1198             case SP_CONNECTOR_CONTEXT_DRAGGING:
1199                 if ( keyval == GDK_Escape )
1200                 {
1201                     // Cancel connection point dragging
1203                     // Obtain original position
1204                     ConnectionPoint const& cp = cc->connpthandles[cc->selected_handle];
1205                     SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1206                     const Geom::Matrix& i2doc = (cc->active_shape)->i2doc_affine();
1207                     sp_knot_set_position(cc->selected_handle, cp.pos * i2doc * desktop->doc2dt(), 0);
1208                     cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1209                     desktop->messageStack()->flash( Inkscape::NORMAL_MESSAGE,
1210                         _("Connection point drag cancelled."));
1211                     ret = TRUE;
1212                 }
1213                 else if ( keyval == GDK_Return || keyval == GDK_KP_Enter )
1214                 {
1215                     // Put connection point at current position
1217                     Geom::Point p = cc->selected_handle->pos;
1219                     if (!cc->within_tolerance)
1220                     {
1221                         SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1222                         SnapManager &m = desktop->namedview->snap_manager;
1223                         m.setup(desktop);
1224                         m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
1225                         m.unSetup();
1226                         sp_knot_set_position(cc->selected_handle, p, 0);
1227                         ConnectionPoint& cp = cc->connpthandles[cc->selected_handle];
1228                         cp.pos = p * (cc->active_shape)->dt2i_affine();
1229                         cc->active_shape->avoidRef->updateConnectionPoint(cp);
1230                     }
1232                     cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1233                     ret = TRUE;
1234                 }
1235                 break;
1236             case SP_CONNECTOR_CONTEXT_NEWCONNPOINT:
1237                 if ( keyval == GDK_Escape )
1238                 {
1239                     // Just destroy the knot
1240                     g_object_unref( cc->selected_handle );
1241                     cc->selected_handle = NULL;
1242                     cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1243                     ret = TRUE;
1244                 }
1245                 else if ( keyval == GDK_Return || keyval == GDK_KP_Enter )
1246                 {
1247                     SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1248                     SPDocument *doc = sp_desktop_document(desktop);
1249                     SnapManager &m = desktop->namedview->snap_manager;
1250                     m.setup(desktop);
1251                     Geom::Point p = cc->selected_handle->pos;
1252                     m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
1253                     m.unSetup();
1254                     sp_knot_set_position(cc->selected_handle, p, 0);
1256                     ConnectionPoint cp;
1257                     cp.type = ConnPointUserDefined;
1258                     cp.pos = p * (cc->active_shape)->dt2i_affine();
1259                     cp.dir = Avoid::ConnDirAll;
1260                     g_object_unref(cc->selected_handle);
1261                     cc->active_shape->avoidRef->addConnectionPoint(cp);
1262                     doc->ensureUpToDate();
1263                     for (ConnectionPointMap::iterator it = cc->connpthandles.begin(); it != cc->connpthandles.end(); ++it)
1264                         if (it->second.type == ConnPointUserDefined && it->second.id == cp.id)
1265                         {
1266                             cc->selected_handle = it->first;
1267                             break;
1268                         }
1269                     cc_select_handle( cc->selected_handle );
1270                     cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1271                     ret = TRUE;
1272                 }
1274                 break;
1275             case SP_CONNECTOR_CONTEXT_IDLE:
1276                 if ( keyval == GDK_Delete && cc->selected_handle )
1277                 {
1278                     cc->active_shape->avoidRef->deleteConnectionPoint(cc->connpthandles[cc->selected_handle]);
1279                     cc->selected_handle = NULL;
1280                     ret = TRUE;
1281                 }
1283                 break;
1284         }
1285     }
1287     return ret;
1291 static void
1292 cc_connector_rerouting_finish(SPConnectorContext *const cc, Geom::Point *const p)
1294     SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1295     SPDocument *doc = sp_desktop_document(desktop);
1297     // Clear the temporary path:
1298     cc->red_curve->reset();
1299     sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
1301     if (p != NULL)
1302     {
1303         // Test whether we clicked on a connection point
1304         gchar *shape_label, *cpid;
1305         bool found = conn_pt_handle_test(cc, *p, &shape_label, &cpid);
1307         if (found) {
1308             if (cc->clickedhandle == cc->endpt_handle[0]) {
1309                 cc->clickeditem->setAttribute("inkscape:connection-start", shape_label, false);
1310                 cc->clickeditem->setAttribute("inkscape:connection-start-point", cpid, false);
1311             }
1312             else {
1313                 cc->clickeditem->setAttribute("inkscape:connection-end", shape_label, false);
1314                 cc->clickeditem->setAttribute("inkscape:connection-end-point", cpid, false);
1315             }
1316             g_free(shape_label);
1317         }
1318     }
1319     cc->clickeditem->setHidden(false);
1320     sp_conn_reroute_path_immediate(SP_PATH(cc->clickeditem));
1321     cc->clickeditem->updateRepr();
1322     DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR,
1323                      _("Reroute connector"));
1324     cc_set_active_conn(cc, cc->clickeditem);
1328 static void
1329 spcc_reset_colors(SPConnectorContext *cc)
1331     /* Red */
1332     cc->red_curve->reset();
1333     sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
1335     cc->green_curve->reset();
1336     cc->npoints = 0;
1340 static void
1341 spcc_connector_set_initial_point(SPConnectorContext *const cc, Geom::Point const p)
1343     g_assert( cc->npoints == 0 );
1345     cc->p[0] = p;
1346     cc->p[1] = p;
1347     cc->npoints = 2;
1348     sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
1352 static void
1353 spcc_connector_set_subsequent_point(SPConnectorContext *const cc, Geom::Point const p)
1355     g_assert( cc->npoints != 0 );
1357     SPDesktop *dt = cc->desktop;
1358     Geom::Point o = dt->dt2doc(cc->p[0]);
1359     Geom::Point d = dt->dt2doc(p);
1360     Avoid::Point src(o[Geom::X], o[Geom::Y]);
1361     Avoid::Point dst(d[Geom::X], d[Geom::Y]);
1363     if (!cc->newConnRef) {
1364         Avoid::Router *router = sp_desktop_document(dt)->router;
1365         cc->newConnRef = new Avoid::ConnRef(router);
1366         cc->newConnRef->setEndpoint(Avoid::VertID::src, src);
1367         if (cc->isOrthogonal)
1368             cc->newConnRef->setRoutingType(Avoid::ConnType_Orthogonal);
1369         else
1370             cc->newConnRef->setRoutingType(Avoid::ConnType_PolyLine);
1371     }
1372     // Set new endpoint.
1373     cc->newConnRef->setEndpoint(Avoid::VertID::tar, dst);
1374     // Immediately generate new routes for connector.
1375     cc->newConnRef->makePathInvalid();
1376     cc->newConnRef->router()->processTransaction();
1377     // Recreate curve from libavoid route.
1378     recreateCurve( cc->red_curve, cc->newConnRef, cc->curvature );
1379     cc->red_curve->transform(dt->doc2dt());
1380     sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
1384 /**
1385  * Concats red, blue and green.
1386  * If any anchors are defined, process these, optionally removing curves from white list
1387  * Invoke _flush_white to write result back to object.
1388  */
1389 static void
1390 spcc_concat_colors_and_flush(SPConnectorContext *cc)
1392     SPCurve *c = cc->green_curve;
1393     cc->green_curve = new SPCurve();
1395     cc->red_curve->reset();
1396     sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
1398     if (c->is_empty()) {
1399         c->unref();
1400         return;
1401     }
1403     spcc_flush_white(cc, c);
1405     c->unref();
1409 /*
1410  * Flushes white curve(s) and additional curve into object
1411  *
1412  * No cleaning of colored curves - this has to be done by caller
1413  * No rereading of white data, so if you cannot rely on ::modified, do it in caller
1414  *
1415  */
1417 static void
1418 spcc_flush_white(SPConnectorContext *cc, SPCurve *gc)
1420     SPCurve *c;
1422     if (gc) {
1423         c = gc;
1424         c->ref();
1425     } else {
1426         return;
1427     }
1429     /* Now we have to go back to item coordinates at last */
1430     c->transform(SP_EVENT_CONTEXT_DESKTOP(cc)->dt2doc());
1432     SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1433     SPDocument *doc = sp_desktop_document(desktop);
1434     Inkscape::XML::Document *xml_doc = doc->getReprDoc();
1436     if ( c && !c->is_empty() ) {
1437         /* We actually have something to write */
1439         Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
1440         /* Set style */
1441         sp_desktop_apply_style_tool(desktop, repr, "/tools/connector", false);
1443         gchar *str = sp_svg_write_path( c->get_pathvector() );
1444         g_assert( str != NULL );
1445         repr->setAttribute("d", str);
1446         g_free(str);
1448         /* Attach repr */
1449         cc->newconn = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
1450         cc->newconn->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
1452         bool connection = false;
1453         cc->newconn->setAttribute( "inkscape:connector-type",
1454                                    cc->isOrthogonal ? "orthogonal" : "polyline", false );
1455         cc->newconn->setAttribute( "inkscape:connector-curvature",
1456                                    Glib::Ascii::dtostr(cc->curvature).c_str(), false );
1457         if (cc->shref)
1458         {
1459             cc->newconn->setAttribute( "inkscape:connection-start", cc->shref, false);
1460             if (cc->scpid) {
1461                 cc->newconn->setAttribute( "inkscape:connection-start-point", cc->scpid, false);
1462             }
1463             connection = true;
1464         }
1466         if (cc->ehref)
1467         {
1468             cc->newconn->setAttribute( "inkscape:connection-end", cc->ehref, false);
1469             if (cc->ecpid) {
1470                 cc->newconn->setAttribute( "inkscape:connection-end-point", cc->ecpid, false);
1471             }
1472             connection = true;
1473         }
1474         // Process pending updates.
1475         cc->newconn->updateRepr();
1476         doc->ensureUpToDate();
1478         if (connection) {
1479             // Adjust endpoints to shape edge.
1480             sp_conn_reroute_path_immediate(SP_PATH(cc->newconn));
1481             cc->newconn->updateRepr();
1482         }
1484         // Only set the selection after we are finished with creating the attributes of
1485         // the connector.  Otherwise, the selection change may alter the defaults for
1486         // values like curvature in the connector context, preventing subsequent lookup
1487         // of their original values.
1488         cc->selection->set(repr);
1489         Inkscape::GC::release(repr);
1490     }
1492     c->unref();
1494     DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR, _("Create connector"));
1498 static void
1499 spcc_connector_finish_segment(SPConnectorContext *const cc, Geom::Point const /*p*/)
1501     if (!cc->red_curve->is_empty()) {
1502         cc->green_curve->append_continuous(cc->red_curve, 0.0625);
1504         cc->p[0] = cc->p[3];
1505         cc->p[1] = cc->p[4];
1506         cc->npoints = 2;
1508         cc->red_curve->reset();
1509     }
1513 static void
1514 spcc_connector_finish(SPConnectorContext *const cc)
1516     SPDesktop *const desktop = cc->desktop;
1517     desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing connector"));
1519     cc->red_curve->reset();
1520     spcc_concat_colors_and_flush(cc);
1522     cc->npoints = 0;
1524     if (cc->newConnRef) {
1525         cc->newConnRef->removeFromGraph();
1526         delete cc->newConnRef;
1527         cc->newConnRef = NULL;
1528     }
1532 static gboolean
1533 cc_generic_knot_handler(SPCanvasItem *, GdkEvent *event, SPKnot *knot)
1535     g_assert (knot != NULL);
1537     g_object_ref(knot);
1539     SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(
1540             knot->desktop->event_context);
1542     gboolean consumed = FALSE;
1544     gchar* knot_tip = knot->tip ? knot->tip : cc->knot_tip;
1545     switch (event->type) {
1546         case GDK_ENTER_NOTIFY:
1547             sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, TRUE);
1549             cc->active_handle = knot;
1550             if (knot_tip)
1551             {
1552                 knot->desktop->event_context->defaultMessageContext()->set(
1553                         Inkscape::NORMAL_MESSAGE, knot_tip);
1554             }
1556             consumed = TRUE;
1557             break;
1558         case GDK_LEAVE_NOTIFY:
1559             sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE);
1561             cc->active_handle = NULL;
1563             if (knot_tip) {
1564                 knot->desktop->event_context->defaultMessageContext()->clear();
1565             }
1567             consumed = TRUE;
1568             break;
1569         default:
1570             break;
1571     }
1573     g_object_unref(knot);
1575     return consumed;
1579 static gboolean
1580 endpt_handler(SPKnot */*knot*/, GdkEvent *event, SPConnectorContext *cc)
1582     g_assert( SP_IS_CONNECTOR_CONTEXT(cc) );
1584     gboolean consumed = FALSE;
1586     switch (event->type) {
1587         case GDK_BUTTON_PRESS:
1588             g_assert( (cc->active_handle == cc->endpt_handle[0]) ||
1589                       (cc->active_handle == cc->endpt_handle[1]) );
1590             if (cc->state == SP_CONNECTOR_CONTEXT_IDLE) {
1591                 cc->clickeditem = cc->active_conn;
1592                 cc->clickedhandle = cc->active_handle;
1593                 cc_clear_active_conn(cc);
1594                 cc->state = SP_CONNECTOR_CONTEXT_REROUTING;
1596                 // Disconnect from attached shape
1597                 unsigned ind = (cc->active_handle == cc->endpt_handle[0]) ? 0 : 1;
1598                 sp_conn_end_detach(cc->clickeditem, ind);
1600                 Geom::Point origin;
1601                 if (cc->clickedhandle == cc->endpt_handle[0]) {
1602                     origin = cc->endpt_handle[1]->pos;
1603                 }
1604                 else {
1605                     origin = cc->endpt_handle[0]->pos;
1606                 }
1608                 // Show the red path for dragging.
1609                 cc->red_curve = SP_PATH(cc->clickeditem)->original_curve ? SP_PATH(cc->clickeditem)->original_curve->copy() : SP_PATH(cc->clickeditem)->curve->copy();
1610                 Geom::Matrix i2d = (cc->clickeditem)->i2d_affine();
1611                 cc->red_curve->transform(i2d);
1612                 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
1614                 cc->clickeditem->setHidden(true);
1616                 // The rest of the interaction rerouting the connector is
1617                 // handled by the context root handler.
1618                 consumed = TRUE;
1619             }
1620             break;
1621         default:
1622             break;
1623     }
1625     return consumed;
1628 static void cc_active_shape_add_knot(SPDesktop* desktop, SPItem* item, ConnectionPointMap &cphandles, ConnectionPoint& cp)
1630         SPKnot *knot = sp_knot_new(desktop, 0);
1632         knot->setShape(SP_KNOT_SHAPE_SQUARE);
1633         knot->setSize(8);
1634         knot->setAnchor(GTK_ANCHOR_CENTER);
1635         knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff);
1636         sp_knot_update_ctrl(knot);
1638         // We don't want to use the standard knot handler.
1639         g_signal_handler_disconnect(G_OBJECT(knot->item),
1640                 knot->_event_handler_id);
1641         knot->_event_handler_id = 0;
1643         gtk_signal_connect(GTK_OBJECT(knot->item), "event",
1644                 GTK_SIGNAL_FUNC(cc_generic_knot_handler), knot);
1645         sp_knot_set_position(knot, item->avoidRef->getConnectionPointPos(cp.type, cp.id) * desktop->doc2dt(), 0);
1646         sp_knot_show(knot);
1647         cphandles[knot] = cp;
1650 static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item)
1652     g_assert(item != NULL );
1654     std::map<int, ConnectionPoint>* connpts = &item->avoidRef->connection_points;
1656     if (cc->active_shape != item)
1657     {
1658         // The active shape has changed
1659         // Rebuild everything
1660         cc->active_shape = item;
1661         // Remove existing active shape listeners
1662         if (cc->active_shape_repr) {
1663             sp_repr_remove_listener_by_data(cc->active_shape_repr, cc);
1664             Inkscape::GC::release(cc->active_shape_repr);
1666             sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc);
1667             Inkscape::GC::release(cc->active_shape_layer_repr);
1668         }
1670         // Listen in case the active shape changes
1671         cc->active_shape_repr = SP_OBJECT_REPR(item);
1672         if (cc->active_shape_repr) {
1673             Inkscape::GC::anchor(cc->active_shape_repr);
1674             sp_repr_add_listener(cc->active_shape_repr, &shape_repr_events, cc);
1676             cc->active_shape_layer_repr = cc->active_shape_repr->parent();
1677             Inkscape::GC::anchor(cc->active_shape_layer_repr);
1678             sp_repr_add_listener(cc->active_shape_layer_repr, &layer_repr_events, cc);
1679         }
1682         // Set the connection points.
1683         if ( cc->connpthandles.size() )
1684             // destroy the old list
1685             while (! cc->connpthandles.empty() )
1686             {
1687                 g_object_unref(cc->connpthandles.begin()->first);
1688                 cc->connpthandles.erase(cc->connpthandles.begin());
1689             }
1690         // build the new one
1691         if ( connpts->size() )
1692         for (std::map<int, ConnectionPoint>::iterator it = connpts->begin(); it != connpts->end(); ++it)
1693             cc_active_shape_add_knot(cc->desktop, item, cc->connpthandles, it->second);
1695         // Also add default connection points
1696         // For now, only centre default connection point will
1697         // be available
1698         ConnectionPoint centre;
1699         centre.type = ConnPointDefault;
1700         centre.id = ConnPointPosCC;
1701         cc_active_shape_add_knot(cc->desktop, item, cc->connpthandles, centre);
1702     }
1703     else
1704     {
1705         // The active shape didn't change
1706         // Update only the connection point knots
1708         // Ensure the item's connection_points map
1709         // has been updated
1710         item->document->ensureUpToDate();
1712         std::set<int> seen;
1713         for  ( ConnectionPointMap::iterator it = cc->connpthandles.begin(); it != cc->connpthandles.end() ;)
1714         {
1715             bool removed = false;
1716             if ( it->second.type == ConnPointUserDefined )
1717             {
1718                 std::map<int, ConnectionPoint>::iterator p = connpts->find(it->second.id);
1719                 if (p != connpts->end())
1720                 {
1721                     if ( it->second != p->second )
1722                         // Connection point position has changed
1723                         // Update knot position
1724                         sp_knot_set_position(it->first,
1725                                              item->avoidRef->getConnectionPointPos(it->second.type, it->second.id) * cc->desktop->doc2dt(), 0);
1726                     seen.insert(it->second.id);
1727                     sp_knot_show(it->first);
1728                 }
1729                 else
1730                 {
1731                     // This connection point does no longer exist,
1732                     // remove the knot
1733                     ConnectionPointMap::iterator curr = it;
1734                     ++it;
1735                     g_object_unref( curr->first );
1736                     cc->connpthandles.erase(curr);
1737                     removed = true;
1738                 }
1739             }
1740             else
1741             {
1742                 // It's a default connection point
1743                 // Just make sure it's position is correct
1744                 sp_knot_set_position(it->first,
1745                                      item->avoidRef->getConnectionPointPos(it->second.type, it->second.id) * cc->desktop->doc2dt(), 0);
1746                 sp_knot_show(it->first);
1748             }
1749             if ( !removed )
1750                 ++it;
1751         }
1752         // Add knots for new connection points.
1753         if (connpts->size())
1754             for ( std::map<int, ConnectionPoint>::iterator it = connpts->begin(); it != connpts->end(); ++it )
1755                 if ( seen.find(it->first) == seen.end() )
1756                     // A new connection point has been added
1757                     // to the shape. Add a knot for it.
1758                     cc_active_shape_add_knot(cc->desktop, item, cc->connpthandles, it->second);
1759     }
1763 static void
1764 cc_set_active_conn(SPConnectorContext *cc, SPItem *item)
1766     g_assert( SP_IS_PATH(item) );
1768     SPCurve *curve = SP_PATH(item)->original_curve ? SP_PATH(item)->original_curve : SP_PATH(item)->curve;
1769     Geom::Matrix i2d = item->i2d_affine();
1771     if (cc->active_conn == item)
1772     {
1773         if (curve->is_empty())
1774         {
1775             // Connector is invisible because it is clipped to the boundary of
1776             // two overlpapping shapes.
1777             sp_knot_hide(cc->endpt_handle[0]);
1778             sp_knot_hide(cc->endpt_handle[1]);
1779         }
1780         else
1781         {
1782             // Just adjust handle positions.
1783             Geom::Point startpt = *(curve->first_point()) * i2d;
1784             sp_knot_set_position(cc->endpt_handle[0], startpt, 0);
1786             Geom::Point endpt = *(curve->last_point()) * i2d;
1787             sp_knot_set_position(cc->endpt_handle[1], endpt, 0);
1788         }
1790         return;
1791     }
1793     cc->active_conn = item;
1795     // Remove existing active conn listeners
1796     if (cc->active_conn_repr) {
1797         sp_repr_remove_listener_by_data(cc->active_conn_repr, cc);
1798         Inkscape::GC::release(cc->active_conn_repr);
1799         cc->active_conn_repr = NULL;
1800     }
1802     // Listen in case the active conn changes
1803     cc->active_conn_repr = SP_OBJECT_REPR(item);
1804     if (cc->active_conn_repr) {
1805         Inkscape::GC::anchor(cc->active_conn_repr);
1806         sp_repr_add_listener(cc->active_conn_repr, &shape_repr_events, cc);
1807     }
1809     for (int i = 0; i < 2; ++i) {
1811         // Create the handle if it doesn't exist
1812         if ( cc->endpt_handle[i] == NULL ) {
1813             SPKnot *knot = sp_knot_new(cc->desktop,
1814                     _("<b>Connector endpoint</b>: drag to reroute or connect to new shapes"));
1816             knot->setShape(SP_KNOT_SHAPE_SQUARE);
1817             knot->setSize(7);
1818             knot->setAnchor(GTK_ANCHOR_CENTER);
1819             knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff);
1820             knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
1821             sp_knot_update_ctrl(knot);
1823             // We don't want to use the standard knot handler,
1824             // since we don't want this knot to be draggable.
1825             g_signal_handler_disconnect(G_OBJECT(knot->item),
1826                     knot->_event_handler_id);
1827             knot->_event_handler_id = 0;
1829             gtk_signal_connect(GTK_OBJECT(knot->item), "event",
1830                     GTK_SIGNAL_FUNC(cc_generic_knot_handler), knot);
1832             cc->endpt_handle[i] = knot;
1833         }
1835         // Remove any existing handlers
1836         if (cc->endpt_handler_id[i]) {
1837             g_signal_handlers_disconnect_by_func(
1838                     G_OBJECT(cc->endpt_handle[i]->item),
1839                     (void*)G_CALLBACK(endpt_handler), (gpointer) cc );
1840             cc->endpt_handler_id[i] = 0;
1841         }
1843         // Setup handlers for connector endpoints, this is
1844         // is as 'after' so that cc_generic_knot_handler is
1845         // triggered first for any endpoint.
1846         cc->endpt_handler_id[i] = g_signal_connect_after(
1847                 G_OBJECT(cc->endpt_handle[i]->item), "event",
1848                 G_CALLBACK(endpt_handler), cc);
1849     }
1851     if (curve->is_empty())
1852     {
1853         // Connector is invisible because it is clipped to the boundary 
1854         // of two overlpapping shapes.  So, it doesn't need endpoints.
1855         return;
1856     }
1858     Geom::Point startpt = *(curve->first_point()) * i2d;
1859     sp_knot_set_position(cc->endpt_handle[0], startpt, 0);
1861     Geom::Point endpt = *(curve->last_point()) * i2d;
1862     sp_knot_set_position(cc->endpt_handle[1], endpt, 0);
1864     sp_knot_show(cc->endpt_handle[0]);
1865     sp_knot_show(cc->endpt_handle[1]);
1868 void cc_create_connection_point(SPConnectorContext* cc)
1870     if (cc->active_shape && cc->state == SP_CONNECTOR_CONTEXT_IDLE)
1871     {
1872         if (cc->selected_handle)
1873         {
1874             cc_deselect_handle( cc->selected_handle );
1875         }
1876         SPKnot *knot = sp_knot_new(cc->desktop, 0);
1877         // We do not process events on this knot.
1878         g_signal_handler_disconnect(G_OBJECT(knot->item),
1879                                     knot->_event_handler_id);
1880         knot->_event_handler_id = 0;
1882         cc_select_handle( knot );
1883         cc->selected_handle = knot;
1884         sp_knot_show(cc->selected_handle);
1885         cc->state = SP_CONNECTOR_CONTEXT_NEWCONNPOINT;
1886     }
1889 void cc_remove_connection_point(SPConnectorContext* cc)
1891     if (cc->selected_handle && cc->state == SP_CONNECTOR_CONTEXT_IDLE )
1892     {
1893         cc->active_shape->avoidRef->deleteConnectionPoint(cc->connpthandles[cc->selected_handle]);
1894         cc->selected_handle = NULL;
1895     }
1898 static bool cc_item_is_shape(SPItem *item)
1900     if (SP_IS_PATH(item)) {
1901         SPCurve *curve = (SP_SHAPE(item))->curve;
1902         if ( curve && !(curve->is_closed()) ) {
1903             // Open paths are connectors.
1904             return false;
1905         }
1906     }
1907     else if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1908         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1909         if (prefs->getBool("/tools/connector/ignoretext", true)) {
1910             // Don't count text as a shape we can connect connector to.
1911             return false;
1912         }
1913     }
1914     return true;
1918 bool cc_item_is_connector(SPItem *item)
1920     if (SP_IS_PATH(item)) {
1921         bool closed = SP_PATH(item)->original_curve ? SP_PATH(item)->original_curve->is_closed() : SP_PATH(item)->curve->is_closed();
1922         if (SP_PATH(item)->connEndPair.isAutoRoutingConn() && !closed) {
1923             // To be considered a connector, an object must be a non-closed 
1924             // path that is marked with a "inkscape:connector-type" attribute.
1925             return true;
1926         }
1927     }
1928     return false;
1932 void cc_selection_set_avoid(bool const set_avoid)
1934     SPDesktop *desktop = inkscape_active_desktop();
1935     if (desktop == NULL) {
1936         return;
1937     }
1939     SPDocument *document = sp_desktop_document(desktop);
1941     Inkscape::Selection *selection = sp_desktop_selection(desktop);
1943     GSList *l = (GSList *) selection->itemList();
1945     int changes = 0;
1947     while (l) {
1948         SPItem *item = (SPItem *) l->data;
1950         char const *value = (set_avoid) ? "true" : NULL;
1952         if (cc_item_is_shape(item)) {
1953             item->setAttribute("inkscape:connector-avoid", value, false);
1954             item->avoidRef->handleSettingChange();
1955             changes++;
1956         }
1958         l = l->next;
1959     }
1961     if (changes == 0) {
1962         desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE,
1963                 _("Select <b>at least one non-connector object</b>."));
1964         return;
1965     }
1967     char *event_desc = (set_avoid) ?
1968             _("Make connectors avoid selected objects") :
1969             _("Make connectors ignore selected objects");
1970     DocumentUndo::done(document, SP_VERB_CONTEXT_CONNECTOR, event_desc);
1974 static void
1975 cc_selection_changed(Inkscape::Selection *selection, gpointer data)
1977     SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data);
1978     //SPEventContext *ec = SP_EVENT_CONTEXT(cc);
1980     SPItem *item = selection->singleItem();
1982     if (cc->active_conn == item)
1983     {
1984         // Nothing to change.
1985         return;
1986     }
1987     if (item == NULL)
1988     {
1989         cc_clear_active_conn(cc);
1990         return;
1991     }
1993     if (cc_item_is_connector(item)) {
1994         cc_set_active_conn(cc, item);
1995     }
1999 static void
2000 shape_event_attr_deleted(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child,
2001                          Inkscape::XML::Node */*ref*/, gpointer data)
2003     g_assert(data);
2004     SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data);
2006     if (child == cc->active_shape_repr) {
2007         // The active shape has been deleted.  Clear active shape.
2008         cc_clear_active_shape(cc);
2009     }
2013 static void
2014 shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
2015                          gchar const */*old_value*/, gchar const */*new_value*/,
2016                          bool /*is_interactive*/, gpointer data)
2018     g_assert(data);
2019     SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data);
2021     // Look for changes that result in onscreen movement.
2022     if (!strcmp(name, "d") || !strcmp(name, "x") || !strcmp(name, "y") ||
2023             !strcmp(name, "width") || !strcmp(name, "height") ||
2024             !strcmp(name, "transform"))
2025     {
2026         if (repr == cc->active_shape_repr) {
2027             // Active shape has moved. Clear active shape.
2028             cc_clear_active_shape(cc);
2029         }
2030         else if (repr == cc->active_conn_repr) {
2031             // The active conn has been moved.
2032             // Set it again, which just sets new handle positions.
2033             cc_set_active_conn(cc, cc->active_conn);
2034         }
2035     }
2036     else
2037         if ( !strcmp(name, "inkscape:connection-points") )
2038             if (repr == cc->active_shape_repr)
2039                 // The connection points of the active shape
2040                 // have changed. Update them.
2041                 cc_set_active_shape(cc, cc->active_shape);
2045 /*
2046   Local Variables:
2047   mode:c++
2048   c-file-style:"stroustrup"
2049   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2050   indent-tabs-mode:nil
2051   fill-column:99
2052   End:
2053 */
2054 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :