Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
connector-tool.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Connector creation tool
4 *
5 * Authors:
6 * Michael Wybrow <mjwybrow@users.sourceforge.net>
7 * Abhishek Sharma
8 * Jon A. Cruz <jon@joncruz.org>
9 * Martin Owens <doctormo@gmail.com>
10 *
11 * Copyright (C) 2005-2008 Michael Wybrow
12 * Copyright (C) 2009 Monash University
13 * Copyright (C) 2012 Authors
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 *
17 * TODO:
18 * o Show a visual indicator for objects with the 'avoid' property set.
19 * o Allow user to change a object between a path and connector through
20 * the interface.
21 * o Create an interface for setting markers (arrow heads).
22 * o Better distinguish between paths and connectors to prevent problems
23 * in the node tool and paths accidentally being turned into connectors
24 * in the connector tool. Perhaps have a way to convert between.
25 * o Only call libavoid's updateEndPoint as required. Currently we do it
26 * for both endpoints, even if only one is moving.
27 * o Deal sanely with connectors with both endpoints attached to the
28 * same connection point, and drawing of connectors attaching
29 * overlapping shapes (currently tries to adjust connector to be
30 * outside both bounding boxes).
31 * o Fix many special cases related to connectors updating,
32 * e.g., copying a couple of shapes and a connector that are
33 * attached to each other.
34 * e.g., detach connector when it is moved or transformed in
35 * one of the other contexts.
36 * o Cope with shapes whose ids change when they have attached
37 * connectors.
38 * o During dragging motion, gobble up to and use the final motion event.
39 * Gobbling away all duplicates after the current can occasionally result
40 * in the path lagging behind the mouse cursor if it is no longer being
41 * dragged.
42 * o Fix up libavoid's representation after undo actions. It doesn't see
43 * any transform signals and hence doesn't know shapes have moved back to
44 * there earlier positions.
45 *
46 * ----------------------------------------------------------------------------
47 *
48 * Notes:
49 *
50 * Much of the way connectors work for user-defined points has been
51 * changed so that it no longer defines special attributes to record
52 * the points. Instead it uses single node paths to define points
53 * who are then separate objects that can be fixed on the canvas,
54 * grouped into objects and take full advantage of all transform, snap
55 * and align functionality of all other objects.
56 *
57 * I think that the style change between polyline and orthogonal
58 * would be much clearer with two buttons (radio behaviour -- just
59 * one is true).
60 *
61 * The other tools show a label change from "New:" to "Change:"
62 * depending on whether an object is selected. We could consider
63 * this but there may not be space.
64 *
65 * Likewise for the avoid/ignore shapes buttons. These should be
66 * inactive when a shape is not selected in the connector context.
67 *
68 */
69
70#include "connector-tool.h"
71
72#include <string>
73#include <cstring>
74
75#include <glibmm/i18n.h>
76#include <glibmm/stringutils.h>
77#include <gdk/gdkkeysyms.h>
78
79#include "context-fns.h"
80#include "desktop-style.h"
81#include "desktop.h"
82#include "document-undo.h"
83#include "document.h"
84#include "message-context.h"
85#include "message-stack.h"
86#include "selection.h"
87#include "snap.h"
88
91#include "display/curve.h"
92
94
95#include "object/sp-conn-end.h"
96#include "object/sp-flowtext.h"
97#include "object/sp-namedview.h"
98#include "object/sp-path.h"
99#include "object/sp-text.h"
100#include "object/sp-use.h"
101
102#include "ui/icon-names.h"
103#include "ui/knot/knot.h"
104#include "ui/widget/canvas.h" // Enter events hack
106
107#include "xml/node.h"
108
109#include "svg/svg.h"
110
111namespace Inkscape::UI::Tools {
112
114{
115 auto tool = static_cast<ConnectorTool*>(this);
116
117 auto const name = g_quark_to_string(name_);
118 // Look for changes that result in onscreen movement.
119 if (!strcmp(name, "d") || !strcmp(name, "x") || !strcmp(name, "y") ||
120 !strcmp(name, "width") || !strcmp(name, "height") ||
121 !strcmp(name, "transform")) {
122 if (&repr == tool->active_shape_repr) {
123 // Active shape has moved. Clear active shape.
124 tool->cc_clear_active_shape();
125 } else if (&repr == tool->active_conn_repr) {
126 // The active conn has been moved.
127 // Set it again, which just sets new handle positions.
128 tool->cc_set_active_conn(tool->active_conn);
129 }
130 }
131}
132
134{
135 auto tool = static_cast<ConnectorTool*>(this);
136
137 if (&child == tool->active_shape_repr) {
138 // The active shape has been deleted. Clear active shape.
139 tool->cc_clear_active_shape();
140 }
141}
142
144
145static void cc_clear_active_knots(SPKnotList k);
146
147static void cc_select_handle(SPKnot* knot);
148static void cc_deselect_handle(SPKnot* knot);
149static bool cc_item_is_shape(SPItem *item);
150
151/*static Geom::Point connector_drag_origin_w(0, 0);
152static bool connector_within_tolerance = false;*/
153
155 : ToolBase(desktop, "/tools/connector", "connector.svg")
157{
158 this->selection = desktop->getSelection();
159
160 this->sel_changed_connection.disconnect();
162 sigc::mem_fun(*this, &ConnectorTool::_selectionChanged)
163 );
164
165 /* Create red bpath */
169
170 /* Create red curve */
171 red_curve.emplace();
172
173 /* Create green curve */
174 green_curve.emplace();
175
176 // Notice the initial selection.
177 //cc_selection_changed(this->selection, (gpointer) this);
178 this->_selectionChanged(this->selection);
179
180 this->within_tolerance = false;
181
182 sp_event_context_read(this, "curvature");
183 sp_event_context_read(this, "orthogonal");
185 if (prefs->getBool("/tools/connector/selcue", false)) {
186 this->enableSelectionCue();
187 }
188
189 // Make sure we see all enter events for canvas items,
190 // even if a mouse button is depressed.
191 desktop->getCanvas()->set_all_enter_events(true);
192}
193
195{
196 this->_finish();
198
199 if (this->selection) {
200 this->selection = nullptr;
201 }
202
203 this->cc_clear_active_shape();
204 this->cc_clear_active_conn();
205
206 // Restore the default event generating behaviour.
208
209 this->sel_changed_connection.disconnect();
210
211 for (auto &i : endpt_handle) {
212 if (i) {
213 SPKnot::unref(i);
214 i = nullptr;
215 }
216 }
217
218 if (this->shref) {
219 g_free(this->shref);
220 this->shref = nullptr;
221 }
222
223 if (this->ehref) {
224 g_free(this->shref);
225 this->shref = nullptr;
226 }
227
228 g_assert(this->newConnRef == nullptr);
229}
230
232{
233 /* fixme: Proper error handling for non-numeric data. Use a locale-independent function like
234 * g_ascii_strtod (or a thin wrapper that does the right thing for invalid values inf/nan). */
235 Glib::ustring name = val.getEntryName();
236
237 if (name == "curvature") {
238 this->curvature = val.getDoubleLimited(); // prevents NaN and +/-Inf from messing up
239 } else if (name == "orthogonal") {
240 this->isOrthogonal = val.getBool();
241 }
242}
243
244//-----------------------------------------------------------------------------
245
246
248{
249 if (this->active_shape == nullptr) {
250 return;
251 }
252 g_assert( this->active_shape_repr );
253 g_assert( this->active_shape_layer_repr );
254
255 this->active_shape = nullptr;
256
257 if (this->active_shape_repr) {
260 this->active_shape_repr = nullptr;
261
264 this->active_shape_layer_repr = nullptr;
265 }
266
268}
269
271{
272 // Hide the connection points if they exist.
273 if (k.size()) {
274 for (auto & it : k) {
275 it.first->hide();
276 }
277 }
278}
279
281{
282 if (this->active_conn == nullptr) {
283 return;
284 }
285 g_assert( this->active_conn_repr );
286
287 this->active_conn = nullptr;
288
289 if (this->active_conn_repr) {
292 this->active_conn_repr = nullptr;
293 }
294
295 // Hide the endpoint handles.
296 for (auto & i : this->endpt_handle) {
297 if (i) {
298 i->hide();
299 }
300 }
301}
302
303
304bool ConnectorTool::_ptHandleTest(Geom::Point& p, gchar **href, gchar **subhref)
305{
306 if (this->active_handle && (this->knots.find(this->active_handle) != this->knots.end())) {
307 p = this->active_handle->pos;
308 *href = g_strdup_printf("#%s", this->active_handle->owner->getId());
309 if(this->active_handle->sub_owner) {
310 auto id = this->active_handle->sub_owner->getAttribute("id");
311 if(id) {
312 *subhref = g_strdup_printf("#%s", id);
313 }
314 } else {
315 *subhref = nullptr;
316 }
317 return true;
318 }
319 *href = nullptr;
320 *subhref = nullptr;
321 return false;
322}
323
324static void cc_select_handle(SPKnot* knot)
325{
326 knot->ctrl->set_selected(true);
329 knot->updateCtrl();
330}
331
332static void cc_deselect_handle(SPKnot* knot)
333{
334 knot->ctrl->set_selected(false);
337 knot->updateCtrl();
338}
339
341{
342 bool ret = false;
343
344 inspect_event(event,
345 [&] (ButtonReleaseEvent const &event) {
346 if (event.button == 1) {
347 if ((this->state == SP_CONNECTOR_CONTEXT_DRAGGING) && this->within_tolerance) {
348 this->_resetColors();
349 this->state = SP_CONNECTOR_CONTEXT_IDLE;
350 }
351
352 if (this->state != SP_CONNECTOR_CONTEXT_IDLE) {
353 // Doing something else like rerouting.
354 return;
355 }
356
357 // find out clicked item, honoring Alt
358 auto const item = sp_event_context_find_item(_desktop, event.pos, event.modifiers & GDK_ALT_MASK, false);
359
360 if (event.modifiers & GDK_SHIFT_MASK) {
361 this->selection->toggle(item);
362 } else {
363 this->selection->set(item);
364 /* When selecting a new item, do not allow showing
365 connection points on connectors. (yet?)
366 */
367
368 if (item != this->active_shape && !cc_item_is_connector(item)) {
369 this->_setActiveShape(item);
370 }
371 }
372
373 ret = true;
374 }
375 },
376 [&] (MotionEvent const &event) {
377 auto const item = _desktop->getItemAtPoint(event.pos, false);
378 if (cc_item_is_shape(item)) {
379 _setActiveShape(item);
380 }
381 },
382 [&] (CanvasEvent const &event) {}
383 );
384
385 return ret;
386}
387
388bool ConnectorTool::root_handler(CanvasEvent const &event)
389{
390 bool ret = false;
391
392 inspect_event(event,
393 [&] (ButtonPressEvent const &event) {
394 if (event.num_press == 1) {
395 ret = _handleButtonPress(event);
396 }
397 },
398 [&] (MotionEvent const &event) {
399 ret = _handleMotionNotify(event);
400 },
401 [&] (ButtonReleaseEvent const &event) {
402 ret = _handleButtonRelease(event);
403 },
404 [&] (KeyPressEvent const &event) {
405 ret = _handleKeyPress(get_latin_keyval(event));
406 },
407 [&] (CanvasEvent const &event) {}
408 );
409
410 return ret || ToolBase::root_handler(event);
411}
412
413bool ConnectorTool::_handleButtonPress(ButtonPressEvent const &bevent)
414{
415 Geom::Point const event_w = bevent.pos;
416 /* Find desktop coordinates */
417 Geom::Point p = _desktop->w2d(event_w);
418
419 bool ret = false;
420
421 if (bevent.button == 1) {
422 if (Inkscape::have_viable_layer(_desktop, defaultMessageContext()) == false) {
423 return true;
424 }
425
426 auto const event_w = bevent.pos;
427
428 saveDragOrigin(event_w);
429
430 Geom::Point const event_dt = _desktop->w2d(event_w);
431
432 SnapManager &m = _desktop->getNamedView()->snap_manager;
433
434 switch (this->state) {
436
437 /* This is allowed, if we just canceled curve */
439 {
440 if ( this->npoints == 0 ) {
441 this->cc_clear_active_conn();
442
443 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new connector"));
444
445 /* Set start anchor */
446 /* Create green anchor */
447 Geom::Point p = event_dt;
448
449 // Test whether we clicked on a connection point
450 bool found = this->_ptHandleTest(p, &this->shref, &this->sub_shref);
451
452 if (!found) {
453 // This is the first point, so just snap it to the grid
454 // as there's no other points to go off.
455 m.setup(_desktop);
457 m.unSetup();
458 }
459 this->_setInitialPoint(p);
460
461 }
462 this->state = SP_CONNECTOR_CONTEXT_DRAGGING;
463 ret = true;
464 break;
465 }
467 {
468 // This is the second click of a connector creation.
469 m.setup(_desktop);
471 m.unSetup();
472
473 this->_setSubsequentPoint(p);
474 this->_finishSegment(p);
475
476 this->_ptHandleTest(p, &this->ehref, &this->sub_ehref);
477 if (this->npoints != 0) {
478 this->_finish();
479 }
480 this->cc_set_active_conn(this->newconn);
481 this->state = SP_CONNECTOR_CONTEXT_IDLE;
482 ret = true;
483 break;
484 }
486 {
487 g_warning("Button down in CLOSE state");
488 break;
489 }
490 default:
491 break;
492 }
493 } else if (bevent.button == 3) {
494 if (this->state == SP_CONNECTOR_CONTEXT_REROUTING) {
495 // A context menu is going to be triggered here,
496 // so end the rerouting operation.
497 this->_reroutingFinish(&p);
498
499 this->state = SP_CONNECTOR_CONTEXT_IDLE;
500
501 // Don't set ret to TRUE, so we drop through to the
502 // parent handler which will open the context menu.
503 } else if (this->npoints != 0) {
504 this->_finish();
505 this->state = SP_CONNECTOR_CONTEXT_IDLE;
506 ret = true;
507 }
508 }
509
510 return ret;
511}
512
513bool ConnectorTool::_handleMotionNotify(MotionEvent const &mevent)
514{
515 bool ret = false;
516
517 if (mevent.modifiers & (GDK_BUTTON2_MASK | GDK_BUTTON3_MASK)) {
518 // allow middle-button scrolling
519 return false;
520 }
521
522 auto const event_w = mevent.pos;
523
524 if (!checkDragMoved(event_w)) {
525 return false;
526 }
527
528 // Find desktop coordinates.
529 Geom::Point p = _desktop->w2d(event_w);
530
531 SnapManager &m = _desktop->getNamedView()->snap_manager;
532
533 switch (this->state) {
535 {
537 // This is movement during a connector creation.
538 if ( this->npoints > 0 ) {
539 m.setup(_desktop);
541 m.unSetup();
542 this->selection->clear();
543 this->_setSubsequentPoint(p);
544 ret = true;
545 }
546 break;
547 }
549 {
550 gobble_motion_events(GDK_BUTTON1_MASK);
551 g_assert(is<SPPath>(clickeditem));
552
553 m.setup(_desktop);
555 m.unSetup();
556
557 // Update the hidden path
558 auto i2d = clickeditem->i2dt_affine();
559 auto d2i = i2d.inverse();
560 auto path = cast<SPPath>(clickeditem);
561 auto curve = *path->curve();
562 if (clickedhandle == endpt_handle[0]) {
563 auto o = endpt_handle[1]->pos;
564 curve.stretch_endpoints(p * d2i, o * d2i);
565 } else {
566 auto o = endpt_handle[0]->pos;
567 curve.stretch_endpoints(o * d2i, p * d2i);
568 }
569 path->setCurve(std::move(curve));
571
572 // Copy this to the temporary visible path
573 red_curve = path->curveForEdit()->transformed(i2d);
574 red_bpath->set_bpath(&*red_curve);
575
576 ret = true;
577 break;
578 }
580 /* This is perfectly valid */
581 break;
582 default:
583 if (!this->sp_event_context_knot_mouseover()) {
584 m.setup(_desktop);
586 m.unSetup();
587 }
588 break;
589 }
590 return ret;
591}
592
593bool ConnectorTool::_handleButtonRelease(ButtonReleaseEvent const &revent)
594{
595 bool ret = false;
596
597 if (revent.button == 1) {
598 SPDocument *doc = _desktop->getDocument();
599 SnapManager &m = _desktop->getNamedView()->snap_manager;
600
601 auto const event_w = revent.pos;
602
603 // Find desktop coordinates.
604 Geom::Point p = _desktop->w2d(event_w);
605
606 switch (this->state) {
607 //case SP_CONNECTOR_CONTEXT_POINT:
609 {
610 m.setup(_desktop);
612 m.unSetup();
613
614 if (this->within_tolerance) {
615 this->_finishSegment(p);
616 return true;
617 }
618 // Connector has been created via a drag, end it now.
619 this->_setSubsequentPoint(p);
620 this->_finishSegment(p);
621 // Test whether we clicked on a connection point
622 this->_ptHandleTest(p, &this->ehref, &this->sub_ehref);
623 if (this->npoints != 0) {
624 this->_finish();
625 }
626 this->cc_set_active_conn(this->newconn);
627 this->state = SP_CONNECTOR_CONTEXT_IDLE;
628 break;
629 }
631 {
632 m.setup(_desktop);
634 m.unSetup();
635 this->_reroutingFinish(&p);
636
637 doc->ensureUpToDate();
638 this->state = SP_CONNECTOR_CONTEXT_IDLE;
639 return true;
640 break;
641 }
643 /* This is allowed, if we just cancelled curve */
644 break;
645 default:
646 break;
647 }
648 ret = true;
649 }
650 return ret;
651}
652
653bool ConnectorTool::_handleKeyPress(guint const keyval)
654{
655 bool ret = false;
656
657 switch (keyval) {
658 case GDK_KEY_Return:
659 case GDK_KEY_KP_Enter:
660 if (this->npoints != 0) {
661 this->_finish();
662 this->state = SP_CONNECTOR_CONTEXT_IDLE;
663 ret = true;
664 }
665 break;
666 case GDK_KEY_Escape:
667 if (this->state == SP_CONNECTOR_CONTEXT_REROUTING) {
668 SPDocument *doc = _desktop->getDocument();
669
670 this->_reroutingFinish(nullptr);
671
673
674 this->state = SP_CONNECTOR_CONTEXT_IDLE;
675 _desktop->messageStack()->flash( Inkscape::NORMAL_MESSAGE,
676 _("Connector endpoint drag cancelled."));
677 ret = true;
678 } else if (this->npoints != 0) {
679 // if drawing, cancel, otherwise pass it up for deselecting
680 this->state = SP_CONNECTOR_CONTEXT_STOP;
681 this->_resetColors();
682 ret = true;
683 }
684 break;
685 default:
686 break;
687 }
688 return ret;
689}
690
691void ConnectorTool::_reroutingFinish(Geom::Point *const p)
692{
693 SPDocument *doc = _desktop->getDocument();
694
695 // Clear the temporary path:
696 this->red_curve->reset();
697 red_bpath->set_bpath(nullptr);
698
699 if (p != nullptr) {
700 // Test whether we clicked on a connection point
701 gchar *shape_label;
702 gchar *sub_label;
703 bool found = this->_ptHandleTest(*p, &shape_label, &sub_label);
704
705 if (found) {
706 if (this->clickedhandle == this->endpt_handle[0]) {
707 this->clickeditem->setAttribute("inkscape:connection-start", shape_label);
708 this->clickeditem->setAttribute("inkscape:connection-start-point", sub_label);
709 } else {
710 this->clickeditem->setAttribute("inkscape:connection-end", shape_label);
711 this->clickeditem->setAttribute("inkscape:connection-end-point", sub_label);
712 }
713 g_free(shape_label);
714 if(sub_label) {
715 g_free(sub_label);
716 }
717 }
718 }
719 this->clickeditem->setHidden(false);
720 sp_conn_reroute_path_immediate(cast<SPPath>(this->clickeditem));
721 this->clickeditem->updateRepr();
722 DocumentUndo::done(doc, _("Reroute connector"), INKSCAPE_ICON("draw-connector"));
723 this->cc_set_active_conn(this->clickeditem);
724}
725
726
727void ConnectorTool::_resetColors()
728{
729 /* Red */
730 this->red_curve->reset();
731 red_bpath->set_bpath(nullptr);
732
733 this->green_curve->reset();
734 this->npoints = 0;
735}
736
737void ConnectorTool::_setInitialPoint(Geom::Point const p)
738{
739 g_assert( this->npoints == 0 );
740
741 this->p[0] = p;
742 this->p[1] = p;
743 this->npoints = 2;
744 red_bpath->set_bpath(nullptr);
745}
746
747void ConnectorTool::_setSubsequentPoint(Geom::Point const p)
748{
749 g_assert( this->npoints != 0 );
750
751 Geom::Point o = _desktop->dt2doc(this->p[0]);
752 Geom::Point d = _desktop->dt2doc(p);
753 Avoid::Point src(o[Geom::X], o[Geom::Y]);
754 Avoid::Point dst(d[Geom::X], d[Geom::Y]);
755
756 if (!this->newConnRef) {
757 Avoid::Router *router = _desktop->getDocument()->getRouter();
758 this->newConnRef = new Avoid::ConnRef(router);
759 this->newConnRef->setEndpoint(Avoid::VertID::src, src);
760 if (this->isOrthogonal) {
761 this->newConnRef->setRoutingType(Avoid::ConnType_Orthogonal);
762 } else {
763 this->newConnRef->setRoutingType(Avoid::ConnType_PolyLine);
764 }
765 }
766 // Set new endpoint.
767 this->newConnRef->setEndpoint(Avoid::VertID::tar, dst);
768 // Immediately generate new routes for connector.
769 this->newConnRef->makePathInvalid();
770 this->newConnRef->router()->processTransaction();
771 // Recreate curve from libavoid route.
772 red_curve = SPConnEndPair::createCurve(newConnRef, curvature);
773 red_curve->transform(_desktop->doc2dt());
774 red_bpath->set_bpath(&*red_curve, true);
775}
776
777
783void ConnectorTool::_concatColorsAndFlush()
784{
785 auto c = std::make_optional<SPCurve>();
786 std::swap(c, green_curve);
787
788 red_curve->reset();
789 red_bpath->set_bpath(nullptr);
790
791 if (c->is_empty()) {
792 return;
793 }
794
795 _flushWhite(&*c);
796}
797
798
799/*
800 * Flushes white curve(s) and additional curve into object
801 *
802 * No cleaning of colored curves - this has to be done by caller
803 * No rereading of white data, so if you cannot rely on ::modified, do it in caller
804 *
805 */
806
807void ConnectorTool::_flushWhite(SPCurve *c)
808{
809 /* Now we have to go back to item coordinates at last */
810 c->transform(_desktop->dt2doc());
811
812 SPDocument *doc = _desktop->getDocument();
813 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
814
815 if ( !c->is_empty() ) {
816 /* We actually have something to write */
817
818 Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
819 /* Set style */
820 sp_desktop_apply_style_tool(_desktop, repr, "/tools/connector", false);
821
822 repr->setAttribute("d", sp_svg_write_path(c->get_pathvector()));
823
824 /* Attach repr */
825 auto layer = currentLayer();
826 this->newconn = cast<SPItem>(layer->appendChildRepr(repr));
827 this->newconn->transform = layer->i2doc_affine().inverse();
828
829 bool connection = false;
830 this->newconn->setAttribute( "inkscape:connector-type",
831 this->isOrthogonal ? "orthogonal" : "polyline");
832 this->newconn->setAttribute( "inkscape:connector-curvature",
833 Glib::Ascii::dtostr(this->curvature).c_str());
834 if (this->shref) {
835 connection = true;
836 this->newconn->setAttribute( "inkscape:connection-start", this->shref);
837 if(this->sub_shref) {
838 this->newconn->setAttribute( "inkscape:connection-start-point", this->sub_shref);
839 }
840 }
841
842 if (this->ehref) {
843 connection = true;
844 this->newconn->setAttribute( "inkscape:connection-end", this->ehref);
845 if(this->sub_ehref) {
846 this->newconn->setAttribute( "inkscape:connection-end-point", this->sub_ehref);
847 }
848 }
849 // Process pending updates.
850 this->newconn->updateRepr();
851 doc->ensureUpToDate();
852
853 if (connection) {
854 // Adjust endpoints to shape edge.
855 sp_conn_reroute_path_immediate(cast<SPPath>(this->newconn));
856 this->newconn->updateRepr();
857 }
858
859 this->newconn->doWriteTransform(this->newconn->transform, nullptr, true);
860
861 // Only set the selection after we are finished with creating the attributes of
862 // the connector. Otherwise, the selection change may alter the defaults for
863 // values like curvature in the connector context, preventing subsequent lookup
864 // of their original values.
865 this->selection->set(repr);
867 }
868
869 DocumentUndo::done(doc, _("Create connector"), INKSCAPE_ICON("draw-connector"));
870}
871
872
873void ConnectorTool::_finishSegment(Geom::Point const /*p*/)
874{
875 if (!this->red_curve->is_empty()) {
876 green_curve->append_continuous(*red_curve);
877
878 this->p[0] = this->p[3];
879 this->p[1] = this->p[4];
880 this->npoints = 2;
881
882 this->red_curve->reset();
883 }
884}
885
886void ConnectorTool::_finish()
887{
888 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing connector"));
889
890 this->red_curve->reset();
891 this->_concatColorsAndFlush();
892
893 this->npoints = 0;
894
895 if (this->newConnRef) {
896 this->newConnRef->router()->deleteConnector(this->newConnRef);
897 this->newConnRef = nullptr;
898 }
899}
900
901
902static bool cc_generic_knot_handler(CanvasEvent const &event, SPKnot *knot)
903{
904 g_assert(knot);
905
906 SPKnot::ref(knot);
907
908 auto cc = SP_CONNECTOR_CONTEXT(knot->desktop->getTool());
909
910 bool consumed = false;
911
912 inspect_event(event,
913 [&] (EnterEvent const &event) {
914 knot->setFlag(SP_KNOT_MOUSEOVER, true);
915
916 cc->active_handle = knot;
917 knot->desktop->getTool()->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Click to join at this point"));
918
919 consumed = true;
920 },
921 [&] (LeaveEvent const &event) {
922 knot->setFlag(SP_KNOT_MOUSEOVER, false);
923
924 /* FIXME: the following test is a workaround for LP Bug #1273510.
925 * It seems that a signal is not correctly disconnected, maybe
926 * something missing in cc_clear_active_conn()? */
927 if (cc) {
928 cc->active_handle = nullptr;
929 }
930
932
933 consumed = true;
934 },
935 [&] (CanvasEvent const &event) {}
936 );
937
938 SPKnot::unref(knot);
939
940 return consumed;
941}
942
943static bool endpt_handler(CanvasEvent const &event, ConnectorTool *cc)
944{
945 bool consumed = false;
946
947 inspect_event(event,
948 [&] (ButtonPressEvent const &event) {
949 g_assert( (cc->active_handle == cc->endpt_handle[0]) ||
950 (cc->active_handle == cc->endpt_handle[1]) );
951 if (cc->state == SP_CONNECTOR_CONTEXT_IDLE) {
952 cc->clickeditem = cc->active_conn;
956
957 // Disconnect from attached shape
958 unsigned ind = (cc->active_handle == cc->endpt_handle[0]) ? 0 : 1;
960
962 if (cc->clickedhandle == cc->endpt_handle[0]) {
963 origin = cc->endpt_handle[1]->pos;
964 } else {
965 origin = cc->endpt_handle[0]->pos;
966 }
967
968 // Show the red path for dragging.
969 auto path = static_cast<SPPath const *>(cc->clickeditem);
970 cc->red_curve = path->curveForEdit()->transformed(cc->clickeditem->i2dt_affine());
971 cc->red_bpath->set_bpath(&*cc->red_curve, true);
972
973 cc->clickeditem->setHidden(true);
974
975 // The rest of the interaction rerouting the connector is
976 // handled by the context root handler.
977 consumed = true;
978 }
979 },
980 [&] (CanvasEvent const &event) {}
981 );
982
983 return consumed;
984}
985
986void ConnectorTool::_activeShapeAddKnot(SPItem* item, SPItem* subitem)
987{
988 SPKnot *knot = new SPKnot(_desktop, "", Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "CanvasItemCtrl:ConnectorTool:Shape");
989 knot->owner = item;
990
991 if (subitem) {
992 auto use = cast<SPUse>(item);
993 g_assert(use != nullptr);
994 knot->sub_owner = subitem;
997
998 // Set the point to the middle of the sub item
999 knot->setPosition(subitem->getAvoidRef().getConnectionPointPos() * _desktop->doc2dt(), 0);
1000 } else {
1003 // Set the point to the middle of the object
1004 knot->setPosition(item->getAvoidRef().getConnectionPointPos() * _desktop->doc2dt(), 0);
1005 }
1006
1007 knot->updateCtrl();
1008
1009 // We don't want to use the standard knot handler.
1010 knot->_event_connection.disconnect();
1011 knot->_event_connection =
1012 knot->ctrl->connect_event(sigc::bind(sigc::ptr_fun(cc_generic_knot_handler), knot));
1013
1014 knot->show();
1015 this->knots[knot] = 1;
1016}
1017
1018void ConnectorTool::_setActiveShape(SPItem *item)
1019{
1020 g_assert(item != nullptr );
1021
1022 if (this->active_shape != item) {
1023 // The active shape has changed
1024 // Rebuild everything
1025 this->active_shape = item;
1026 // Remove existing active shape listeners
1027 if (this->active_shape_repr) {
1028 this->active_shape_repr->removeObserver(shapeNodeObserver());
1029 Inkscape::GC::release(this->active_shape_repr);
1030
1031 this->active_shape_layer_repr->removeObserver(layerNodeObserver());
1032 Inkscape::GC::release(this->active_shape_layer_repr);
1033 }
1034
1035 // Listen in case the active shape changes
1036 this->active_shape_repr = item->getRepr();
1037 if (this->active_shape_repr) {
1038 Inkscape::GC::anchor(this->active_shape_repr);
1039 this->active_shape_repr->addObserver(shapeNodeObserver());
1040
1041 this->active_shape_layer_repr = this->active_shape_repr->parent();
1042 Inkscape::GC::anchor(this->active_shape_layer_repr);
1043 this->active_shape_layer_repr->addObserver(layerNodeObserver());
1044 }
1045
1046 cc_clear_active_knots(this->knots);
1047
1048 // The idea here is to try and add a group's children to solidify
1049 // connection handling. We react to path objects with only one node.
1050 for (auto& child: item->children) {
1051 if(child.getAttribute("inkscape:connector")) {
1052 this->_activeShapeAddKnot((SPItem *) &child, nullptr);
1053 }
1054 }
1055 // Special connector points in a symbol
1056 if (auto use = cast<SPUse>(item)) {
1057 SPItem *orig = use->root();
1058 //SPItem *orig = use->get_original();
1059 for (auto& child: orig->children) {
1060 if(child.getAttribute("inkscape:connector")) {
1061 this->_activeShapeAddKnot(item, (SPItem *) &child);
1062 }
1063 }
1064 }
1065 // Center point to any object
1066 this->_activeShapeAddKnot(item, nullptr);
1067
1068 } else {
1069 // Ensure the item's connection_points map
1070 // has been updated
1072 }
1073}
1074
1075void ConnectorTool::cc_set_active_conn(SPItem *item)
1076{
1077 g_assert( is<SPPath>(item) );
1078
1079 const SPCurve *curve = cast<SPPath>(item)->curveForEdit();
1080 Geom::Affine i2dt = item->i2dt_affine();
1081
1082 if (this->active_conn == item) {
1083 if (curve->is_empty()) {
1084 // Connector is invisible because it is clipped to the boundary of
1085 // two overlapping shapes.
1086 this->endpt_handle[0]->hide();
1087 this->endpt_handle[1]->hide();
1088 } else {
1089 // Just adjust handle positions.
1090 Geom::Point startpt = *(curve->first_point()) * i2dt;
1091 this->endpt_handle[0]->setPosition(startpt, 0);
1092
1093 Geom::Point endpt = *(curve->last_point()) * i2dt;
1094 this->endpt_handle[1]->setPosition(endpt, 0);
1095 }
1096
1097 return;
1098 }
1099
1100 this->active_conn = item;
1101
1102 // Remove existing active conn listeners
1103 if (this->active_conn_repr) {
1104 this->active_conn_repr->removeObserver(shapeNodeObserver());
1105 Inkscape::GC::release(this->active_conn_repr);
1106 this->active_conn_repr = nullptr;
1107 }
1108
1109 // Listen in case the active conn changes
1110 this->active_conn_repr = item->getRepr();
1111 if (this->active_conn_repr) {
1112 Inkscape::GC::anchor(this->active_conn_repr);
1113 this->active_conn_repr->addObserver(shapeNodeObserver());
1114 }
1115
1116 for (int i = 0; i < 2; ++i) {
1117 // Create the handle if it doesn't exist
1118 if ( this->endpt_handle[i] == nullptr ) {
1119 SPKnot *knot = new SPKnot(_desktop,
1120 _("<b>Connector endpoint</b>: drag to reroute or connect to new shapes"),
1121 Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "CanvasItemCtrl:ConnectorTool:Endpoint");
1122
1125 knot->updateCtrl();
1126
1127 // We don't want to use the standard knot handler,
1128 // since we don't want this knot to be draggable.
1129 knot->_event_connection.disconnect();
1130 knot->_event_connection =
1131 knot->ctrl->connect_event(sigc::bind(sigc::ptr_fun(cc_generic_knot_handler), knot));
1132
1133 this->endpt_handle[i] = knot;
1134 }
1135
1136 // Remove any existing handlers
1137 this->endpt_handler_connection[i].disconnect();
1138 this->endpt_handler_connection[i] =
1139 this->endpt_handle[i]->ctrl->connect_event(sigc::bind(sigc::ptr_fun(endpt_handler), this));
1140 }
1141
1142 if (curve->is_empty()) {
1143 // Connector is invisible because it is clipped to the boundary
1144 // of two overlpapping shapes. So, it doesn't need endpoints.
1145 return;
1146 }
1147
1148 Geom::Point startpt = *(curve->first_point()) * i2dt;
1149 this->endpt_handle[0]->setPosition(startpt, 0);
1150
1151 Geom::Point endpt = *(curve->last_point()) * i2dt;
1152 this->endpt_handle[1]->setPosition(endpt, 0);
1153
1154 this->endpt_handle[0]->show();
1155 this->endpt_handle[1]->show();
1156}
1157
1159{
1160 if (cc->active_shape && cc->state == SP_CONNECTOR_CONTEXT_IDLE) {
1161 if (cc->selected_handle) {
1163 }
1164
1166 "CanvasItemCtrl::ConnectorTool:ConnectionPoint");
1167
1168 // We do not process events on this knot.
1169 knot->_event_connection.disconnect();
1170
1171 cc_select_handle( knot );
1172 cc->selected_handle = knot;
1173 cc->selected_handle->show();
1175 }
1176}
1177
1179{
1180 if (auto path = cast<SPPath>(item)) {
1181 SPCurve const *curve = path->curve();
1182 if ( curve && !(curve->is_closed()) ) {
1183 // Open paths are connectors.
1184 return false;
1185 }
1186 } else if (is<SPText>(item) || is<SPFlowtext>(item)) {
1188 if (prefs->getBool("/tools/connector/ignoretext", true)) {
1189 // Don't count text as a shape we can connect connector to.
1190 return false;
1191 }
1192 }
1193 return true;
1194}
1195
1196
1198{
1199 if (auto path = cast<SPPath>(item)) {
1200 bool closed = path->curveForEdit()->is_closed();
1201 if (path->connEndPair.isAutoRoutingConn() && !closed) {
1202 // To be considered a connector, an object must be a non-closed
1203 // path that is marked with a "inkscape:connector-type" attribute.
1204 return true;
1205 }
1206 }
1207 return false;
1208}
1209
1210
1211void cc_selection_set_avoid(SPDesktop *desktop, bool const set_avoid)
1212{
1213 if (desktop == nullptr) {
1214 return;
1215 }
1216
1217 SPDocument *document = desktop->getDocument();
1218
1220
1221
1222 int changes = 0;
1223
1224 for (SPItem *item: selection->items()) {
1225 char const *value = (set_avoid) ? "true" : nullptr;
1226
1227 if (cc_item_is_shape(item)) {
1228 item->setAttribute("inkscape:connector-avoid", value);
1230 changes++;
1231 }
1232 }
1233
1234 if (changes == 0) {
1236 _("Select <b>at least one non-connector object</b>."));
1237 return;
1238 }
1239
1240 char *event_desc = (set_avoid) ?
1241 _("Make connectors avoid selected objects") :
1242 _("Make connectors ignore selected objects");
1243 DocumentUndo::done(document, event_desc, INKSCAPE_ICON("draw-connector"));
1244}
1245
1246void ConnectorTool::_selectionChanged(Inkscape::Selection *selection)
1247{
1248 SPItem *item = selection->singleItem();
1249 if (this->active_conn == item) {
1250 // Nothing to change.
1251 return;
1252 }
1253
1254 if (item == nullptr) {
1255 this->cc_clear_active_conn();
1256 return;
1257 }
1258
1260 this->cc_set_active_conn(item);
1261 }
1262}
1263
1264} // namespace Inkscape::UI::Tools
1265
1266/*
1267 Local Variables:
1268 mode:c++
1269 c-file-style:"stroustrup"
1270 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1271 indent-tabs-mode:nil
1272 fill-column:99
1273 End:
1274*/
1275// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Point origin
Definition aa.cpp:227
Inkscape canvas widget.
The ConnRef class represents a connector object.
Definition connector.h:132
The Point class defines a point in the plane.
Definition geomtypes.h:53
The Router class represents a libavoid router instance.
Definition router.h:386
static const unsigned short tar
Definition vertices.h:60
static const unsigned short src
Definition vertices.h:59
3x3 matrix representing an affine transformation.
Definition affine.h:70
Two-dimensional point that doubles as a vector.
Definition point.h:66
void set_bpath(SPCurve const *curve, bool phantom_line=false)
Set a control bpath.
void set_fill(uint32_t rgba, SPWindRule fill_rule)
Set the fill color and fill rule.
virtual void set_stroke(uint32_t rgba)
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static gboolean undo(SPDocument *document)
void clear()
removes our current message from the stack
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
SPItemRange items()
Returns a range of selected SPItems.
Definition object-set.h:255
void toggle(SPObject *obj)
Removes an item if selected, adds otherwise.
SPItem * singleItem()
Returns a single selected item.
Data type representing a typeless value of a preference.
Glib::ustring getEntryName() const
Get the last component of the preference's path.
bool getBool(bool def=false) const
Interpret the preference as a Boolean value.
double getDoubleLimited(double def=0.0, double min=DBL_MIN, double max=DBL_MAX, Glib::ustring const &unit="") const
Interpret the preference as a limited floating point value.
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
void set(XML::Node *repr)
Set the selection to an XML node's SPObject.
Definition selection.h:118
sigc::connection connectChanged(sigc::slot< void(Selection *)> slot)
Connects a slot to be notified of selection changes.
Definition selection.h:179
Class to store data for points which are snap candidates, either as a source or as a target.
void notifyChildRemoved(Inkscape::XML::Node &, Inkscape::XML::Node &, Inkscape::XML::Node *) final
Child removal callback.
void notifyAttributeChanged(Inkscape::XML::Node &, GQuark, Util::ptr_shared, Util::ptr_shared) final
Attribute change callback.
std::optional< SPCurve > red_curve
std::optional< SPCurve > green_curve
Inkscape::XML::Node * active_shape_layer_repr
CCToolShapeNodeObserver & shapeNodeObserver()
bool item_handler(SPItem *item, CanvasEvent const &event) override
Handles item specific events.
CCToolLayerNodeObserver & layerNodeObserver()
Inkscape::CanvasItemBpath * red_bpath
void set(Preferences::Entry const &val) override
Called by our pref_observer if a preference has been changed.
bool _ptHandleTest(Geom::Point &p, gchar **href, gchar **subhref)
void _selectionChanged(Inkscape::Selection *selection)
Inkscape::XML::Node * active_shape_repr
Inkscape::XML::Node * active_conn_repr
Base class for Event processors.
Definition tool-base.h:107
bool within_tolerance
are we still within tolerance of origin
Definition tool-base.h:148
SPDesktop * getDesktop() const
Definition tool-base.h:125
void enableSelectionCue(bool enable=true)
Enables/disables the ToolBase's SelCue.
MessageContext * defaultMessageContext() const
Definition tool-base.h:123
void set_all_enter_events(bool on)
Definition canvas.h:145
Interface for refcounted XML nodes.
Definition node.h:80
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual void removeObserver(NodeObserver &observer)=0
Remove an object from the list of observers.
Geom::Point getConnectionPointPos()
void handleSettingChange()
static SPCurve createCurve(Avoid::ConnRef *connRef, double curvature)
Wrapper around a Geom::PathVector object.
Definition curve.h:28
To do: update description of desktop.
Definition desktop.h:149
Inkscape::UI::Widget::Canvas * getCanvas() const
Definition desktop.h:190
SPDocument * getDocument() const
Definition desktop.h:189
Inkscape::CanvasItemGroup * getCanvasSketch() const
Definition desktop.h:201
Inkscape::MessageStack * messageStack() const
Definition desktop.h:160
Inkscape::Selection * getSelection() const
Definition desktop.h:188
Inkscape::UI::Tools::ToolBase * getTool() const
Definition desktop.h:187
Typed SVG document implementation.
Definition document.h:101
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:211
int ensureUpToDate(unsigned int object_modified_tag=0)
Repeatedly works on getting the document updated, since sometimes it takes more than one pass to get ...
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1837
void setHidden(bool hidden)
Definition sp-item.cpp:248
SPAvoidRef & getAvoidRef()
Definition sp-item.cpp:202
Desktop-bound visual control object.
Definition knot.h:51
static void ref(SPKnot *knot)
Definition knot.h:175
SPItem * owner
Optional Owner Item.
Definition knot.h:61
void setPosition(Geom::Point const &p, unsigned int state)
Move knot to new position and emits "moved" signal.
Definition knot.cpp:342
Geom::Point pos
Our desktop coordinates.
Definition knot.h:69
void setSize(Inkscape::HandleSize size)
Definition knot.cpp:413
CanvasItemPtr< Inkscape::CanvasItemCtrl > ctrl
Our CanvasItemCtrl.
Definition knot.h:60
static void unref(SPKnot *knot)
Definition knot.cpp:47
void setAnchor(unsigned int i)
Definition knot.cpp:418
sigc::scoped_connection _event_connection
Definition knot.h:87
void show()
Show knot on its canvas.
Definition knot.cpp:322
void setFlag(unsigned int flag, bool set)
Set flag in knot, with side effects.
Definition knot.cpp:362
void updateCtrl()
Update knot's control state.
Definition knot.cpp:389
SPItem * sub_owner
Optional SubOwner Item.
Definition knot.h:62
SPDesktop * desktop
Desktop we are on.
Definition knot.h:59
SnapManager snap_manager
void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
SPDocument * document
Definition sp-object.h:188
char const * getId() const
Returns the objects current ID string.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
char const * getAttribute(char const *name) const
ChildrenList children
Definition sp-object.h:907
SVG <path> implementation.
Definition sp-path.h:29
Class to coordinate snapping operations.
Definition snap.h:80
void setup(SPDesktop const *desktop, bool snapindicator=true, SPObject const *item_to_ignore=nullptr, std::vector< Inkscape::SnapCandidatePoint > *unselected_nodes=nullptr)
Convenience shortcut when there is only one item to ignore.
Definition snap.cpp:663
void freeSnapReturnByRef(Geom::Point &p, Inkscape::SnapSourceType const source_type, Geom::OptRect const &bbox_to_snap=Geom::OptRect()) const
Try to snap a point to grids, guides or objects.
Definition snap.cpp:135
void preSnap(Inkscape::SnapCandidatePoint const &p, bool to_path_only=false)
Definition snap.cpp:161
SPNamedView const * getNamedView() const
Definition snap.h:371
void unSetup()
Definition snap.h:147
std::map< SPKnot *, int > SPKnotList
@ SP_CONNECTOR_CONTEXT_REROUTING
@ SP_CONNECTOR_CONTEXT_DRAGGING
@ SP_CONNECTOR_CONTEXT_IDLE
@ SP_CONNECTOR_CONTEXT_STOP
@ SP_CONNECTOR_CONTEXT_CLOSE
@ SP_CONNECTOR_CONTEXT_NEWCONNPOINT
double c[8][4]
Geom::Point orig
void sp_desktop_apply_style_tool(SPDesktop *desktop, Inkscape::XML::Node *repr, Glib::ustring const &tool_path, bool with_text)
Apply the desktop's current style or the tool style to repr.
Editable view implementation.
TODO: insert short description here.
@ SP_ANCHOR_CENTER
Definition enums.h:28
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Macro for icon names used in Inkscape.
SPItem * item
@ SP_KNOT_MOUSEOVER
Definition knot-enums.h:31
Declarations for SPKnot: Desktop-bound visual control object.
Interface for locally managing a current status message.
Raw stack of active status messages.
@ ConnType_Orthogonal
The connector path will be a shortest-path orthogonal poly-line (only vertical and horizontal line se...
Definition connector.h:61
@ ConnType_PolyLine
The connector path will be a shortest-path poly-line that routes around obstacles.
Definition connector.h:57
static R & anchor(R &r)
Increments the reference count of a anchored object.
Definition gc-anchored.h:92
static R & release(R &r)
Decrements the reference count of a anchored object.
static void cc_select_handle(SPKnot *knot)
static bool cc_item_is_shape(SPItem *item)
bool cc_item_is_connector(SPItem *item)
static bool cc_generic_knot_handler(CanvasEvent const &event, SPKnot *knot)
SPItem * sp_event_context_find_item(SPDesktop *desktop, Geom::Point const &p, bool select_under, bool into_groups)
Returns item at point p in desktop.
static void cc_clear_active_knots(SPKnotList k)
void gobble_motion_events(unsigned mask)
Definition tool-base.h:244
unsigned get_latin_keyval(GtkEventControllerKey const *const controller, unsigned const keyval, unsigned const keycode, GdkModifierType const state, unsigned *consumed_modifiers)
Return the keyval corresponding to the event controller key in Latin group.
static void cc_deselect_handle(SPKnot *knot)
void cc_selection_set_avoid(SPDesktop *desktop, bool const set_avoid)
void sp_event_context_read(ToolBase *tool, char const *key)
Calls virtual set() function of ToolBase.
void cc_create_connection_point(ConnectorTool *cc)
static bool endpt_handler(CanvasEvent const &event, ConnectorTool *cc)
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
@ SNAPSOURCE_OTHER_HANDLE
Definition snap-enums.h:56
@ CANVAS_ITEM_CTRL_TYPE_SHAPER
@ NORMAL_MESSAGE
Definition message.h:26
@ WARNING_MESSAGE
Definition message.h:28
bool have_viable_layer(SPDesktop *desktop, MessageContext *message)
Check to see if the current layer is both unhidden and unlocked.
Ocnode * child[8]
Definition quantize.cpp:33
Contains the interface for the Router class.
double curvature(Point const &a, Point const &b, Point const &c)
void sp_conn_reroute_path_immediate(SPPath *const path)
void sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix)
TODO: insert short description here.
TODO: insert short description here.
guint32 GQuark
unsigned button
The button that was pressed/released. (Matches GDK_BUTTON_*.)
Geom::Point pos
Location of the cursor, in world coordinates.
A mouse button (left/right/middle) is pressed.
A mouse button (left/right/middle) is released.
Abstract base class for events.
unsigned modifiers
The modifiers mask immediately before the event.
The pointer has entered a widget or item.
A key has been pressed.
The pointer has exited a widget or item.
Movement of the mouse pointer.
Geom::Point pos
Location of the cursor.
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
Definition curve.h:24
@ SP_WIND_RULE_NONZERO
Definition style-enums.h:24
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
Definition svg-path.cpp:109
SPDesktop * desktop
Glib::ustring name
Definition toolbars.cpp:55
Interface for XML nodes.