1 /*
2 * Selection and transformation context
3 *
4 * Authors:
5 * Lauris Kaplinski <lauris@kaplinski.com>
6 * bulia byak <buliabyak@users.sf.net>
7 * Abhishek Sharma
8 * Jon A. Cruz <jon@joncruz.org>
9 *
10 * Copyright (C) 2010 authors
11 * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl>
12 * Copyright (C) 1999-2005 Authors
13 *
14 * Released under GNU GPL, read the file 'COPYING' for more information
15 */
17 #ifdef HAVE_CONFIG_H
18 # include "config.h"
19 #endif
20 #include <cstring>
21 #include <string>
22 #include <gdk/gdkkeysyms.h>
23 #include "macros.h"
24 #include "rubberband.h"
25 #include "document.h"
26 #include "selection.h"
27 #include "seltrans-handles.h"
28 #include "sp-cursor.h"
29 #include "pixmaps/cursor-select-m.xpm"
30 #include "pixmaps/cursor-select-d.xpm"
31 #include "pixmaps/handles.xpm"
32 #include <glibmm/i18n.h>
34 #include "select-context.h"
35 #include "selection-chemistry.h"
36 #include "desktop.h"
37 #include "desktop-handles.h"
38 #include "sp-root.h"
39 #include "preferences.h"
40 #include "tools-switch.h"
41 #include "message-stack.h"
42 #include "selection-describer.h"
43 #include "seltrans.h"
44 #include "box3d.h"
45 #include "display/sp-canvas.h"
46 #include "display/nr-arena-item.h"
48 using Inkscape::DocumentUndo;
50 static void sp_select_context_class_init(SPSelectContextClass *klass);
51 static void sp_select_context_init(SPSelectContext *select_context);
52 static void sp_select_context_dispose(GObject *object);
54 static void sp_select_context_setup(SPEventContext *ec);
55 static void sp_select_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val);
56 static gint sp_select_context_root_handler(SPEventContext *event_context, GdkEvent *event);
57 static gint sp_select_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
59 static SPEventContextClass *parent_class;
61 static GdkCursor *CursorSelectMouseover = NULL;
62 static GdkCursor *CursorSelectDragging = NULL;
63 GdkPixbuf *handles[13];
65 static gint rb_escaped = 0; // if non-zero, rubberband was canceled by esc, so the next button release should not deselect
66 static gint drag_escaped = 0; // if non-zero, drag was canceled by esc
68 static gint xp = 0, yp = 0; // where drag started
69 static gint tolerance = 0;
70 static bool within_tolerance = false;
72 GtkType
73 sp_select_context_get_type(void)
74 {
75 static GType type = 0;
76 if (!type) {
77 GTypeInfo info = {
78 sizeof(SPSelectContextClass),
79 NULL, NULL,
80 (GClassInitFunc) sp_select_context_class_init,
81 NULL, NULL,
82 sizeof(SPSelectContext),
83 4,
84 (GInstanceInitFunc) sp_select_context_init,
85 NULL, /* value_table */
86 };
87 type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPSelectContext", &info, (GTypeFlags)0);
88 }
89 return type;
90 }
92 static void
93 sp_select_context_class_init(SPSelectContextClass *klass)
94 {
95 GObjectClass *object_class = (GObjectClass *) klass;
96 SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
98 parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
100 object_class->dispose = sp_select_context_dispose;
102 event_context_class->setup = sp_select_context_setup;
103 event_context_class->set = sp_select_context_set;
104 event_context_class->root_handler = sp_select_context_root_handler;
105 event_context_class->item_handler = sp_select_context_item_handler;
106 }
108 static void
109 sp_select_context_init(SPSelectContext *sc)
110 {
111 sc->dragging = FALSE;
112 sc->moved = FALSE;
113 sc->button_press_shift = false;
114 sc->button_press_ctrl = false;
115 sc->button_press_alt = false;
116 sc->cycling_items = NULL;
117 sc->cycling_items_selected_before = NULL;
118 sc->cycling_cur_item = NULL;
119 sc->_seltrans = NULL;
120 sc->_describer = NULL;
122 // cursors in select context
123 CursorSelectMouseover = sp_cursor_new_from_xpm(cursor_select_m_xpm , 1, 1);
124 CursorSelectDragging = sp_cursor_new_from_xpm(cursor_select_d_xpm , 1, 1);
125 // selection handles
126 handles[0] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_scale_nw_xpm);
127 handles[1] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_scale_ne_xpm);
128 handles[2] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_scale_h_xpm);
129 handles[3] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_scale_v_xpm);
130 handles[4] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_nw_xpm);
131 handles[5] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_n_xpm);
132 handles[6] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_ne_xpm);
133 handles[7] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_e_xpm);
134 handles[8] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_se_xpm);
135 handles[9] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_s_xpm);
136 handles[10] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_sw_xpm);
137 handles[11] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_w_xpm);
138 handles[12] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_center_xpm);
139 }
141 static void
142 sp_select_context_dispose(GObject *object)
143 {
144 SPSelectContext *sc = SP_SELECT_CONTEXT(object);
145 SPEventContext * ec = SP_EVENT_CONTEXT (object);
147 ec->enableGrDrag(false);
149 if (sc->grabbed) {
150 sp_canvas_item_ungrab(sc->grabbed, GDK_CURRENT_TIME);
151 sc->grabbed = NULL;
152 }
154 delete sc->_seltrans;
155 sc->_seltrans = NULL;
156 delete sc->_describer;
157 sc->_describer = NULL;
159 if (CursorSelectDragging) {
160 gdk_cursor_unref (CursorSelectDragging);
161 CursorSelectDragging = NULL;
162 }
163 if (CursorSelectMouseover) {
164 gdk_cursor_unref (CursorSelectMouseover);
165 CursorSelectMouseover = NULL;
166 }
168 G_OBJECT_CLASS(parent_class)->dispose(object);
169 }
171 static void
172 sp_select_context_setup(SPEventContext *ec)
173 {
174 SPSelectContext *select_context = SP_SELECT_CONTEXT(ec);
176 if (((SPEventContextClass *) parent_class)->setup) {
177 ((SPEventContextClass *) parent_class)->setup(ec);
178 }
180 SPDesktop *desktop = ec->desktop;
182 select_context->_describer = new Inkscape::SelectionDescriber(
183 desktop->selection,
184 desktop->messageStack(),
185 _("Click selection to toggle scale/rotation handles"),
186 _("No objects selected. Click, Shift+click, Alt+scroll mouse on top of objects, or drag around objects to select.")
187 );
189 select_context->_seltrans = new Inkscape::SelTrans(desktop);
191 sp_event_context_read(ec, "show");
192 sp_event_context_read(ec, "transform");
194 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
195 if (prefs->getBool("/tools/select/gradientdrag")) {
196 ec->enableGrDrag();
197 }
198 }
200 static void
201 sp_select_context_set(SPEventContext *ec, Inkscape::Preferences::Entry *val)
202 {
203 SPSelectContext *sc = SP_SELECT_CONTEXT(ec);
204 Glib::ustring path = val->getEntryName();
206 if (path == "show") {
207 if (val->getString() == "outline") {
208 sc->_seltrans->setShow(Inkscape::SelTrans::SHOW_OUTLINE);
209 } else {
210 sc->_seltrans->setShow(Inkscape::SelTrans::SHOW_CONTENT);
211 }
212 }
213 }
215 static bool
216 sp_select_context_abort(SPEventContext *event_context)
217 {
218 SPDesktop *desktop = event_context->desktop;
219 SPSelectContext *sc = SP_SELECT_CONTEXT(event_context);
220 Inkscape::SelTrans *seltrans = sc->_seltrans;
222 if (sc->dragging) {
223 if (sc->moved) { // cancel dragging an object
224 seltrans->ungrab();
225 sc->moved = FALSE;
226 sc->dragging = FALSE;
227 sp_event_context_discard_delayed_snap_event(event_context);
228 drag_escaped = 1;
230 if (sc->item) {
231 // only undo if the item is still valid
232 if (SP_OBJECT_DOCUMENT( SP_OBJECT(sc->item))) {
233 DocumentUndo::undo(sp_desktop_document(desktop));
234 }
236 sp_object_unref( SP_OBJECT(sc->item), NULL);
237 } else if (sc->button_press_ctrl) {
238 // NOTE: This is a workaround to a bug.
239 // When the ctrl key is held, sc->item is not defined
240 // so in this case (only), we skip the object doc check
241 DocumentUndo::undo(sp_desktop_document(desktop));
242 }
243 sc->item = NULL;
245 SP_EVENT_CONTEXT(sc)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Move canceled."));
246 return true;
247 }
248 } else {
249 if (Inkscape::Rubberband::get(desktop)->is_started()) {
250 Inkscape::Rubberband::get(desktop)->stop();
251 rb_escaped = 1;
252 SP_EVENT_CONTEXT(sc)->defaultMessageContext()->clear();
253 SP_EVENT_CONTEXT(sc)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selection canceled."));
254 return true;
255 }
256 }
257 return false;
258 }
260 bool
261 key_is_a_modifier (guint key) {
262 return (key == GDK_Alt_L ||
263 key == GDK_Alt_R ||
264 key == GDK_Control_L ||
265 key == GDK_Control_R ||
266 key == GDK_Shift_L ||
267 key == GDK_Shift_R ||
268 key == GDK_Meta_L || // Meta is when you press Shift+Alt (at least on my machine)
269 key == GDK_Meta_R);
270 }
272 void
273 sp_select_context_up_one_layer(SPDesktop *desktop)
274 {
275 /* Click in empty place, go up one level -- but don't leave a layer to root.
276 *
277 * (Rationale: we don't usually allow users to go to the root, since that
278 * detracts from the layer metaphor: objects at the root level can in front
279 * of or behind layers. Whereas it's fine to go to the root if editing
280 * a document that has no layers (e.g. a non-Inkscape document).)
281 *
282 * Once we support editing SVG "islands" (e.g. <svg> embedded in an xhtml
283 * document), we might consider further restricting the below to disallow
284 * leaving a layer to go to a non-layer.
285 */
286 SPObject *const current_layer = desktop->currentLayer();
287 if (current_layer) {
288 SPObject *const parent = SP_OBJECT_PARENT(current_layer);
289 if ( parent
290 && ( SP_OBJECT_PARENT(parent)
291 || !( SP_IS_GROUP(current_layer)
292 && ( SPGroup::LAYER
293 == SP_GROUP(current_layer)->layerMode() ) ) ) )
294 {
295 desktop->setCurrentLayer(parent);
296 if (SP_IS_GROUP(current_layer) && SPGroup::LAYER != SP_GROUP(current_layer)->layerMode())
297 sp_desktop_selection(desktop)->set(current_layer);
298 }
299 }
300 }
302 static gint
303 sp_select_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
304 {
305 gint ret = FALSE;
307 SPDesktop *desktop = event_context->desktop;
308 SPSelectContext *sc = SP_SELECT_CONTEXT(event_context);
309 Inkscape::SelTrans *seltrans = sc->_seltrans;
311 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
312 tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
314 // make sure we still have valid objects to move around
315 if (sc->item && SP_OBJECT_DOCUMENT( SP_OBJECT(sc->item))==NULL) {
316 sp_select_context_abort(event_context);
317 }
319 switch (event->type) {
320 case GDK_BUTTON_PRESS:
321 if (event->button.button == 1 && !event_context->space_panning) {
322 /* Left mousebutton */
324 // save drag origin
325 xp = (gint) event->button.x;
326 yp = (gint) event->button.y;
327 within_tolerance = true;
329 // remember what modifiers were on before button press
330 sc->button_press_shift = (event->button.state & GDK_SHIFT_MASK) ? true : false;
331 sc->button_press_ctrl = (event->button.state & GDK_CONTROL_MASK) ? true : false;
332 sc->button_press_alt = (event->button.state & GDK_MOD1_MASK) ? true : false;
334 if (event->button.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) {
335 // if shift or ctrl was pressed, do not move objects;
336 // pass the event to root handler which will perform rubberband, shift-click, ctrl-click, ctrl-drag
337 } else {
338 sc->dragging = TRUE;
339 sc->moved = FALSE;
341 gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(desktop))->window, CursorSelectDragging);
343 sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 5);
345 // remember the clicked item in sc->item:
346 if (sc->item) {
347 sp_object_unref(sc->item, NULL);
348 sc->item = NULL;
349 }
350 sc->item = sp_event_context_find_item (desktop,
351 Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, FALSE);
352 sp_object_ref(sc->item, NULL);
354 rb_escaped = drag_escaped = 0;
356 if (sc->grabbed) {
357 sp_canvas_item_ungrab(sc->grabbed, event->button.time);
358 sc->grabbed = NULL;
359 }
360 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->drawing),
361 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
362 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
363 NULL, event->button.time);
364 sc->grabbed = SP_CANVAS_ITEM(desktop->drawing);
366 sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 5);
368 ret = TRUE;
369 }
370 } else if (event->button.button == 3) {
371 // right click; do not eat it so that right-click menu can appear, but cancel dragging & rubberband
372 sp_select_context_abort(event_context);
373 }
374 break;
376 case GDK_ENTER_NOTIFY:
377 {
378 if (!desktop->isWaitingCursor() && !sc->dragging) {
379 gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(desktop))->window, CursorSelectMouseover);
380 }
381 break;
382 }
384 case GDK_LEAVE_NOTIFY:
385 if (!desktop->isWaitingCursor() && !sc->dragging)
386 gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(desktop))->window, event_context->cursor);
387 break;
389 case GDK_KEY_PRESS:
390 if (get_group0_keyval (&event->key) == GDK_space) {
391 if (sc->dragging && sc->grabbed) {
392 /* stamping mode: show content mode moving */
393 seltrans->stamp();
394 ret = TRUE;
395 }
396 }
397 break;
399 default:
400 break;
401 }
403 if (!ret) {
404 if (((SPEventContextClass *) parent_class)->item_handler)
405 ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
406 }
408 return ret;
409 }
411 static void
412 sp_select_context_cycle_through_items(SPSelectContext *sc, Inkscape::Selection *selection, GdkEventScroll *scroll_event, bool shift_pressed) {
413 if (!sc->cycling_cur_item)
414 return;
416 NRArenaItem *arenaitem;
417 SPDesktop *desktop = SP_EVENT_CONTEXT(sc)->desktop;
418 SPItem *item = SP_ITEM(sc->cycling_cur_item->data);
420 // Deactivate current item
421 if (!g_list_find(sc->cycling_items_selected_before, item) && selection->includes(item))
422 selection->remove(item);
423 arenaitem = item->get_arenaitem(desktop->dkey);
424 nr_arena_item_set_opacity (arenaitem, 0.3);
426 // Find next item and activate it
427 GList *next;
428 if (scroll_event->direction == GDK_SCROLL_UP) {
429 next = sc->cycling_cur_item->next;
430 if (next == NULL)
431 next = sc->cycling_items;
432 } else {
433 next = sc->cycling_cur_item->prev;
434 if (next == NULL)
435 next = g_list_last(sc->cycling_items);
436 }
437 sc->cycling_cur_item = next;
438 item = SP_ITEM(sc->cycling_cur_item->data);
439 arenaitem = item->get_arenaitem(desktop->dkey);
440 nr_arena_item_set_opacity (arenaitem, 1.0);
442 if (shift_pressed)
443 selection->add(item);
444 else
445 selection->set(item);
446 }
448 static gint
449 sp_select_context_root_handler(SPEventContext *event_context, GdkEvent *event)
450 {
451 SPItem *item = NULL;
452 SPItem *item_at_point = NULL, *group_at_point = NULL, *item_in_group = NULL;
453 gint ret = FALSE;
455 SPDesktop *desktop = event_context->desktop;
456 SPSelectContext *sc = SP_SELECT_CONTEXT(event_context);
457 Inkscape::SelTrans *seltrans = sc->_seltrans;
458 Inkscape::Selection *selection = sp_desktop_selection(desktop);
459 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
461 // make sure we still have valid objects to move around
462 if (sc->item && SP_OBJECT_DOCUMENT( SP_OBJECT(sc->item))==NULL) {
463 sp_select_context_abort(event_context);
464 }
466 switch (event->type) {
467 case GDK_2BUTTON_PRESS:
468 if (event->button.button == 1) {
469 if (!selection->isEmpty()) {
470 SPItem *clicked_item = (SPItem *) selection->itemList()->data;
471 if (SP_IS_GROUP(clicked_item) && !SP_IS_BOX3D(clicked_item)) { // enter group if it's not a 3D box
472 desktop->setCurrentLayer(reinterpret_cast<SPObject *>(clicked_item));
473 sp_desktop_selection(desktop)->clear();
474 sc->dragging = false;
475 sp_event_context_discard_delayed_snap_event(event_context);
477 sp_canvas_end_forced_full_redraws(desktop->canvas);
478 } else { // switch tool
479 Geom::Point const button_pt(event->button.x, event->button.y);
480 Geom::Point const p(desktop->w2d(button_pt));
481 tools_switch_by_item (desktop, clicked_item, p);
482 }
483 } else {
484 sp_select_context_up_one_layer(desktop);
485 }
486 ret = TRUE;
487 }
488 break;
489 case GDK_BUTTON_PRESS:
490 if (event->button.button == 1 && !event_context->space_panning) {
492 // save drag origin
493 xp = (gint) event->button.x;
494 yp = (gint) event->button.y;
495 within_tolerance = true;
497 Geom::Point const button_pt(event->button.x, event->button.y);
498 Geom::Point const p(desktop->w2d(button_pt));
499 if (event->button.state & GDK_MOD1_MASK)
500 Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH);
501 Inkscape::Rubberband::get(desktop)->start(desktop, p);
502 if (sc->grabbed) {
503 sp_canvas_item_ungrab(sc->grabbed, event->button.time);
504 sc->grabbed = NULL;
505 }
506 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
507 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK,
508 NULL, event->button.time);
509 sc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
511 // remember what modifiers were on before button press
512 sc->button_press_shift = (event->button.state & GDK_SHIFT_MASK) ? true : false;
513 sc->button_press_ctrl = (event->button.state & GDK_CONTROL_MASK) ? true : false;
514 sc->button_press_alt = (event->button.state & GDK_MOD1_MASK) ? true : false;
516 sc->moved = FALSE;
518 rb_escaped = drag_escaped = 0;
520 ret = TRUE;
521 } else if (event->button.button == 3) {
522 // right click; do not eat it so that right-click menu can appear, but cancel dragging & rubberband
523 sp_select_context_abort(event_context);
524 }
525 break;
527 case GDK_MOTION_NOTIFY:
528 {
529 tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
530 if (event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
531 Geom::Point const motion_pt(event->motion.x, event->motion.y);
532 Geom::Point const p(desktop->w2d(motion_pt));
534 if ( within_tolerance
535 && ( abs( (gint) event->motion.x - xp ) < tolerance )
536 && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) {
537 break; // do not drag if we're within tolerance from origin
538 }
539 // Once the user has moved farther than tolerance from the original location
540 // (indicating they intend to move the object, not click), then always process the
541 // motion notify coordinates as given (no snapping back to origin)
542 within_tolerance = false;
544 if (sc->button_press_ctrl || (sc->button_press_alt && !sc->button_press_shift && !selection->isEmpty())) {
545 // if it's not click and ctrl or alt was pressed (the latter with some selection
546 // but not with shift) we want to drag rather than rubberband
547 sc->dragging = TRUE;
548 gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(desktop))->window, CursorSelectDragging);
550 sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 5);
551 }
553 if (sc->dragging) {
554 /* User has dragged fast, so we get events on root (lauris)*/
555 // not only that; we will end up here when ctrl-dragging as well
556 // and also when we started within tolerance, but trespassed tolerance outside of item
557 Inkscape::Rubberband::get(desktop)->stop();
558 SP_EVENT_CONTEXT(sc)->defaultMessageContext()->clear();
559 item_at_point = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), FALSE);
560 if (!item_at_point) // if no item at this point, try at the click point (bug 1012200)
561 item_at_point = desktop->getItemAtPoint(Geom::Point(xp, yp), FALSE);
562 if (item_at_point || sc->moved || sc->button_press_alt) {
563 // drag only if starting from an item, or if something is already grabbed, or if alt-dragging
564 if (!sc->moved) {
565 item_in_group = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE);
566 group_at_point = desktop->getGroupAtPoint(Geom::Point(event->button.x, event->button.y));
567 if (SP_IS_LAYER(selection->single()))
568 group_at_point = SP_GROUP(selection->single());
570 // group-at-point is meant to be topmost item if it's a group,
571 // not topmost group of all items at point
572 if (group_at_point != item_in_group &&
573 !(group_at_point && item_at_point &&
574 group_at_point->isAncestorOf(item_at_point)))
575 group_at_point = NULL;
577 // if neither a group nor an item (possibly in a group) at point are selected, set selection to the item at point
578 if ((!item_in_group || !selection->includes(item_in_group)) &&
579 (!group_at_point || !selection->includes(group_at_point))
580 && !sc->button_press_alt) {
581 // select what is under cursor
582 if (!seltrans->isEmpty()) {
583 seltrans->resetState();
584 }
585 // when simply ctrl-dragging, we don't want to go into groups
586 if (item_at_point && !selection->includes(item_at_point))
587 selection->set(item_at_point);
588 } // otherwise, do not change selection so that dragging selected-within-group items, as well as alt-dragging, is possible
589 seltrans->grab(p, -1, -1, FALSE, TRUE);
590 sc->moved = TRUE;
591 }
592 if (!seltrans->isEmpty())
593 seltrans->moveTo(p, event->button.state);
594 desktop->scroll_to_point(p);
595 gobble_motion_events(GDK_BUTTON1_MASK);
596 ret = TRUE;
597 } else {
598 sc->dragging = FALSE;
599 sp_event_context_discard_delayed_snap_event(event_context);
600 sp_canvas_end_forced_full_redraws(desktop->canvas);
601 }
602 } else {
603 if (Inkscape::Rubberband::get(desktop)->is_started()) {
604 Inkscape::Rubberband::get(desktop)->move(p);
605 if (Inkscape::Rubberband::get(desktop)->getMode() == RUBBERBAND_MODE_TOUCHPATH) {
606 event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Draw over</b> objects to select them; release <b>Alt</b> to switch to rubberband selection"));
607 } else {
608 event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Drag around</b> objects to select them; press <b>Alt</b> to switch to touch selection"));
609 }
610 gobble_motion_events(GDK_BUTTON1_MASK);
611 }
612 }
613 }
614 break;
615 }
616 case GDK_BUTTON_RELEASE:
617 xp = yp = 0;
618 if ((event->button.button == 1) && (sc->grabbed) && !event_context->space_panning) {
619 if (sc->dragging) {
620 if (sc->moved) {
621 // item has been moved
622 seltrans->ungrab();
623 sc->moved = FALSE;
624 } else if (sc->item && !drag_escaped) {
625 // item has not been moved -> simply a click, do selecting
626 if (!selection->isEmpty()) {
627 if (event->button.state & GDK_SHIFT_MASK) {
628 // with shift, toggle selection
629 seltrans->resetState();
630 selection->toggle(sc->item);
631 } else {
632 SPObject* single = selection->single();
633 // without shift, increase state (i.e. toggle scale/rotation handles)
634 if (selection->includes(sc->item)) {
635 seltrans->increaseState();
636 } else if (SP_IS_LAYER(single) && single->isAncestorOf(sc->item)) {
637 seltrans->increaseState();
638 } else {
639 seltrans->resetState();
640 selection->set(sc->item);
641 }
642 }
643 } else { // simple or shift click, no previous selection
644 seltrans->resetState();
645 selection->set(sc->item);
646 }
647 }
648 sc->dragging = FALSE;
649 gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(desktop))->window, CursorSelectMouseover);
650 sp_event_context_discard_delayed_snap_event(event_context);
651 sp_canvas_end_forced_full_redraws(desktop->canvas);
653 if (sc->item) {
654 sp_object_unref( SP_OBJECT(sc->item), NULL);
655 }
656 sc->item = NULL;
657 } else {
658 Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop);
659 if (r->is_started() && !within_tolerance) {
660 // this was a rubberband drag
661 GSList *items = NULL;
662 if (r->getMode() == RUBBERBAND_MODE_RECT) {
663 Geom::OptRect const b = r->getRectangle();
664 items = sp_desktop_document(desktop)->getItemsInBox(desktop->dkey, *b);
665 } else if (r->getMode() == RUBBERBAND_MODE_TOUCHPATH) {
666 items = sp_desktop_document(desktop)->getItemsAtPoints(desktop->dkey, r->getPoints());
667 }
669 seltrans->resetState();
670 r->stop();
671 SP_EVENT_CONTEXT(sc)->defaultMessageContext()->clear();
673 if (event->button.state & GDK_SHIFT_MASK) {
674 // with shift, add to selection
675 selection->addList (items);
676 } else {
677 // without shift, simply select anew
678 selection->setList (items);
679 }
680 g_slist_free (items);
681 } else { // it was just a click, or a too small rubberband
682 r->stop();
683 if (sc->button_press_shift && !rb_escaped && !drag_escaped) {
684 // this was a shift+click or alt+shift+click, select what was clicked upon
686 sc->button_press_shift = false;
688 if (sc->button_press_ctrl) {
689 // go into groups, honoring Alt
690 item = sp_event_context_find_item (desktop,
691 Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, TRUE);
692 sc->button_press_ctrl = FALSE;
693 } else {
694 // don't go into groups, honoring Alt
695 item = sp_event_context_find_item (desktop,
696 Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, FALSE);
697 }
699 if (item) {
700 selection->toggle(item);
701 item = NULL;
702 }
704 } else if ((sc->button_press_ctrl || sc->button_press_alt) && !rb_escaped && !drag_escaped) { // ctrl+click, alt+click
706 item = sp_event_context_find_item (desktop,
707 Geom::Point(event->button.x, event->button.y), sc->button_press_alt, sc->button_press_ctrl);
709 sc->button_press_ctrl = FALSE;
710 sc->button_press_alt = FALSE;
712 if (item) {
713 if (selection->includes(item)) {
714 seltrans->increaseState();
715 } else {
716 seltrans->resetState();
717 selection->set(item);
718 }
719 item = NULL;
720 }
722 } else { // click without shift, simply deselect, unless with Alt or something was cancelled
723 if (!selection->isEmpty()) {
724 if (!(rb_escaped) && !(drag_escaped) && !(event->button.state & GDK_MOD1_MASK))
725 selection->clear();
726 rb_escaped = 0;
727 ret = TRUE;
728 }
729 }
730 }
731 ret = TRUE;
732 }
733 if (sc->grabbed) {
734 sp_canvas_item_ungrab(sc->grabbed, event->button.time);
735 sc->grabbed = NULL;
736 }
738 desktop->updateNow();
739 }
740 if (event->button.button == 1) {
741 Inkscape::Rubberband::get(desktop)->stop(); // might have been started in another tool!
742 }
743 sc->button_press_shift = false;
744 sc->button_press_ctrl = false;
745 sc->button_press_alt = false;
746 break;
748 case GDK_SCROLL:
749 {
750 SPSelectContext *sc = SP_SELECT_CONTEXT(event_context);
751 GdkEventScroll *scroll_event = (GdkEventScroll*) event;
753 if (scroll_event->state & GDK_MOD1_MASK) { // alt modified pressed
754 bool shift_pressed = scroll_event->state & GDK_SHIFT_MASK;
756 /* Rebuild list of items underneath the mouse pointer */
757 Geom::Point p = desktop->d2w(desktop->point());
758 SPItem *item = desktop->getItemAtPoint(p, true, NULL);
760 // Save pointer to current cycle-item so that we can find it again later, in the freshly built list
761 SPItem *tmp_cur_item = sc->cycling_cur_item ? SP_ITEM(sc->cycling_cur_item->data) : NULL;
762 g_list_free(sc->cycling_items);
763 sc->cycling_items = NULL;
764 sc->cycling_cur_item = NULL;
766 while(item != NULL) {
767 sc->cycling_items = g_list_append(sc->cycling_items, item);
768 item = desktop->getItemAtPoint(p, true, item);
769 }
771 /* Compare current item list with item list during previous scroll ... */
772 GList *l1, *l2;
773 bool item_lists_differ = false;
774 // Note that we can do an 'or' comparison in the loop because it is safe to call g_list_next with a NULL pointer.
775 for (l1 = sc->cycling_items, l2 = sc->cycling_items_cmp; l1 != NULL || l2 != NULL; l1 = g_list_next(l1), l2 = g_list_next(l2)) {
776 if ((l1 !=NULL && l2 == NULL) || (l1 == NULL && l2 != NULL) || (l1->data != l2->data)) {
777 item_lists_differ = true;
778 break;
779 }
780 }
782 /* If list of items under mouse pointer hasn't changed ... */
783 if (!item_lists_differ) {
784 // ... find current item in the freshly built list and continue cycling ...
785 // TODO: This wouldn't be necessary if cycling_cur_item pointed to an element of cycling_items_cmp instead
786 sc->cycling_cur_item = g_list_find(sc->cycling_items, tmp_cur_item);
787 g_assert(sc->cycling_cur_item != NULL || sc->cycling_items == NULL);
788 } else {
789 // ... otherwise reset opacities for outdated items ...
790 NRArenaItem *arenaitem;
791 for(GList *l = sc->cycling_items_cmp; l != NULL; l = l->next) {
792 arenaitem = SP_ITEM(l->data)->get_arenaitem(desktop->dkey);
793 nr_arena_item_set_opacity (arenaitem, 1.0);
794 //if (!shift_pressed && !g_list_find(sc->cycling_items_selected_before, SP_ITEM(l->data)) && selection->includes(SP_ITEM(l->data)))
795 if (!g_list_find(sc->cycling_items_selected_before, SP_ITEM(l->data)) && selection->includes(SP_ITEM(l->data)))
796 selection->remove(SP_ITEM(l->data));
797 }
799 // ... clear the lists ...
800 g_list_free(sc->cycling_items_cmp);
801 g_list_free(sc->cycling_items_selected_before);
802 sc->cycling_items_cmp = NULL;
803 sc->cycling_items_selected_before = NULL;
804 sc->cycling_cur_item = NULL;
806 // ... and rebuild them with the new items.
807 sc->cycling_items_cmp = g_list_copy(sc->cycling_items);
808 SPItem *item;
809 for(GList *l = sc->cycling_items; l != NULL; l = l->next) {
810 item = SP_ITEM(l->data);
811 arenaitem = item->get_arenaitem(desktop->dkey);
812 nr_arena_item_set_opacity (arenaitem, 0.3);
813 if (selection->includes(item)) {
814 // already selected items are stored separately, too
815 sc->cycling_items_selected_before = g_list_append(sc->cycling_items_selected_before, item);
816 }
817 }
819 // set the current item to the bottommost one so that the cycling step below re-starts at the top
820 sc->cycling_cur_item = g_list_last(sc->cycling_items);
821 }
823 // Cycle through the items underneath the mouse pointer, one-by-one
824 sp_select_context_cycle_through_items(sc, selection, scroll_event, shift_pressed);
826 ret = TRUE;
827 }
828 break;
829 }
831 case GDK_KEY_PRESS: // keybindings for select context
833 {
834 {
835 guint keyval = get_group0_keyval(&event->key);
836 bool alt = ( MOD__ALT
837 || (keyval == GDK_Alt_L)
838 || (keyval == GDK_Alt_R)
839 || (keyval == GDK_Meta_L)
840 || (keyval == GDK_Meta_R));
842 if (!key_is_a_modifier (keyval)) {
843 event_context->defaultMessageContext()->clear();
844 } else if (sc->grabbed || seltrans->isGrabbed()) {
845 if (Inkscape::Rubberband::get(desktop)->is_started()) {
846 // if Alt then change cursor to moving cursor:
847 if (alt) {
848 Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH);
849 }
850 } else {
851 // do not change the statusbar text when mousekey is down to move or transform the object,
852 // because the statusbar text is already updated somewhere else.
853 break;
854 }
855 } else {
856 sp_event_show_modifier_tip (event_context->defaultMessageContext(), event,
857 _("<b>Ctrl</b>: click to select in groups; drag to move hor/vert"),
858 _("<b>Shift</b>: click to toggle select; drag for rubberband selection"),
859 _("<b>Alt</b>: click to select under; scroll mouse-wheel to cycle-select; drag to move selected or select by touch"));
860 // if Alt and nonempty selection, show moving cursor ("move selected"):
861 if (alt && !selection->isEmpty() && !desktop->isWaitingCursor()) {
862 gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(desktop))->window, CursorSelectDragging);
863 }
864 //*/
865 break;
866 }
867 }
869 gdouble const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000); // in px
870 gdouble const offset = prefs->getDoubleLimited("/options/defaultscale/value", 2, 0, 1000);
871 int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
873 switch (get_group0_keyval (&event->key)) {
874 case GDK_Left: // move selection left
875 case GDK_KP_Left:
876 case GDK_KP_4:
877 if (!MOD__CTRL) { // not ctrl
878 gint mul = 1 + gobble_key_events(
879 get_group0_keyval(&event->key), 0); // with any mask
880 if (MOD__ALT) { // alt
881 if (MOD__SHIFT) sp_selection_move_screen(desktop, mul*-10, 0); // shift
882 else sp_selection_move_screen(desktop, mul*-1, 0); // no shift
883 }
884 else { // no alt
885 if (MOD__SHIFT) sp_selection_move(desktop, mul*-10*nudge, 0); // shift
886 else sp_selection_move(desktop, mul*-nudge, 0); // no shift
887 }
888 ret = TRUE;
889 }
890 break;
891 case GDK_Up: // move selection up
892 case GDK_KP_Up:
893 case GDK_KP_8:
894 if (!MOD__CTRL) { // not ctrl
895 gint mul = 1 + gobble_key_events(
896 get_group0_keyval(&event->key), 0); // with any mask
897 if (MOD__ALT) { // alt
898 if (MOD__SHIFT) sp_selection_move_screen(desktop, 0, mul*10); // shift
899 else sp_selection_move_screen(desktop, 0, mul*1); // no shift
900 }
901 else { // no alt
902 if (MOD__SHIFT) sp_selection_move(desktop, 0, mul*10*nudge); // shift
903 else sp_selection_move(desktop, 0, mul*nudge); // no shift
904 }
905 ret = TRUE;
906 }
907 break;
908 case GDK_Right: // move selection right
909 case GDK_KP_Right:
910 case GDK_KP_6:
911 if (!MOD__CTRL) { // not ctrl
912 gint mul = 1 + gobble_key_events(
913 get_group0_keyval(&event->key), 0); // with any mask
914 if (MOD__ALT) { // alt
915 if (MOD__SHIFT) sp_selection_move_screen(desktop, mul*10, 0); // shift
916 else sp_selection_move_screen(desktop, mul*1, 0); // no shift
917 }
918 else { // no alt
919 if (MOD__SHIFT) sp_selection_move(desktop, mul*10*nudge, 0); // shift
920 else sp_selection_move(desktop, mul*nudge, 0); // no shift
921 }
922 ret = TRUE;
923 }
924 break;
925 case GDK_Down: // move selection down
926 case GDK_KP_Down:
927 case GDK_KP_2:
928 if (!MOD__CTRL) { // not ctrl
929 gint mul = 1 + gobble_key_events(
930 get_group0_keyval(&event->key), 0); // with any mask
931 if (MOD__ALT) { // alt
932 if (MOD__SHIFT) sp_selection_move_screen(desktop, 0, mul*-10); // shift
933 else sp_selection_move_screen(desktop, 0, mul*-1); // no shift
934 }
935 else { // no alt
936 if (MOD__SHIFT) sp_selection_move(desktop, 0, mul*-10*nudge); // shift
937 else sp_selection_move(desktop, 0, mul*-nudge); // no shift
938 }
939 ret = TRUE;
940 }
941 break;
942 case GDK_Escape:
943 if (!sp_select_context_abort(event_context))
944 selection->clear();
945 ret = TRUE;
946 break;
948 case GDK_a:
949 case GDK_A:
950 if (MOD__CTRL_ONLY) {
951 sp_edit_select_all(desktop);
952 ret = TRUE;
953 }
954 break;
955 case GDK_space:
956 /* stamping mode: show outline mode moving */
957 /* FIXME: Is next condition ok? (lauris) */
958 if (sc->dragging && sc->grabbed) {
959 seltrans->stamp();
960 ret = TRUE;
961 }
962 break;
963 case GDK_x:
964 case GDK_X:
965 if (MOD__ALT_ONLY) {
966 desktop->setToolboxFocusTo ("altx");
967 ret = TRUE;
968 }
969 break;
970 case GDK_bracketleft:
971 if (MOD__ALT) {
972 gint mul = 1 + gobble_key_events(
973 get_group0_keyval(&event->key), 0); // with any mask
974 sp_selection_rotate_screen(selection, mul*1);
975 } else if (MOD__CTRL) {
976 sp_selection_rotate(selection, 90);
977 } else if (snaps) {
978 sp_selection_rotate(selection, 180/snaps);
979 }
980 ret = TRUE;
981 break;
982 case GDK_bracketright:
983 if (MOD__ALT) {
984 gint mul = 1 + gobble_key_events(
985 get_group0_keyval(&event->key), 0); // with any mask
986 sp_selection_rotate_screen(selection, -1*mul);
987 } else if (MOD__CTRL) {
988 sp_selection_rotate(selection, -90);
989 } else if (snaps) {
990 sp_selection_rotate(selection, -180/snaps);
991 }
992 ret = TRUE;
993 break;
994 case GDK_less:
995 case GDK_comma:
996 if (MOD__ALT) {
997 gint mul = 1 + gobble_key_events(
998 get_group0_keyval(&event->key), 0); // with any mask
999 sp_selection_scale_screen(selection, -2*mul);
1000 } else if (MOD__CTRL) {
1001 sp_selection_scale_times(selection, 0.5);
1002 } else {
1003 gint mul = 1 + gobble_key_events(
1004 get_group0_keyval(&event->key), 0); // with any mask
1005 sp_selection_scale(selection, -offset*mul);
1006 }
1007 ret = TRUE;
1008 break;
1009 case GDK_greater:
1010 case GDK_period:
1011 if (MOD__ALT) {
1012 gint mul = 1 + gobble_key_events(
1013 get_group0_keyval(&event->key), 0); // with any mask
1014 sp_selection_scale_screen(selection, 2*mul);
1015 } else if (MOD__CTRL) {
1016 sp_selection_scale_times(selection, 2);
1017 } else {
1018 gint mul = 1 + gobble_key_events(
1019 get_group0_keyval(&event->key), 0); // with any mask
1020 sp_selection_scale(selection, offset*mul);
1021 }
1022 ret = TRUE;
1023 break;
1024 case GDK_Return:
1025 if (MOD__CTRL_ONLY) {
1026 if (selection->singleItem()) {
1027 SPItem *clicked_item = selection->singleItem();
1028 if ( SP_IS_GROUP(clicked_item) ||
1029 SP_IS_BOX3D(clicked_item)) { // enter group or a 3D box
1030 desktop->setCurrentLayer(reinterpret_cast<SPObject *>(clicked_item));
1031 sp_desktop_selection(desktop)->clear();
1032 } else {
1033 SP_EVENT_CONTEXT(sc)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selected object is not a group. Cannot enter."));
1034 }
1035 }
1036 ret = TRUE;
1037 }
1038 break;
1039 case GDK_BackSpace:
1040 if (MOD__CTRL_ONLY) {
1041 sp_select_context_up_one_layer(desktop);
1042 ret = TRUE;
1043 }
1044 break;
1045 case GDK_s:
1046 case GDK_S:
1047 if (MOD__SHIFT_ONLY) {
1048 if (!selection->isEmpty()) {
1049 seltrans->increaseState();
1050 }
1051 ret = TRUE;
1052 }
1053 break;
1054 case GDK_g:
1055 case GDK_G:
1056 if (MOD__SHIFT_ONLY) {
1057 sp_selection_to_guides(desktop);
1058 ret = true;
1059 }
1060 break;
1061 default:
1062 break;
1063 }
1064 break;
1065 }
1066 case GDK_KEY_RELEASE:
1067 {
1068 guint keyval = get_group0_keyval(&event->key);
1069 if (key_is_a_modifier (keyval))
1070 event_context->defaultMessageContext()->clear();
1072 bool alt = ( MOD__ALT
1073 || (keyval == GDK_Alt_L)
1074 || (keyval == GDK_Alt_R)
1075 || (keyval == GDK_Meta_L)
1076 || (keyval == GDK_Meta_R));
1078 if (Inkscape::Rubberband::get(desktop)->is_started()) {
1079 // if Alt then change cursor to moving cursor:
1080 if (alt) {
1081 Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_RECT);
1082 }
1083 } else {
1084 if (alt) { // TODO: Should we have a variable like is_cycling or is it harmless to run this piece of code each time?
1085 // quit cycle-selection and reset opacities
1086 SPSelectContext *sc = SP_SELECT_CONTEXT(event_context);
1087 NRArenaItem *arenaitem;
1088 for (GList *l = sc->cycling_items; l != NULL; l = g_list_next(l)) {
1089 arenaitem = SP_ITEM(l->data)->get_arenaitem(desktop->dkey);
1090 nr_arena_item_set_opacity (arenaitem, 1.0);
1091 }
1092 g_list_free(sc->cycling_items);
1093 g_list_free(sc->cycling_items_selected_before);
1094 g_list_free(sc->cycling_items_cmp);
1095 sc->cycling_items = NULL;
1096 sc->cycling_items_selected_before = NULL;
1097 sc->cycling_cur_item = NULL;
1098 sc->cycling_items_cmp = NULL;
1099 }
1100 }
1102 }
1103 // set cursor to default.
1104 if (!desktop->isWaitingCursor()) {
1105 gdk_window_set_cursor(GTK_WIDGET(sp_desktop_canvas(desktop))->window, event_context->cursor);
1106 }
1107 break;
1108 default:
1109 break;
1110 }
1112 if (!ret) {
1113 if (((SPEventContextClass *) parent_class)->root_handler)
1114 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
1115 }
1117 return ret;
1118 }
1121 /*
1122 Local Variables:
1123 mode:c++
1124 c-file-style:"stroustrup"
1125 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1126 indent-tabs-mode:nil
1127 fill-column:99
1128 End:
1129 */
1130 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :