2
* Connector creation tool
5
* Michael Wybrow <mjwybrow@users.sourceforge.net>
7
* Copyright (C) 2005-2008 Michael Wybrow
8
* Copyright (C) 2009 Monash University
10
* Released under GNU GPL, read the file 'COPYING' for more information
13
* o Show a visual indicator for objects with the 'avoid' property set.
14
* o Allow user to change a object between a path and connector through
16
* o Create an interface for setting markers (arrow heads).
17
* o Better distinguish between paths and connectors to prevent problems
18
* in the node tool and paths accidentally being turned into connectors
19
* in the connector tool. Perhaps have a way to convert between.
20
* o Only call libavoid's updateEndPoint as required. Currently we do it
21
* for both endpoints, even if only one is moving.
22
* o Allow user-placeable connection points.
23
* o Deal sanely with connectors with both endpoints attached to the
24
* same connection point, and drawing of connectors attaching
25
* overlapping shapes (currently tries to adjust connector to be
26
* outside both bounding boxes).
27
* o Fix many special cases related to connectors updating,
28
* e.g., copying a couple of shapes and a connector that are
29
* attached to each other.
30
* e.g., detach connector when it is moved or transformed in
31
* one of the other contexts.
32
* o Cope with shapes whose ids change when they have attached
34
* o During dragging motion, gobble up to and use the final motion event.
35
* Gobbling away all duplicates after the current can occasionally result
36
* in the path lagging behind the mouse cursor if it is no longer being
38
* o Fix up libavoid's representation after undo actions. It doesn't see
39
* any transform signals and hence doesn't know shapes have moved back to
40
* there earlier positions.
41
* o Decide whether drawing/editing mode should be an Inkscape preference
42
* or the connector tool should always start in drawing mode.
43
* o Correct the problem with switching to the select tool when pressing
44
* space bar (there are moments when it refuses to do so).
46
* ----------------------------------------------------------------------------
48
* mjwybrow's observations on acracan's Summer of Code connector work:
52
* - Buttons for adding and removing user-specified connection
53
* points should probably have "+" and "-" symbols on them so they
54
* are consistent with the similar buttons for the node tool.
55
* - Controls on the connector tool be should be reordered logically,
56
* possibly as follows:
58
* *Connector*: [Polyline-radio-button] [Orthgonal-radio-button]
59
* [Curvature-control] | *Shape*: [Avoid-button] [Dont-avoid-button]
60
* [Spacing-control] | *Connection pts*: [Edit-mode] [Add-pt] [Rm-pt]
62
* I think that the network layout controls be moved to the
63
* Align and Distribute dialog (there is already the layout button
64
* there, but no options are exposed).
66
* I think that the style change between polyline and orthogonal
67
* would be much clearer with two buttons (radio behaviour -- just
70
* The other tools show a label change from "New:" to "Change:"
71
* depending on whether an object is selected. We could consider
72
* this but there may not be space.
74
* The Add-pt and Rm-pt buttons should be greyed out (inactive) if
75
* we are not in connection point editing mode. And probably also
76
* if there is no shape selected, i.e. at the times they have no
77
* effect when clicked.
79
* Likewise for the avoid/ignore shapes buttons. These should be
80
* inactive when a shape is not selected in the connector context.
82
* - When creating/editing connection points:
84
* - Strange things can happen if you have connectors selected, or
85
* try rerouting connectors by dragging their endpoints when in
86
* connection point editing mode.
88
* - Possibly the selected shape's connection points should always
89
* be shown (i.e., have knots) when in editing mode.
91
* - It is a little strange to be able to place connection points
92
* competely outside shapes. Especially when you later can't draw
93
* connectors to them since the knots are only visible when you
94
* are over the shape. I think that you should only be able to
95
* place connection points inside or on the boundary of the shape
98
* - The intended ability to place a new point at the current cursor
99
* position by pressing RETURN does not seem to work.
101
* - The Status bar tooltip should change to reflect editing mode
102
* and tell the user about RETURN and how to use the tool.
104
* - Connection points general:
106
* - Connection points that were inside the shape can end up outside
107
* after a rotation is applied to the shape in the select tool.
108
* It doesn't seem like the correct transform is being applied to
109
* these, or it is being applied at the wrong time. I'd expect
110
* connection points to rotate with the shape, and stay at the
111
* same position "on the shape"
113
* - I was able to make the connectors attached to a shape fall off
114
* the shape after scaling it. Not sure the exact cause, but may
115
* require more investigation/debugging.
117
* - The user-defined connection points should be either absolute
118
* (as the current ones are) or defined as a percentage of the
119
* shape. These would be based on a toggle setting on the
120
* toolbar, and they would be placed in exactly the same way by
121
* the user. The only difference would be that they would be
122
* store as percentage positions in the SVG connection-points
123
* property and that they would update/move automatically if the
124
* object was resized or scaled.
126
* - Thinking more, I think you always want to store and think about
127
* the positions of connection points to be pre-transform, but
128
* obviously the shape transform is applied to them. That way,
129
* they will rotate and scale automatically with the shape, when
130
* the shape transform is altered. The Percentage version would
131
* compute their position from the pre-transform dimensions and
132
* then have the transform applied to them, for example.
134
* - The connection points in the test_connection_points.svg file
135
* seem to follow the shape when it is moved, but connection
136
* points I add to new shapes, do not follow the shape, either
137
* when the shape is just moved or transformed. There is
138
* something wrong here. What exactly should the behaviour be
141
* - I see that connection points are specified at absolute canvas
142
* positions. I really think that they should be specified in
143
* shape coordinated relative to the shapes. There may be
144
* transforms applied to layers and the canvas which would make
145
* specifying them quite difficult. I'd expect a position of 0, 0
146
* to be on the shape in question or very close to it, for example.
152
#include <gdk/gdkkeysyms.h>
156
#include "connector-context.h"
157
#include "pixmaps/cursor-connector.xpm"
158
#include "pixmaps/cursor-node.xpm"
159
//#include "pixmaps/cursor-node-m.xpm"
160
//#include "pixmaps/cursor-node-d.xpm"
161
#include "xml/node-event-vector.h"
162
#include "xml/repr.h"
165
#include "desktop-style.h"
166
#include "desktop-handles.h"
167
#include "document.h"
168
#include "message-context.h"
169
#include "message-stack.h"
170
#include "selection.h"
171
#include "inkscape.h"
172
#include "preferences.h"
174
#include "display/canvas-bpath.h"
175
#include "display/sodipodi-ctrl.h"
176
#include <glibmm/i18n.h>
177
#include <glibmm/stringutils.h>
180
#include "sp-conn-end.h"
181
#include "sp-conn-end-pair.h"
182
#include "conn-avoid-ref.h"
183
#include "libavoid/vertices.h"
184
#include "libavoid/router.h"
185
#include "context-fns.h"
186
#include "sp-namedview.h"
188
#include "sp-flowtext.h"
189
#include "display/curve.h"
191
static void sp_connector_context_class_init(SPConnectorContextClass *klass);
192
static void sp_connector_context_init(SPConnectorContext *conn_context);
193
static void sp_connector_context_dispose(GObject *object);
195
static void sp_connector_context_setup(SPEventContext *ec);
196
static void sp_connector_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val);
197
static void sp_connector_context_finish(SPEventContext *ec);
198
static gint sp_connector_context_root_handler(SPEventContext *ec, GdkEvent *event);
199
static gint sp_connector_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
201
// Stuff borrowed from DrawContext
202
static void spcc_connector_set_initial_point(SPConnectorContext *cc, Geom::Point const p);
203
static void spcc_connector_set_subsequent_point(SPConnectorContext *cc, Geom::Point const p);
204
static void spcc_connector_finish_segment(SPConnectorContext *cc, Geom::Point p);
205
static void spcc_reset_colors(SPConnectorContext *cc);
206
static void spcc_connector_finish(SPConnectorContext *cc);
207
static void spcc_concat_colors_and_flush(SPConnectorContext *cc);
208
static void spcc_flush_white(SPConnectorContext *cc, SPCurve *gc);
210
// Context event handlers
211
static gint connector_handle_button_press(SPConnectorContext *const cc, GdkEventButton const &bevent);
212
static gint connector_handle_motion_notify(SPConnectorContext *const cc, GdkEventMotion const &mevent);
213
static gint connector_handle_button_release(SPConnectorContext *const cc, GdkEventButton const &revent);
214
static gint connector_handle_key_press(SPConnectorContext *const cc, guint const keyval);
216
static void cc_active_shape_add_knot(SPDesktop* desktop, SPItem* item, ConnectionPointMap &cphandles, ConnectionPoint& cp);
217
static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item);
218
static void cc_clear_active_shape(SPConnectorContext *cc);
219
static void cc_set_active_conn(SPConnectorContext *cc, SPItem *item);
220
static void cc_clear_active_conn(SPConnectorContext *cc);
221
static bool conn_pt_handle_test(SPConnectorContext *cc, Geom::Point& p, gchar **href, gchar **cpid);
222
static void cc_select_handle(SPKnot* knot);
223
static void cc_deselect_handle(SPKnot* knot);
224
static bool cc_item_is_shape(SPItem *item);
225
static void cc_selection_changed(Inkscape::Selection *selection, gpointer data);
226
static void cc_connector_rerouting_finish(SPConnectorContext *const cc,
227
Geom::Point *const p);
229
static void shape_event_attr_deleted(Inkscape::XML::Node *repr,
230
Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data);
231
static void shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
232
gchar const *old_value, gchar const *new_value, bool is_interactive,
236
static char* cc_knot_tips[] = { _("<b>Connection point</b>: click or drag to create a new connector"),
237
_("<b>Connection point</b>: click to select, drag to move") };
239
/*static Geom::Point connector_drag_origin_w(0, 0);
240
static bool connector_within_tolerance = false;*/
241
static SPEventContextClass *parent_class;
244
static Inkscape::XML::NodeEventVector shape_repr_events = {
245
NULL, /* child_added */
246
NULL, /* child_added */
247
shape_event_attr_changed,
248
NULL, /* content_changed */
249
NULL /* order_changed */
252
static Inkscape::XML::NodeEventVector layer_repr_events = {
253
NULL, /* child_added */
254
shape_event_attr_deleted,
255
NULL, /* child_added */
256
NULL, /* content_changed */
257
NULL /* order_changed */
262
sp_connector_context_get_type(void)
264
static GType type = 0;
267
sizeof(SPConnectorContextClass),
269
(GClassInitFunc) sp_connector_context_class_init,
271
sizeof(SPConnectorContext),
273
(GInstanceInitFunc) sp_connector_context_init,
274
NULL, /* value_table */
276
type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPConnectorContext", &info, (GTypeFlags)0);
282
sp_connector_context_class_init(SPConnectorContextClass *klass)
284
GObjectClass *object_class;
285
SPEventContextClass *event_context_class;
287
object_class = (GObjectClass *) klass;
288
event_context_class = (SPEventContextClass *) klass;
290
parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
292
object_class->dispose = sp_connector_context_dispose;
294
event_context_class->setup = sp_connector_context_setup;
295
event_context_class->set = sp_connector_context_set;
296
event_context_class->finish = sp_connector_context_finish;
297
event_context_class->root_handler = sp_connector_context_root_handler;
298
event_context_class->item_handler = sp_connector_context_item_handler;
303
sp_connector_context_init(SPConnectorContext *cc)
305
SPEventContext *ec = SP_EVENT_CONTEXT(cc);
307
ec->cursor_shape = cursor_connector_xpm;
313
cc->mode = SP_CONNECTOR_CONTEXT_DRAWING_MODE;
316
cc->red_color = 0xff00007f;
319
cc->newConnRef = NULL;
322
cc->sel_changed_connection = sigc::connection();
324
cc->active_shape = NULL;
325
cc->active_shape_repr = NULL;
326
cc->active_shape_layer_repr = NULL;
328
cc->active_conn = NULL;
329
cc->active_conn_repr = NULL;
331
cc->active_handle = NULL;
333
cc->selected_handle = NULL;
335
cc->clickeditem = NULL;
336
cc->clickedhandle = NULL;
338
new (&cc->connpthandles) ConnectionPointMap();
340
for (int i = 0; i < 2; ++i) {
341
cc->endpt_handle[i] = NULL;
342
cc->endpt_handler_id[i] = 0;
349
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
354
sp_connector_context_dispose(GObject *object)
356
SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(object);
358
cc->sel_changed_connection.disconnect();
360
if (!cc->connpthandles.empty()) {
361
for (ConnectionPointMap::iterator it = cc->connpthandles.begin();
362
it != cc->connpthandles.end(); ++it) {
363
g_object_unref(it->first);
365
cc->connpthandles.clear();
367
cc->connpthandles.~ConnectionPointMap();
368
for (int i = 0; i < 2; ++i) {
369
if (cc->endpt_handle[1]) {
370
g_object_unref(cc->endpt_handle[i]);
371
cc->endpt_handle[i] = NULL;
390
g_assert( cc->newConnRef == NULL );
392
G_OBJECT_CLASS(parent_class)->dispose(object);
397
sp_connector_context_setup(SPEventContext *ec)
399
SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec);
400
SPDesktop *dt = ec->desktop;
402
if (((SPEventContextClass *) parent_class)->setup) {
403
((SPEventContextClass *) parent_class)->setup(ec);
406
cc->selection = sp_desktop_selection(dt);
408
cc->sel_changed_connection.disconnect();
409
cc->sel_changed_connection = cc->selection->connectChanged(
410
sigc::bind(sigc::ptr_fun(&cc_selection_changed),
413
/* Create red bpath */
414
cc->red_bpath = sp_canvas_bpath_new(sp_desktop_sketch(ec->desktop), NULL);
415
sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cc->red_bpath), cc->red_color,
416
1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
417
sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cc->red_bpath), 0x00000000,
418
SP_WIND_RULE_NONZERO);
419
/* Create red curve */
420
cc->red_curve = new SPCurve();
422
/* Create green curve */
423
cc->green_curve = new SPCurve();
425
// Notice the initial selection.
426
cc_selection_changed(cc->selection, (gpointer) cc);
428
cc->within_tolerance = false;
430
sp_event_context_read(ec, "curvature");
431
sp_event_context_read(ec, "orthogonal");
432
sp_event_context_read(ec, "mode");
433
cc->knot_tip = cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE ? cc_knot_tips[0] : cc_knot_tips[1];
434
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
435
if (prefs->getBool("/tools/connector/selcue", 0)) {
436
ec->enableSelectionCue();
439
// Make sure we see all enter events for canvas items,
440
// even if a mouse button is depressed.
441
dt->canvas->gen_all_enter_events = true;
446
sp_connector_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val)
448
SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec);
450
/* fixme: Proper error handling for non-numeric data. Use a locale-independent function like
451
* g_ascii_strtod (or a thin wrapper that does the right thing for invalid values inf/nan). */
452
Glib::ustring name = val->getEntryName();
453
if ( name == "curvature" ) {
454
cc->curvature = val->getDoubleLimited(); // prevents NaN and +/-Inf from messing up
456
else if ( name == "orthogonal" ) {
457
cc->isOrthogonal = val->getBool();
459
else if ( name == "mode")
461
sp_connector_context_switch_mode(ec, val->getBool() ? SP_CONNECTOR_CONTEXT_EDITING_MODE : SP_CONNECTOR_CONTEXT_DRAWING_MODE);
465
void sp_connector_context_switch_mode(SPEventContext* ec, unsigned int newMode)
467
SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec);
470
if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE )
472
ec->cursor_shape = cursor_connector_xpm;
473
cc->knot_tip = cc_knot_tips[0];
474
if (cc->selected_handle)
475
cc_deselect_handle( cc->selected_handle );
476
cc->selected_handle = NULL;
477
// Show all default connection points
480
else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE )
482
ec->cursor_shape = cursor_node_xpm;
483
cc->knot_tip = cc_knot_tips[1];
484
/* if (cc->active_shape)
486
cc->selection->set( SP_OBJECT( cc->active_shape ) );
490
SPItem* item = cc->selection->singleItem();
493
cc_set_active_shape(cc, item);
494
cc->selection->set( SP_OBJECT( item ) );
498
sp_event_context_update_cursor(ec);
504
sp_connector_context_finish(SPEventContext *ec)
506
SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec);
508
spcc_connector_finish(cc);
509
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
511
if (((SPEventContextClass *) parent_class)->finish) {
512
((SPEventContextClass *) parent_class)->finish(ec);
516
cc->selection = NULL;
518
cc_clear_active_shape(cc);
519
cc_clear_active_conn(cc);
521
// Restore the default event generating behaviour.
522
SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(ec);
523
desktop->canvas->gen_all_enter_events = false;
527
//-----------------------------------------------------------------------------
531
cc_clear_active_shape(SPConnectorContext *cc)
533
if (cc->active_shape == NULL) {
536
g_assert( cc->active_shape_repr );
537
g_assert( cc->active_shape_layer_repr );
539
cc->active_shape = NULL;
541
if (cc->active_shape_repr) {
542
sp_repr_remove_listener_by_data(cc->active_shape_repr, cc);
543
Inkscape::GC::release(cc->active_shape_repr);
544
cc->active_shape_repr = NULL;
546
sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc);
547
Inkscape::GC::release(cc->active_shape_layer_repr);
548
cc->active_shape_layer_repr = NULL;
551
// Hide the connection points if they exist.
552
if (cc->connpthandles.size()) {
553
for (ConnectionPointMap::iterator it = cc->connpthandles.begin();
554
it != cc->connpthandles.end(); ++it) {
555
sp_knot_hide(it->first);
562
cc_clear_active_conn(SPConnectorContext *cc)
564
if (cc->active_conn == NULL) {
567
g_assert( cc->active_conn_repr );
569
cc->active_conn = NULL;
571
if (cc->active_conn_repr) {
572
sp_repr_remove_listener_by_data(cc->active_conn_repr, cc);
573
Inkscape::GC::release(cc->active_conn_repr);
574
cc->active_conn_repr = NULL;
577
// Hide the endpoint handles.
578
for (int i = 0; i < 2; ++i) {
579
if (cc->endpt_handle[i]) {
580
sp_knot_hide(cc->endpt_handle[i]);
587
conn_pt_handle_test(SPConnectorContext *cc, Geom::Point& p, gchar **href, gchar **cpid)
589
// TODO: this will need to change when there are more connection
590
// points available for each shape.
592
if (cc->active_handle && (cc->connpthandles.find(cc->active_handle) != cc->connpthandles.end()))
594
p = cc->active_handle->pos;
595
const ConnectionPoint& cp = cc->connpthandles[cc->active_handle];
596
*href = g_strdup_printf("#%s", cc->active_shape->getId());
597
*cpid = g_strdup_printf("%c%d", cp.type == ConnPointDefault ? 'd' : 'u' , cp.id);
606
cc_select_handle(SPKnot* knot)
608
knot->setShape(SP_KNOT_SHAPE_SQUARE);
610
knot->setAnchor(GTK_ANCHOR_CENTER);
611
knot->setFill(0x0000ffff, 0x0000ffff, 0x0000ffff);
612
sp_knot_update_ctrl(knot);
616
cc_deselect_handle(SPKnot* knot)
618
knot->setShape(SP_KNOT_SHAPE_SQUARE);
620
knot->setAnchor(GTK_ANCHOR_CENTER);
621
knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff);
622
sp_knot_update_ctrl(knot);
626
sp_connector_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
630
SPDesktop *desktop = event_context->desktop;
632
SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(event_context);
634
Geom::Point p(event->button.x, event->button.y);
636
switch (event->type) {
637
case GDK_BUTTON_RELEASE:
638
if (event->button.button == 1 && !event_context->space_panning) {
639
if ((cc->state == SP_CONNECTOR_CONTEXT_DRAGGING) &&
640
(event_context->within_tolerance))
642
spcc_reset_colors(cc);
643
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
645
if (cc->state != SP_CONNECTOR_CONTEXT_IDLE) {
646
// Doing something else like rerouting.
649
// find out clicked item, honoring Alt
650
SPItem *item = sp_event_context_find_item(desktop,
651
p, event->button.state & GDK_MOD1_MASK, FALSE);
653
if (event->button.state & GDK_SHIFT_MASK) {
654
cc->selection->toggle(item);
656
cc->selection->set(item);
657
if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE && cc->selected_handle )
659
cc_deselect_handle( cc->selected_handle );
660
cc->selected_handle = NULL;
662
/* When selecting a new item,
663
do not allow showing connection points
664
on connectors. (yet?)
666
if ( item != cc->active_shape && !cc_item_is_connector( item ) )
667
cc_set_active_shape( cc, item );
673
case GDK_ENTER_NOTIFY:
675
if (cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE || (cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE && !cc->selected_handle))
677
if (cc_item_is_shape(item)) {
679
// I don't really understand what the above does,
680
// so I commented it.
681
// This is a shape, so show connection point(s).
682
/* if (!(cc->active_shape)
683
// Don't show handle for another handle.
684
// || (cc->connpthandles.find((SPKnot*) item) != cc->connpthandles.end())
687
cc_set_active_shape(cc, item);
689
cc_set_active_shape(cc, item);
704
sp_connector_context_root_handler(SPEventContext *ec, GdkEvent *event)
706
SPConnectorContext *const cc = SP_CONNECTOR_CONTEXT(ec);
710
switch (event->type) {
711
case GDK_BUTTON_PRESS:
712
ret = connector_handle_button_press(cc, event->button);
715
case GDK_MOTION_NOTIFY:
716
ret = connector_handle_motion_notify(cc, event->motion);
719
case GDK_BUTTON_RELEASE:
720
ret = connector_handle_button_release(cc, event->button);
723
ret = connector_handle_key_press(cc, get_group0_keyval (&event->key));
731
gint (*const parent_root_handler)(SPEventContext *, GdkEvent *)
732
= ((SPEventContextClass *) parent_class)->root_handler;
733
if (parent_root_handler) {
734
ret = parent_root_handler(ec, event);
743
connector_handle_button_press(SPConnectorContext *const cc, GdkEventButton const &bevent)
745
Geom::Point const event_w(bevent.x, bevent.y);
746
/* Find desktop coordinates */
747
Geom::Point p = cc->desktop->w2d(event_w);
748
SPEventContext *event_context = SP_EVENT_CONTEXT(cc);
751
if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE )
753
if ( bevent.button == 1 && !event_context->space_panning ) {
755
SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
757
if (Inkscape::have_viable_layer(desktop, cc->_message_context) == false) {
761
Geom::Point const event_w(bevent.x,
763
// connector_drag_origin_w = event_w;
766
cc->within_tolerance = true;
768
Geom::Point const event_dt = cc->desktop->w2d(event_w);
770
SnapManager &m = cc->desktop->namedview->snap_manager;
771
m.setup(cc->desktop);
774
case SP_CONNECTOR_CONTEXT_STOP:
775
/* This is allowed, if we just canceled curve */
776
case SP_CONNECTOR_CONTEXT_IDLE:
778
if ( cc->npoints == 0 ) {
779
cc_clear_active_conn(cc);
781
SP_EVENT_CONTEXT_DESKTOP(cc)->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new connector"));
783
/* Set start anchor */
784
/* Create green anchor */
785
Geom::Point p = event_dt;
787
// Test whether we clicked on a connection point
788
bool found = conn_pt_handle_test(cc, p, &cc->shref, &cc->scpid);
791
// This is the first point, so just snap it to the grid
792
// as there's no other points to go off.
793
m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
795
spcc_connector_set_initial_point(cc, p);
798
cc->state = SP_CONNECTOR_CONTEXT_DRAGGING;
802
case SP_CONNECTOR_CONTEXT_DRAGGING:
804
// This is the second click of a connector creation.
805
m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
807
spcc_connector_set_subsequent_point(cc, p);
808
spcc_connector_finish_segment(cc, p);
809
// Test whether we clicked on a connection point
810
/*bool found = */conn_pt_handle_test(cc, p, &cc->ehref, &cc->ecpid);
811
if (cc->npoints != 0) {
812
spcc_connector_finish(cc);
814
cc_set_active_conn(cc, cc->newconn);
815
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
819
case SP_CONNECTOR_CONTEXT_CLOSE:
821
g_warning("Button down in CLOSE state");
827
} else if (bevent.button == 3) {
828
if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) {
829
// A context menu is going to be triggered here,
830
// so end the rerouting operation.
831
cc_connector_rerouting_finish(cc, &p);
833
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
835
// Don't set ret to TRUE, so we drop through to the
836
// parent handler which will open the context menu.
838
else if (cc->npoints != 0) {
839
spcc_connector_finish(cc);
840
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
845
else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE )
847
if ( bevent.button == 1 && !event_context->space_panning )
849
// Initialize variables in case of dragging
851
SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
853
if (Inkscape::have_viable_layer(desktop, cc->_message_context) == false) {
859
cc->within_tolerance = true;
861
ConnectionPointMap::iterator const& active_knot_it = cc->connpthandles.find( cc->active_handle );
865
case SP_CONNECTOR_CONTEXT_IDLE:
866
if ( active_knot_it != cc->connpthandles.end() )
868
// We do not allow selecting and, thereby, moving default knots
869
if ( active_knot_it->second.type != ConnPointDefault)
871
if (cc->selected_handle != cc->active_handle)
873
if ( cc->selected_handle )
874
cc_deselect_handle( cc->selected_handle );
875
cc->selected_handle = cc->active_handle;
876
cc_select_handle( cc->selected_handle );
880
// Just ignore the default connection point
884
if ( cc->selected_handle )
886
cc_deselect_handle( cc->selected_handle );
887
cc->selected_handle = NULL;
890
if ( cc->selected_handle )
892
cc->state = SP_CONNECTOR_CONTEXT_DRAGGING;
893
cc->selection->set( SP_OBJECT( cc->active_shape ) );
898
// Dragging valid because of the way we create
899
// new connection points.
900
case SP_CONNECTOR_CONTEXT_DRAGGING:
912
connector_handle_motion_notify(SPConnectorContext *const cc, GdkEventMotion const &mevent)
915
SPEventContext *event_context = SP_EVENT_CONTEXT(cc);
916
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
918
if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) {
919
// allow middle-button scrolling
923
Geom::Point const event_w(mevent.x, mevent.y);
925
if (cc->within_tolerance) {
926
cc->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
927
if ( ( abs( (gint) mevent.x - cc->xp ) < cc->tolerance ) &&
928
( abs( (gint) mevent.y - cc->yp ) < cc->tolerance ) ) {
929
return FALSE; // Do not drag if we're within tolerance from origin.
932
// Once the user has moved farther than tolerance from the original location
933
// (indicating they intend to move the object, not click), then always process
934
// the motion notify coordinates as given (no snapping back to origin)
935
cc->within_tolerance = false;
937
SPDesktop *const dt = cc->desktop;
939
/* Find desktop coordinates */
940
Geom::Point p = dt->w2d(event_w);
942
if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE )
944
SnapManager &m = dt->namedview->snap_manager;
948
case SP_CONNECTOR_CONTEXT_DRAGGING:
950
gobble_motion_events(mevent.state);
951
// This is movement during a connector creation.
952
if ( cc->npoints > 0 ) {
953
m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
954
cc->selection->clear();
955
spcc_connector_set_subsequent_point(cc, p);
960
case SP_CONNECTOR_CONTEXT_REROUTING:
962
gobble_motion_events(GDK_BUTTON1_MASK);
963
g_assert( SP_IS_PATH(cc->clickeditem));
965
m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
967
// Update the hidden path
968
Geom::Matrix i2d = sp_item_i2d_affine(cc->clickeditem);
969
Geom::Matrix d2i = i2d.inverse();
970
SPPath *path = SP_PATH(cc->clickeditem);
971
SPCurve *curve = path->original_curve ? path->original_curve : path->curve;
972
if (cc->clickedhandle == cc->endpt_handle[0]) {
973
Geom::Point o = cc->endpt_handle[1]->pos;
974
curve->stretch_endpoints(p * d2i, o * d2i);
977
Geom::Point o = cc->endpt_handle[0]->pos;
978
curve->stretch_endpoints(o * d2i, p * d2i);
980
sp_conn_reroute_path_immediate(path);
982
// Copy this to the temporary visible path
983
cc->red_curve = path->original_curve ?
984
path->original_curve->copy() : path->curve->copy();
985
cc->red_curve->transform(i2d);
987
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
991
case SP_CONNECTOR_CONTEXT_STOP:
992
/* This is perfectly valid */
995
if (!sp_event_context_knot_mouseover(cc)) {
996
m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_OTHER_HANDLE));
1001
else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE )
1003
switch ( cc->state )
1005
case SP_CONNECTOR_CONTEXT_DRAGGING:
1006
sp_knot_set_position(cc->selected_handle, p, 0);
1009
case SP_CONNECTOR_CONTEXT_NEWCONNPOINT:
1010
sp_knot_set_position(cc->selected_handle, p, 0);
1021
connector_handle_button_release(SPConnectorContext *const cc, GdkEventButton const &revent)
1024
SPEventContext *event_context = SP_EVENT_CONTEXT(cc);
1025
if ( revent.button == 1 && !event_context->space_panning ) {
1027
SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1028
SPDocument *doc = sp_desktop_document(desktop);
1030
SnapManager &m = desktop->namedview->snap_manager;
1033
Geom::Point const event_w(revent.x, revent.y);
1035
/* Find desktop coordinates */
1036
Geom::Point p = cc->desktop->w2d(event_w);
1037
if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE )
1039
switch (cc->state) {
1040
//case SP_CONNECTOR_CONTEXT_POINT:
1041
case SP_CONNECTOR_CONTEXT_DRAGGING:
1043
m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
1045
if (cc->within_tolerance)
1047
spcc_connector_finish_segment(cc, p);
1050
// Connector has been created via a drag, end it now.
1051
spcc_connector_set_subsequent_point(cc, p);
1052
spcc_connector_finish_segment(cc, p);
1053
// Test whether we clicked on a connection point
1054
/*bool found = */conn_pt_handle_test(cc, p, &cc->ehref, &cc->ecpid);
1055
if (cc->npoints != 0) {
1056
spcc_connector_finish(cc);
1058
cc_set_active_conn(cc, cc->newconn);
1059
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1062
case SP_CONNECTOR_CONTEXT_REROUTING:
1064
m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
1065
cc_connector_rerouting_finish(cc, &p);
1067
sp_document_ensure_up_to_date(doc);
1068
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1072
case SP_CONNECTOR_CONTEXT_STOP:
1073
/* This is allowed, if we just cancelled curve */
1080
else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE )
1082
switch ( cc->state )
1084
case SP_CONNECTOR_CONTEXT_DRAGGING:
1086
if (!cc->within_tolerance)
1088
m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
1089
sp_knot_set_position(cc->selected_handle, p, 0);
1090
ConnectionPoint& cp = cc->connpthandles[cc->selected_handle];
1091
cp.pos = p * sp_item_dt2i_affine(cc->active_shape);
1092
cc->active_shape->avoidRef->updateConnectionPoint(cp);
1095
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1100
case SP_CONNECTOR_CONTEXT_NEWCONNPOINT:
1101
m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
1103
sp_knot_set_position(cc->selected_handle, p, 0);
1106
cp.type = ConnPointUserDefined;
1107
cp.pos = p * sp_item_dt2i_affine(cc->active_shape);
1108
cp.dir = Avoid::ConnDirAll;
1109
g_object_unref(cc->selected_handle);
1110
cc->active_shape->avoidRef->addConnectionPoint(cp);
1111
sp_document_ensure_up_to_date(doc);
1112
for (ConnectionPointMap::iterator it = cc->connpthandles.begin(); it != cc->connpthandles.end(); ++it)
1113
if (it->second.type == ConnPointUserDefined && it->second.id == cp.id)
1115
cc->selected_handle = it->first;
1118
cc_select_handle( cc->selected_handle );
1119
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1132
connector_handle_key_press(SPConnectorContext *const cc, guint const keyval)
1136
if ( cc->mode == SP_CONNECTOR_CONTEXT_DRAWING_MODE )
1141
if (cc->npoints != 0) {
1142
spcc_connector_finish(cc);
1143
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1148
if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) {
1150
SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1151
SPDocument *doc = sp_desktop_document(desktop);
1153
cc_connector_rerouting_finish(cc, NULL);
1155
sp_document_undo(doc);
1157
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1158
desktop->messageStack()->flash( Inkscape::NORMAL_MESSAGE,
1159
_("Connector endpoint drag cancelled."));
1162
else if (cc->npoints != 0) {
1163
// if drawing, cancel, otherwise pass it up for deselecting
1164
cc->state = SP_CONNECTOR_CONTEXT_STOP;
1165
spcc_reset_colors(cc);
1173
else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE )
1175
switch ( cc->state )
1177
case SP_CONNECTOR_CONTEXT_DRAGGING:
1178
if ( keyval == GDK_Escape )
1180
// Cancel connection point dragging
1182
// Obtain original position
1183
ConnectionPoint const& cp = cc->connpthandles[cc->selected_handle];
1184
SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1185
const Geom::Matrix& i2doc = sp_item_i2doc_affine(cc->active_shape);
1186
sp_knot_set_position(cc->selected_handle, cp.pos * i2doc * desktop->doc2dt(), 0);
1187
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1188
desktop->messageStack()->flash( Inkscape::NORMAL_MESSAGE,
1189
_("Connection point drag cancelled."));
1192
else if ( keyval == GDK_Return || keyval == GDK_KP_Enter )
1194
// Put connection point at current position
1196
SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1197
SnapManager &m = desktop->namedview->snap_manager;
1199
Geom::Point p = cc->selected_handle->pos;
1200
// SPEventContext* event_context = SP_EVENT_CONTEXT( cc );
1202
if (!cc->within_tolerance)
1204
m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
1205
sp_knot_set_position(cc->selected_handle, p, 0);
1206
ConnectionPoint& cp = cc->connpthandles[cc->selected_handle];
1207
cp.pos = p * sp_item_dt2i_affine(cc->active_shape);
1208
cc->active_shape->avoidRef->updateConnectionPoint(cp);
1211
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1215
case SP_CONNECTOR_CONTEXT_NEWCONNPOINT:
1216
if ( keyval == GDK_Escape )
1218
// Just destroy the knot
1219
g_object_unref( cc->selected_handle );
1220
cc->selected_handle = NULL;
1221
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1224
else if ( keyval == GDK_Return || keyval == GDK_KP_Enter )
1226
SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1227
SPDocument *doc = sp_desktop_document(desktop);
1228
SnapManager &m = desktop->namedview->snap_manager;
1230
Geom::Point p = cc->selected_handle->pos;
1232
m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
1234
sp_knot_set_position(cc->selected_handle, p, 0);
1237
cp.type = ConnPointUserDefined;
1238
cp.pos = p * sp_item_dt2i_affine(cc->active_shape);
1239
cp.dir = Avoid::ConnDirAll;
1240
g_object_unref(cc->selected_handle);
1241
cc->active_shape->avoidRef->addConnectionPoint(cp);
1242
sp_document_ensure_up_to_date(doc);
1243
for (ConnectionPointMap::iterator it = cc->connpthandles.begin(); it != cc->connpthandles.end(); ++it)
1244
if (it->second.type == ConnPointUserDefined && it->second.id == cp.id)
1246
cc->selected_handle = it->first;
1249
cc_select_handle( cc->selected_handle );
1250
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
1255
case SP_CONNECTOR_CONTEXT_IDLE:
1256
if ( keyval == GDK_Delete && cc->selected_handle )
1258
cc->active_shape->avoidRef->deleteConnectionPoint(cc->connpthandles[cc->selected_handle]);
1259
cc->selected_handle = NULL;
1272
cc_connector_rerouting_finish(SPConnectorContext *const cc, Geom::Point *const p)
1274
SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1275
SPDocument *doc = sp_desktop_document(desktop);
1277
// Clear the temporary path:
1278
cc->red_curve->reset();
1279
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
1283
// Test whether we clicked on a connection point
1284
gchar *shape_label, *cpid;
1285
bool found = conn_pt_handle_test(cc, *p, &shape_label, &cpid);
1288
if (cc->clickedhandle == cc->endpt_handle[0]) {
1289
sp_object_setAttribute(cc->clickeditem,
1290
"inkscape:connection-start", shape_label, false);
1291
sp_object_setAttribute(cc->clickeditem,
1292
"inkscape:connection-start-point", cpid, false);
1295
sp_object_setAttribute(cc->clickeditem,
1296
"inkscape:connection-end", shape_label, false);
1297
sp_object_setAttribute(cc->clickeditem,
1298
"inkscape:connection-end-point", cpid, false);
1300
g_free(shape_label);
1303
cc->clickeditem->setHidden(false);
1304
sp_conn_reroute_path_immediate(SP_PATH(cc->clickeditem));
1305
cc->clickeditem->updateRepr();
1306
sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR,
1307
_("Reroute connector"));
1308
cc_set_active_conn(cc, cc->clickeditem);
1313
spcc_reset_colors(SPConnectorContext *cc)
1316
cc->red_curve->reset();
1317
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
1319
cc->green_curve->reset();
1325
spcc_connector_set_initial_point(SPConnectorContext *const cc, Geom::Point const p)
1327
g_assert( cc->npoints == 0 );
1332
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
1337
spcc_connector_set_subsequent_point(SPConnectorContext *const cc, Geom::Point const p)
1339
g_assert( cc->npoints != 0 );
1341
SPDesktop *dt = cc->desktop;
1342
Geom::Point o = dt->dt2doc(cc->p[0]);
1343
Geom::Point d = dt->dt2doc(p);
1344
Avoid::Point src(o[Geom::X], o[Geom::Y]);
1345
Avoid::Point dst(d[Geom::X], d[Geom::Y]);
1347
if (!cc->newConnRef) {
1348
Avoid::Router *router = sp_desktop_document(dt)->router;
1349
cc->newConnRef = new Avoid::ConnRef(router);
1350
cc->newConnRef->setEndpoint(Avoid::VertID::src, src);
1351
if (cc->isOrthogonal)
1352
cc->newConnRef->setRoutingType(Avoid::ConnType_Orthogonal);
1354
cc->newConnRef->setRoutingType(Avoid::ConnType_PolyLine);
1356
// Set new endpoint.
1357
cc->newConnRef->setEndpoint(Avoid::VertID::tar, dst);
1358
// Immediately generate new routes for connector.
1359
cc->newConnRef->makePathInvalid();
1360
cc->newConnRef->router()->processTransaction();
1361
// Recreate curve from libavoid route.
1362
recreateCurve( cc->red_curve, cc->newConnRef, cc->curvature );
1363
cc->red_curve->transform(dt->doc2dt());
1364
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
1369
* Concats red, blue and green.
1370
* If any anchors are defined, process these, optionally removing curves from white list
1371
* Invoke _flush_white to write result back to object.
1374
spcc_concat_colors_and_flush(SPConnectorContext *cc)
1376
SPCurve *c = cc->green_curve;
1377
cc->green_curve = new SPCurve();
1379
cc->red_curve->reset();
1380
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL);
1382
if (c->is_empty()) {
1387
spcc_flush_white(cc, c);
1394
* Flushes white curve(s) and additional curve into object
1396
* No cleaning of colored curves - this has to be done by caller
1397
* No rereading of white data, so if you cannot rely on ::modified, do it in caller
1402
spcc_flush_white(SPConnectorContext *cc, SPCurve *gc)
1413
/* Now we have to go back to item coordinates at last */
1414
c->transform(SP_EVENT_CONTEXT_DESKTOP(cc)->dt2doc());
1416
SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1417
SPDocument *doc = sp_desktop_document(desktop);
1418
Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1420
if ( c && !c->is_empty() ) {
1421
/* We actually have something to write */
1423
Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
1425
sp_desktop_apply_style_tool(desktop, repr, "/tools/connector", false);
1427
gchar *str = sp_svg_write_path( c->get_pathvector() );
1428
g_assert( str != NULL );
1429
repr->setAttribute("d", str);
1433
cc->newconn = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
1434
cc->newconn->transform = sp_item_i2doc_affine(SP_ITEM(desktop->currentLayer())).inverse();
1436
bool connection = false;
1437
sp_object_setAttribute(cc->newconn, "inkscape:connector-type",
1438
cc->isOrthogonal ? "orthogonal" : "polyline", false);
1439
sp_object_setAttribute(cc->newconn, "inkscape:connector-curvature",
1440
Glib::Ascii::dtostr(cc->curvature).c_str(), false);
1443
sp_object_setAttribute(cc->newconn, "inkscape:connection-start",
1446
sp_object_setAttribute(cc->newconn, "inkscape:connection-start-point",
1453
sp_object_setAttribute(cc->newconn, "inkscape:connection-end",
1456
sp_object_setAttribute(cc->newconn, "inkscape:connection-end-point",
1460
// Process pending updates.
1461
cc->newconn->updateRepr();
1462
sp_document_ensure_up_to_date(doc);
1465
// Adjust endpoints to shape edge.
1466
sp_conn_reroute_path_immediate(SP_PATH(cc->newconn));
1467
cc->newconn->updateRepr();
1470
// Only set the selection after we are finished with creating the attributes of
1471
// the connector. Otherwise, the selection change may alter the defaults for
1472
// values like curvature in the connector context, preventing subsequent lookup
1473
// of their original values.
1474
cc->selection->set(repr);
1475
Inkscape::GC::release(repr);
1480
sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR, _("Create connector"));
1485
spcc_connector_finish_segment(SPConnectorContext *const cc, Geom::Point const /*p*/)
1487
if (!cc->red_curve->is_empty()) {
1488
cc->green_curve->append_continuous(cc->red_curve, 0.0625);
1490
cc->p[0] = cc->p[3];
1491
cc->p[1] = cc->p[4];
1494
cc->red_curve->reset();
1500
spcc_connector_finish(SPConnectorContext *const cc)
1502
SPDesktop *const desktop = cc->desktop;
1503
desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing connector"));
1505
cc->red_curve->reset();
1506
spcc_concat_colors_and_flush(cc);
1510
if (cc->newConnRef) {
1511
cc->newConnRef->removeFromGraph();
1512
delete cc->newConnRef;
1513
cc->newConnRef = NULL;
1519
cc_generic_knot_handler(SPCanvasItem *, GdkEvent *event, SPKnot *knot)
1521
g_assert (knot != NULL);
1525
SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(
1526
knot->desktop->event_context);
1528
gboolean consumed = FALSE;
1530
gchar* knot_tip = knot->tip ? knot->tip : cc->knot_tip;
1531
switch (event->type) {
1532
case GDK_ENTER_NOTIFY:
1533
sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, TRUE);
1535
cc->active_handle = knot;
1538
knot->desktop->event_context->defaultMessageContext()->set(
1539
Inkscape::NORMAL_MESSAGE, knot_tip);
1544
case GDK_LEAVE_NOTIFY:
1545
sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE);
1547
cc->active_handle = NULL;
1550
knot->desktop->event_context->defaultMessageContext()->clear();
1559
g_object_unref(knot);
1566
endpt_handler(SPKnot */*knot*/, GdkEvent *event, SPConnectorContext *cc)
1568
g_assert( SP_IS_CONNECTOR_CONTEXT(cc) );
1570
gboolean consumed = FALSE;
1572
switch (event->type) {
1573
case GDK_BUTTON_PRESS:
1574
g_assert( (cc->active_handle == cc->endpt_handle[0]) ||
1575
(cc->active_handle == cc->endpt_handle[1]) );
1576
if (cc->state == SP_CONNECTOR_CONTEXT_IDLE) {
1577
cc->clickeditem = cc->active_conn;
1578
cc->clickedhandle = cc->active_handle;
1579
cc_clear_active_conn(cc);
1580
cc->state = SP_CONNECTOR_CONTEXT_REROUTING;
1582
// Disconnect from attached shape
1583
unsigned ind = (cc->active_handle == cc->endpt_handle[0]) ? 0 : 1;
1584
sp_conn_end_detach(cc->clickeditem, ind);
1587
if (cc->clickedhandle == cc->endpt_handle[0]) {
1588
origin = cc->endpt_handle[1]->pos;
1591
origin = cc->endpt_handle[0]->pos;
1594
// Show the red path for dragging.
1595
cc->red_curve = SP_PATH(cc->clickeditem)->original_curve ? SP_PATH(cc->clickeditem)->original_curve->copy() : SP_PATH(cc->clickeditem)->curve->copy();
1596
Geom::Matrix i2d = sp_item_i2d_affine(cc->clickeditem);
1597
cc->red_curve->transform(i2d);
1598
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
1600
cc->clickeditem->setHidden(true);
1602
// The rest of the interaction rerouting the connector is
1603
// handled by the context root handler.
1614
static void cc_active_shape_add_knot(SPDesktop* desktop, SPItem* item, ConnectionPointMap &cphandles, ConnectionPoint& cp)
1616
SPKnot *knot = sp_knot_new(desktop, 0);
1618
knot->setShape(SP_KNOT_SHAPE_SQUARE);
1620
knot->setAnchor(GTK_ANCHOR_CENTER);
1621
knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff);
1622
sp_knot_update_ctrl(knot);
1624
// We don't want to use the standard knot handler.
1625
g_signal_handler_disconnect(G_OBJECT(knot->item),
1626
knot->_event_handler_id);
1627
knot->_event_handler_id = 0;
1629
gtk_signal_connect(GTK_OBJECT(knot->item), "event",
1630
GTK_SIGNAL_FUNC(cc_generic_knot_handler), knot);
1631
sp_knot_set_position(knot, item->avoidRef->getConnectionPointPos(cp.type, cp.id) * desktop->doc2dt(), 0);
1633
cphandles[knot] = cp;
1636
static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item)
1638
g_assert(item != NULL );
1640
std::map<int, ConnectionPoint>* connpts = &item->avoidRef->connection_points;
1642
if (cc->active_shape != item)
1644
// The active shape has changed
1645
// Rebuild everything
1646
cc->active_shape = item;
1647
// Remove existing active shape listeners
1648
if (cc->active_shape_repr) {
1649
sp_repr_remove_listener_by_data(cc->active_shape_repr, cc);
1650
Inkscape::GC::release(cc->active_shape_repr);
1652
sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc);
1653
Inkscape::GC::release(cc->active_shape_layer_repr);
1656
// Listen in case the active shape changes
1657
cc->active_shape_repr = SP_OBJECT_REPR(item);
1658
if (cc->active_shape_repr) {
1659
Inkscape::GC::anchor(cc->active_shape_repr);
1660
sp_repr_add_listener(cc->active_shape_repr, &shape_repr_events, cc);
1662
cc->active_shape_layer_repr = cc->active_shape_repr->parent();
1663
Inkscape::GC::anchor(cc->active_shape_layer_repr);
1664
sp_repr_add_listener(cc->active_shape_layer_repr, &layer_repr_events, cc);
1668
// Set the connection points.
1669
if ( cc->connpthandles.size() )
1670
// destroy the old list
1671
while (! cc->connpthandles.empty() )
1673
g_object_unref(cc->connpthandles.begin()->first);
1674
cc->connpthandles.erase(cc->connpthandles.begin());
1676
// build the new one
1677
if ( connpts->size() )
1678
for (std::map<int, ConnectionPoint>::iterator it = connpts->begin(); it != connpts->end(); ++it)
1679
cc_active_shape_add_knot(cc->desktop, item, cc->connpthandles, it->second);
1681
// Also add default connection points
1682
// For now, only centre default connection point will
1684
ConnectionPoint centre;
1685
centre.type = ConnPointDefault;
1686
centre.id = ConnPointPosCC;
1687
cc_active_shape_add_knot(cc->desktop, item, cc->connpthandles, centre);
1691
// The active shape didn't change
1692
// Update only the connection point knots
1694
// Ensure the item's connection_points map
1696
sp_document_ensure_up_to_date(SP_OBJECT_DOCUMENT(item));
1699
for ( ConnectionPointMap::iterator it = cc->connpthandles.begin(); it != cc->connpthandles.end() ;)
1701
bool removed = false;
1702
if ( it->second.type == ConnPointUserDefined )
1704
std::map<int, ConnectionPoint>::iterator p = connpts->find(it->second.id);
1705
if (p != connpts->end())
1707
if ( it->second != p->second )
1708
// Connection point position has changed
1709
// Update knot position
1710
sp_knot_set_position(it->first,
1711
item->avoidRef->getConnectionPointPos(it->second.type, it->second.id) * cc->desktop->doc2dt(), 0);
1712
seen.insert(it->second.id);
1713
sp_knot_show(it->first);
1717
// This connection point does no longer exist,
1719
ConnectionPointMap::iterator curr = it;
1721
g_object_unref( curr->first );
1722
cc->connpthandles.erase(curr);
1728
// It's a default connection point
1729
// Just make sure it's position is correct
1730
sp_knot_set_position(it->first,
1731
item->avoidRef->getConnectionPointPos(it->second.type, it->second.id) * cc->desktop->doc2dt(), 0);
1732
sp_knot_show(it->first);
1738
// Add knots for new connection points.
1739
if (connpts->size())
1740
for ( std::map<int, ConnectionPoint>::iterator it = connpts->begin(); it != connpts->end(); ++it )
1741
if ( seen.find(it->first) == seen.end() )
1742
// A new connection point has been added
1743
// to the shape. Add a knot for it.
1744
cc_active_shape_add_knot(cc->desktop, item, cc->connpthandles, it->second);
1750
cc_set_active_conn(SPConnectorContext *cc, SPItem *item)
1752
g_assert( SP_IS_PATH(item) );
1754
SPCurve *curve = SP_PATH(item)->original_curve ? SP_PATH(item)->original_curve : SP_PATH(item)->curve;
1755
Geom::Matrix i2d = sp_item_i2d_affine(item);
1757
if (cc->active_conn == item)
1759
if (curve->is_empty())
1761
// Connector is invisible because it is clipped to the boundary of
1762
// two overlpapping shapes.
1763
sp_knot_hide(cc->endpt_handle[0]);
1764
sp_knot_hide(cc->endpt_handle[1]);
1768
// Just adjust handle positions.
1769
Geom::Point startpt = *(curve->first_point()) * i2d;
1770
sp_knot_set_position(cc->endpt_handle[0], startpt, 0);
1772
Geom::Point endpt = *(curve->last_point()) * i2d;
1773
sp_knot_set_position(cc->endpt_handle[1], endpt, 0);
1779
cc->active_conn = item;
1781
// Remove existing active conn listeners
1782
if (cc->active_conn_repr) {
1783
sp_repr_remove_listener_by_data(cc->active_conn_repr, cc);
1784
Inkscape::GC::release(cc->active_conn_repr);
1785
cc->active_conn_repr = NULL;
1788
// Listen in case the active conn changes
1789
cc->active_conn_repr = SP_OBJECT_REPR(item);
1790
if (cc->active_conn_repr) {
1791
Inkscape::GC::anchor(cc->active_conn_repr);
1792
sp_repr_add_listener(cc->active_conn_repr, &shape_repr_events, cc);
1795
for (int i = 0; i < 2; ++i) {
1797
// Create the handle if it doesn't exist
1798
if ( cc->endpt_handle[i] == NULL ) {
1799
SPKnot *knot = sp_knot_new(cc->desktop,
1800
_("<b>Connector endpoint</b>: drag to reroute or connect to new shapes"));
1802
knot->setShape(SP_KNOT_SHAPE_SQUARE);
1804
knot->setAnchor(GTK_ANCHOR_CENTER);
1805
knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff);
1806
knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
1807
sp_knot_update_ctrl(knot);
1809
// We don't want to use the standard knot handler,
1810
// since we don't want this knot to be draggable.
1811
g_signal_handler_disconnect(G_OBJECT(knot->item),
1812
knot->_event_handler_id);
1813
knot->_event_handler_id = 0;
1815
gtk_signal_connect(GTK_OBJECT(knot->item), "event",
1816
GTK_SIGNAL_FUNC(cc_generic_knot_handler), knot);
1818
cc->endpt_handle[i] = knot;
1821
// Remove any existing handlers
1822
if (cc->endpt_handler_id[i]) {
1823
g_signal_handlers_disconnect_by_func(
1824
G_OBJECT(cc->endpt_handle[i]->item),
1825
(void*)G_CALLBACK(endpt_handler), (gpointer) cc );
1826
cc->endpt_handler_id[i] = 0;
1829
// Setup handlers for connector endpoints, this is
1830
// is as 'after' so that cc_generic_knot_handler is
1831
// triggered first for any endpoint.
1832
cc->endpt_handler_id[i] = g_signal_connect_after(
1833
G_OBJECT(cc->endpt_handle[i]->item), "event",
1834
G_CALLBACK(endpt_handler), cc);
1837
if (curve->is_empty())
1839
// Connector is invisible because it is clipped to the boundary
1840
// of two overlpapping shapes. So, it doesn't need endpoints.
1844
Geom::Point startpt = *(curve->first_point()) * i2d;
1845
sp_knot_set_position(cc->endpt_handle[0], startpt, 0);
1847
Geom::Point endpt = *(curve->last_point()) * i2d;
1848
sp_knot_set_position(cc->endpt_handle[1], endpt, 0);
1850
sp_knot_show(cc->endpt_handle[0]);
1851
sp_knot_show(cc->endpt_handle[1]);
1854
void cc_create_connection_point(SPConnectorContext* cc)
1856
if (cc->active_shape && cc->state == SP_CONNECTOR_CONTEXT_IDLE)
1858
if (cc->selected_handle)
1860
cc_deselect_handle( cc->selected_handle );
1862
SPKnot *knot = sp_knot_new(cc->desktop, 0);
1863
// We do not process events on this knot.
1864
g_signal_handler_disconnect(G_OBJECT(knot->item),
1865
knot->_event_handler_id);
1866
knot->_event_handler_id = 0;
1868
cc_select_handle( knot );
1869
cc->selected_handle = knot;
1870
sp_knot_show(cc->selected_handle);
1871
cc->state = SP_CONNECTOR_CONTEXT_NEWCONNPOINT;
1875
void cc_remove_connection_point(SPConnectorContext* cc)
1877
if (cc->selected_handle && cc->state == SP_CONNECTOR_CONTEXT_IDLE )
1879
cc->active_shape->avoidRef->deleteConnectionPoint(cc->connpthandles[cc->selected_handle]);
1880
cc->selected_handle = NULL;
1884
static bool cc_item_is_shape(SPItem *item)
1886
if (SP_IS_PATH(item)) {
1887
SPCurve *curve = (SP_SHAPE(item))->curve;
1888
if ( curve && !(curve->is_closed()) ) {
1889
// Open paths are connectors.
1893
else if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1894
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1895
if (prefs->getBool("/tools/connector/ignoretext", true)) {
1896
// Don't count text as a shape we can connect connector to.
1904
bool cc_item_is_connector(SPItem *item)
1906
if (SP_IS_PATH(item)) {
1907
bool closed = SP_PATH(item)->original_curve ? SP_PATH(item)->original_curve->is_closed() : SP_PATH(item)->curve->is_closed();
1908
if (SP_PATH(item)->connEndPair.isAutoRoutingConn() && !closed) {
1909
// To be considered a connector, an object must be a non-closed
1910
// path that is marked with a "inkscape:connector-type" attribute.
1918
void cc_selection_set_avoid(bool const set_avoid)
1920
SPDesktop *desktop = inkscape_active_desktop();
1921
if (desktop == NULL) {
1925
SPDocument *document = sp_desktop_document(desktop);
1927
Inkscape::Selection *selection = sp_desktop_selection(desktop);
1929
GSList *l = (GSList *) selection->itemList();
1934
SPItem *item = (SPItem *) l->data;
1936
char const *value = (set_avoid) ? "true" : NULL;
1938
if (cc_item_is_shape(item)) {
1939
sp_object_setAttribute(item, "inkscape:connector-avoid",
1941
item->avoidRef->handleSettingChange();
1949
desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE,
1950
_("Select <b>at least one non-connector object</b>."));
1954
char *event_desc = (set_avoid) ?
1955
_("Make connectors avoid selected objects") :
1956
_("Make connectors ignore selected objects");
1957
sp_document_done(document, SP_VERB_CONTEXT_CONNECTOR, event_desc);
1962
cc_selection_changed(Inkscape::Selection *selection, gpointer data)
1964
SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data);
1965
//SPEventContext *ec = SP_EVENT_CONTEXT(cc);
1967
SPItem *item = selection->singleItem();
1969
if (cc->active_conn == item)
1971
// Nothing to change.
1976
cc_clear_active_conn(cc);
1980
if (cc_item_is_connector(item)) {
1981
cc_set_active_conn(cc, item);
1987
shape_event_attr_deleted(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child,
1988
Inkscape::XML::Node */*ref*/, gpointer data)
1991
SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data);
1993
if (child == cc->active_shape_repr) {
1994
// The active shape has been deleted. Clear active shape.
1995
cc_clear_active_shape(cc);
2001
shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
2002
gchar const */*old_value*/, gchar const */*new_value*/,
2003
bool /*is_interactive*/, gpointer data)
2006
SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(data);
2008
// Look for changes that result in onscreen movement.
2009
if (!strcmp(name, "d") || !strcmp(name, "x") || !strcmp(name, "y") ||
2010
!strcmp(name, "width") || !strcmp(name, "height") ||
2011
!strcmp(name, "transform"))
2013
if (repr == cc->active_shape_repr) {
2014
// Active shape has moved. Clear active shape.
2015
cc_clear_active_shape(cc);
2017
else if (repr == cc->active_conn_repr) {
2018
// The active conn has been moved.
2019
// Set it again, which just sets new handle positions.
2020
cc_set_active_conn(cc, cc->active_conn);
2024
if ( !strcmp(name, "inkscape:connection-points") )
2025
if (repr == cc->active_shape_repr)
2026
// The connection points of the active shape
2027
// have changed. Update them.
2028
cc_set_active_shape(cc, cc->active_shape);
2035
c-file-style:"stroustrup"
2036
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2037
indent-tabs-mode:nil
2041
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :