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