Code

bulk trailing spaces removal. consistency through MD5 of binary
[inkscape.git] / src / select-context.cpp
1 #define __SP_SELECT_CONTEXT_C__
3 /*
4  * Selection and transformation context
5  *
6  * Author:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   bulia byak <buliabyak@users.sf.net>
9   *
10  * Copyright (C) 1999-2005 authors
11  *
12  * Released under GNU GPL, read the file 'COPYING' for more information
13  */
15 #ifdef HAVE_CONFIG_H
16 # include "config.h"
17 #endif
18 #include <gdk/gdkkeysyms.h>
19 #include "macros.h"
20 #include "rubberband.h"
21 #include "document.h"
22 #include "selection.h"
23 #include "seltrans-handles.h"
24 #include "sp-cursor.h"
25 #include "pixmaps/cursor-select-m.xpm"
26 #include "pixmaps/cursor-select-d.xpm"
27 #include "pixmaps/handles.xpm"
28 #include <glibmm/i18n.h>
30 #include "select-context.h"
31 #include "selection-chemistry.h"
32 #include "desktop.h"
33 #include "desktop-handles.h"
34 #include "sp-root.h"
35 #include "prefs-utils.h"
36 #include "tools-switch.h"
37 #include "message-stack.h"
38 #include "selection-describer.h"
39 #include "seltrans.h"
41 static void sp_select_context_class_init(SPSelectContextClass *klass);
42 static void sp_select_context_init(SPSelectContext *select_context);
43 static void sp_select_context_dispose(GObject *object);
45 static void sp_select_context_setup(SPEventContext *ec);
46 static void sp_select_context_set(SPEventContext *ec, gchar const *key, gchar const *val);
47 static gint sp_select_context_root_handler(SPEventContext *event_context, GdkEvent *event);
48 static gint sp_select_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
50 static SPEventContextClass *parent_class;
52 static GdkCursor *CursorSelectMouseover = NULL;
53 static GdkCursor *CursorSelectDragging = NULL;
54 GdkPixbuf *handles[13];
56 static gint rb_escaped = 0; // if non-zero, rubberband was canceled by esc, so the next button release should not deselect
57 static gint drag_escaped = 0; // if non-zero, drag was canceled by esc
59 static gint xp = 0, yp = 0; // where drag started
60 static gint tolerance = 0;
61 static bool within_tolerance = false;
63 GtkType
64 sp_select_context_get_type(void)
65 {
66     static GType type = 0;
67     if (!type) {
68         GTypeInfo info = {
69             sizeof(SPSelectContextClass),
70             NULL, NULL,
71             (GClassInitFunc) sp_select_context_class_init,
72             NULL, NULL,
73             sizeof(SPSelectContext),
74             4,
75             (GInstanceInitFunc) sp_select_context_init,
76             NULL,   /* value_table */
77         };
78         type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPSelectContext", &info, (GTypeFlags)0);
79     }
80     return type;
81 }
83 static void
84 sp_select_context_class_init(SPSelectContextClass *klass)
85 {
86     GObjectClass *object_class = (GObjectClass *) klass;
87     SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
89     parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
91     object_class->dispose = sp_select_context_dispose;
93     event_context_class->setup = sp_select_context_setup;
94     event_context_class->set = sp_select_context_set;
95     event_context_class->root_handler = sp_select_context_root_handler;
96     event_context_class->item_handler = sp_select_context_item_handler;
98     // cursors in select context
99     CursorSelectMouseover = sp_cursor_new_from_xpm(cursor_select_m_xpm , 1, 1);
100     CursorSelectDragging = sp_cursor_new_from_xpm(cursor_select_d_xpm , 1, 1);
101     // selection handles
102     handles[0]  = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_scale_nw_xpm);
103     handles[1]  = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_scale_ne_xpm);
104     handles[2]  = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_scale_h_xpm);
105     handles[3]  = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_scale_v_xpm);
106     handles[4]  = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_nw_xpm);
107     handles[5]  = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_n_xpm);
108     handles[6]  = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_ne_xpm);
109     handles[7]  = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_e_xpm);
110     handles[8]  = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_se_xpm);
111     handles[9]  = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_s_xpm);
112     handles[10] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_sw_xpm);
113     handles[11] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_rotate_w_xpm);
114     handles[12] = gdk_pixbuf_new_from_xpm_data((gchar const **)handle_center_xpm);
118 static void
119 sp_select_context_init(SPSelectContext *sc)
121     sc->dragging = FALSE;
122     sc->moved = FALSE;
123     sc->button_press_shift = false;
124     sc->button_press_ctrl = false;
125     sc->button_press_alt = false;
126     sc->_seltrans = NULL;
127     sc->_describer = NULL;
130 static void
131 sp_select_context_dispose(GObject *object)
133     SPSelectContext *sc = SP_SELECT_CONTEXT(object);
134     SPEventContext * ec = SP_EVENT_CONTEXT (object);
136     ec->enableGrDrag(false);
138     if (sc->grabbed) {
139         sp_canvas_item_ungrab(sc->grabbed, GDK_CURRENT_TIME);
140         sc->grabbed = NULL;
141     }
143     delete sc->_seltrans;
144     sc->_seltrans = NULL;
145     delete sc->_describer;
146     sc->_describer = NULL;
148     if (CursorSelectDragging) {
149         gdk_cursor_unref (CursorSelectDragging);
150         CursorSelectDragging = NULL;
151     }
152     if (CursorSelectMouseover) {
153         gdk_cursor_unref (CursorSelectMouseover);
154         CursorSelectMouseover = NULL;
155     }
157     G_OBJECT_CLASS(parent_class)->dispose(object);
160 static void
161 sp_select_context_setup(SPEventContext *ec)
163     SPSelectContext *select_context = SP_SELECT_CONTEXT(ec);
165     if (((SPEventContextClass *) parent_class)->setup) {
166         ((SPEventContextClass *) parent_class)->setup(ec);
167     }
169     SPDesktop *desktop = ec->desktop;
171     select_context->_describer = new Inkscape::SelectionDescriber(desktop->selection, desktop->messageStack());
173     select_context->_seltrans = new Inkscape::SelTrans(desktop);
175     sp_event_context_read(ec, "show");
176     sp_event_context_read(ec, "transform");
178     if (prefs_get_int_attribute("tools.select", "gradientdrag", 0) != 0) {
179         ec->enableGrDrag();
180     }
183 static void
184 sp_select_context_set(SPEventContext *ec, gchar const *key, gchar const *val)
186     SPSelectContext *sc = SP_SELECT_CONTEXT(ec);
188     if (!strcmp(key, "show")) {
189         if (val && !strcmp(val, "outline")) {
190             sc->_seltrans->setShow(Inkscape::SelTrans::SHOW_OUTLINE);
191         } else {
192             sc->_seltrans->setShow(Inkscape::SelTrans::SHOW_CONTENT);
193         }
194     }
197 static bool
198 sp_select_context_abort(SPEventContext *event_context)
200     SPDesktop *desktop = event_context->desktop;
201     SPSelectContext *sc = SP_SELECT_CONTEXT(event_context);
202     Inkscape::SelTrans *seltrans = sc->_seltrans;
204     if (sc->dragging) {
205         if (sc->moved) { // cancel dragging an object
206             seltrans->ungrab();
207             sc->moved = FALSE;
208             sc->dragging = FALSE;
209             drag_escaped = 1;
211             if (sc->item) {
212                 // only undo if the item is still valid
213                 if (SP_OBJECT_DOCUMENT( SP_OBJECT(sc->item))) {
214                     sp_document_undo(SP_DT_DOCUMENT(desktop));
215                 }
217                 sp_object_unref( SP_OBJECT(sc->item), NULL);
218             } else if (sc->button_press_ctrl) {
219                 // NOTE:  This is a workaround to a bug.
220                 // When the ctrl key is held, sc->item is not defined
221                 // so in this case (only), we skip the object doc check
222                 sp_document_undo(SP_DT_DOCUMENT(desktop));
223             }
224             sc->item = NULL;
226             SP_EVENT_CONTEXT(sc)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Move canceled."));
227             return true;
228         }
229     } else {
230         NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
231         if (b != NR::Nothing()) {
232             Inkscape::Rubberband::get()->stop();
233             rb_escaped = 1;
234             SP_EVENT_CONTEXT(sc)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selection canceled."));
235             return true;
236         }
237     }
238     return false;
241 bool
242 key_is_a_modifier (guint key) {
243     return (key == GDK_Alt_L ||
244                 key == GDK_Alt_R ||
245                 key == GDK_Control_L ||
246                 key == GDK_Control_R ||
247                 key == GDK_Shift_L ||
248                 key == GDK_Shift_R ||
249                 key == GDK_Meta_L ||  // Meta is when you press Shift+Alt (at least on my machine)
250             key == GDK_Meta_R);
253 void
254 sp_select_context_up_one_layer(SPDesktop *desktop)
256     /* Click in empty place, go up one level -- but don't leave a layer to root.
257      *
258      * (Rationale: we don't usually allow users to go to the root, since that
259      * detracts from the layer metaphor: objects at the root level can in front
260      * of or behind layers.  Whereas it's fine to go to the root if editing
261      * a document that has no layers (e.g. a non-Inkscape document).)
262      *
263      * Once we support editing SVG "islands" (e.g. <svg> embedded in an xhtml
264      * document), we might consider further restricting the below to disallow
265      * leaving a layer to go to a non-layer.
266      */
267     SPObject *const current_layer = desktop->currentLayer();
268     if (current_layer) {
269         SPObject *const parent = SP_OBJECT_PARENT(current_layer);
270         if ( parent
271              && ( SP_OBJECT_PARENT(parent)
272                   || !( SP_IS_GROUP(current_layer)
273                         && ( SPGroup::LAYER
274                              == SP_GROUP(current_layer)->layerMode() ) ) ) )
275         {
276             desktop->setCurrentLayer(parent);
277             if (SP_IS_GROUP(current_layer) && SPGroup::LAYER != SP_GROUP(current_layer)->layerMode())
278                 SP_DT_SELECTION(desktop)->set(current_layer);
279         }
280     }
283 static gint
284 sp_select_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
286     gint ret = FALSE;
288     SPDesktop *desktop = event_context->desktop;
289     SPSelectContext *sc = SP_SELECT_CONTEXT(event_context);
290     Inkscape::SelTrans *seltrans = sc->_seltrans;
292     tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
294     // make sure we still have valid objects to move around
295     if (sc->item && SP_OBJECT_DOCUMENT( SP_OBJECT(sc->item))==NULL) {
296         sp_select_context_abort(event_context);
297     }
299     switch (event->type) {
300         case GDK_BUTTON_PRESS:
301             if (event->button.button == 1) {
302                 /* Left mousebutton */
304                 // save drag origin
305                 xp = (gint) event->button.x;
306                 yp = (gint) event->button.y;
307                 within_tolerance = true;
309                 // remember what modifiers were on before button press
310                 sc->button_press_shift = (event->button.state & GDK_SHIFT_MASK) ? true : false;
311                 sc->button_press_ctrl = (event->button.state & GDK_CONTROL_MASK) ? true : false;
312                 sc->button_press_alt = (event->button.state & GDK_MOD1_MASK) ? true : false;
314                 if (event->button.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {
315                     // if shift or ctrl was pressed, do not move objects;
316                     // pass the event to root handler which will perform rubberband, shift-click, ctrl-click, ctrl-drag
317                 } else {
318                     sc->dragging = TRUE;
319                     sc->moved = FALSE;
321                     // remember the clicked item in sc->item:
322                     sc->item = sp_event_context_find_item (desktop,
323                                               NR::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, FALSE);
324                     sp_object_ref(sc->item, NULL);
326                     rb_escaped = drag_escaped = 0;
328                     sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->drawing),
329                                         GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
330                                         GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK,
331                                         NULL, event->button.time);
332                     sc->grabbed = SP_CANVAS_ITEM(desktop->drawing);
334                     ret = TRUE;
335                 }
336             } else if (event->button.button == 3) {
337                 // right click; do not eat it so that right-click menu can appear, but cancel dragging & rubberband
338                 sp_select_context_abort(event_context);
339             }
340             break;
342         case GDK_ENTER_NOTIFY:
343         {
344             GdkCursor *cursor = gdk_cursor_new(GDK_FLEUR);
345             gdk_window_set_cursor(GTK_WIDGET(SP_DT_CANVAS(desktop))->window, cursor);
346             gdk_cursor_destroy(cursor);
347             break;
348         }
350         case GDK_LEAVE_NOTIFY:
351             gdk_window_set_cursor(GTK_WIDGET(SP_DT_CANVAS(desktop))->window, event_context->cursor);
352             break;
354         case GDK_KEY_PRESS:
355             if (get_group0_keyval (&event->key) == GDK_space) {
356                 if (sc->dragging && sc->grabbed) {
357                     /* stamping mode: show content mode moving */
358                     seltrans->stamp();
359                     ret = TRUE;
360                 }
361             }
362             break;
364         default:
365             break;
366     }
368     if (!ret) {
369         if (((SPEventContextClass *) parent_class)->item_handler)
370             ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
371     }
373     return ret;
376 static gint
377 sp_select_context_root_handler(SPEventContext *event_context, GdkEvent *event)
379     SPItem *item = NULL;
380     SPItem *item_at_point = NULL, *group_at_point = NULL, *item_in_group = NULL;
381     gint ret = FALSE;
383     SPDesktop *desktop = event_context->desktop;
384     SPSelectContext *sc = SP_SELECT_CONTEXT(event_context);
385     Inkscape::SelTrans *seltrans = sc->_seltrans;
386     Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
387     gdouble const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
388     gdouble const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
389     tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
390     int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
392     // make sure we still have valid objects to move around
393     if (sc->item && SP_OBJECT_DOCUMENT( SP_OBJECT(sc->item))==NULL) {
394         sp_select_context_abort(event_context);
395     }
397     switch (event->type) {
398         case GDK_2BUTTON_PRESS:
399             if (event->button.button == 1) {
400                 if (!selection->isEmpty()) {
401                     SPItem *clicked_item = (SPItem *) selection->itemList()->data;
402                     if (SP_IS_GROUP (clicked_item)) { // enter group
403                         desktop->setCurrentLayer(reinterpret_cast<SPObject *>(clicked_item));
404                         SP_DT_SELECTION(desktop)->clear();
405                         sc->dragging = false;
406                     } else { // switch tool
407                         tools_switch_by_item (desktop, clicked_item);
408                     }
409                 } else {
410                     sp_select_context_up_one_layer(desktop);
411                 }
412                 ret = TRUE;
413             }
414             break;
415         case GDK_BUTTON_PRESS:
416             if (event->button.button == 1) {
418                 // save drag origin
419                 xp = (gint) event->button.x;
420                 yp = (gint) event->button.y;
421                 within_tolerance = true;
423                 NR::Point const button_pt(event->button.x, event->button.y);
424                 NR::Point const p(desktop->w2d(button_pt));
425                 Inkscape::Rubberband::get()->start(desktop, p);
426                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
427                                     GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK,
428                                     NULL, event->button.time);
429                 sc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
431                 // remember what modifiers were on before button press
432                 sc->button_press_shift = (event->button.state & GDK_SHIFT_MASK) ? true : false;
433                 sc->button_press_ctrl = (event->button.state & GDK_CONTROL_MASK) ? true : false;
434                 sc->button_press_alt = (event->button.state & GDK_MOD1_MASK) ? true : false;
436                 sc->moved = FALSE;
438                 rb_escaped = drag_escaped = 0;
440                 ret = TRUE;
441             } else if (event->button.button == 3) {
442                 // right click; do not eat it so that right-click menu can appear, but cancel dragging & rubberband
443                 sp_select_context_abort(event_context);
444             }
445             break;
447         case GDK_MOTION_NOTIFY:
448             if (event->motion.state & GDK_BUTTON1_MASK) {
449                 NR::Point const motion_pt(event->motion.x, event->motion.y);
450                 NR::Point const p(desktop->w2d(motion_pt));
452                 if ( within_tolerance
453                      && ( abs( (gint) event->motion.x - xp ) < tolerance )
454                      && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) {
455                     break; // do not drag if we're within tolerance from origin
456                 }
457                 // Once the user has moved farther than tolerance from the original location
458                 // (indicating they intend to move the object, not click), then always process the
459                 // motion notify coordinates as given (no snapping back to origin)
460                 within_tolerance = false;
462                 if (sc->button_press_ctrl || sc->button_press_alt) // if ctrl or alt was pressed and it's not click, we want to drag rather than rubberband
463                     sc->dragging = TRUE;
465                 if (sc->dragging) {
466                     /* User has dragged fast, so we get events on root (lauris)*/
467                     // not only that; we will end up here when ctrl-dragging as well
468                     // and also when we started within tolerance, but trespassed tolerance outside of item
469                     Inkscape::Rubberband::get()->stop();
470                     item_at_point = desktop->item_at_point(NR::Point(event->button.x, event->button.y), FALSE);
471                     if (!item_at_point) // if no item at this point, try at the click point (bug 1012200)
472                         item_at_point = desktop->item_at_point(NR::Point(xp, yp), FALSE);
473                     if (item_at_point || sc->moved || sc->button_press_alt) {
474                         // drag only if starting from an item, or if something is already grabbed, or if alt-dragging
475                         if (!sc->moved) {
476                             item_in_group = desktop->item_at_point(NR::Point(event->button.x, event->button.y), TRUE);
477                             group_at_point = desktop->group_at_point(NR::Point(event->button.x, event->button.y));
478                             // if neither a group nor an item (possibly in a group) at point are selected, set selection to the item at point
479                             if ((!item_in_group || !selection->includes(item_in_group)) &&
480                                 (!group_at_point || !selection->includes(group_at_point))
481                                 && !sc->button_press_alt) {
482                                 // select what is under cursor
483                                 if (!seltrans->isEmpty()) {
484                                     seltrans->resetState();
485                                 }
486                                 // when simply ctrl-dragging, we don't want to go into groups
487                                 if (item_at_point && !selection->includes(item_at_point))
488                                     selection->set(item_at_point);
489                             } // otherwise, do not change selection so that dragging selected-within-group items, as well as alt-dragging, is possible
490                             seltrans->grab(p, -1, -1, FALSE);
491                             sc->moved = TRUE;
492                         }
493                         if (!seltrans->isEmpty())
494                             seltrans->moveTo(p, event->button.state);
495                         if (desktop->scroll_to_point(&p))
496                             // unfortunately in complex drawings, gobbling results in losing grab of the object, for some mysterious reason
497                             ; //gobble_motion_events(GDK_BUTTON1_MASK);
498                         ret = TRUE;
499                     } else {
500                         sc->dragging = FALSE;
501                     }
502                 } else {
503                     Inkscape::Rubberband::get()->move(p);
504                     gobble_motion_events(GDK_BUTTON1_MASK);
505                 }
506             }
507             break;
508         case GDK_BUTTON_RELEASE:
509             xp = yp = 0;
510             if ((event->button.button == 1) && (sc->grabbed)) {
511                 if (sc->dragging) {
512                     if (sc->moved) {
513                         // item has been moved
514                         seltrans->ungrab();
515                         sc->moved = FALSE;
516                     } else if (sc->item && !drag_escaped) {
517                         // item has not been moved -> simply a click, do selecting
518                         if (!selection->isEmpty()) {
519                             if (event->button.state & GDK_SHIFT_MASK) {
520                                 // with shift, toggle selection
521                                 seltrans->resetState();
522                                 selection->toggle(sc->item);
523                             } else {
524                                 // without shift, increase state (i.e. toggle scale/rotation handles)
525                                 if (selection->includes(sc->item)) {
526                                     seltrans->increaseState();
527                                 } else {
528                                     seltrans->resetState();
529                                     selection->set(sc->item);
530                                 }
531                             }
532                         } else { // simple or shift click, no previous selection
533                             seltrans->resetState();
534                             selection->set(sc->item);
535                         }
536                     }
537                     sc->dragging = FALSE;
538                     if (sc->item) {
539                         sp_object_unref( SP_OBJECT(sc->item), NULL);
540                     }
541                     sc->item = NULL;
542                 } else {
543                     NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
544                     if (b != NR::Nothing() && !within_tolerance) {
545                         // this was a rubberband drag
546                         Inkscape::Rubberband::get()->stop();
547                         seltrans->resetState();
548                         // find out affected items:
549                         GSList *items = sp_document_items_in_box(SP_DT_DOCUMENT(desktop), desktop->dkey, b.assume());
550                         if (event->button.state & GDK_SHIFT_MASK) {
551                             // with shift, add to selection
552                             selection->addList (items);
553                         } else {
554                             // without shift, simply select anew
555                             selection->setList (items);
556                         }
557                         g_slist_free (items);
558                     } else { // it was just a click, or a too small rubberband
559                         Inkscape::Rubberband::get()->stop();
560                         if (sc->button_press_shift && !rb_escaped && !drag_escaped) {
561                             // this was a shift-click, select what was clicked upon
563                             sc->button_press_shift = false;
565                             if (sc->button_press_ctrl) {
566                                 // go into groups, honoring Alt
567                                 item = sp_event_context_find_item (desktop,
568                                                    NR::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, TRUE);
569                                 sc->button_press_ctrl = FALSE;
570                             } else {
571                                 // don't go into groups, honoring Alt
572                                 item = sp_event_context_find_item (desktop,
573                                                    NR::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, FALSE);
574                             }
576                             if (item) {
577                                 selection->toggle(item);
578                                 item = NULL;
579                             }
581                         } else if (sc->button_press_ctrl && !rb_escaped && !drag_escaped) { // ctrl-click
583                             sc->button_press_ctrl = FALSE;
585                             item = sp_event_context_find_item (desktop,
586                                          NR::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, TRUE);
588                             if (item) {
589                                 if (selection->includes(item)) {
590                                     seltrans->increaseState();
591                                 } else {
592                                     seltrans->resetState();
593                                     selection->set(item);
594                                 }
595                                 item = NULL;
596                             }
598                         } else { // click without shift, simply deselect, unless with Alt or something was cancelled
599                             if (!selection->isEmpty()) {
600                                 if (!(rb_escaped) && !(drag_escaped) && !(event->button.state & GDK_MOD1_MASK))
601                                     selection->clear();
602                                 rb_escaped = 0;
603                                 ret = TRUE;
604                             }
605                         }
606                     }
607                     ret = TRUE;
608                 }
609                 if (sc->grabbed) {
610                     sp_canvas_item_ungrab(sc->grabbed, event->button.time);
611                     sc->grabbed = NULL;
612                 }
613             }
614             sc->button_press_shift = false;
615             sc->button_press_ctrl = false;
616             sc->button_press_alt = false;
617             break;
619         case GDK_KEY_PRESS: // keybindings for select context
621             if (!key_is_a_modifier (get_group0_keyval (&event->key))) {
622                     event_context->defaultMessageContext()->clear();
623             } else {
624                     sp_event_show_modifier_tip (event_context->defaultMessageContext(), event,
625                                                 _("<b>Ctrl</b>: select in groups, move hor/vert"),
626                                                 _("<b>Shift</b>: toggle select, force rubberband, disable snapping"),
627                                                 _("<b>Alt</b>: select under, move selected"));
628                     break;
629             }
631             switch (get_group0_keyval (&event->key)) {
632                 case GDK_Left: // move selection left
633                 case GDK_KP_Left:
634                 case GDK_KP_4:
635                     if (!MOD__CTRL) { // not ctrl
636                         if (MOD__ALT) { // alt
637                             if (MOD__SHIFT) sp_selection_move_screen(-10, 0); // shift
638                             else sp_selection_move_screen(-1, 0); // no shift
639                         }
640                         else { // no alt
641                             if (MOD__SHIFT) sp_selection_move(-10*nudge, 0); // shift
642                             else sp_selection_move(-nudge, 0); // no shift
643                         }
644                         ret = TRUE;
645                     }
646                     break;
647                 case GDK_Up: // move selection up
648                 case GDK_KP_Up:
649                 case GDK_KP_8:
650                     if (!MOD__CTRL) { // not ctrl
651                         if (MOD__ALT) { // alt
652                             if (MOD__SHIFT) sp_selection_move_screen(0, 10); // shift
653                             else sp_selection_move_screen(0, 1); // no shift
654                         }
655                         else { // no alt
656                             if (MOD__SHIFT) sp_selection_move(0, 10*nudge); // shift
657                             else sp_selection_move(0, nudge); // no shift
658                         }
659                         ret = TRUE;
660                     }
661                     break;
662                 case GDK_Right: // move selection right
663                 case GDK_KP_Right:
664                 case GDK_KP_6:
665                     if (!MOD__CTRL) { // not ctrl
666                         if (MOD__ALT) { // alt
667                             if (MOD__SHIFT) sp_selection_move_screen(10, 0); // shift
668                             else sp_selection_move_screen(1, 0); // no shift
669                         }
670                         else { // no alt
671                             if (MOD__SHIFT) sp_selection_move(10*nudge, 0); // shift
672                             else sp_selection_move(nudge, 0); // no shift
673                         }
674                         ret = TRUE;
675                     }
676                     break;
677                 case GDK_Down: // move selection down
678                 case GDK_KP_Down:
679                 case GDK_KP_2:
680                     if (!MOD__CTRL) { // not ctrl
681                         if (MOD__ALT) { // alt
682                             if (MOD__SHIFT) sp_selection_move_screen(0, -10); // shift
683                             else sp_selection_move_screen(0, -1); // no shift
684                         }
685                         else { // no alt
686                             if (MOD__SHIFT) sp_selection_move(0, -10*nudge); // shift
687                             else sp_selection_move(0, -nudge); // no shift
688                         }
689                         ret = TRUE;
690                     }
691                     break;
692                 case GDK_Escape:
693                     if (!sp_select_context_abort(event_context))
694                         selection->clear();
695                     ret = TRUE;
696                     break;
697                 case GDK_a:
698                 case GDK_A:
699                     if (MOD__CTRL_ONLY) {
700                         sp_edit_select_all();
701                         ret = TRUE;
702                     }
703                     break;
704                 case GDK_Tab: // Tab - cycle selection forward
705                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
706                         sp_selection_item_next();
707                         ret = TRUE;
708                     }
709                     break;
710                 case GDK_ISO_Left_Tab: // Shift Tab - cycle selection backward
711                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
712                         sp_selection_item_prev();
713                         ret = TRUE;
714                     }
715                     break;
716                 case GDK_space:
717                     /* stamping mode: show outline mode moving */
718                     /* FIXME: Is next condition ok? (lauris) */
719                     if (sc->dragging && sc->grabbed) {
720                         seltrans->stamp();
721                         ret = TRUE;
722                     }
723                     break;
724                 case GDK_x:
725                 case GDK_X:
726                     if (MOD__ALT_ONLY) {
727                         desktop->setToolboxFocusTo ("altx");
728                         ret = TRUE;
729                     }
730                     break;
731                 case GDK_bracketleft:
732                     if (MOD__ALT) {
733                         sp_selection_rotate_screen(selection, 1);
734                     } else if (MOD__CTRL) {
735                         sp_selection_rotate(selection, 90);
736                     } else if (snaps) {
737                         sp_selection_rotate(selection, 180/snaps);
738                     }
739                     ret = TRUE;
740                     break;
741                 case GDK_bracketright:
742                     if (MOD__ALT) {
743                         sp_selection_rotate_screen(selection, -1);
744                     } else if (MOD__CTRL) {
745                         sp_selection_rotate(selection, -90);
746                     } else if (snaps) {
747                         sp_selection_rotate(selection, -180/snaps);
748                     }
749                     ret = TRUE;
750                     break;
751                 case GDK_less:
752                 case GDK_comma:
753                     if (MOD__ALT) {
754                         sp_selection_scale_screen(selection, -2);
755                     } else if (MOD__CTRL) {
756                         sp_selection_scale_times(selection, 0.5);
757                     } else {
758                         sp_selection_scale(selection, -offset);
759                     }
760                     ret = TRUE;
761                     break;
762                 case GDK_greater:
763                 case GDK_period:
764                     if (MOD__ALT) {
765                         sp_selection_scale_screen(selection, 2);
766                     } else if (MOD__CTRL) {
767                         sp_selection_scale_times(selection, 2);
768                     } else {
769                         sp_selection_scale(selection, offset);
770                     }
771                     ret = TRUE;
772                     break;
773                 case GDK_Return:
774                     if (MOD__CTRL_ONLY) {
775                         if (selection->singleItem()) {
776                             SPItem *clicked_item = selection->singleItem();
777                             if (SP_IS_GROUP (clicked_item)) { // enter group
778                                 desktop->setCurrentLayer(reinterpret_cast<SPObject *>(clicked_item));
779                                 SP_DT_SELECTION(desktop)->clear();
780                             } else {
781                                 SP_EVENT_CONTEXT(sc)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selected object is not a group. Cannot enter."));
782                             }
783                         }
784                         ret = TRUE;
785                     }
786                     break;
787                 case GDK_BackSpace:
788                     if (MOD__CTRL_ONLY) {
789                         sp_select_context_up_one_layer(desktop);
790                         ret = TRUE;
791                     }
792                     break;
793                 default:
794                     break;
795             }
796             break;
797         case GDK_KEY_RELEASE:
798             if (key_is_a_modifier (get_group0_keyval (&event->key)))
799                 event_context->defaultMessageContext()->clear();
800             break;
801         default:
802             break;
803     }
805     if (!ret) {
806         if (((SPEventContextClass *) parent_class)->root_handler)
807             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
808     }
810     return ret;
814 /*
815   Local Variables:
816   mode:c++
817   c-file-style:"stroustrup"
818   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
819   indent-tabs-mode:nil
820   fill-column:99
821   End:
822 */
823 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :