5
5
* Michael Wybrow <mjwybrow@users.sourceforge.net>
7
* Copyright (C) 2005 Michael Wybrow
7
* Copyright (C) 2005-2008 Michael Wybrow
8
* Copyright (C) 2009 Monash University
9
10
* 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
13
* o Show a visual indicator for objects with the 'avoid' property set.
20
14
* o Allow user to change a object between a path and connector through
22
16
* o Create an interface for setting markers (arrow heads).
23
17
* o Better distinguish between paths and connectors to prevent problems
24
* in the node tool and paths accidently being turned into connectors
18
* in the node tool and paths accidentally being turned into connectors
25
19
* in the connector tool. Perhaps have a way to convert between.
26
20
* o Only call libavoid's updateEndPoint as required. Currently we do it
27
21
* for both endpoints, even if only one is moving.
28
22
* o Allow user-placeable connection points.
29
23
* o Deal sanely with connectors with both endpoints attached to the
30
24
* same connection point, and drawing of connectors attaching
31
* overlaping shapes (currently tries to adjust connector to be
25
* overlapping shapes (currently tries to adjust connector to be
32
26
* outside both bounding boxes).
33
27
* o Fix many special cases related to connectors updating,
34
28
* e.g., copying a couple of shapes and a connector that are
37
31
* one of the other contexts.
38
32
* o Cope with shapes whose ids change when they have attached
40
* o gobble_motion_events(GDK_BUTTON1_MASK)?;
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.
44
152
#include <gdk/gdkkeysyms.h>
46
154
#include <cstring>
48
156
#include "connector-context.h"
49
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"
50
161
#include "xml/node-event-vector.h"
51
162
#include "xml/repr.h"
52
163
#include "svg/svg.h"
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);
297
504
sp_connector_context_finish(SPEventContext *ec)
299
506
SPConnectorContext *cc = SP_CONNECTOR_CONTEXT(ec);
377
conn_pt_handle_test(SPConnectorContext *cc, Geom::Point& p)
587
conn_pt_handle_test(SPConnectorContext *cc, Geom::Point& p, gchar **href, gchar **cpid)
379
589
// TODO: this will need to change when there are more connection
380
590
// points available for each shape.
382
SPKnot *centerpt = cc->connpthandle;
383
if (cc->active_handle && (cc->active_handle == centerpt))
592
if (cc->active_handle && (cc->connpthandles.find(cc->active_handle) != cc->connpthandles.end()))
386
return g_strdup_printf("#%s", SP_OBJECT_ID(cc->active_shape));
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);
394
626
sp_connector_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
405
637
case GDK_BUTTON_RELEASE:
406
638
if (event->button.button == 1 && !event_context->space_panning) {
407
639
if ((cc->state == SP_CONNECTOR_CONTEXT_DRAGGING) &&
408
(connector_within_tolerance))
640
(event_context->within_tolerance))
410
642
spcc_reset_colors(cc);
411
643
cc->state = SP_CONNECTOR_CONTEXT_IDLE;
412
sp_event_context_discard_delayed_snap_event(event_context);
414
645
if (cc->state != SP_CONNECTOR_CONTEXT_IDLE) {
415
// Doing simething else like rerouting.
646
// Doing something 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);
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);
422
653
if (event->button.state & GDK_SHIFT_MASK) {
423
cc->selection->toggle(item_ungrouped);
654
cc->selection->toggle(item);
425
cc->selection->set(item_ungrouped);
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 );
497
748
SPEventContext *event_context = SP_EVENT_CONTEXT(cc);
499
750
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) {
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;
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);
532
809
// Test whether we clicked on a connection point
533
cc->sid = conn_pt_handle_test(cc, p);
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);
540
spcc_connector_set_initial_point(cc, p);
543
cc->state = SP_CONNECTOR_CONTEXT_DRAGGING;
547
case SP_CONNECTOR_CONTEXT_DRAGGING:
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);
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);
565
case SP_CONNECTOR_CONTEXT_CLOSE:
567
g_warning("Button down in CLOSE state");
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.
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);
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:
608
923
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 ) {
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 ) ) {
613
929
return FALSE; // Do not drag if we're within tolerance from origin.
616
932
// Once the user has moved farther than tolerance from the original location
617
933
// (indicating they intend to move the object, not click), then always process
618
934
// the motion notify coordinates as given (no snapping back to origin)
619
connector_within_tolerance = false;
935
cc->within_tolerance = false;
621
937
SPDesktop *const dt = cc->desktop;
623
939
/* Find desktop coordinates */
624
940
Geom::Point p = dt->w2d(event_w);
626
SnapManager &m = dt->namedview->snap_manager;
630
case SP_CONNECTOR_CONTEXT_DRAGGING:
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);
641
case SP_CONNECTOR_CONTEXT_REROUTING:
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);
657
Geom::Point o = cc->endpt_handle[0]->pos;
658
curve->stretch_endpoints(o * d2i, p * d2i);
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);
670
case SP_CONNECTOR_CONTEXT_STOP:
671
/* This is perfectly valid */
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);
685
1024
SPEventContext *event_context = SP_EVENT_CONTEXT(cc);
686
1025
if ( revent.button == 1 && !event_context->space_panning ) {
688
SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
1027
SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc);
689
1028
SPDocument *doc = sp_desktop_document(desktop);
691
1030
SnapManager &m = desktop->namedview->snap_manager;
694
1033
Geom::Point const event_w(revent.x, revent.y);
696
1035
/* Find desktop coordinates */
697
1036
Geom::Point p = cc->desktop->w2d(event_w);
700
//case SP_CONNECTOR_CONTEXT_POINT:
701
case SP_CONNECTOR_CONTEXT_DRAGGING:
703
m.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::SNAPSOURCE_HANDLE);
705
if (connector_within_tolerance)
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);
707
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;
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);
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);
1072
case SP_CONNECTOR_CONTEXT_STOP:
1073
/* This is allowed, if we just cancelled curve */
723
case SP_CONNECTOR_CONTEXT_REROUTING:
1080
else if ( cc->mode == SP_CONNECTOR_CONTEXT_EDITING_MODE )
1082
switch ( cc->state )
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);
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;
734
case SP_CONNECTOR_CONTEXT_STOP:
735
/* This is allowed, if we just cancelled curve */
750
1134
gint ret = FALSE;
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));
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."));
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);
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;
805
1283
// Test whether we clicked on a connection point
806
gchar *shape_label = conn_pt_handle_test(cc, *p);
1284
gchar *shape_label, *cpid;
1285
bool found = conn_pt_handle_test(cc, *p, &shape_label, &cpid);
809
1288
if (cc->clickedhandle == cc->endpt_handle[0]) {
810
1289
sp_object_setAttribute(cc->clickeditem,
811
"inkscape:connection-start",shape_label, false);
1290
"inkscape:connection-start", shape_label, false);
1291
sp_object_setAttribute(cc->clickeditem,
1292
"inkscape:connection-start-point", cpid, false);
814
1295
sp_object_setAttribute(cc->clickeditem,
815
"inkscape:connection-end",shape_label, false);
1296
"inkscape:connection-end", shape_label, false);
1297
sp_object_setAttribute(cc->clickeditem,
1298
"inkscape:connection-end-point", cpid, false);
817
1300
g_free(shape_label);
820
1303
cc->clickeditem->setHidden(false);
821
sp_conn_adjust_path(SP_PATH(cc->clickeditem));
1304
sp_conn_reroute_path_immediate(SP_PATH(cc->clickeditem));
822
1305
cc->clickeditem->updateRepr();
823
1306
sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR,
824
1307
_("Reroute connector"));
864
1347
if (!cc->newConnRef) {
865
1348
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);
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);
869
cc->newConnRef->updateEndPoint(Avoid::VertID::tar, dst);
1356
// Set new endpoint.
1357
cc->newConnRef->setEndpoint(Avoid::VertID::tar, dst);
1358
// Immediately generate new routes for connector.
871
1359
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);
1360
cc->newConnRef->router()->processTransaction();
1361
// Recreate curve from libavoid route.
1362
recreateCurve( cc->red_curve, cc->newConnRef, cc->curvature );
885
1363
cc->red_curve->transform(dt->doc2dt());
886
1364
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve);
954
1432
/* Attach repr */
955
1433
cc->newconn = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
956
cc->selection->set(repr);
957
Inkscape::GC::release(repr);
958
1434
cc->newconn->transform = sp_item_i2doc_affine(SP_ITEM(desktop->currentLayer())).inverse();
959
cc->newconn->updateRepr();
961
1436
bool connection = false;
962
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);
966
1443
sp_object_setAttribute(cc->newconn, "inkscape:connection-start",
1446
sp_object_setAttribute(cc->newconn, "inkscape:connection-start-point",
968
1448
connection = true;
973
1453
sp_object_setAttribute(cc->newconn, "inkscape:connection-end",
1456
sp_object_setAttribute(cc->newconn, "inkscape:connection-end-point",
975
1458
connection = true;
1460
// Process pending updates.
977
1461
cc->newconn->updateRepr();
1462
sp_document_ensure_up_to_date(doc);
978
1464
if (connection) {
979
1465
// Adjust endpoints to shape edge.
980
sp_conn_adjust_path(SP_PATH(cc->newconn));
1466
sp_conn_reroute_path_immediate(SP_PATH(cc->newconn));
1467
cc->newconn->updateRepr();
982
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);
987
/* Flush pending updates */
988
1480
sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR, _("Create connector"));
989
sp_document_ensure_up_to_date(doc);
1120
1611
return consumed;
1124
static void cc_set_active_shape(SPConnectorContext *cc, SPItem *item)
1614
static void cc_active_shape_add_knot(SPDesktop* desktop, SPItem* item, ConnectionPointMap &cphandles, ConnectionPoint& cp)
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);
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);
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"));
1616
SPKnot *knot = sp_knot_new(desktop, 0);
1156
1618
knot->setShape(SP_KNOT_SHAPE_SQUARE);
1157
1619
knot->setSize(8);
1159
1621
knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff);
1160
1622
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.
1624
// We don't want to use the standard knot handler.
1164
1625
g_signal_handler_disconnect(G_OBJECT(knot->item),
1165
1626
knot->_event_handler_id);
1166
1627
knot->_event_handler_id = 0;
1168
1629
gtk_signal_connect(GTK_OBJECT(knot->item), "event",
1169
1630
GTK_SIGNAL_FUNC(cc_generic_knot_handler), knot);
1171
cc->connpthandle = 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);
1175
Geom::OptRect bbox = sp_item_bbox_desktop(cc->active_shape);
1177
Geom::Point center = bbox->midpoint();
1178
sp_knot_set_position(cc->connpthandle, center, 0);
1179
sp_knot_show(cc->connpthandle);
1181
sp_knot_hide(cc->connpthandle);
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);