Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
select-tool.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Selection and transformation context
4 *
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * bulia byak <buliabyak@users.sf.net>
8 * Abhishek Sharma
9 * Jon A. Cruz <jon@joncruz.org>
10 *
11 * Copyright (C) 2010 authors
12 * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl>
13 * Copyright (C) 1999-2005 Authors
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 */
17
18#ifdef HAVE_CONFIG_H
19# include "config.h" // only include where actually required!
20#endif
21
22#include <cstring>
23#include <string>
24
25#include <gtkmm/widget.h>
26#include <gdk/gdkkeysyms.h>
27#include <glibmm/i18n.h>
28
29#include "desktop.h"
30#include "document-undo.h"
31#include "document.h"
32#include "layer-manager.h"
33#include "message-stack.h"
34#include "rubberband.h"
35#include "selection-chemistry.h"
36#include "selection-describer.h"
37#include "selection.h"
38#include "seltrans.h"
39
40#include "actions/actions-tools.h" // set_active_tool()
41
46
47#include "object/box3d.h"
48#include "style.h"
49
50#include "ui/modifiers.h"
52#include "ui/widget/canvas.h"
54
57
58namespace Inkscape::UI::Tools {
59
60static gint rb_escaped = 0; // if non-zero, rubberband was canceled by esc, so the next button release should not deselect
61static gint drag_escaped = 0; // if non-zero, drag was canceled by esc
62static bool is_cycling = false;
63
65 : ToolBase(desktop, "/tools/select", "select.svg")
66{
67 auto select_click = Modifier::get(Modifiers::Type::SELECT_ADD_TO)->get_label();
68 auto select_scroll = Modifier::get(Modifiers::Type::SELECT_CYCLE)->get_label();
69
70 // cursors in select context
71 _default_cursor = "select.svg";
72
73 no_selection_msg = g_strdup_printf(
74 _("No objects selected. Click, %s+click, %s+scroll mouse on top of objects, or drag around objects to select."),
75 select_click.c_str(), select_scroll.c_str());
76
80 _("Click selection again to toggle scale/rotation handles"),
82
84
85 sp_event_context_read(this, "show");
86 sp_event_context_read(this, "transform");
87
89
90 if (prefs->getBool("/tools/select/gradientdrag")) {
92 }
93}
94
96{
97 enableGrDrag(false);
98
99 if (grabbed) {
100 grabbed->ungrab();
101 grabbed = nullptr;
102 }
103
104 delete _seltrans;
105 _seltrans = nullptr;
106
107 delete _describer;
108 _describer = nullptr;
109 g_free(no_selection_msg);
110
111 if (item) {
113 item = nullptr;
114 }
115}
116
118 Glib::ustring path = val.getEntryName();
119
120 if (path == "show") {
121 if (val.getString() == "outline") {
123 } else {
125 }
126 }
127}
128
130
131 if (dragging) {
132 if (moved) { // cancel dragging an object
133 _seltrans->ungrab();
134 moved = false;
135 dragging = false;
137 drag_escaped = 1;
138
139 if (item) {
140 // only undo if the item is still valid
141 if (item->document) {
143 }
144
145 sp_object_unref( item, nullptr);
146 }
147 item = nullptr;
148
149 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Move canceled."));
150 return true;
151 }
152 } else {
153 if (Inkscape::Rubberband::get(_desktop)->isStarted()) {
155 rb_escaped = 1;
157 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selection canceled."));
158 return true;
159 }
160 }
161 return false;
162}
163
164static bool
166 return (key == GDK_KEY_Alt_L ||
167 key == GDK_KEY_Alt_R ||
168 key == GDK_KEY_Control_L ||
169 key == GDK_KEY_Control_R ||
170 key == GDK_KEY_Shift_L ||
171 key == GDK_KEY_Shift_R ||
172 key == GDK_KEY_Meta_L || // Meta is when you press Shift+Alt (at least on my machine)
173 key == GDK_KEY_Meta_R);
174}
175
176static void
178{
179 /* Click in empty place, go up one level -- but don't leave a layer to root.
180 *
181 * (Rationale: we don't usually allow users to go to the root, since that
182 * detracts from the layer metaphor: objects at the root level can in front
183 * of or behind layers. Whereas it's fine to go to the root if editing
184 * a document that has no layers (e.g. a non-Inkscape document).)
185 *
186 * Once we support editing SVG "islands" (e.g. <svg> embedded in an xhtml
187 * document), we might consider further restricting the below to disallow
188 * leaving a layer to go to a non-layer.
189 */
190 if (SPObject *const current_layer = desktop->layerManager().currentLayer()) {
191 SPObject *const parent = current_layer->parent;
192 auto current_group = cast<SPGroup>(current_layer);
193 if ( parent
194 && ( parent->parent
195 || !( current_group
196 && ( SPGroup::LAYER == current_group->layerMode() ) ) ) )
197 {
199 if (current_group && (SPGroup::LAYER != current_group->layerMode())) {
200 desktop->getSelection()->set(current_layer);
201 }
202 }
203 }
204}
205
206bool SelectTool::item_handler(SPItem *local_item, CanvasEvent const &event)
207{
208 // Make sure we still have valid objects to move around.
209 if (item && item->document == nullptr) {
211 }
212
213 auto *prefs = Inkscape::Preferences::get();
214 tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
215
216 bool ret = false;
217
218 inspect_event(event,
219 [&] (ButtonPressEvent const &event) {
220 if (event.num_press == 1 && event.button == 1) {
221 /* Left mousebutton */
222
223 saveDragOrigin(event.pos);
224
225 // remember what modifiers were on before button press
226 button_press_state = event.modifiers;
227 bool remove_from = Modifier::get(Modifiers::Type::SELECT_REMOVE_FROM)->active(button_press_state);
228 bool force_drag = Modifier::get(Modifiers::Type::SELECT_FORCE_DRAG)->active(button_press_state);
229 bool always_box = Modifier::get(Modifiers::Type::SELECT_ALWAYS_BOX)->active(button_press_state);
230 bool touch_path = Modifier::get(Modifiers::Type::SELECT_TOUCH_PATH)->active(button_press_state);
231
232 // if shift or ctrl was pressed, do not move objects;
233 // pass the event to root handler which will perform rubberband, shift-click, ctrl-click, ctrl-drag
234 if (!(always_box || remove_from || touch_path)) {
235
236 dragging = true;
237 moved = false;
238
239 set_cursor("select-dragging.svg");
240
241 // Remember the clicked item in item:
242 if (item) {
243 sp_object_unref(item, nullptr);
244 item = nullptr;
245 }
246
247 item = sp_event_context_find_item (_desktop, event.pos, force_drag, false);
248 sp_object_ref(item, nullptr);
249
250 rb_escaped = drag_escaped = 0;
251
252 if (grabbed) {
253 grabbed->ungrab();
254 grabbed = nullptr;
255 }
256
257 grabbed = _desktop->getCanvasDrawing();
258 grabbed->grab(EventType::KEY_PRESS |
259 EventType::KEY_RELEASE |
260 EventType::BUTTON_PRESS |
261 EventType::BUTTON_RELEASE |
262 EventType::MOTION);
263
264 ret = true;
265 }
266 } else if (event.button == 3 && !dragging) {
267 // right click; do not eat it so that right-click menu can appear, but cancel dragging & rubberband
268 sp_select_context_abort();
269 }
270 },
271 [&] (EnterEvent const &event) {
272 if (!dragging && !_alt_on && !_desktop->isWaitingCursor()) {
273 set_cursor("select-mouseover.svg");
274 }
275 },
276 [&] (LeaveEvent const &event) {
277 if (!dragging && !_force_dragging && !_desktop->isWaitingCursor()) {
278 set_cursor("select.svg");
279 }
280 },
281 [&] (KeyPressEvent const &event) {
282 switch (get_latin_keyval (event)) {
283 case GDK_KEY_space:
284 if (dragging && grabbed) {
285 /* stamping mode: show content mode moving */
286 _seltrans->stamp();
287 ret = true;
288 }
289 break;
290 case GDK_KEY_Tab:
291 if (dragging && grabbed) {
292 _seltrans->getNextClosestPoint(false);
293 } else {
294 sp_selection_item_next(_desktop);
295 }
296 ret = true;
297 break;
298 case GDK_KEY_ISO_Left_Tab:
299 if (dragging && grabbed) {
300 _seltrans->getNextClosestPoint(true);
301 } else {
302 sp_selection_item_prev(_desktop);
303 }
304 ret = true;
305 break;
306 }
307 },
308 [&] (ButtonReleaseEvent const &event) {
309 if (_alt_on) {
310 _default_cursor = "select-mouseover.svg";
311 }
312 },
313 [&] (KeyReleaseEvent const &event) {
314 if (_alt_on) {
315 _default_cursor = "select-mouseover.svg";
316 }
317 },
318 [&] (CanvasEvent const &event) {}
319 );
320
321 return ret || ToolBase::item_handler(item, event);
322}
323
324void SelectTool::sp_select_context_cycle_through_items(Selection *selection, ScrollEvent const &scroll_event)
325{
326 if ( cycling_items.empty() )
327 return;
328
329 Inkscape::DrawingItem *arenaitem;
330
331 if(cycling_cur_item) {
332 arenaitem = cycling_cur_item->get_arenaitem(_desktop->dkey);
333 arenaitem->setOpacity(0.3);
334 }
335
336 // Find next item and activate it
337
338
339 std::vector<SPItem *>::iterator next = cycling_items.end();
340
341 if (scroll_event.delta.y() < 0) {
342 if (! cycling_cur_item) {
343 next = cycling_items.begin();
344 } else {
345 next = std::find( cycling_items.begin(), cycling_items.end(), cycling_cur_item );
346 g_assert (next != cycling_items.end());
347 ++next;
348 if (next == cycling_items.end()) {
349 if ( cycling_wrap ) {
350 next = cycling_items.begin();
351 } else {
352 --next;
353 }
354 }
355 }
356 } else if (scroll_event.delta.y() > 0) {
357 if (! cycling_cur_item) {
358 next = cycling_items.end();
359 --next;
360 } else {
361 next = std::find( cycling_items.begin(), cycling_items.end(), cycling_cur_item );
362 g_assert (next != cycling_items.end());
363 if (next == cycling_items.begin()){
364 if ( cycling_wrap ) {
365 next = cycling_items.end();
366 --next;
367 }
368 } else {
369 --next;
370 }
371 }
372 }
373
374 cycling_cur_item = *next;
375 g_assert(next != cycling_items.end());
376 g_assert(cycling_cur_item != nullptr);
377
378 arenaitem = cycling_cur_item->get_arenaitem(_desktop->dkey);
379 arenaitem->setOpacity(1.0);
380
381 if (Modifier::get(Modifiers::Type::SELECT_ADD_TO)->active(scroll_event.modifiers)) {
382 selection->add(cycling_cur_item);
383 } else {
384 selection->set(cycling_cur_item);
385 }
386}
387
388void SelectTool::sp_select_context_reset_opacities() {
389 for (auto item : cycling_items_cmp) {
390 if (item) {
391 Inkscape::DrawingItem *arenaitem = item->get_arenaitem(_desktop->dkey);
392 arenaitem->setOpacity(SP_SCALE24_TO_FLOAT(item->style->opacity.value));
393 } else {
394 g_assert_not_reached();
395 }
396 }
397
398 cycling_items_cmp.clear();
399 cycling_cur_item = nullptr;
400}
401
402bool SelectTool::root_handler(CanvasEvent const &event)
403{
404 SPItem *item_at_point = nullptr, *group_at_point = nullptr, *item_in_group = nullptr;
405
406 auto selection = _desktop->getSelection();
407 auto prefs = Preferences::get();
408
409 // make sure we still have valid objects to move around
410 if (item && item->document == nullptr) {
411 sp_select_context_abort();
412 }
413
414 bool ret = false;
415
416 inspect_event(event,
417 [&] (ButtonPressEvent const &event) {
418 if (event.num_press == 2 && event.button == 1) {
419 if (!selection->isEmpty()) {
420 SPItem *clicked_item = selection->items().front();
421
422 if (is<SPGroup>(clicked_item) && !is<SPBox3D>(clicked_item)) { // enter group if it's not a 3D box
423 _desktop->layerManager().setCurrentLayer(clicked_item);
424 _desktop->getSelection()->clear();
425 dragging = false;
426 discard_delayed_snap_event();
427
428 } else { // switch tool
429 Geom::Point const p(_desktop->w2d(event.pos));
430 set_active_tool(_desktop, clicked_item, p);
431 }
432 } else {
433 sp_select_context_up_one_layer(_desktop);
434 }
435
436 ret = true;
437 }
438 if (event.num_press == 1 && event.button == 1) {
439
440 saveDragOrigin(event.pos);
441
442 auto rubberband = Inkscape::Rubberband::get(_desktop);
443 if (Modifier::get(Modifiers::Type::SELECT_TOUCH_PATH)->active(event.modifiers)) {
444 rubberband->setMode(Rubberband::Mode::TOUCHPATH);
446 } else {
447 auto const [mode, handle] = get_default_rubberband_state();
448 rubberband->setMode(mode);
449 rubberband->setHandle(handle);
450 }
451
452 Geom::Point const p(_desktop->w2d(event.pos));
453 rubberband->start(_desktop, p);
454
455 if (grabbed) {
456 grabbed->ungrab();
457 grabbed = nullptr;
458 }
459
460 grabbed = _desktop->getCanvasCatchall();
461 grabbed->grab(EventType::KEY_PRESS |
466
467 // remember what modifiers were on before button press
468 button_press_state = event.modifiers;
469
470 moved = false;
471
473
474 ret = true;
475 } else if (event.button == 3) {
476 // right click; do not eat it so that right-click menu can appear, but cancel dragging & rubberband
477 sp_select_context_abort();
478 }
479 },
480 [&] (MotionEvent const &event) {
481 if (grabbed && event.modifiers & (GDK_SHIFT_MASK | GDK_ALT_MASK)) {
482 _desktop->getSnapIndicator()->remove_snaptarget();
483 }
484
485 tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
486
487 bool force_drag = Modifier::get(Modifiers::Type::SELECT_FORCE_DRAG)->active(button_press_state);
488 bool always_box = Modifier::get(Modifiers::Type::SELECT_ALWAYS_BOX)->active(button_press_state);
489
490 if (event.modifiers & GDK_BUTTON1_MASK) {
491 if (!checkDragMoved(event.pos)) {
492 return;
493 }
494
495 Geom::Point const p(_desktop->w2d(event.pos));
496
497 if (force_drag && !always_box && !selection->isEmpty()) {
498 // if it's not click and alt was pressed (with some selection
499 // but not with shift) we want to drag rather than rubberband
500 dragging = true;
501 set_cursor("select-dragging.svg");
502 }
503
504 if (dragging) {
505 /* User has dragged fast, so we get events on root (lauris)*/
506 // not only that; we will end up here when ctrl-dragging as well
507 // and also when we started within tolerance, but trespassed tolerance outside of item
508 if (Inkscape::Rubberband::get(_desktop)->isStarted()) {
509 Inkscape::Rubberband::get(_desktop)->stop();
510 }
511 defaultMessageContext()->clear();
512
513 // Look for an item where the mouse was reported to be by mouse press (not mouse move).
514 item_at_point = _desktop->getItemAtPoint(xyp, false);
515
516 if (item_at_point || moved || force_drag) {
517 // drag only if starting from an item, or if something is already grabbed, or if alt-dragging
518 if (!moved) {
519 item_in_group = _desktop->getItemAtPoint(event.pos, true);
520 group_at_point = _desktop->getGroupAtPoint(event.pos);
521
522 {
523 auto selGroup = cast<SPGroup>(selection->single());
524 if (selGroup && (selGroup->layerMode() == SPGroup::LAYER)) {
525 group_at_point = selGroup;
526 }
527 }
528
529 // group-at-point is meant to be topmost item if it's a group,
530 // not topmost group of all items at point
531 if (group_at_point != item_in_group &&
532 !(group_at_point && item_at_point &&
533 group_at_point->isAncestorOf(item_at_point))) {
534 group_at_point = nullptr;
535 }
536
537 // if neither a group nor an item (possibly in a group) at point are selected, set selection to the item at point
538 if ((!item_in_group || !selection->includes(item_in_group)) &&
539 (!group_at_point || !selection->includes(group_at_point)) && !force_drag) {
540 // select what is under cursor
541 if (!_seltrans->isEmpty()) {
542 _seltrans->resetState();
543 }
544
545 // when simply ctrl-dragging, we don't want to go into groups
546 if (item_at_point && !selection->includes(item_at_point)) {
547 selection->set(item_at_point);
548 }
549 } // otherwise, do not change selection so that dragging selected-within-group items, as well as alt-dragging, is possible
550
551 _seltrans->grab(p, -1, -1, false, true);
552 moved = true;
553 }
554
555 if (!_seltrans->isEmpty()) {
556 // discard_delayed_snap_event();
557 _seltrans->moveTo(p, event.modifiers);
558 }
559
560 _desktop->getCanvas()->enable_autoscroll();
561 gobble_motion_events(GDK_BUTTON1_MASK);
562 ret = true;
563 } else {
564 dragging = false;
565 discard_delayed_snap_event();
566 }
567
568 } else {
569 if (Inkscape::Rubberband::get(_desktop)->isStarted()) {
570 auto rubberband = Inkscape::Rubberband::get(_desktop);
571 rubberband->move(p);
572
573 // set selection color
574 if (Modifier::get(Modifiers::Type::SELECT_REMOVE_FROM)->active(event.modifiers)) {
575 rubberband->setOperation(Rubberband::Operation::REMOVE);
576 } else {
577 rubberband->setOperation(Rubberband::Operation::ADD);
578 }
579
580 auto touch_path = Modifier::get(Modifiers::Type::SELECT_TOUCH_PATH)->get_label();
581 auto remove_from = Modifier::get(Modifiers::Type::SELECT_REMOVE_FROM)->get_label();
582 auto mode = Inkscape::Rubberband::get(_desktop)->getMode();
584 defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
585 _("<b>Draw over</b> objects to select them; press <b>%s</b> to deselect them; release <b>%s</b> to switch to rubberband selection"), remove_from.c_str(), touch_path.c_str());
586 } else if (mode == Rubberband::Mode::TOUCHRECT) {
587 defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
588 _("<b>Drag near</b> objects to select them; press <b>%s</b> to deselect them; press <b>%s</b> to switch to touch selection"), remove_from.c_str(), touch_path.c_str());
589 } else {
590 defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE,
591 _("<b>Drag around</b> objects to select them; press <b>%s</b> to deselect them; press <b>%s</b> to switch to touch selection"), remove_from.c_str(), touch_path.c_str());
592 }
593
594 gobble_motion_events(GDK_BUTTON1_MASK);
595 }
596 }
597 }
598 },
599 [&] (ButtonReleaseEvent const &event) {
600 xyp = {};
601
602 if ((event.button == 1) && (grabbed)) {
603 if (dragging) {
604 if (moved) {
605 // item has been moved
606 _seltrans->ungrab();
607 moved = false;
608 } else if (item && !drag_escaped) {
609 // item has not been moved -> simply a click, do selecting
610 if (!selection->isEmpty()) {
611 if(Modifier::get(Modifiers::Type::SELECT_ADD_TO)->active(event.modifiers)) {
612 // with shift, toggle selection
613 _seltrans->resetState();
614 selection->toggle(item);
615 } else {
616 SPObject* single = selection->single();
617 auto singleGroup = cast<SPGroup>(single);
618 // without shift, increase state (i.e. toggle scale/rotation handles)
619 if (selection->includes(item)) {
620 _seltrans->increaseState();
621 } else if (singleGroup && (singleGroup->layerMode() == SPGroup::LAYER) && single->isAncestorOf(item)) {
622 _seltrans->increaseState();
623 } else {
624 _seltrans->resetState();
625 selection->set(item);
626 }
627 }
628 } else { // simple or shift click, no previous selection
629 _seltrans->resetState();
630 selection->set(item);
631 }
632 }
633
634 dragging = false;
635
636 if (!_alt_on) {
637 if (_force_dragging) {
638 set_cursor(_default_cursor);
639 _force_dragging = false;
640 } else {
641 set_cursor("select-mouseover.svg");
642 }
643 }
644
645 discard_delayed_snap_event();
646
647 if (item) {
648 sp_object_unref( item, nullptr);
649 }
650
651 item = nullptr;
652 } else {
654
655 if (r->isStarted() && !within_tolerance) {
656 // this was a rubberband drag
657 set_cursor(_default_cursor);
658 std::vector<SPItem*> items;
659
660 if (r->getMode() == Rubberband::Mode::RECT) {
661 Geom::OptRect const b = r->getRectangle();
662 items = _desktop->getDocument()->getItemsInBox(_desktop->dkey, (*b) * _desktop->dt2doc());
663 } else if (r->getMode() == Rubberband::Mode::TOUCHRECT) {
664 Geom::OptRect const b = r->getRectangle();
665 items = _desktop->getDocument()->getItemsPartiallyInBox(_desktop->dkey, (*b) * _desktop->dt2doc());
666 } else if (r->getMode() == Rubberband::Mode::TOUCHPATH) {
667 bool topmost_items_only = prefs->getBool("/options/selection/touchsel_topmost_only");
668 items = _desktop->getDocument()->getItemsAtPoints(_desktop->dkey, r->getPoints(), true, topmost_items_only);
669 }
670
671 _seltrans->resetState();
672 r->stop();
673 defaultMessageContext()->clear();
674
675 if (Modifier::get(Modifiers::Type::SELECT_REMOVE_FROM)->active(event.modifiers)) {
676 // with ctrl and shift, remove from selection
677 selection->removeList(items);
678 } else if (Modifier::get(Modifiers::Type::SELECT_ADD_TO)->active(event.modifiers)) {
679 // with shift, add to selection
680 selection->addList(items);
681 } else {
682 // without shift, simply select anew
683 selection->setList(items);
684 }
685
686 } else { // it was just a click, or a too small rubberband
687 r->stop();
688
689 bool add_to = Modifier::get(Modifiers::Type::SELECT_ADD_TO)->active(event.modifiers);
690 bool in_groups = Modifier::get(Modifiers::Type::SELECT_IN_GROUPS)->active(event.modifiers);
691 bool force_drag = Modifier::get(Modifiers::Type::SELECT_FORCE_DRAG)->active(event.modifiers);
692
693 if (add_to && !rb_escaped && !drag_escaped) {
694 // this was a shift+click or alt+shift+click, select what was clicked upon
695
696 SPItem *local_item = nullptr;
697
698 if (in_groups) {
699 // go into groups, honoring force_drag (Alt)
700 local_item = sp_event_context_find_item (_desktop, event.pos, force_drag, true);
701 } else {
702 // don't go into groups, honoring Alt
703 local_item = sp_event_context_find_item (_desktop, event.pos, force_drag, false);
704 }
705
706 if (local_item) {
707 selection->toggle(local_item);
708 }
709
710 } else if ((in_groups || force_drag) && !rb_escaped && !drag_escaped) { // ctrl+click, alt+click
711 SPItem *local_item = sp_event_context_find_item (_desktop, event.pos, force_drag, in_groups);
712
713 if (local_item) {
714 if (selection->includes(local_item)) {
715 _seltrans->increaseState();
716 } else {
717 _seltrans->resetState();
718 selection->set(local_item);
719 }
720 }
721 } else { // click without shift, simply deselect, unless with Alt or something was cancelled
722 if (!selection->isEmpty()) {
723 if (!(rb_escaped) && !(drag_escaped) && !force_drag) {
724 selection->clear();
725 }
726
727 rb_escaped = 0;
728 }
729 }
730 }
731
732 ret = true;
733 }
734 if (grabbed) {
735 grabbed->ungrab();
736 grabbed = nullptr;
737 }
738 }
739
740 if (event.button == 1) {
741 Inkscape::Rubberband::get(_desktop)->stop(); // might have been started in another tool!
742 }
743
744 button_press_state = 0;
745 },
746 [&] (ScrollEvent const &event) {
747 // do nothing specific if alt was not pressed
748 if ( ! Modifier::get(Modifiers::Type::SELECT_CYCLE)->active(event.modifiers)) {
749 return;
750 }
751
752 is_cycling = true;
753
754 /* Rebuild list of items underneath the mouse pointer */
755 Geom::Point p = _desktop->d2w(_desktop->point());
756 SPItem *local_item = _desktop->getItemAtPoint(p, true, nullptr);
757 cycling_items.clear();
758
759 SPItem *tmp = nullptr;
760 while(local_item != nullptr) {
761 cycling_items.push_back(local_item);
762 local_item = _desktop->getItemAtPoint(p, true, local_item);
763 if (local_item && selection->includes(local_item)) tmp = local_item;
764 }
765
766 /* Compare current item list with item list during previous scroll ... */
767 bool item_lists_differ = cycling_items != cycling_items_cmp;
768
769 if(item_lists_differ) {
770 sp_select_context_reset_opacities();
771 for (auto l : cycling_items_cmp)
772 selection->remove(l); // deselects the previous content of the cycling loop
773 cycling_items_cmp = (cycling_items);
774
775 // set opacities in new stack
776 for(auto cycling_item : cycling_items) {
777 if (cycling_item) {
778 Inkscape::DrawingItem *arenaitem = cycling_item->get_arenaitem(_desktop->dkey);
779 arenaitem->setOpacity(0.3);
780 }
781 }
782 }
783 if(!cycling_cur_item) cycling_cur_item = tmp;
784
785 cycling_wrap = prefs->getBool("/options/selection/cycleWrap", true);
786
787 // Cycle through the items underneath the mouse pointer, one-by-one
788 sp_select_context_cycle_through_items(selection, event);
789
790 ret = true;
791
792 // TODO Simplify this (or remove it, if canvas exists, window must exist).
793 GtkWindow *w = GTK_WINDOW(gtk_widget_get_root(_desktop->getCanvas()->Gtk::Widget::gobj()));
794 if (w) {
795 gtk_window_present(w);
796 _desktop->getCanvas()->grab_focus();
797 }
798 },
799 [&] (KeyPressEvent const &event) {
800 auto keyval = get_latin_keyval (event);
801
802 // Workaround for non-working modifiers code
803 // TODO check what the Option key emits
804 bool alt = keyval == GDK_KEY_Alt_L ||
805 keyval == GDK_KEY_Alt_R ||
806 keyval == GDK_KEY_Meta_L ||
807 keyval == GDK_KEY_Meta_R;
808 if (alt) {
809 _alt_on = true; // Turn off in KeyReleaseEvent
810 }
811
812 if (!key_is_a_modifier (keyval)) {
813 defaultMessageContext()->clear();
814 } else if (grabbed || _seltrans->isGrabbed()) {
815 if (auto rubberband = Inkscape::Rubberband::get(_desktop); rubberband->isStarted()) {
816 // if Ctrl then change rubberband operation to remove (changes color)
817 if (Modifier::get(Modifiers::Type::SELECT_REMOVE_FROM)->active(event.modifiersAfter())) {
818 rubberband->setOperation(Rubberband::Operation::REMOVE);
819 // update the rubberband
820 rubberband->move(_desktop->point());
821 }
822 // if Alt then change mode to touch path mode
823 if (Modifier::get(Modifiers::Type::SELECT_TOUCH_PATH)->active(event.modifiersAfter())) {
824 rubberband->setMode(Rubberband::Mode::TOUCHPATH);
826 }
827 } else {
828 // do not change the statusbar text when mousekey is down to move or transform the object,
829 // because the statusbar text is already updated somewhere else.
830 return;
831 }
832 } else {
833 Modifiers::responsive_tooltip(defaultMessageContext(), event, 6,
837
838 // if Alt and nonempty selection, show moving cursor ("move selected"):
839 if (alt && !selection->isEmpty() && !_desktop->isWaitingCursor()) {
840 set_cursor("select-dragging.svg");
841 _force_dragging = true;
842 _default_cursor = "select.svg";
843 }
844 return;
845 }
846
847 gdouble const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px
848 auto const y_dir = _desktop->yaxisdir();
849
850 bool const rotated = prefs->getBool("/options/moverotated/value", true);
851
852 double delta = 1.0;
853 if (mod_shift(event)) { // shift
854 delta = 10.0;
855 }
856
857 bool screen = true;
858 if (!mod_alt(event)) { // no alt
859 delta *= nudge;
860 screen = false;
861 }
862
863 int const mul = 1 + gobble_key_events(keyval, 0);
864
865 switch (keyval) {
866 case GDK_KEY_Left: // move selection left
867 case GDK_KEY_KP_Left:
868 if (!mod_ctrl(event)) { // not ctrl
869 _desktop->getSelection()->move(-delta * mul, 0, rotated, screen);
870 ret = true;
871 }
872 break;
873
874 case GDK_KEY_Up: // move selection up
875 case GDK_KEY_KP_Up:
876 if (!mod_ctrl(event)) { // not ctrl
877 _desktop->getSelection()->move(0, -delta * mul * y_dir, rotated, screen);
878 ret = true;
879 }
880 break;
881
882 case GDK_KEY_Right: // move selection right
883 case GDK_KEY_KP_Right:
884 if (!mod_ctrl(event)) { // not ctrl
885 _desktop->getSelection()->move(delta * mul, 0, rotated, screen);
886 ret = true;
887 }
888 break;
889
890 case GDK_KEY_Down: // move selection down
891 case GDK_KEY_KP_Down:
892 if (!mod_ctrl(event)) { // not ctrl
893 _desktop->getSelection()->move(0, delta * mul * y_dir, rotated, screen);
894 ret = true;
895 }
896 break;
897
898 case GDK_KEY_Escape:
899 if (!sp_select_context_abort()) {
900 selection->clear();
901 }
902
903 ret = true;
904 break;
905
906 case GDK_KEY_a:
907 case GDK_KEY_A:
908 if (mod_ctrl_only(event)) {
909 sp_edit_select_all(_desktop);
910 ret = true;
911 }
912 break;
913
914 case GDK_KEY_space:
915 case GDK_KEY_c:
916 case GDK_KEY_C:
917 /* stamping mode: show outline mode moving */
918 if (dragging && grabbed) {
919 _seltrans->stamp(keyval != GDK_KEY_space);
920 ret = true;
921 }
922 break;
923
924 case GDK_KEY_x:
925 case GDK_KEY_X:
926 if (mod_alt_only(event)) {
927 _desktop->setToolboxFocusTo("select-x");
928 ret = true;
929 }
930 break;
931
932 case GDK_KEY_Return:
933 if (mod_ctrl_only(event)) {
934 if (selection->singleItem()) {
935 SPItem *clicked_item = selection->singleItem();
936 auto clickedGroup = cast<SPGroup>(clicked_item);
937 if ( (clickedGroup && (clickedGroup->layerMode() != SPGroup::LAYER)) || is<SPBox3D>(clicked_item)) { // enter group or a 3D box
938 _desktop->layerManager().setCurrentLayer(clicked_item);
939 _desktop->getSelection()->clear();
940 } else {
941 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selected object is not a group. Cannot enter."));
942 }
943 }
944
945 ret = true;
946 }
947 break;
948
949 case GDK_KEY_BackSpace:
950 if (mod_ctrl_only(event)) {
952 ret = true;
953 }
954 break;
955
956 case GDK_KEY_s:
957 case GDK_KEY_S:
958 if (mod_shift_only(event)) {
959 if (!selection->isEmpty()) {
960 _seltrans->increaseState();
961 }
962
963 ret = true;
964 }
965 break;
966
967 case GDK_KEY_g:
968 case GDK_KEY_G:
969 if (mod_shift_only(event)) {
970 _desktop->getSelection()->toGuides();
971 ret = true;
972 }
973 break;
974
975 default:
976 break;
977 }
978 },
979 [&] (KeyReleaseEvent const &event) {
980 auto keyval = get_latin_keyval(event);
981
982 if (key_is_a_modifier (keyval)) {
983 defaultMessageContext()->clear();
984 }
985
986 // Workaround for non-working modifier detection
987 bool alt = keyval == GDK_KEY_Alt_L ||
988 keyval == GDK_KEY_Alt_R ||
989 keyval == GDK_KEY_Meta_L ||
990 keyval == GDK_KEY_Meta_R;
991 if (alt) {
992 _alt_on = false; // Turned on in KeyPressEvent
993 }
994
995 if (auto rubberband = Inkscape::Rubberband::get(_desktop); rubberband->isStarted()) {
996 // if Alt release then change mode back to default
997 if (alt) {
998 auto const [mode, handle] = get_default_rubberband_state();
999 rubberband->setMode(mode);
1000 rubberband->setHandle(handle);
1001 }
1002 // if Ctrl release then change rubberband operation to add
1003 if (!Modifier::get(Modifiers::Type::SELECT_REMOVE_FROM)->active(event.modifiersAfter())) {
1004 rubberband->setOperation(Rubberband::Operation::ADD);
1005 // update the rubberband
1006 rubberband->move(_desktop->point());
1007 }
1008 } else {
1009 if (alt) {
1010 // quit cycle-selection and reset opacities
1011 if (is_cycling) {
1012 sp_select_context_reset_opacities();
1013 is_cycling = false;
1014 }
1015 }
1016 }
1017
1018 // set cursor to default.
1019 if (alt && !(grabbed || _seltrans->isGrabbed()) && !selection->isEmpty() && !_desktop->isWaitingCursor()) {
1020 set_cursor(_default_cursor);
1021 _force_dragging = false;
1022 }
1023 },
1024 [&] (CanvasEvent const &event) {}
1025 );
1026
1027 return ret || ToolBase::root_handler(event);
1028}
1029
1033void SelectTool::updateDescriber(Inkscape::Selection *selection)
1034{
1035 _describer->updateMessage(selection);
1036}
1037
1041std::pair<Rubberband::Mode, CanvasItemCtrlType> SelectTool::get_default_rubberband_state()
1042{
1044 auto handle = Rubberband::default_handle;
1045 if (Inkscape::Preferences::get()->getBool("/tools/select/touch_box", false)) {
1048 }
1049 return {mode, handle};
1050}
1051
1052} // namespace Inkscape::UI::Tools
1053
1054/*
1055 Local Variables:
1056 mode:c++
1057 c-file-style:"stroustrup"
1058 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1059 indent-tabs-mode:nil
1060 fill-column:99
1061 End:
1062*/
1063// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
Inkscape canvas widget.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Two-dimensional point that doubles as a vector.
Definition point.h:66
static gboolean undo(SPDocument *document)
SVG drawing item for display.
void setOpacity(float opacity)
void setCurrentLayer(SPObject *object, bool clear=false)
Sets the current layer of the desktop.
SPGroup * currentLayer() const
Returns current top layer.
void clear()
removes our current message from the stack
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
A class to represent ways functionality is driven by shift modifiers.
Definition modifiers.h:103
Data type representing a typeless value of a preference.
Glib::ustring getEntryName() const
Get the last component of the preference's path.
Glib::ustring getString(Glib::ustring const &def="") const
Interpret the preference as an UTF-8 string.
Preference storage class.
Definition preferences.h:61
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
Rubberbanding selector.
Definition rubberband.h:35
static constexpr auto default_mode
Definition rubberband.h:59
std::vector< Geom::Point > getPoints() const
static Rubberband * get(SPDesktop *desktop)
static constexpr auto default_handle
Definition rubberband.h:61
Geom::OptRect getRectangle() const
Rubberband::Mode getMode() const
Definition rubberband.h:55
bool isStarted() const
Definition rubberband.h:52
void setShow(Show s)
Definition seltrans.h:86
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
void add(XML::Node *repr)
Add an XML node's SPObject to the set of selected objects.
Definition selection.h:107
void set(XML::Node *repr)
Set the selection to an XML node's SPObject.
Definition selection.h:118
SelectionDescriber * _describer
Definition select-tool.h:47
bool item_handler(SPItem *item, CanvasEvent const &event) override
Handles item specific events.
SelectTool(SPDesktop *desktop)
void set(Preferences::Entry const &val) override
Called by our pref_observer if a preference has been changed.
Base class for Event processors.
Definition tool-base.h:107
bool dragging
are we dragging?
Definition tool-base.h:146
virtual bool item_handler(SPItem *item, CanvasEvent const &event)
Handles item specific events.
void enableGrDrag(bool enable=true)
void discard_delayed_snap_event()
If a delayed snap event has been scheduled, this function will cancel it.
MessageContext * defaultMessageContext() const
Definition tool-base.h:123
To do: update description of desktop.
Definition desktop.h:149
SPDocument * getDocument() const
Definition desktop.h:189
Inkscape::MessageStack * messageStack() const
Definition desktop.h:160
Inkscape::Selection * getSelection() const
Definition desktop.h:188
Inkscape::LayerManager & layerManager()
Definition desktop.h:287
Base class for visual SVG elements.
Definition sp-item.h:109
Inkscape::DrawingItem * get_arenaitem(unsigned key) const
Return the arenaitem corresponding to the given item in the display with the given key.
Definition sp-item.cpp:1864
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPDocument * document
Definition sp-object.h:188
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
SPObject * parent
Definition sp-object.h:189
bool isAncestorOf(SPObject const *object) const
True if object is non-NULL and this is some in/direct parent of object.
T< SPAttr::OPACITY, SPIScale24 > opacity
opacity
Definition style.h:216
const double w
Definition conic-4.cpp:19
Editable view implementation.
static char const *const parent
Definition dir-util.cpp:70
TODO: insert short description here.
Canvas item belonging to an SVG drawing element.
SPItem * item
Raw stack of active status messages.
void responsive_tooltip(MessageContext *message_context, KeyEvent const &event, int num_args,...)
Set the responsive tooltip for this tool, given the selected types.
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.
void gobble_motion_events(unsigned mask)
Definition tool-base.h:244
static bool key_is_a_modifier(guint key)
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 sp_select_context_up_one_layer(SPDesktop *desktop)
static gint drag_escaped
static bool is_cycling
int gobble_key_events(unsigned keyval, unsigned mask)
Definition tool-base.h:243
void sp_event_context_read(ToolBase *tool, char const *key)
Calls virtual set() function of ToolBase.
static gint rb_escaped
bool mod_alt(unsigned modifiers)
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
bool mod_ctrl_only(unsigned modifiers)
bool mod_ctrl(unsigned modifiers)
bool mod_shift_only(unsigned modifiers)
@ RUBBERBAND_TOUCHPATH_SELECT
bool mod_shift(unsigned modifiers)
bool mod_alt_only(unsigned modifiers)
@ NORMAL_MESSAGE
Definition message.h:26
static cairo_user_data_key_t key
int mode
GList * items
void remove(std::vector< T > &vec, T const &val)
Definition sanitize.cpp:94
void sp_selection_item_next(SPDesktop *desktop)
void sp_selection_item_prev(SPDesktop *desktop)
void sp_edit_select_all(SPDesktop *desktop)
Provides a class that shows a temporary indicator on the canvas of where the snap was,...
SPObject * sp_object_unref(SPObject *object, SPObject *owner)
Decrease reference count of object, with possible debugging and finalization.
A mouse button (left/right/middle) is pressed.
Abstract base class for events.
unsigned modifiers
The modifiers mask immediately before the event.
The pointer has entered a widget or item.
The pointer has exited a widget or item.
Movement of the mouse pointer.
Scroll the item or widget by the provided amount.
SPStyle - a style object for SPItem objects.
int delta
SPDesktop * desktop
static gboolean scroll_event(GtkEventControllerScroll *self, double dx, double dy, gpointer user_data)