1 /*
2 * Connector creation tool
3 *
4 * Authors:
5 * Michael Wybrow <mjwybrow@users.sourceforge.net>
6 *
7 * Copyright (C) 2005 Michael Wybrow
8 *
9 * Released under GNU GPL, read the file 'COPYING' for more information
10 *
11 * TODO:
12 * o Have shapes avoid convex hulls of objects, rather than their
13 * bounding box. Possibly implement the unfinished ConvexHull
14 * class in libnr.
15 * (HOWEVER, using the convex hull C of a shape S does the wrong thing if a
16 * connector starts outside of S but inside C, or if the best route around
17 * an object involves going inside C but without entering S.)
18 * o Draw connectors to shape edges rather than bounding box.
19 * o Show a visual indicator for objects with the 'avoid' property set.
20 * o Allow user to change a object between a path and connector through
21 * the interface.
22 * o Create an interface for setting markers (arrow heads).
23 * o Better distinguish between paths and connectors to prevent problems
24 * in the node tool and paths accidently being turned into connectors
25 * in the connector tool. Perhaps have a way to convert between.
26 * o Only call libavoid's updateEndPoint as required. Currently we do it
27 * for both endpoints, even if only one is moving.
28 * o Allow user-placeable connection points.
29 * o Deal sanely with connectors with both endpoints attached to the
30 * same connection point, and drawing of connectors attaching
31 * overlaping shapes (currently tries to adjust connector to be
32 * outside both bounding boxes).
33 * o Fix many special cases related to connectors updating,
34 * e.g., copying a couple of shapes and a connector that are
35 * attached to each other.
36 * e.g., detach connector when it is moved or transformed in
37 * one of the other contexts.
38 * o Cope with shapes whose ids change when they have attached
39 * connectors.
40 * o gobble_motion_events(GDK_BUTTON1_MASK)?;
41 *
42 */
44 #include <gdk/gdkkeysyms.h>
45 #include <string>
46 #include <cstring>
48 #include "connector-context.h"
49 #include "pixmaps/cursor-connector.xpm"
50 #include "xml/node-event-vector.h"
51 #include "xml/repr.h"
52 #include "svg/svg.h"
53 #include "desktop.h"
54 #include "desktop-style.h"
55 #include "desktop-handles.h"
56 #include "document.h"
57 #include "message-context.h"
58 #include "message-stack.h"
59 #include "selection.h"
60 #include "inkscape.h"
61 #include "preferences.h"
62 #include "sp-path.h"
63 #include "display/canvas-bpath.h"
64 #include "display/sodipodi-ctrl.h"
65 #include <glibmm/i18n.h>
66 #include "snap.h"
67 #include "knot.h"
68 #include "sp-conn-end.h"
69 #include "conn-avoid-ref.h"
70 #include "libavoid/vertices.h"
71 #include "context-fns.h"
72 #include "sp-namedview.h"
73 #include "sp-text.h"
74 #include "sp-flowtext.h"
75 #include "display/curve.h"
77 static void sp_connector_context_class_init(SPConnectorContextClass *klass);
78 static void sp_connector_context_init(SPConnectorContext *conn_context);
79 static void sp_connector_context_dispose(GObject *object);
81 static void sp_connector_context_setup(SPEventContext *ec);
82 static void sp_connector_context_finish(SPEventContext *ec);
83 static gint sp_connector_context_root_handler(SPEventContext *ec, GdkEvent *event);
84 static gint sp_connector_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
86 // Stuff borrowed from DrawContext
87 static void spcc_connector_set_initial_point(SPConnectorContext *cc, Geom::Point const p);
88 static void spcc_connector_set_subsequent_point(SPConnectorContext *cc, Geom::Point const p);
89 static void spcc_connector_finish_segment(SPConnectorContext *cc, Geom::Point p);
90 static void spcc_reset_colors(SPConnectorContext *cc);
91 static void spcc_connector_finish(SPConnectorContext *cc);
92 static void spcc_concat_colors_and_flush(SPConnectorContext *cc);
93 static void spcc_flush_white(SPConnectorContext *cc, SPCurve *gc);
95 // Context event handlers
96 static gint connector_handle_button_press(SPConnectorContext *const cc, GdkEventButton const &bevent);
97 static gint connector_handle_motion_notify(SPConnectorContext *const cc, GdkEventMotion const &mevent);
98 static gint connector_handle_button_release(SPConnectorContext *const cc, GdkEventButton const &revent);
99 static gint connector_handle_key_press(SPConnectorContext *const cc, guint const keyval);
101 static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item);
102 static void cc_clear_active_shape(SPConnectorContext *cc);
103 static void cc_set_active_conn(SPConnectorContext *cc, SPItem *item);
104 static void cc_clear_active_conn(SPConnectorContext *cc);
105 static gchar *conn_pt_handle_test(SPConnectorContext *cc, Geom::Point& w);
106 static bool cc_item_is_shape(SPItem *item);
107 static void cc_selection_changed(Inkscape::Selection *selection, gpointer data);
108 static void cc_connector_rerouting_finish(SPConnectorContext *const cc,
109 Geom::Point *const p);
111 static void shape_event_attr_deleted(Inkscape::XML::Node *repr,
112 Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data);
113 static void shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
114 gchar const *old_value, gchar const *new_value, bool is_interactive,
115 gpointer data);
118 static Geom::Point connector_drag_origin_w(0, 0);
119 static bool connector_within_tolerance = false;
120 static SPEventContextClass *parent_class;
123 static Inkscape::XML::NodeEventVector shape_repr_events = {
124 NULL, /* child_added */
125 NULL, /* child_added */
126 shape_event_attr_changed,
127 NULL, /* content_changed */
128 NULL /* order_changed */
129 };
131 static Inkscape::XML::NodeEventVector layer_repr_events = {
132 NULL, /* child_added */
133 shape_event_attr_deleted,
134 NULL, /* child_added */
135 NULL, /* content_changed */
136 NULL /* order_changed */
137 };
140 GType
141 sp_connector_context_get_type(void)
142 {
143 static GType type = 0;
144 if (!type) {
145 GTypeInfo info = {
146 sizeof(SPConnectorContextClass),
147 NULL, NULL,
148 (GClassInitFunc) sp_connector_context_class_init,
149 NULL, NULL,
150 sizeof(SPConnectorContext),
151 4,
152 (GInstanceInitFunc) sp_connector_context_init,
153 NULL, /* value_table */
154 };
155 type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPConnectorContext", &info, (GTypeFlags)0);
156 }
157 return type;
158 }
160 static void
161 sp_connector_context_class_init(SPConnectorContextClass *klass)
162 {
163 GObjectClass *object_class;
164 SPEventContextClass *event_context_class;
166 object_class = (GObjectClass *) klass;
167 event_context_class = (SPEventContextClass *) klass;
169 parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
171 object_class->dispose = sp_connector_context_dispose;
173 event_context_class->setup = sp_connector_context_setup;
174 event_context_class->finish = sp_connector_context_finish;
175 event_context_class->root_handler = sp_connector_context_root_handler;
176 event_context_class->item_handler = sp_connector_context_item_handler;
177 }
180 static void
181 sp_connector_context_init(SPConnectorContext *cc)
182 {
183 SPEventContext *ec = SP_EVENT_CONTEXT(cc);
185 ec->cursor_shape = cursor_connector_xpm;
186 ec->hot_x = 1;
187 ec->hot_y = 1;
188 ec->xp = 0;
189 ec->yp = 0;
191 cc->red_color = 0xff00007f;
193 cc->newconn = NULL;
194 cc->newConnRef = NULL;
196 cc->sel_changed_connection = sigc::connection();
198 cc->active_shape = NULL;
199 cc->active_shape_repr = NULL;
200 cc->active_shape_layer_repr = NULL;
202 cc->active_conn = NULL;
203 cc->active_conn_repr = NULL;
205 cc->active_handle = NULL;
207 cc->clickeditem = NULL;
208 cc->clickedhandle = NULL;
210 cc->connpthandle = NULL;
211 for (int i = 0; i < 2; ++i) {
212 cc->endpt_handle[i] = NULL;
213 cc->endpt_handler_id[i] = 0;
214 }
215 cc->sid = NULL;
216 cc->eid = NULL;
217 cc->npoints = 0;
218 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
219 }
222 static void
223 sp_connector_context_dispose(GObject *object)
224 {
225 SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(object);
227 cc->sel_changed_connection.disconnect();
229 if (cc->connpthandle) {
230 g_object_unref(cc->connpthandle);
231 cc->connpthandle = NULL;
232 }
233 for (int i = 0; i < 2; ++i) {
234 if (cc->endpt_handle[1]) {
235 g_object_unref(cc->endpt_handle[i]);
236 cc->endpt_handle[i] = NULL;
237 }
238 }
239 if (cc->sid) {
240 g_free(cc->sid);
241 cc->sid = NULL;
242 }
243 if (cc->eid) {
244 g_free(cc->eid);
245 cc->eid = NULL;
246 }
247 g_assert( cc->newConnRef == NULL );
249 G_OBJECT_CLASS(parent_class)->dispose(object);
250 }
253 static void
254 sp_connector_context_setup(SPEventContext *ec)
255 {
256 SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec);
257 SPDesktop *dt = ec->desktop;
259 if (((SPEventContextClass *) parent_class)->setup) {
260 ((SPEventContextClass *) parent_class)->setup(ec);
261 }
263 cc->selection = sp_desktop_selection(dt);
265 cc->sel_changed_connection.disconnect();
266 cc->sel_changed_connection = cc->selection->connectChanged(
267 sigc::bind(sigc::ptr_fun(&cc_selection_changed),
268 (gpointer) cc));
270 /* Create red bpath */
271 cc->red_bpath = sp_canvas_bpath_new(sp_desktop_sketch(ec->desktop), NULL);
272 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cc->red_bpath), cc->red_color,
273 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
274 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cc->red_bpath), 0x00000000,
275 SP_WIND_RULE_NONZERO);
276 /* Create red curve */
277 cc->red_curve = new SPCurve();
279 /* Create green curve */
280 cc->green_curve = new SPCurve();
282 // Notice the initial selection.
283 cc_selection_changed(cc->selection, (gpointer) cc);
285 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
286 if (prefs->getBool("/tools/connector/selcue", 0)) {
287 ec->enableSelectionCue();
288 }
290 // Make sure we see all enter events for canvas items,
291 // even if a mouse button is depressed.
292 dt->canvas->gen_all_enter_events = true;
293 }
296 static void
297 sp_connector_context_finish(SPEventContext *ec)
298 {
299 SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec);
301 spcc_connector_finish(cc);
302 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
304 if (((SPEventContextClass *) parent_class)->finish) {
305 ((SPEventContextClass *) parent_class)->finish(ec);
306 }
308 if (cc->selection) {
309 cc->selection = NULL;
310 }
311 cc_clear_active_shape(cc);
312 cc_clear_active_conn(cc);
314 // Restore the default event generating behaviour.
315 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(ec);
316 desktop->canvas->gen_all_enter_events = false;
317 }
320 //-----------------------------------------------------------------------------
323 static void
324 cc_clear_active_shape(SPConnectorContext *cc)
325 {
326 if (cc->active_shape == NULL) {
327 return;
328 }
329 g_assert( cc->active_shape_repr );
330 g_assert( cc->active_shape_layer_repr );
332 cc->active_shape = NULL;
334 if (cc->active_shape_repr) {
335 sp_repr_remove_listener_by_data(cc->active_shape_repr, cc);
336 Inkscape::GC::release(cc->active_shape_repr);
337 cc->active_shape_repr = NULL;
339 sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc);
340 Inkscape::GC::release(cc->active_shape_layer_repr);
341 cc->active_shape_layer_repr = NULL;
342 }
344 // Hide the center connection point if it exists.
345 if (cc->connpthandle) {
346 sp_knot_hide(cc->connpthandle);
347 }
348 }
351 static void
352 cc_clear_active_conn(SPConnectorContext *cc)
353 {
354 if (cc->active_conn == NULL) {
355 return;
356 }
357 g_assert( cc->active_conn_repr );
359 cc->active_conn = NULL;
361 if (cc->active_conn_repr) {
362 sp_repr_remove_listener_by_data(cc->active_conn_repr, cc);
363 Inkscape::GC::release(cc->active_conn_repr);
364 cc->active_conn_repr = NULL;
365 }
367 // Hide the endpoint handles.
368 for (int i = 0; i < 2; ++i) {
369 if (cc->endpt_handle[i]) {
370 sp_knot_hide(cc->endpt_handle[i]);
371 }
372 }
373 }
376 static gchar *
377 conn_pt_handle_test(SPConnectorContext *cc, Geom::Point& p)
378 {
379 // TODO: this will need to change when there are more connection
380 // points available for each shape.
382 SPKnot *centerpt = cc->connpthandle;
383 if (cc->active_handle && (cc->active_handle == centerpt))
384 {
385 p = centerpt->pos;
386 return g_strdup_printf("#%s", SP_OBJECT_ID(cc->active_shape));
387 }
388 return NULL;
389 }
393 static gint
394 sp_connector_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
395 {
396 gint ret = FALSE;
398 SPDesktop *desktop = event_context->desktop;
400 SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(event_context);
402 Geom::Point p(event->button.x, event->button.y);
404 switch (event->type) {
405 case GDK_BUTTON_RELEASE:
406 if (event->button.button == 1 && !event_context->space_panning) {
407 if ((cc->state == SP_CONNECTOR_CONTEXT_DRAGGING) &&
408 (connector_within_tolerance))
409 {
410 spcc_reset_colors(cc);
411 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
412 sp_event_context_snap_window_closed(event_context);
413 }
414 if (cc->state != SP_CONNECTOR_CONTEXT_IDLE) {
415 // Doing simething else like rerouting.
416 break;
417 }
418 // find out clicked item, disregarding groups, honoring Alt
419 SPItem *item_ungrouped = sp_event_context_find_item(desktop,
420 p, event->button.state & GDK_MOD1_MASK, TRUE);
422 if (event->button.state & GDK_SHIFT_MASK) {
423 cc->selection->toggle(item_ungrouped);
424 } else {
425 cc->selection->set(item_ungrouped);
426 }
427 ret = TRUE;
429 }
430 break;
431 case GDK_ENTER_NOTIFY:
432 {
433 if (cc_item_is_shape(item)) {
434 // This is a shape, so show connection point(s).
435 if (!(cc->active_shape) ||
436 // Don't show handle for another handle.
437 (item != ((SPItem *) cc->connpthandle))) {
438 cc_set_active_shape(cc, item);
439 }
440 }
441 ret = TRUE;
442 break;
443 }
444 default:
445 break;
446 }
448 return ret;
449 }
452 gint
453 sp_connector_context_root_handler(SPEventContext *ec, GdkEvent *event)
454 {
455 SPConnectorContext *const cc = SP_CONNECTOR_CONTEXT(ec);
457 gint ret = FALSE;
459 switch (event->type) {
460 case GDK_BUTTON_PRESS:
461 ret = connector_handle_button_press(cc, event->button);
462 break;
464 case GDK_MOTION_NOTIFY:
465 ret = connector_handle_motion_notify(cc, event->motion);
466 break;
468 case GDK_BUTTON_RELEASE:
469 ret = connector_handle_button_release(cc, event->button);
470 break;
471 case GDK_KEY_PRESS:
472 ret = connector_handle_key_press(cc, get_group0_keyval (&event->key));
473 break;
475 default:
476 break;
477 }
479 if (!ret) {
480 gint (*const parent_root_handler)(SPEventContext *, GdkEvent *)
481 = ((SPEventContextClass *) parent_class)->root_handler;
482 if (parent_root_handler) {
483 ret = parent_root_handler(ec, event);
484 }
485 }
487 return ret;
488 }
491 static gint
492 connector_handle_button_press(SPConnectorContext *const cc, GdkEventButton const &bevent)
493 {
494 Geom::Point const event_w(bevent.x, bevent.y);
495 /* Find desktop coordinates */
496 Geom::Point p = cc->desktop->w2d(event_w);
497 SPEventContext *event_context = SP_EVENT_CONTEXT(cc);
499 gint ret = FALSE;
500 if ( bevent.button == 1 && !event_context->space_panning ) {
502 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
504 if (Inkscape::have_viable_layer(desktop, cc->_message_context) == false) {
505 return TRUE;
506 }
508 Geom::Point const event_w(bevent.x,
509 bevent.y);
510 connector_drag_origin_w = event_w;
511 connector_within_tolerance = true;
513 Geom::Point const event_dt = cc->desktop->w2d(event_w);
515 SnapManager &m = cc->desktop->namedview->snap_manager;
516 m.setup(cc->desktop);
518 switch (cc->state) {
519 case SP_CONNECTOR_CONTEXT_STOP:
520 /* This is allowed, if we just cancelled curve */
521 case SP_CONNECTOR_CONTEXT_IDLE:
522 {
523 if ( cc->npoints == 0 ) {
524 cc_clear_active_conn(cc);
526 SP_EVENT_CONTEXT_DESKTOP(cc)->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new connector"));
528 /* Set start anchor */
529 /* Create green anchor */
530 Geom::Point p = event_dt;
532 // Test whether we clicked on a connection point
533 cc->sid = conn_pt_handle_test(cc, p);
535 sp_event_context_snap_window_open(event_context);
537 if (!cc->sid) {
538 // This is the first point, so just snap it to the grid
539 // as there's no other points to go off.
540 m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
541 }
542 spcc_connector_set_initial_point(cc, p);
544 }
545 cc->state = SP_CONNECTOR_CONTEXT_DRAGGING;
546 ret = TRUE;
547 break;
548 }
549 case SP_CONNECTOR_CONTEXT_DRAGGING:
550 {
551 // This is the second click of a connector creation.
552 m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
554 spcc_connector_set_subsequent_point(cc, p);
555 spcc_connector_finish_segment(cc, p);
556 // Test whether we clicked on a connection point
557 cc->eid = conn_pt_handle_test(cc, p);
558 if (cc->npoints != 0) {
559 spcc_connector_finish(cc);
560 }
561 cc_set_active_conn(cc, cc->newconn);
562 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
563 sp_event_context_snap_window_closed(event_context);
564 ret = TRUE;
565 break;
566 }
567 case SP_CONNECTOR_CONTEXT_CLOSE:
568 {
569 g_warning("Button down in CLOSE state");
570 break;
571 }
572 default:
573 break;
574 }
575 } else if (bevent.button == 3) {
576 if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) {
577 // A context menu is going to be triggered here,
578 // so end the rerouting operation.
579 cc_connector_rerouting_finish(cc, &p);
581 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
582 sp_event_context_snap_window_closed(event_context);
584 // Don't set ret to TRUE, so we drop through to the
585 // parent handler which will open the context menu.
586 }
587 else if (cc->npoints != 0) {
588 spcc_connector_finish(cc);
589 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
590 sp_event_context_snap_window_closed(event_context);
591 ret = TRUE;
592 }
593 }
594 return ret;
595 }
598 static gint
599 connector_handle_motion_notify(SPConnectorContext *const cc, GdkEventMotion const &mevent)
600 {
601 gint ret = FALSE;
602 SPEventContext *event_context = SP_EVENT_CONTEXT(cc);
603 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
605 if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) {
606 // allow middle-button scrolling
607 return FALSE;
608 }
610 Geom::Point const event_w(mevent.x, mevent.y);
612 if (connector_within_tolerance) {
613 gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
614 if ( Geom::LInfty( event_w - connector_drag_origin_w ) < tolerance ) {
615 return FALSE; // Do not drag if we're within tolerance from origin.
616 }
617 }
618 // Once the user has moved farther than tolerance from the original location
619 // (indicating they intend to move the object, not click), then always process
620 // the motion notify coordinates as given (no snapping back to origin)
621 connector_within_tolerance = false;
623 SPDesktop *const dt = cc->desktop;
625 /* Find desktop coordinates */
626 Geom::Point p = dt->w2d(event_w);
628 SnapManager &m = dt->namedview->snap_manager;
629 m.setup(dt);
631 switch (cc->state) {
632 case SP_CONNECTOR_CONTEXT_DRAGGING:
633 {
634 // This is movement during a connector creation.
635 if ( cc->npoints > 0 ) {
636 m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
637 cc->selection->clear();
638 spcc_connector_set_subsequent_point(cc, p);
639 ret = TRUE;
640 }
641 break;
642 }
643 case SP_CONNECTOR_CONTEXT_REROUTING:
644 {
645 g_assert( SP_IS_PATH(cc->clickeditem));
647 m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
649 // Update the hidden path
650 Geom::Matrix i2d = sp_item_i2d_affine(cc->clickeditem);
651 Geom::Matrix d2i = i2d.inverse();
652 SPPath *path = SP_PATH(cc->clickeditem);
653 SPCurve *curve = (SP_SHAPE(path))->curve;
654 if (cc->clickedhandle == cc->endpt_handle[0]) {
655 Geom::Point o = cc->endpt_handle[1]->pos;
656 curve->stretch_endpoints(p * d2i, o * d2i);
657 }
658 else {
659 Geom::Point o = cc->endpt_handle[0]->pos;
660 curve->stretch_endpoints(o * d2i, p * d2i);
661 }
662 sp_conn_adjust_path(path);
664 // Copy this to the temporary visible path
665 cc->red_curve = SP_SHAPE(path)->curve->copy();
666 cc->red_curve->transform(i2d);
668 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
669 ret = TRUE;
670 break;
671 }
672 case SP_CONNECTOR_CONTEXT_STOP:
673 /* This is perfectly valid */
674 break;
675 default:
676 break;
677 }
679 return ret;
680 }
683 static gint
684 connector_handle_button_release(SPConnectorContext *const cc, GdkEventButton const &revent)
685 {
686 gint ret = FALSE;
687 SPEventContext *event_context = SP_EVENT_CONTEXT(cc);
688 if ( revent.button == 1 && !event_context->space_panning ) {
690 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
691 SPDocument *doc = sp_desktop_document(desktop);
693 SnapManager &m = desktop->namedview->snap_manager;
694 m.setup(desktop);
696 Geom::Point const event_w(revent.x, revent.y);
698 /* Find desktop coordinates */
699 Geom::Point p = cc->desktop->w2d(event_w);
701 switch (cc->state) {
702 //case SP_CONNECTOR_CONTEXT_POINT:
703 case SP_CONNECTOR_CONTEXT_DRAGGING:
704 {
705 m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
707 if (connector_within_tolerance)
708 {
709 spcc_connector_finish_segment(cc, p);
710 return TRUE;
711 }
712 // Connector has been created via a drag, end it now.
713 spcc_connector_set_subsequent_point(cc, p);
714 spcc_connector_finish_segment(cc, p);
715 // Test whether we clicked on a connection point
716 cc->eid = conn_pt_handle_test(cc, p);
717 if (cc->npoints != 0) {
718 spcc_connector_finish(cc);
719 }
720 cc_set_active_conn(cc, cc->newconn);
721 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
722 sp_event_context_snap_window_closed(event_context);
723 break;
724 }
725 case SP_CONNECTOR_CONTEXT_REROUTING:
726 {
727 m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
728 cc_connector_rerouting_finish(cc, &p);
730 sp_document_ensure_up_to_date(doc);
731 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
732 sp_event_context_snap_window_closed(event_context);
733 return TRUE;
734 break;
735 }
736 case SP_CONNECTOR_CONTEXT_STOP:
737 /* This is allowed, if we just cancelled curve */
738 break;
739 default:
740 break;
741 }
742 ret = TRUE;
743 }
745 return ret;
746 }
749 static gint
750 connector_handle_key_press(SPConnectorContext *const cc, guint const keyval)
751 {
752 gint ret = FALSE;
753 /* fixme: */
754 switch (keyval) {
755 case GDK_Return:
756 case GDK_KP_Enter:
757 if (cc->npoints != 0) {
758 spcc_connector_finish(cc);
759 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
760 sp_event_context_snap_window_closed(SP_EVENT_CONTEXT(cc));
761 ret = TRUE;
762 }
763 break;
764 case GDK_Escape:
765 if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) {
767 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
768 SPDocument *doc = sp_desktop_document(desktop);
770 cc_connector_rerouting_finish(cc, NULL);
772 sp_document_undo(doc);
774 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
775 sp_event_context_snap_window_closed(SP_EVENT_CONTEXT(cc));
776 desktop->messageStack()->flash( Inkscape::NORMAL_MESSAGE,
777 _("Connector endpoint drag cancelled."));
778 ret = TRUE;
779 }
780 else if (cc->npoints != 0) {
781 // if drawing, cancel, otherwise pass it up for deselecting
782 cc->state = SP_CONNECTOR_CONTEXT_STOP;
783 sp_event_context_snap_window_closed(SP_EVENT_CONTEXT(cc));
784 spcc_reset_colors(cc);
785 ret = TRUE;
786 }
787 break;
788 default:
789 break;
790 }
791 return ret;
792 }
795 static void
796 cc_connector_rerouting_finish(SPConnectorContext *const cc, Geom::Point *const p)
797 {
798 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
799 SPDocument *doc = sp_desktop_document(desktop);
801 // Clear the temporary path:
802 cc->red_curve->reset();
803 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
805 if (p != NULL)
806 {
807 // Test whether we clicked on a connection point
808 gchar *shape_label = conn_pt_handle_test(cc, *p);
810 if (shape_label) {
811 if (cc->clickedhandle == cc->endpt_handle[0]) {
812 sp_object_setAttribute(cc->clickeditem,
813 "inkscape:connection-start",shape_label, false);
814 }
815 else {
816 sp_object_setAttribute(cc->clickeditem,
817 "inkscape:connection-end",shape_label, false);
818 }
819 g_free(shape_label);
820 }
821 }
822 cc->clickeditem->setHidden(false);
823 sp_conn_adjust_path(SP_PATH(cc->clickeditem));
824 cc->clickeditem->updateRepr();
825 sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR,
826 _("Reroute connector"));
827 cc_set_active_conn(cc, cc->clickeditem);
828 }
831 static void
832 spcc_reset_colors(SPConnectorContext *cc)
833 {
834 /* Red */
835 cc->red_curve->reset();
836 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
838 cc->green_curve->reset();
839 cc->npoints = 0;
840 }
843 static void
844 spcc_connector_set_initial_point(SPConnectorContext *const cc, Geom::Point const p)
845 {
846 g_assert( cc->npoints == 0 );
848 cc->p[0] = p;
849 cc->p[1] = p;
850 cc->npoints = 2;
851 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
852 }
855 static void
856 spcc_connector_set_subsequent_point(SPConnectorContext *const cc, Geom::Point const p)
857 {
858 g_assert( cc->npoints != 0 );
860 SPDesktop *dt = cc->desktop;
861 Geom::Point o = dt->dt2doc(cc->p[0]);
862 Geom::Point d = dt->dt2doc(p);
863 Avoid::Point src(o[Geom::X], o[Geom::Y]);
864 Avoid::Point dst(d[Geom::X], d[Geom::Y]);
866 if (!cc->newConnRef) {
867 Avoid::Router *router = sp_desktop_document(dt)->router;
868 cc->newConnRef = new Avoid::ConnRef(router, 0, src, dst);
869 cc->newConnRef->updateEndPoint(Avoid::VertID::src, src);
870 }
871 cc->newConnRef->updateEndPoint(Avoid::VertID::tar, dst);
873 cc->newConnRef->makePathInvalid();
874 cc->newConnRef->generatePath(src, dst);
876 Avoid::PolyLine route = cc->newConnRef->route();
877 cc->newConnRef->calcRouteDist();
879 cc->red_curve->reset();
880 Geom::Point pt(route.ps[0].x, route.ps[0].y);
881 cc->red_curve->moveto(pt);
883 for (int i = 1; i < route.pn; ++i) {
884 Geom::Point p(route.ps[i].x, route.ps[i].y);
885 cc->red_curve->lineto(p);
886 }
887 cc->red_curve->transform(dt->doc2dt());
888 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
889 }
892 /**
893 * Concats red, blue and green.
894 * If any anchors are defined, process these, optionally removing curves from white list
895 * Invoke _flush_white to write result back to object.
896 */
897 static void
898 spcc_concat_colors_and_flush(SPConnectorContext *cc)
899 {
900 SPCurve *c = cc->green_curve;
901 cc->green_curve = new SPCurve();
903 cc->red_curve->reset();
904 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
906 if (c->is_empty()) {
907 c->unref();
908 return;
909 }
911 spcc_flush_white(cc, c);
913 c->unref();
914 }
917 /*
918 * Flushes white curve(s) and additional curve into object
919 *
920 * No cleaning of colored curves - this has to be done by caller
921 * No rereading of white data, so if you cannot rely on ::modified, do it in caller
922 *
923 */
925 static void
926 spcc_flush_white(SPConnectorContext *cc, SPCurve *gc)
927 {
928 SPCurve *c;
930 if (gc) {
931 c = gc;
932 c->ref();
933 } else {
934 return;
935 }
937 /* Now we have to go back to item coordinates at last */
938 c->transform(SP_EVENT_CONTEXT_DESKTOP(cc)->dt2doc());
940 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
941 SPDocument *doc = sp_desktop_document(desktop);
942 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
944 if ( c && !c->is_empty() ) {
945 /* We actually have something to write */
947 Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
948 /* Set style */
949 sp_desktop_apply_style_tool(desktop, repr, "/tools/connector", false);
951 gchar *str = sp_svg_write_path( c->get_pathvector() );
952 g_assert( str != NULL );
953 repr->setAttribute("d", str);
954 g_free(str);
956 /* Attach repr */
957 cc->newconn = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
958 cc->selection->set(repr);
959 Inkscape::GC::release(repr);
960 cc->newconn->transform = sp_item_i2doc_affine(SP_ITEM(desktop->currentLayer())).inverse();
961 cc->newconn->updateRepr();
963 bool connection = false;
964 sp_object_setAttribute(cc->newconn, "inkscape:connector-type",
965 "polyline", false);
966 if (cc->sid)
967 {
968 sp_object_setAttribute(cc->newconn, "inkscape:connection-start",
969 cc->sid, false);
970 connection = true;
971 }
973 if (cc->eid)
974 {
975 sp_object_setAttribute(cc->newconn, "inkscape:connection-end",
976 cc->eid, false);
977 connection = true;
978 }
979 cc->newconn->updateRepr();
980 if (connection) {
981 // Adjust endpoints to shape edge.
982 sp_conn_adjust_path(SP_PATH(cc->newconn));
983 }
984 cc->newconn->updateRepr();
985 }
987 c->unref();
989 /* Flush pending updates */
990 sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR, _("Create connector"));
991 sp_document_ensure_up_to_date(doc);
992 }
995 static void
996 spcc_connector_finish_segment(SPConnectorContext *const cc, Geom::Point const /*p*/)
997 {
998 if (!cc->red_curve->is_empty()) {
999 cc->green_curve->append_continuous(cc->red_curve, 0.0625);
1001 cc->p[0] = cc->p[3];
1002 cc->p[1] = cc->p[4];
1003 cc->npoints = 2;
1005 cc->red_curve->reset();
1006 }
1007 }
1010 static void
1011 spcc_connector_finish(SPConnectorContext *const cc)
1012 {
1013 SPDesktop *const desktop = cc->desktop;
1014 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing connector"));
1016 cc->red_curve->reset();
1017 spcc_concat_colors_and_flush(cc);
1019 cc->npoints = 0;
1021 if (cc->newConnRef) {
1022 cc->newConnRef->removeFromGraph();
1023 delete cc->newConnRef;
1024 cc->newConnRef = NULL;
1025 }
1026 }
1029 static gboolean
1030 cc_generic_knot_handler(SPCanvasItem *, GdkEvent *event, SPKnot *knot)
1031 {
1032 g_assert (knot != NULL);
1034 g_object_ref(knot);
1036 SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(
1037 knot->desktop->event_context);
1039 gboolean consumed = FALSE;
1041 switch (event->type) {
1042 case GDK_ENTER_NOTIFY:
1043 sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, TRUE);
1045 cc->active_handle = knot;
1047 if (knot->tip)
1048 {
1049 knot->desktop->event_context->defaultMessageContext()->set(
1050 Inkscape::NORMAL_MESSAGE, knot->tip);
1051 }
1053 consumed = TRUE;
1054 break;
1055 case GDK_LEAVE_NOTIFY:
1056 sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE);
1058 cc->active_handle = NULL;
1060 if (knot->tip) {
1061 knot->desktop->event_context->defaultMessageContext()->clear();
1062 }
1064 consumed = TRUE;
1065 break;
1066 default:
1067 break;
1068 }
1070 g_object_unref(knot);
1072 return consumed;
1073 }
1076 static gboolean
1077 endpt_handler(SPKnot */*knot*/, GdkEvent *event, SPConnectorContext *cc)
1078 {
1079 g_assert( SP_IS_CONNECTOR_CONTEXT(cc) );
1081 gboolean consumed = FALSE;
1083 switch (event->type) {
1084 case GDK_BUTTON_PRESS:
1085 g_assert( (cc->active_handle == cc->endpt_handle[0]) ||
1086 (cc->active_handle == cc->endpt_handle[1]) );
1087 if (cc->state == SP_CONNECTOR_CONTEXT_IDLE) {
1088 cc->clickeditem = cc->active_conn;
1089 cc->clickedhandle = cc->active_handle;
1090 cc_clear_active_conn(cc);
1091 cc->state = SP_CONNECTOR_CONTEXT_REROUTING;
1092 sp_event_context_snap_window_open(SP_EVENT_CONTEXT(cc));
1094 // Disconnect from attached shape
1095 unsigned ind = (cc->active_handle == cc->endpt_handle[0]) ? 0 : 1;
1096 sp_conn_end_detach(cc->clickeditem, ind);
1098 Geom::Point origin;
1099 if (cc->clickedhandle == cc->endpt_handle[0]) {
1100 origin = cc->endpt_handle[1]->pos;
1101 }
1102 else {
1103 origin = cc->endpt_handle[0]->pos;
1104 }
1106 // Show the red path for dragging.
1107 cc->red_curve = SP_PATH(cc->clickeditem)->curve->copy();
1108 Geom::Matrix i2d = sp_item_i2d_affine(cc->clickeditem);
1109 cc->red_curve->transform(i2d);
1110 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
1112 cc->clickeditem->setHidden(true);
1114 // The rest of the interaction rerouting the connector is
1115 // handled by the context root handler.
1116 consumed = TRUE;
1117 }
1118 break;
1119 default:
1120 break;
1121 }
1123 return consumed;
1124 }
1127 static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item)
1128 {
1129 g_assert(item != NULL );
1131 cc->active_shape = item;
1133 // Remove existing active shape listeners
1134 if (cc->active_shape_repr) {
1135 sp_repr_remove_listener_by_data(cc->active_shape_repr, cc);
1136 Inkscape::GC::release(cc->active_shape_repr);
1138 sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc);
1139 Inkscape::GC::release(cc->active_shape_layer_repr);
1140 }
1142 // Listen in case the active shape changes
1143 cc->active_shape_repr = SP_OBJECT_REPR(item);
1144 if (cc->active_shape_repr) {
1145 Inkscape::GC::anchor(cc->active_shape_repr);
1146 sp_repr_add_listener(cc->active_shape_repr, &shape_repr_events, cc);
1148 cc->active_shape_layer_repr = cc->active_shape_repr->parent();
1149 Inkscape::GC::anchor(cc->active_shape_layer_repr);
1150 sp_repr_add_listener(cc->active_shape_layer_repr, &layer_repr_events, cc);
1151 }
1154 // Set center connection point.
1155 if ( cc->connpthandle == NULL ) {
1156 SPKnot *knot = sp_knot_new(cc->desktop,
1157 _("<b>Connection point</b>: click or drag to create a new connector"));
1159 knot->setShape(SP_KNOT_SHAPE_SQUARE);
1160 knot->setSize(8);
1161 knot->setAnchor(GTK_ANCHOR_CENTER);
1162 knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff);
1163 sp_knot_update_ctrl(knot);
1165 // We don't want to use the standard knot handler,
1166 //since we don't want this knot to be draggable.
1167 g_signal_handler_disconnect(G_OBJECT(knot->item),
1168 knot->_event_handler_id);
1169 knot->_event_handler_id = 0;
1171 gtk_signal_connect(GTK_OBJECT(knot->item), "event",
1172 GTK_SIGNAL_FUNC(cc_generic_knot_handler), knot);
1174 cc->connpthandle = knot;
1175 }
1178 Geom::OptRect bbox = sp_item_bbox_desktop(cc->active_shape);
1179 if (bbox) {
1180 Geom::Point center = bbox->midpoint();
1181 sp_knot_set_position(cc->connpthandle, center, 0);
1182 sp_knot_show(cc->connpthandle);
1183 } else {
1184 sp_knot_hide(cc->connpthandle);
1185 }
1186 }
1189 static void
1190 cc_set_active_conn(SPConnectorContext *cc, SPItem *item)
1191 {
1192 g_assert( SP_IS_PATH(item) );
1194 SPCurve *curve = SP_SHAPE(SP_PATH(item))->curve;
1195 Geom::Matrix i2d = sp_item_i2d_affine(item);
1197 if (cc->active_conn == item)
1198 {
1199 // Just adjust handle positions.
1200 Geom::Point startpt = *(curve->first_point()) * i2d;
1201 sp_knot_set_position(cc->endpt_handle[0], startpt, 0);
1203 Geom::Point endpt = *(curve->last_point()) * i2d;
1204 sp_knot_set_position(cc->endpt_handle[1], endpt, 0);
1206 return;
1207 }
1209 cc->active_conn = item;
1211 // Remove existing active conn listeners
1212 if (cc->active_conn_repr) {
1213 sp_repr_remove_listener_by_data(cc->active_conn_repr, cc);
1214 Inkscape::GC::release(cc->active_conn_repr);
1215 cc->active_conn_repr = NULL;
1216 }
1218 // Listen in case the active conn changes
1219 cc->active_conn_repr = SP_OBJECT_REPR(item);
1220 if (cc->active_conn_repr) {
1221 Inkscape::GC::anchor(cc->active_conn_repr);
1222 sp_repr_add_listener(cc->active_conn_repr, &shape_repr_events, cc);
1223 }
1225 for (int i = 0; i < 2; ++i) {
1227 // Create the handle if it doesn't exist
1228 if ( cc->endpt_handle[i] == NULL ) {
1229 SPKnot *knot = sp_knot_new(cc->desktop,
1230 _("<b>Connector endpoint</b>: drag to reroute or connect to new shapes"));
1232 knot->setShape(SP_KNOT_SHAPE_SQUARE);
1233 knot->setSize(7);
1234 knot->setAnchor(GTK_ANCHOR_CENTER);
1235 knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff);
1236 knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
1237 sp_knot_update_ctrl(knot);
1239 // We don't want to use the standard knot handler,
1240 //since we don't want this knot to be draggable.
1241 g_signal_handler_disconnect(G_OBJECT(knot->item),
1242 knot->_event_handler_id);
1243 knot->_event_handler_id = 0;
1245 gtk_signal_connect(GTK_OBJECT(knot->item), "event",
1246 GTK_SIGNAL_FUNC(cc_generic_knot_handler), knot);
1248 cc->endpt_handle[i] = knot;
1249 }
1251 // Remove any existing handlers
1252 if (cc->endpt_handler_id[i]) {
1253 g_signal_handlers_disconnect_by_func(
1254 G_OBJECT(cc->endpt_handle[i]->item),
1255 (void*)G_CALLBACK(endpt_handler), (gpointer) cc );
1256 cc->endpt_handler_id[i] = 0;
1257 }
1259 // Setup handlers for connector endpoints, this is
1260 // is as 'after' so that cc_generic_knot_handler is
1261 // triggered first for any endpoint.
1262 cc->endpt_handler_id[i] = g_signal_connect_after(
1263 G_OBJECT(cc->endpt_handle[i]->item), "event",
1264 G_CALLBACK(endpt_handler), cc);
1265 }
1267 Geom::Point startpt = *(curve->first_point()) * i2d;
1268 sp_knot_set_position(cc->endpt_handle[0], startpt, 0);
1270 Geom::Point endpt = *(curve->last_point()) * i2d;
1271 sp_knot_set_position(cc->endpt_handle[1], endpt, 0);
1273 sp_knot_show(cc->endpt_handle[0]);
1274 sp_knot_show(cc->endpt_handle[1]);
1275 }
1278 static bool cc_item_is_shape(SPItem *item)
1279 {
1280 if (SP_IS_PATH(item)) {
1281 SPCurve *curve = (SP_SHAPE(item))->curve;
1282 if ( curve && !(curve->is_closed()) ) {
1283 // Open paths are connectors.
1284 return false;
1285 }
1286 }
1287 else if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1288 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1289 if (prefs->getBool("/tools/connector/ignoretext", true)) {
1290 // Don't count text as a shape we can connect connector to.
1291 return false;
1292 }
1293 }
1294 return true;
1295 }
1298 bool cc_item_is_connector(SPItem *item)
1299 {
1300 if (SP_IS_PATH(item)) {
1301 if (SP_PATH(item)->connEndPair.isAutoRoutingConn()) {
1302 g_assert( !(SP_SHAPE(item)->curve->is_closed()) );
1303 return true;
1304 }
1305 }
1306 return false;
1307 }
1310 void cc_selection_set_avoid(bool const set_avoid)
1311 {
1312 SPDesktop *desktop = inkscape_active_desktop();
1313 if (desktop == NULL) {
1314 return;
1315 }
1317 SPDocument *document = sp_desktop_document(desktop);
1319 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1321 GSList *l = (GSList *) selection->itemList();
1323 int changes = 0;
1325 while (l) {
1326 SPItem *item = (SPItem *) l->data;
1328 char const *value = (set_avoid) ? "true" : NULL;
1330 if (cc_item_is_shape(item)) {
1331 sp_object_setAttribute(item, "inkscape:connector-avoid",
1332 value, false);
1333 item->avoidRef->handleSettingChange();
1334 changes++;
1335 }
1337 l = l->next;
1338 }
1340 if (changes == 0) {
1341 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE,
1342 _("Select <b>at least one non-connector object</b>."));
1343 return;
1344 }
1346 char *event_desc = (set_avoid) ?
1347 _("Make connectors avoid selected objects") :
1348 _("Make connectors ignore selected objects");
1349 sp_document_done(document, SP_VERB_CONTEXT_CONNECTOR, event_desc);
1350 }
1353 static void
1354 cc_selection_changed(Inkscape::Selection *selection, gpointer data)
1355 {
1356 SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data);
1357 //SPEventContext *ec = SP_EVENT_CONTEXT(cc);
1359 SPItem *item = selection->singleItem();
1361 if (cc->active_conn == item)
1362 {
1363 // Nothing to change.
1364 return;
1365 }
1366 if (item == NULL)
1367 {
1368 cc_clear_active_conn(cc);
1369 return;
1370 }
1372 if (cc_item_is_connector(item)) {
1373 cc_set_active_conn(cc, item);
1374 }
1375 }
1378 static void
1379 shape_event_attr_deleted(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child,
1380 Inkscape::XML::Node */*ref*/, gpointer data)
1381 {
1382 g_assert(data);
1383 SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data);
1385 if (child == cc->active_shape_repr) {
1386 // The active shape has been deleted. Clear active shape.
1387 cc_clear_active_shape(cc);
1388 }
1389 }
1392 static void
1393 shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
1394 gchar const */*old_value*/, gchar const */*new_value*/,
1395 bool /*is_interactive*/, gpointer data)
1396 {
1397 g_assert(data);
1398 SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data);
1400 // Look for changes than result in onscreen movement.
1401 if (!strcmp(name, "d") || !strcmp(name, "x") || !strcmp(name, "y") ||
1402 !strcmp(name, "width") || !strcmp(name, "height") ||
1403 !strcmp(name, "transform"))
1404 {
1405 if (repr == cc->active_shape_repr) {
1406 // Active shape has moved. Clear active shape.
1407 cc_clear_active_shape(cc);
1408 }
1409 else if (repr == cc->active_conn_repr) {
1410 // The active conn has been moved.
1411 // Set it again, which just sets new handle positions.
1412 cc_set_active_conn(cc, cc->active_conn);
1413 }
1414 }
1415 }
1418 /*
1419 Local Variables:
1420 mode:c++
1421 c-file-style:"stroustrup"
1422 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1423 indent-tabs-mode:nil
1424 fill-column:99
1425 End:
1426 */
1427 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :