2
* Connector creation tool
5
* Michael Wybrow <mjwybrow@users.sourceforge.net>
7
* Copyright (C) 2005 Michael Wybrow
9
* Released under GNU GPL, read the file 'COPYING' for more information
12
* o Have shapes avoid convex hulls of objects, rather than their
13
* bounding box. Possibly implement the unfinished ConvexHull
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
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
40
* o gobble_motion_events(GDK_BUTTON1_MASK)?;
44
#include <gdk/gdkkeysyms.h>
48
#include "connector-context.h"
49
#include "pixmaps/cursor-connector.xpm"
50
#include "xml/node-event-vector.h"
54
#include "desktop-style.h"
55
#include "desktop-handles.h"
57
#include "message-context.h"
58
#include "message-stack.h"
59
#include "selection.h"
61
#include "preferences.h"
63
#include "display/canvas-bpath.h"
64
#include "display/sodipodi-ctrl.h"
65
#include <glibmm/i18n.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"
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,
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 */
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 */
141
sp_connector_context_get_type(void)
143
static GType type = 0;
146
sizeof(SPConnectorContextClass),
148
(GClassInitFunc) sp_connector_context_class_init,
150
sizeof(SPConnectorContext),
152
(GInstanceInitFunc) sp_connector_context_init,
153
NULL, /* value_table */
155
type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPConnectorContext", &info, (GTypeFlags)0);
161
sp_connector_context_class_init(SPConnectorContextClass *klass)
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;
181
sp_connector_context_init(SPConnectorContext *cc)
183
SPEventContext *ec = SP_EVENT_CONTEXT(cc);
185
ec->cursor_shape = cursor_connector_xpm;
191
cc->red_color = 0xff00007f;
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;
218
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
223
sp_connector_context_dispose(GObject *object)
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;
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;
247
g_assert( cc->newConnRef == NULL );
249
G_OBJECT_CLASS(parent_class)->dispose(object);
254
sp_connector_context_setup(SPEventContext *ec)
256
SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec);
257
SPDesktop *dt = ec->desktop;
259
if (((SPEventContextClass *) parent_class)->setup) {
260
((SPEventContextClass *) parent_class)->setup(ec);
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),
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();
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;
297
sp_connector_context_finish(SPEventContext *ec)
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);
309
cc->selection = NULL;
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;
320
//-----------------------------------------------------------------------------
324
cc_clear_active_shape(SPConnectorContext *cc)
326
if (cc->active_shape == NULL) {
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;
344
// Hide the center connection point if it exists.
345
if (cc->connpthandle) {
346
sp_knot_hide(cc->connpthandle);
352
cc_clear_active_conn(SPConnectorContext *cc)
354
if (cc->active_conn == NULL) {
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;
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]);
377
conn_pt_handle_test(SPConnectorContext *cc, Geom::Point& p)
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))
386
return g_strdup_printf("#%s", SP_OBJECT_ID(cc->active_shape));
394
sp_connector_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
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))
410
spcc_reset_colors(cc);
411
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
412
sp_event_context_snap_window_closed(event_context);
414
if (cc->state != SP_CONNECTOR_CONTEXT_IDLE) {
415
// Doing simething else like rerouting.
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);
425
cc->selection->set(item_ungrouped);
431
case GDK_ENTER_NOTIFY:
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);
453
sp_connector_context_root_handler(SPEventContext *ec, GdkEvent *event)
455
SPConnectorContext *const cc = SP_CONNECTOR_CONTEXT(ec);
459
switch (event->type) {
460
case GDK_BUTTON_PRESS:
461
ret = connector_handle_button_press(cc, event->button);
464
case GDK_MOTION_NOTIFY:
465
ret = connector_handle_motion_notify(cc, event->motion);
468
case GDK_BUTTON_RELEASE:
469
ret = connector_handle_button_release(cc, event->button);
472
ret = connector_handle_key_press(cc, get_group0_keyval (&event->key));
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);
492
connector_handle_button_press(SPConnectorContext *const cc, GdkEventButton const &bevent)
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);
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) {
508
Geom::Point const event_w(bevent.x,
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);
519
case SP_CONNECTOR_CONTEXT_STOP:
520
/* This is allowed, if we just cancelled curve */
521
case SP_CONNECTOR_CONTEXT_IDLE:
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);
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);
542
spcc_connector_set_initial_point(cc, p);
545
cc->state = SP_CONNECTOR_CONTEXT_DRAGGING;
549
case SP_CONNECTOR_CONTEXT_DRAGGING:
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);
561
cc_set_active_conn(cc, cc->newconn);
562
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
563
sp_event_context_snap_window_closed(event_context);
567
case SP_CONNECTOR_CONTEXT_CLOSE:
569
g_warning("Button down in CLOSE state");
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.
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);
599
connector_handle_motion_notify(SPConnectorContext *const cc, GdkEventMotion const &mevent)
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
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.
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;
632
case SP_CONNECTOR_CONTEXT_DRAGGING:
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);
643
case SP_CONNECTOR_CONTEXT_REROUTING:
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);
659
Geom::Point o = cc->endpt_handle[0]->pos;
660
curve->stretch_endpoints(o * d2i, p * d2i);
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);
672
case SP_CONNECTOR_CONTEXT_STOP:
673
/* This is perfectly valid */
684
connector_handle_button_release(SPConnectorContext *const cc, GdkEventButton const &revent)
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;
696
Geom::Point const event_w(revent.x, revent.y);
698
/* Find desktop coordinates */
699
Geom::Point p = cc->desktop->w2d(event_w);
702
//case SP_CONNECTOR_CONTEXT_POINT:
703
case SP_CONNECTOR_CONTEXT_DRAGGING:
705
m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
707
if (connector_within_tolerance)
709
spcc_connector_finish_segment(cc, p);
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);
720
cc_set_active_conn(cc, cc->newconn);
721
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
722
sp_event_context_snap_window_closed(event_context);
725
case SP_CONNECTOR_CONTEXT_REROUTING:
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);
736
case SP_CONNECTOR_CONTEXT_STOP:
737
/* This is allowed, if we just cancelled curve */
750
connector_handle_key_press(SPConnectorContext *const cc, guint const keyval)
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));
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."));
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);
796
cc_connector_rerouting_finish(SPConnectorContext *const cc, Geom::Point *const p)
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);
807
// Test whether we clicked on a connection point
808
gchar *shape_label = conn_pt_handle_test(cc, *p);
811
if (cc->clickedhandle == cc->endpt_handle[0]) {
812
sp_object_setAttribute(cc->clickeditem,
813
"inkscape:connection-start",shape_label, false);
816
sp_object_setAttribute(cc->clickeditem,
817
"inkscape:connection-end",shape_label, false);
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);
832
spcc_reset_colors(SPConnectorContext *cc)
835
cc->red_curve->reset();
836
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
838
cc->green_curve->reset();
844
spcc_connector_set_initial_point(SPConnectorContext *const cc, Geom::Point const p)
846
g_assert( cc->npoints == 0 );
851
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
856
spcc_connector_set_subsequent_point(SPConnectorContext *const cc, Geom::Point const p)
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);
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);
887
cc->red_curve->transform(dt->doc2dt());
888
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
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.
898
spcc_concat_colors_and_flush(SPConnectorContext *cc)
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);
911
spcc_flush_white(cc, c);
918
* Flushes white curve(s) and additional curve into object
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
926
spcc_flush_white(SPConnectorContext *cc, SPCurve *gc)
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");
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);
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",
968
sp_object_setAttribute(cc->newconn, "inkscape:connection-start",
975
sp_object_setAttribute(cc->newconn, "inkscape:connection-end",
979
cc->newconn->updateRepr();
981
// Adjust endpoints to shape edge.
982
sp_conn_adjust_path(SP_PATH(cc->newconn));
984
cc->newconn->updateRepr();
989
/* Flush pending updates */
990
sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR, _("Create connector"));
991
sp_document_ensure_up_to_date(doc);
996
spcc_connector_finish_segment(SPConnectorContext *const cc, Geom::Point const /*p*/)
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];
1005
cc->red_curve->reset();
1011
spcc_connector_finish(SPConnectorContext *const cc)
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);
1021
if (cc->newConnRef) {
1022
cc->newConnRef->removeFromGraph();
1023
delete cc->newConnRef;
1024
cc->newConnRef = NULL;
1030
cc_generic_knot_handler(SPCanvasItem *, GdkEvent *event, SPKnot *knot)
1032
g_assert (knot != NULL);
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;
1049
knot->desktop->event_context->defaultMessageContext()->set(
1050
Inkscape::NORMAL_MESSAGE, knot->tip);
1055
case GDK_LEAVE_NOTIFY:
1056
sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE);
1058
cc->active_handle = NULL;
1061
knot->desktop->event_context->defaultMessageContext()->clear();
1070
g_object_unref(knot);
1077
endpt_handler(SPKnot */*knot*/, GdkEvent *event, SPConnectorContext *cc)
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);
1099
if (cc->clickedhandle == cc->endpt_handle[0]) {
1100
origin = cc->endpt_handle[1]->pos;
1103
origin = cc->endpt_handle[0]->pos;
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.
1127
static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item)
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);
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);
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);
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;
1178
Geom::OptRect bbox = sp_item_bbox_desktop(cc->active_shape);
1180
Geom::Point center = bbox->midpoint();
1181
sp_knot_set_position(cc->connpthandle, center, 0);
1182
sp_knot_show(cc->connpthandle);
1184
sp_knot_hide(cc->connpthandle);
1190
cc_set_active_conn(SPConnectorContext *cc, SPItem *item)
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)
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);
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;
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);
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);
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;
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;
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);
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]);
1278
static bool cc_item_is_shape(SPItem *item)
1280
if (SP_IS_PATH(item)) {
1281
SPCurve *curve = (SP_SHAPE(item))->curve;
1282
if ( curve && !(curve->is_closed()) ) {
1283
// Open paths are connectors.
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.
1298
bool cc_item_is_connector(SPItem *item)
1300
if (SP_IS_PATH(item)) {
1301
if (SP_PATH(item)->connEndPair.isAutoRoutingConn()) {
1302
g_assert( !(SP_SHAPE(item)->curve->is_closed()) );
1310
void cc_selection_set_avoid(bool const set_avoid)
1312
SPDesktop *desktop = inkscape_active_desktop();
1313
if (desktop == NULL) {
1317
SPDocument *document = sp_desktop_document(desktop);
1319
Inkscape::Selection *selection = sp_desktop_selection(desktop);
1321
GSList *l = (GSList *) selection->itemList();
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",
1333
item->avoidRef->handleSettingChange();
1341
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE,
1342
_("Select <b>at least one non-connector object</b>."));
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);
1354
cc_selection_changed(Inkscape::Selection *selection, gpointer data)
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)
1363
// Nothing to change.
1368
cc_clear_active_conn(cc);
1372
if (cc_item_is_connector(item)) {
1373
cc_set_active_conn(cc, item);
1379
shape_event_attr_deleted(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child,
1380
Inkscape::XML::Node */*ref*/, gpointer 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);
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)
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"))
1405
if (repr == cc->active_shape_repr) {
1406
// Active shape has moved. Clear active shape.
1407
cc_clear_active_shape(cc);
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);
1421
c-file-style:"stroustrup"
1422
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1423
indent-tabs-mode:nil
1427
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :