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_discard_delayed_snap_event(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 if (!cc->sid) {
536 // This is the first point, so just snap it to the grid
537 // as there's no other points to go off.
538 m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
539 }
540 spcc_connector_set_initial_point(cc, p);
542 }
543 cc->state = SP_CONNECTOR_CONTEXT_DRAGGING;
544 ret = TRUE;
545 break;
546 }
547 case SP_CONNECTOR_CONTEXT_DRAGGING:
548 {
549 // This is the second click of a connector creation.
550 m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
552 spcc_connector_set_subsequent_point(cc, p);
553 spcc_connector_finish_segment(cc, p);
554 // Test whether we clicked on a connection point
555 cc->eid = conn_pt_handle_test(cc, p);
556 if (cc->npoints != 0) {
557 spcc_connector_finish(cc);
558 }
559 cc_set_active_conn(cc, cc->newconn);
560 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
561 sp_event_context_discard_delayed_snap_event(event_context);
562 ret = TRUE;
563 break;
564 }
565 case SP_CONNECTOR_CONTEXT_CLOSE:
566 {
567 g_warning("Button down in CLOSE state");
568 break;
569 }
570 default:
571 break;
572 }
573 } else if (bevent.button == 3) {
574 if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) {
575 // A context menu is going to be triggered here,
576 // so end the rerouting operation.
577 cc_connector_rerouting_finish(cc, &p);
579 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
580 sp_event_context_discard_delayed_snap_event(event_context);
582 // Don't set ret to TRUE, so we drop through to the
583 // parent handler which will open the context menu.
584 }
585 else if (cc->npoints != 0) {
586 spcc_connector_finish(cc);
587 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
588 sp_event_context_discard_delayed_snap_event(event_context);
589 ret = TRUE;
590 }
591 }
592 return ret;
593 }
596 static gint
597 connector_handle_motion_notify(SPConnectorContext *const cc, GdkEventMotion const &mevent)
598 {
599 gint ret = FALSE;
600 SPEventContext *event_context = SP_EVENT_CONTEXT(cc);
601 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
603 if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) {
604 // allow middle-button scrolling
605 return FALSE;
606 }
608 Geom::Point const event_w(mevent.x, mevent.y);
610 if (connector_within_tolerance) {
611 gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
612 if ( Geom::LInfty( event_w - connector_drag_origin_w ) < tolerance ) {
613 return FALSE; // Do not drag if we're within tolerance from origin.
614 }
615 }
616 // Once the user has moved farther than tolerance from the original location
617 // (indicating they intend to move the object, not click), then always process
618 // the motion notify coordinates as given (no snapping back to origin)
619 connector_within_tolerance = false;
621 SPDesktop *const dt = cc->desktop;
623 /* Find desktop coordinates */
624 Geom::Point p = dt->w2d(event_w);
626 SnapManager &m = dt->namedview->snap_manager;
627 m.setup(dt);
629 switch (cc->state) {
630 case SP_CONNECTOR_CONTEXT_DRAGGING:
631 {
632 // This is movement during a connector creation.
633 if ( cc->npoints > 0 ) {
634 m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
635 cc->selection->clear();
636 spcc_connector_set_subsequent_point(cc, p);
637 ret = TRUE;
638 }
639 break;
640 }
641 case SP_CONNECTOR_CONTEXT_REROUTING:
642 {
643 g_assert( SP_IS_PATH(cc->clickeditem));
645 m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
647 // Update the hidden path
648 Geom::Matrix i2d = sp_item_i2d_affine(cc->clickeditem);
649 Geom::Matrix d2i = i2d.inverse();
650 SPPath *path = SP_PATH(cc->clickeditem);
651 SPCurve *curve = (SP_SHAPE(path))->curve;
652 if (cc->clickedhandle == cc->endpt_handle[0]) {
653 Geom::Point o = cc->endpt_handle[1]->pos;
654 curve->stretch_endpoints(p * d2i, o * d2i);
655 }
656 else {
657 Geom::Point o = cc->endpt_handle[0]->pos;
658 curve->stretch_endpoints(o * d2i, p * d2i);
659 }
660 sp_conn_adjust_path(path);
662 // Copy this to the temporary visible path
663 cc->red_curve = SP_SHAPE(path)->curve->copy();
664 cc->red_curve->transform(i2d);
666 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
667 ret = TRUE;
668 break;
669 }
670 case SP_CONNECTOR_CONTEXT_STOP:
671 /* This is perfectly valid */
672 break;
673 default:
674 break;
675 }
677 return ret;
678 }
681 static gint
682 connector_handle_button_release(SPConnectorContext *const cc, GdkEventButton const &revent)
683 {
684 gint ret = FALSE;
685 SPEventContext *event_context = SP_EVENT_CONTEXT(cc);
686 if ( revent.button == 1 && !event_context->space_panning ) {
688 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
689 SPDocument *doc = sp_desktop_document(desktop);
691 SnapManager &m = desktop->namedview->snap_manager;
692 m.setup(desktop);
694 Geom::Point const event_w(revent.x, revent.y);
696 /* Find desktop coordinates */
697 Geom::Point p = cc->desktop->w2d(event_w);
699 switch (cc->state) {
700 //case SP_CONNECTOR_CONTEXT_POINT:
701 case SP_CONNECTOR_CONTEXT_DRAGGING:
702 {
703 m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
705 if (connector_within_tolerance)
706 {
707 spcc_connector_finish_segment(cc, p);
708 return TRUE;
709 }
710 // Connector has been created via a drag, end it now.
711 spcc_connector_set_subsequent_point(cc, p);
712 spcc_connector_finish_segment(cc, p);
713 // Test whether we clicked on a connection point
714 cc->eid = conn_pt_handle_test(cc, p);
715 if (cc->npoints != 0) {
716 spcc_connector_finish(cc);
717 }
718 cc_set_active_conn(cc, cc->newconn);
719 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
720 sp_event_context_discard_delayed_snap_event(event_context);
721 break;
722 }
723 case SP_CONNECTOR_CONTEXT_REROUTING:
724 {
725 m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
726 cc_connector_rerouting_finish(cc, &p);
728 sp_document_ensure_up_to_date(doc);
729 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
730 sp_event_context_discard_delayed_snap_event(event_context);
731 return TRUE;
732 break;
733 }
734 case SP_CONNECTOR_CONTEXT_STOP:
735 /* This is allowed, if we just cancelled curve */
736 break;
737 default:
738 break;
739 }
740 ret = TRUE;
741 }
743 return ret;
744 }
747 static gint
748 connector_handle_key_press(SPConnectorContext *const cc, guint const keyval)
749 {
750 gint ret = FALSE;
751 /* fixme: */
752 switch (keyval) {
753 case GDK_Return:
754 case GDK_KP_Enter:
755 if (cc->npoints != 0) {
756 spcc_connector_finish(cc);
757 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
758 sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(cc));
759 ret = TRUE;
760 }
761 break;
762 case GDK_Escape:
763 if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) {
765 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
766 SPDocument *doc = sp_desktop_document(desktop);
768 cc_connector_rerouting_finish(cc, NULL);
770 sp_document_undo(doc);
772 cc->state = SP_CONNECTOR_CONTEXT_IDLE;
773 sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(cc));
774 desktop->messageStack()->flash( Inkscape::NORMAL_MESSAGE,
775 _("Connector endpoint drag cancelled."));
776 ret = TRUE;
777 }
778 else if (cc->npoints != 0) {
779 // if drawing, cancel, otherwise pass it up for deselecting
780 cc->state = SP_CONNECTOR_CONTEXT_STOP;
781 sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(cc));
782 spcc_reset_colors(cc);
783 ret = TRUE;
784 }
785 break;
786 default:
787 break;
788 }
789 return ret;
790 }
793 static void
794 cc_connector_rerouting_finish(SPConnectorContext *const cc, Geom::Point *const p)
795 {
796 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
797 SPDocument *doc = sp_desktop_document(desktop);
799 // Clear the temporary path:
800 cc->red_curve->reset();
801 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
803 if (p != NULL)
804 {
805 // Test whether we clicked on a connection point
806 gchar *shape_label = conn_pt_handle_test(cc, *p);
808 if (shape_label) {
809 if (cc->clickedhandle == cc->endpt_handle[0]) {
810 sp_object_setAttribute(cc->clickeditem,
811 "inkscape:connection-start",shape_label, false);
812 }
813 else {
814 sp_object_setAttribute(cc->clickeditem,
815 "inkscape:connection-end",shape_label, false);
816 }
817 g_free(shape_label);
818 }
819 }
820 cc->clickeditem->setHidden(false);
821 sp_conn_adjust_path(SP_PATH(cc->clickeditem));
822 cc->clickeditem->updateRepr();
823 sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR,
824 _("Reroute connector"));
825 cc_set_active_conn(cc, cc->clickeditem);
826 }
829 static void
830 spcc_reset_colors(SPConnectorContext *cc)
831 {
832 /* Red */
833 cc->red_curve->reset();
834 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
836 cc->green_curve->reset();
837 cc->npoints = 0;
838 }
841 static void
842 spcc_connector_set_initial_point(SPConnectorContext *const cc, Geom::Point const p)
843 {
844 g_assert( cc->npoints == 0 );
846 cc->p[0] = p;
847 cc->p[1] = p;
848 cc->npoints = 2;
849 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
850 }
853 static void
854 spcc_connector_set_subsequent_point(SPConnectorContext *const cc, Geom::Point const p)
855 {
856 g_assert( cc->npoints != 0 );
858 SPDesktop *dt = cc->desktop;
859 Geom::Point o = dt->dt2doc(cc->p[0]);
860 Geom::Point d = dt->dt2doc(p);
861 Avoid::Point src(o[Geom::X], o[Geom::Y]);
862 Avoid::Point dst(d[Geom::X], d[Geom::Y]);
864 if (!cc->newConnRef) {
865 Avoid::Router *router = sp_desktop_document(dt)->router;
866 cc->newConnRef = new Avoid::ConnRef(router, 0, src, dst);
867 cc->newConnRef->updateEndPoint(Avoid::VertID::src, src);
868 }
869 cc->newConnRef->updateEndPoint(Avoid::VertID::tar, dst);
871 cc->newConnRef->makePathInvalid();
872 cc->newConnRef->generatePath(src, dst);
874 Avoid::PolyLine route = cc->newConnRef->route();
875 cc->newConnRef->calcRouteDist();
877 cc->red_curve->reset();
878 Geom::Point pt(route.ps[0].x, route.ps[0].y);
879 cc->red_curve->moveto(pt);
881 for (int i = 1; i < route.pn; ++i) {
882 Geom::Point p(route.ps[i].x, route.ps[i].y);
883 cc->red_curve->lineto(p);
884 }
885 cc->red_curve->transform(dt->doc2dt());
886 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
887 }
890 /**
891 * Concats red, blue and green.
892 * If any anchors are defined, process these, optionally removing curves from white list
893 * Invoke _flush_white to write result back to object.
894 */
895 static void
896 spcc_concat_colors_and_flush(SPConnectorContext *cc)
897 {
898 SPCurve *c = cc->green_curve;
899 cc->green_curve = new SPCurve();
901 cc->red_curve->reset();
902 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
904 if (c->is_empty()) {
905 c->unref();
906 return;
907 }
909 spcc_flush_white(cc, c);
911 c->unref();
912 }
915 /*
916 * Flushes white curve(s) and additional curve into object
917 *
918 * No cleaning of colored curves - this has to be done by caller
919 * No rereading of white data, so if you cannot rely on ::modified, do it in caller
920 *
921 */
923 static void
924 spcc_flush_white(SPConnectorContext *cc, SPCurve *gc)
925 {
926 SPCurve *c;
928 if (gc) {
929 c = gc;
930 c->ref();
931 } else {
932 return;
933 }
935 /* Now we have to go back to item coordinates at last */
936 c->transform(SP_EVENT_CONTEXT_DESKTOP(cc)->dt2doc());
938 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
939 SPDocument *doc = sp_desktop_document(desktop);
940 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
942 if ( c && !c->is_empty() ) {
943 /* We actually have something to write */
945 Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
946 /* Set style */
947 sp_desktop_apply_style_tool(desktop, repr, "/tools/connector", false);
949 gchar *str = sp_svg_write_path( c->get_pathvector() );
950 g_assert( str != NULL );
951 repr->setAttribute("d", str);
952 g_free(str);
954 /* Attach repr */
955 cc->newconn = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
956 cc->selection->set(repr);
957 Inkscape::GC::release(repr);
958 cc->newconn->transform = sp_item_i2doc_affine(SP_ITEM(desktop->currentLayer())).inverse();
959 cc->newconn->updateRepr();
961 bool connection = false;
962 sp_object_setAttribute(cc->newconn, "inkscape:connector-type",
963 "polyline", false);
964 if (cc->sid)
965 {
966 sp_object_setAttribute(cc->newconn, "inkscape:connection-start",
967 cc->sid, false);
968 connection = true;
969 }
971 if (cc->eid)
972 {
973 sp_object_setAttribute(cc->newconn, "inkscape:connection-end",
974 cc->eid, false);
975 connection = true;
976 }
977 cc->newconn->updateRepr();
978 if (connection) {
979 // Adjust endpoints to shape edge.
980 sp_conn_adjust_path(SP_PATH(cc->newconn));
981 }
982 cc->newconn->updateRepr();
983 }
985 c->unref();
987 /* Flush pending updates */
988 sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR, _("Create connector"));
989 sp_document_ensure_up_to_date(doc);
990 }
993 static void
994 spcc_connector_finish_segment(SPConnectorContext *const cc, Geom::Point const /*p*/)
995 {
996 if (!cc->red_curve->is_empty()) {
997 cc->green_curve->append_continuous(cc->red_curve, 0.0625);
999 cc->p[0] = cc->p[3];
1000 cc->p[1] = cc->p[4];
1001 cc->npoints = 2;
1003 cc->red_curve->reset();
1004 }
1005 }
1008 static void
1009 spcc_connector_finish(SPConnectorContext *const cc)
1010 {
1011 SPDesktop *const desktop = cc->desktop;
1012 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing connector"));
1014 cc->red_curve->reset();
1015 spcc_concat_colors_and_flush(cc);
1017 cc->npoints = 0;
1019 if (cc->newConnRef) {
1020 cc->newConnRef->removeFromGraph();
1021 delete cc->newConnRef;
1022 cc->newConnRef = NULL;
1023 }
1024 }
1027 static gboolean
1028 cc_generic_knot_handler(SPCanvasItem *, GdkEvent *event, SPKnot *knot)
1029 {
1030 g_assert (knot != NULL);
1032 g_object_ref(knot);
1034 SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(
1035 knot->desktop->event_context);
1037 gboolean consumed = FALSE;
1039 switch (event->type) {
1040 case GDK_ENTER_NOTIFY:
1041 sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, TRUE);
1043 cc->active_handle = knot;
1045 if (knot->tip)
1046 {
1047 knot->desktop->event_context->defaultMessageContext()->set(
1048 Inkscape::NORMAL_MESSAGE, knot->tip);
1049 }
1051 consumed = TRUE;
1052 break;
1053 case GDK_LEAVE_NOTIFY:
1054 sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE);
1056 cc->active_handle = NULL;
1058 if (knot->tip) {
1059 knot->desktop->event_context->defaultMessageContext()->clear();
1060 }
1062 consumed = TRUE;
1063 break;
1064 default:
1065 break;
1066 }
1068 g_object_unref(knot);
1070 return consumed;
1071 }
1074 static gboolean
1075 endpt_handler(SPKnot */*knot*/, GdkEvent *event, SPConnectorContext *cc)
1076 {
1077 g_assert( SP_IS_CONNECTOR_CONTEXT(cc) );
1079 gboolean consumed = FALSE;
1081 switch (event->type) {
1082 case GDK_BUTTON_PRESS:
1083 g_assert( (cc->active_handle == cc->endpt_handle[0]) ||
1084 (cc->active_handle == cc->endpt_handle[1]) );
1085 if (cc->state == SP_CONNECTOR_CONTEXT_IDLE) {
1086 cc->clickeditem = cc->active_conn;
1087 cc->clickedhandle = cc->active_handle;
1088 cc_clear_active_conn(cc);
1089 cc->state = SP_CONNECTOR_CONTEXT_REROUTING;
1091 // Disconnect from attached shape
1092 unsigned ind = (cc->active_handle == cc->endpt_handle[0]) ? 0 : 1;
1093 sp_conn_end_detach(cc->clickeditem, ind);
1095 Geom::Point origin;
1096 if (cc->clickedhandle == cc->endpt_handle[0]) {
1097 origin = cc->endpt_handle[1]->pos;
1098 }
1099 else {
1100 origin = cc->endpt_handle[0]->pos;
1101 }
1103 // Show the red path for dragging.
1104 cc->red_curve = SP_PATH(cc->clickeditem)->curve->copy();
1105 Geom::Matrix i2d = sp_item_i2d_affine(cc->clickeditem);
1106 cc->red_curve->transform(i2d);
1107 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
1109 cc->clickeditem->setHidden(true);
1111 // The rest of the interaction rerouting the connector is
1112 // handled by the context root handler.
1113 consumed = TRUE;
1114 }
1115 break;
1116 default:
1117 break;
1118 }
1120 return consumed;
1121 }
1124 static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item)
1125 {
1126 g_assert(item != NULL );
1128 cc->active_shape = item;
1130 // Remove existing active shape listeners
1131 if (cc->active_shape_repr) {
1132 sp_repr_remove_listener_by_data(cc->active_shape_repr, cc);
1133 Inkscape::GC::release(cc->active_shape_repr);
1135 sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc);
1136 Inkscape::GC::release(cc->active_shape_layer_repr);
1137 }
1139 // Listen in case the active shape changes
1140 cc->active_shape_repr = SP_OBJECT_REPR(item);
1141 if (cc->active_shape_repr) {
1142 Inkscape::GC::anchor(cc->active_shape_repr);
1143 sp_repr_add_listener(cc->active_shape_repr, &shape_repr_events, cc);
1145 cc->active_shape_layer_repr = cc->active_shape_repr->parent();
1146 Inkscape::GC::anchor(cc->active_shape_layer_repr);
1147 sp_repr_add_listener(cc->active_shape_layer_repr, &layer_repr_events, cc);
1148 }
1151 // Set center connection point.
1152 if ( cc->connpthandle == NULL ) {
1153 SPKnot *knot = sp_knot_new(cc->desktop,
1154 _("<b>Connection point</b>: click or drag to create a new connector"));
1156 knot->setShape(SP_KNOT_SHAPE_SQUARE);
1157 knot->setSize(8);
1158 knot->setAnchor(GTK_ANCHOR_CENTER);
1159 knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff);
1160 sp_knot_update_ctrl(knot);
1162 // We don't want to use the standard knot handler,
1163 //since we don't want this knot to be draggable.
1164 g_signal_handler_disconnect(G_OBJECT(knot->item),
1165 knot->_event_handler_id);
1166 knot->_event_handler_id = 0;
1168 gtk_signal_connect(GTK_OBJECT(knot->item), "event",
1169 GTK_SIGNAL_FUNC(cc_generic_knot_handler), knot);
1171 cc->connpthandle = knot;
1172 }
1175 Geom::OptRect bbox = sp_item_bbox_desktop(cc->active_shape);
1176 if (bbox) {
1177 Geom::Point center = bbox->midpoint();
1178 sp_knot_set_position(cc->connpthandle, center, 0);
1179 sp_knot_show(cc->connpthandle);
1180 } else {
1181 sp_knot_hide(cc->connpthandle);
1182 }
1183 }
1186 static void
1187 cc_set_active_conn(SPConnectorContext *cc, SPItem *item)
1188 {
1189 g_assert( SP_IS_PATH(item) );
1191 SPCurve *curve = SP_SHAPE(SP_PATH(item))->curve;
1192 Geom::Matrix i2d = sp_item_i2d_affine(item);
1194 if (cc->active_conn == item)
1195 {
1196 // Just adjust handle positions.
1197 Geom::Point startpt = *(curve->first_point()) * i2d;
1198 sp_knot_set_position(cc->endpt_handle[0], startpt, 0);
1200 Geom::Point endpt = *(curve->last_point()) * i2d;
1201 sp_knot_set_position(cc->endpt_handle[1], endpt, 0);
1203 return;
1204 }
1206 cc->active_conn = item;
1208 // Remove existing active conn listeners
1209 if (cc->active_conn_repr) {
1210 sp_repr_remove_listener_by_data(cc->active_conn_repr, cc);
1211 Inkscape::GC::release(cc->active_conn_repr);
1212 cc->active_conn_repr = NULL;
1213 }
1215 // Listen in case the active conn changes
1216 cc->active_conn_repr = SP_OBJECT_REPR(item);
1217 if (cc->active_conn_repr) {
1218 Inkscape::GC::anchor(cc->active_conn_repr);
1219 sp_repr_add_listener(cc->active_conn_repr, &shape_repr_events, cc);
1220 }
1222 for (int i = 0; i < 2; ++i) {
1224 // Create the handle if it doesn't exist
1225 if ( cc->endpt_handle[i] == NULL ) {
1226 SPKnot *knot = sp_knot_new(cc->desktop,
1227 _("<b>Connector endpoint</b>: drag to reroute or connect to new shapes"));
1229 knot->setShape(SP_KNOT_SHAPE_SQUARE);
1230 knot->setSize(7);
1231 knot->setAnchor(GTK_ANCHOR_CENTER);
1232 knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff);
1233 knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
1234 sp_knot_update_ctrl(knot);
1236 // We don't want to use the standard knot handler,
1237 //since we don't want this knot to be draggable.
1238 g_signal_handler_disconnect(G_OBJECT(knot->item),
1239 knot->_event_handler_id);
1240 knot->_event_handler_id = 0;
1242 gtk_signal_connect(GTK_OBJECT(knot->item), "event",
1243 GTK_SIGNAL_FUNC(cc_generic_knot_handler), knot);
1245 cc->endpt_handle[i] = knot;
1246 }
1248 // Remove any existing handlers
1249 if (cc->endpt_handler_id[i]) {
1250 g_signal_handlers_disconnect_by_func(
1251 G_OBJECT(cc->endpt_handle[i]->item),
1252 (void*)G_CALLBACK(endpt_handler), (gpointer) cc );
1253 cc->endpt_handler_id[i] = 0;
1254 }
1256 // Setup handlers for connector endpoints, this is
1257 // is as 'after' so that cc_generic_knot_handler is
1258 // triggered first for any endpoint.
1259 cc->endpt_handler_id[i] = g_signal_connect_after(
1260 G_OBJECT(cc->endpt_handle[i]->item), "event",
1261 G_CALLBACK(endpt_handler), cc);
1262 }
1264 Geom::Point startpt = *(curve->first_point()) * i2d;
1265 sp_knot_set_position(cc->endpt_handle[0], startpt, 0);
1267 Geom::Point endpt = *(curve->last_point()) * i2d;
1268 sp_knot_set_position(cc->endpt_handle[1], endpt, 0);
1270 sp_knot_show(cc->endpt_handle[0]);
1271 sp_knot_show(cc->endpt_handle[1]);
1272 }
1275 static bool cc_item_is_shape(SPItem *item)
1276 {
1277 if (SP_IS_PATH(item)) {
1278 SPCurve *curve = (SP_SHAPE(item))->curve;
1279 if ( curve && !(curve->is_closed()) ) {
1280 // Open paths are connectors.
1281 return false;
1282 }
1283 }
1284 else if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1285 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1286 if (prefs->getBool("/tools/connector/ignoretext", true)) {
1287 // Don't count text as a shape we can connect connector to.
1288 return false;
1289 }
1290 }
1291 return true;
1292 }
1295 bool cc_item_is_connector(SPItem *item)
1296 {
1297 if (SP_IS_PATH(item)) {
1298 if (SP_PATH(item)->connEndPair.isAutoRoutingConn()) {
1299 g_assert( !(SP_SHAPE(item)->curve->is_closed()) );
1300 return true;
1301 }
1302 }
1303 return false;
1304 }
1307 void cc_selection_set_avoid(bool const set_avoid)
1308 {
1309 SPDesktop *desktop = inkscape_active_desktop();
1310 if (desktop == NULL) {
1311 return;
1312 }
1314 SPDocument *document = sp_desktop_document(desktop);
1316 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1318 GSList *l = (GSList *) selection->itemList();
1320 int changes = 0;
1322 while (l) {
1323 SPItem *item = (SPItem *) l->data;
1325 char const *value = (set_avoid) ? "true" : NULL;
1327 if (cc_item_is_shape(item)) {
1328 sp_object_setAttribute(item, "inkscape:connector-avoid",
1329 value, false);
1330 item->avoidRef->handleSettingChange();
1331 changes++;
1332 }
1334 l = l->next;
1335 }
1337 if (changes == 0) {
1338 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE,
1339 _("Select <b>at least one non-connector object</b>."));
1340 return;
1341 }
1343 char *event_desc = (set_avoid) ?
1344 _("Make connectors avoid selected objects") :
1345 _("Make connectors ignore selected objects");
1346 sp_document_done(document, SP_VERB_CONTEXT_CONNECTOR, event_desc);
1347 }
1350 static void
1351 cc_selection_changed(Inkscape::Selection *selection, gpointer data)
1352 {
1353 SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data);
1354 //SPEventContext *ec = SP_EVENT_CONTEXT(cc);
1356 SPItem *item = selection->singleItem();
1358 if (cc->active_conn == item)
1359 {
1360 // Nothing to change.
1361 return;
1362 }
1363 if (item == NULL)
1364 {
1365 cc_clear_active_conn(cc);
1366 return;
1367 }
1369 if (cc_item_is_connector(item)) {
1370 cc_set_active_conn(cc, item);
1371 }
1372 }
1375 static void
1376 shape_event_attr_deleted(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child,
1377 Inkscape::XML::Node */*ref*/, gpointer data)
1378 {
1379 g_assert(data);
1380 SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data);
1382 if (child == cc->active_shape_repr) {
1383 // The active shape has been deleted. Clear active shape.
1384 cc_clear_active_shape(cc);
1385 }
1386 }
1389 static void
1390 shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
1391 gchar const */*old_value*/, gchar const */*new_value*/,
1392 bool /*is_interactive*/, gpointer data)
1393 {
1394 g_assert(data);
1395 SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data);
1397 // Look for changes than result in onscreen movement.
1398 if (!strcmp(name, "d") || !strcmp(name, "x") || !strcmp(name, "y") ||
1399 !strcmp(name, "width") || !strcmp(name, "height") ||
1400 !strcmp(name, "transform"))
1401 {
1402 if (repr == cc->active_shape_repr) {
1403 // Active shape has moved. Clear active shape.
1404 cc_clear_active_shape(cc);
1405 }
1406 else if (repr == cc->active_conn_repr) {
1407 // The active conn has been moved.
1408 // Set it again, which just sets new handle positions.
1409 cc_set_active_conn(cc, cc->active_conn);
1410 }
1411 }
1412 }
1415 /*
1416 Local Variables:
1417 mode:c++
1418 c-file-style:"stroustrup"
1419 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1420 indent-tabs-mode:nil
1421 fill-column:99
1422 End:
1423 */
1424 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :