Code

Implement selection linear grow
[inkscape.git] / src / node-context.cpp
1 #define __SP_NODE_CONTEXT_C__
3 /*
4  * Node editing context
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   bulia byak <buliabyak@users.sf.net>
9  *
10  * This code is in public domain
11  */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16 #include <cstring>
17 #include <string>
18 #include <gdk/gdkkeysyms.h>
19 #include "macros.h"
20 #include <glibmm/i18n.h>
21 #include "display/sp-canvas-util.h"
22 #include "object-edit.h"
23 #include "sp-path.h"
24 #include "path-chemistry.h"
25 #include "rubberband.h"
26 #include "desktop.h"
27 #include "desktop-handles.h"
28 #include "selection.h"
29 #include "pixmaps/cursor-node.xpm"
30 #include "message-context.h"
31 #include "node-context.h"
32 #include "pixmaps/cursor-node-d.xpm"
33 #include "preferences.h"
34 #include "xml/node-event-vector.h"
35 #include "style.h"
36 #include "splivarot.h"
37 #include "shape-editor.h"
38 #include "live_effects/effect.h"
40 #include "sp-lpe-item.h"
42 // needed for flash nodepath upon mouseover:
43 #include "display/canvas-bpath.h"
44 #include "display/curve.h"
46 static void sp_node_context_class_init(SPNodeContextClass *klass);
47 static void sp_node_context_init(SPNodeContext *node_context);
48 static void sp_node_context_dispose(GObject *object);
50 static void sp_node_context_setup(SPEventContext *ec);
51 static gint sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event);
52 static gint sp_node_context_item_handler(SPEventContext *event_context,
53                                          SPItem *item, GdkEvent *event);
55 static SPEventContextClass *parent_class;
57 GType
58 sp_node_context_get_type()
59 {
60     static GType type = 0;
61     if (!type) {
62         GTypeInfo info = {
63             sizeof(SPNodeContextClass),
64             NULL, NULL,
65             (GClassInitFunc) sp_node_context_class_init,
66             NULL, NULL,
67             sizeof(SPNodeContext),
68             4,
69             (GInstanceInitFunc) sp_node_context_init,
70             NULL,    /* value_table */
71         };
72         type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPNodeContext", &info, (GTypeFlags)0);
73     }
74     return type;
75 }
77 static void
78 sp_node_context_class_init(SPNodeContextClass *klass)
79 {
80     GObjectClass *object_class = (GObjectClass *) klass;
81     SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
83     parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
85     object_class->dispose = sp_node_context_dispose;
87     event_context_class->setup = sp_node_context_setup;
88     event_context_class->root_handler = sp_node_context_root_handler;
89     event_context_class->item_handler = sp_node_context_item_handler;
90 }
92 static void
93 sp_node_context_init(SPNodeContext *node_context)
94 {
95     SPEventContext *event_context = SP_EVENT_CONTEXT(node_context);
97     event_context->cursor_shape = cursor_node_xpm;
98     event_context->hot_x = 1;
99     event_context->hot_y = 1;
101     node_context->leftalt = FALSE;
102     node_context->rightalt = FALSE;
103     node_context->leftctrl = FALSE;
104     node_context->rightctrl = FALSE;
106     new (&node_context->sel_changed_connection) sigc::connection();
108     node_context->flash_tempitem = NULL;
109     node_context->flashed_item = NULL;
110     node_context->remove_flash_counter = 0;
113 static void
114 sp_node_context_dispose(GObject *object)
116         SPNodeContext *nc = SP_NODE_CONTEXT(object);
117     SPEventContext *ec = SP_EVENT_CONTEXT(object);
119     ec->enableGrDrag(false);
120         
121     if (nc->grabbed) {
122         sp_canvas_item_ungrab(nc->grabbed, GDK_CURRENT_TIME);
123         nc->grabbed = NULL;
124     }
126     nc->sel_changed_connection.disconnect();
127     nc->sel_changed_connection.~connection();
129     delete ec->shape_editor;
131     if (nc->_node_message_context) {
132         delete nc->_node_message_context;
133     }
135     G_OBJECT_CLASS(parent_class)->dispose(object);
138 static void
139 sp_node_context_setup(SPEventContext *ec)
141     SPNodeContext *nc = SP_NODE_CONTEXT(ec);
143     if (((SPEventContextClass *) parent_class)->setup)
144         ((SPEventContextClass *) parent_class)->setup(ec);
146     Inkscape::Selection *selection = sp_desktop_selection (ec->desktop);
147     nc->sel_changed_connection.disconnect();
148     nc->sel_changed_connection =
149         selection->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
151     SPItem *item = selection->singleItem();
153     ec->shape_editor = new ShapeEditor(ec->desktop);
155     nc->rb_escaped = false;
157     nc->cursor_drag = false;
159     nc->added_node = false;
161     nc->current_state = SP_NODE_CONTEXT_INACTIVE;
163     if (item) {
164         ec->shape_editor->set_item(item, SH_NODEPATH);
165         ec->shape_editor->set_item(item, SH_KNOTHOLDER);
166     }
168     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
169     if (prefs->getBool("/tools/nodes/selcue")) {
170         ec->enableSelectionCue();
171     }
172     if (prefs->getBool("/tools/nodes/gradientdrag")) {
173         ec->enableGrDrag();
174     }
176     ec->desktop->emitToolSubselectionChanged(NULL); // sets the coord entry fields to inactive
178     nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
180     ec->shape_editor->update_statusbar();
183 static void
184 sp_node_context_flash_path(SPEventContext *event_context, SPItem *item, guint timeout) {
185     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
187     nc->remove_flash_counter = 3; // for some reason root_handler is called twice after each item_handler...
188     if (nc->flashed_item != item) {
189         // we entered a new item
190         nc->flashed_item = item;
191         SPDesktop *desktop = event_context->desktop;
192         if (nc->flash_tempitem) {
193             desktop->remove_temporary_canvasitem(nc->flash_tempitem);
194             nc->flash_tempitem = NULL;
195         }
197         SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, item);
199         if (canvasitem) {
200             nc->flash_tempitem = desktop->add_temporary_canvasitem (canvasitem, timeout);
201         }
202     }
205 /**
206 \brief  Callback that processes the "changed" signal on the selection;
207 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
208 */
209 void
210 sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
212     SPEventContext *ec = SP_EVENT_CONTEXT(data);
214     // TODO: update ShapeEditorsCollective instead
215     ec->shape_editor->unset_item(SH_NODEPATH);
216     ec->shape_editor->unset_item(SH_KNOTHOLDER);
217     SPItem *item = selection->singleItem();
218     ec->shape_editor->set_item(item, SH_NODEPATH);
219     ec->shape_editor->set_item(item, SH_KNOTHOLDER);
220     ec->shape_editor->update_statusbar();
223 void
224 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
226     sp_event_show_modifier_tip
227         (event_context->defaultMessageContext(), event,
228          _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
229          _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
230          _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
233 static gint
234 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
236     gint ret = FALSE;
237     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
238     SPDesktop *desktop = event_context->desktop;
240     switch (event->type) {
241         case GDK_MOTION_NOTIFY:
242         {
243             // find out actual item we're over, disregarding groups
244             SPItem *actual_item = sp_event_context_find_item (desktop,
245                                                               Geom::Point(event->button.x, event->button.y), FALSE, TRUE);
246             if (!actual_item)
247                 break;
250             if (prefs->getBool("/tools/nodes/pathflash_enabled")) {
251                 if (prefs->getBool("/tools/nodes/pathflash_unselected")) {
252                     // do not flash if we have some path selected and a single item in selection (i.e. it
253                     // is the same path that we're editing)
254                     SPDesktop *desktop = event_context->desktop;
255                     ShapeEditor* se = event_context->shape_editor;
256                     Inkscape::Selection *selection = sp_desktop_selection (desktop);
257                     if (se->has_nodepath() && selection->singleItem()) {
258                         break;
259                     }
260                 }
261                 if (SP_IS_LPE_ITEM(actual_item)) {
262                     Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(actual_item));
263                     if (lpe && (lpe->providesOwnFlashPaths() ||
264                                 lpe->pathFlashType() == Inkscape::LivePathEffect::SUPPRESS_FLASH)) {
265                         // path should be suppressed or permanent; this is handled in
266                         // sp_node_context_selection_changed()
267                         break;
268                     }
269                 }
270                 guint timeout = prefs->getInt("/tools/nodes/pathflash_timeout", 500);
271                 sp_node_context_flash_path(event_context, actual_item, timeout);
272             }
273         }
274         break;
276         default:
277             break;
278     }
280     if (((SPEventContextClass *) parent_class)->item_handler)
281         ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
283     return ret;
286 static gint
287 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
289     SPDesktop *desktop = event_context->desktop;
290     ShapeEditor* se = event_context->shape_editor;
291     Inkscape::Selection *selection = sp_desktop_selection (desktop);
292     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
294     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
295     double const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000); // in px
296     event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); // read every time, to make prefs changes really live
297     int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
298     double const offset = prefs->getDoubleLimited("/options/defaultscale/value", 2, 0, 1000);
300     if ( (nc->flash_tempitem) && (nc->remove_flash_counter <= 0) ) {
301         desktop->remove_temporary_canvasitem(nc->flash_tempitem);
302         nc->flash_tempitem = NULL;
303         nc->flashed_item = NULL; // also reset this one, so the next time the same object is hovered over it shows again the highlight
304     } else {
305         nc->remove_flash_counter--;
306     }
308     gint ret = FALSE;
309     switch (event->type) {
310         case GDK_BUTTON_PRESS:
311             if (event->button.button == 1 && !event_context->space_panning) {
312                 // save drag origin
313                 event_context->xp = (gint) event->button.x;
314                 event_context->yp = (gint) event->button.y;
315                 event_context->within_tolerance = true;
316                 se->cancel_hit();
318                 if (!(event->button.state & GDK_SHIFT_MASK)) {
319                     if (!nc->drag) {
320                         if (se->has_nodepath() && selection->single() /* && item_over */) {
321                                 // save drag origin
322                             bool over_stroke = se->is_over_stroke(Geom::Point(event->button.x, event->button.y), true);
323                             //only dragging curves
324                             if (over_stroke) {
325                                 ret = TRUE;
326                                 break;
327                             }
328                         }
329                     }
330                 }
331                 Geom::Point const button_w(event->button.x,
332                                          event->button.y);
333                 Geom::Point const button_dt(desktop->w2d(button_w));
334                 Inkscape::Rubberband::get(desktop)->start(desktop, button_dt);
336                 if (nc->grabbed) {
337                     sp_canvas_item_ungrab(nc->grabbed, event->button.time);
338                     nc->grabbed = NULL;
339                 }
340                                 
341                 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
342                                     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,
343                                     NULL, event->button.time);
344                                 nc->grabbed = SP_CANVAS_ITEM(desktop->acetate);
345                                 
346                 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
347                 desktop->updateNow();
348                 ret = TRUE;
349             }
350             break;
351         case GDK_MOTION_NOTIFY:
352             if (event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
354                 if ( event_context->within_tolerance
355                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
356                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
357                     break; // do not drag if we're within tolerance from origin
358                 }
360                 // The path went away while dragging; throw away any further motion
361                 // events until the mouse pointer is released.
363                 if (se->hits_curve() && !se->has_nodepath()) {
364                   break;
365                 }
367                 // Once the user has moved farther than tolerance from the original location
368                 // (indicating they intend to move the object, not click), then always process the
369                 // motion notify coordinates as given (no snapping back to origin)
370                 event_context->within_tolerance = false;
372                 // Once we determine what the user is doing (dragging either a node or the
373                 // selection rubberband), make sure we continue to perform that operation
374                 // until the mouse pointer is lifted.
375                 if (nc->current_state == SP_NODE_CONTEXT_INACTIVE) {
376                     if (se->hits_curve() && se->has_nodepath()) {
377                         nc->current_state = SP_NODE_CONTEXT_NODE_DRAGGING;
378                     } else {
379                         nc->current_state = SP_NODE_CONTEXT_RUBBERBAND_DRAGGING;
380                     }
381                 }
383                 switch (nc->current_state) {
384                     case SP_NODE_CONTEXT_NODE_DRAGGING:
385                         {
386                             se->curve_drag (event->motion.x, event->motion.y);
388                             gobble_motion_events(GDK_BUTTON1_MASK);
389                             break;
390                         }
391                     case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING:
392                         if (Inkscape::Rubberband::get(desktop)->is_started()) {
393                             Geom::Point const motion_w(event->motion.x,
394                                                 event->motion.y);
395                             Geom::Point const motion_dt(desktop->w2d(motion_w));
396                             Inkscape::Rubberband::get(desktop)->move(motion_dt);
397                         }
398                         break;
399                 }
401                 nc->drag = TRUE;
402                 ret = TRUE;
403             } else {
404                 if (!se->has_nodepath() || selection->singleItem() == NULL) {
405                     break;
406                 }
408                 bool over_stroke = false;
409                 over_stroke = se->is_over_stroke(Geom::Point(event->motion.x, event->motion.y), false);
411                 if (nc->cursor_drag && !over_stroke) {
412                     event_context->cursor_shape = cursor_node_xpm;
413                     event_context->hot_x = 1;
414                     event_context->hot_y = 1;
415                     sp_event_context_update_cursor(event_context);
416                     nc->cursor_drag = false;
417                 } else if (!nc->cursor_drag && over_stroke) {
418                     event_context->cursor_shape = cursor_node_d_xpm;
419                     event_context->hot_x = 1;
420                     event_context->hot_y = 1;
421                     sp_event_context_update_cursor(event_context);
422                     nc->cursor_drag = true;
423                 }
424             }
425             break;
427         case GDK_2BUTTON_PRESS:
428         case GDK_BUTTON_RELEASE:
429             if ( (event->button.button == 1) && (!nc->drag) && !event_context->space_panning) {
430                 // find out clicked item, disregarding groups, honoring Alt
431                 SPItem *item_clicked = sp_event_context_find_item (desktop,
432                         Geom::Point(event->button.x, event->button.y),
433                         (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
435                 event_context->xp = event_context->yp = 0;
437                 bool over_stroke = false;
438                 if (se->has_nodepath()) {
439                     over_stroke = se->is_over_stroke(Geom::Point(event->button.x, event->button.y), false);
440                 }
442                 if (item_clicked || over_stroke) {
443                     if (over_stroke || nc->added_node) {
444                         switch (event->type) {
445                             case GDK_BUTTON_RELEASE:
446                                 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
447                                     //add a node
448                                     se->add_node_near_point();
449                                 } else {
450                                     if (nc->added_node) { // we just received double click, ignore release
451                                         nc->added_node = false;
452                                         break;
453                                     }
454                                     //select the segment
455                                     if (event->button.state & GDK_SHIFT_MASK) {
456                                         se->select_segment_near_point(true);
457                                     } else {
458                                         se->select_segment_near_point(false);
459                                     }
460                                     desktop->updateNow();
461                                 }
462                                 break;
463                             case GDK_2BUTTON_PRESS:
464                                 //add a node
465                                 se->add_node_near_point();
466                                 nc->added_node = true;
467                                 break;
468                             default:
469                                 break;
470                         }
471                     } else if (event->button.state & GDK_SHIFT_MASK) {
472                         selection->toggle(item_clicked);
473                         desktop->updateNow();
474                     } else {
475                         selection->set(item_clicked);
476                         desktop->updateNow();
477                     }
478                     Inkscape::Rubberband::get(desktop)->stop();
479                     if (nc->grabbed) {
480                         sp_canvas_item_ungrab(nc->grabbed, event->button.time);
481                         nc->grabbed = NULL;
482                     }
483                     ret = TRUE;
484                     break;
485                 }
486             }
487             if (event->type == GDK_BUTTON_RELEASE) {
488                 event_context->xp = event_context->yp = 0;
489                 if (event->button.button == 1) {
490                         Geom::OptRect b = Inkscape::Rubberband::get(desktop)->getRectangle();
492                     if (se->hits_curve() && !event_context->within_tolerance) { //drag curve
493                         se->finish_drag();
494                     } else if (b && !event_context->within_tolerance) { // drag to select
495                         se->select_rect(*b, event->button.state & GDK_SHIFT_MASK);
496                     } else {
497                         if (!(nc->rb_escaped)) { // unless something was canceled
498                             if (se->has_selection())
499                                 se->deselect();
500                             else
501                                 sp_desktop_selection(desktop)->clear();
502                         }
503                     }
504                     ret = TRUE;
505                     Inkscape::Rubberband::get(desktop)->stop();
506                                         
507                                         if (nc->grabbed) {
508                                                 sp_canvas_item_ungrab(nc->grabbed, event->button.time);
509                                                 nc->grabbed = NULL;
510                                         }
511                                         
512                     desktop->updateNow();
513                     nc->rb_escaped = false;
514                     nc->drag = FALSE;
515                     se->cancel_hit();
516                     nc->current_state = SP_NODE_CONTEXT_INACTIVE;
517                 }
518             }
519             break;
520         case GDK_KEY_PRESS:
521             switch (get_group0_keyval(&event->key)) {
522                 case GDK_Insert:
523                 case GDK_KP_Insert:
524                     // with any modifiers
525                     se->add_node();
526                     ret = TRUE;
527                     break;
528                 case GDK_I:
529                 case GDK_i:
530                     // apple keyboards have no Insert
531                     if (MOD__SHIFT_ONLY) {
532                         se->add_node();
533                         ret = TRUE;
534                     }
535                     break;
536                 case GDK_Delete:
537                 case GDK_KP_Delete:
538                 case GDK_BackSpace:
539                     if (MOD__CTRL_ONLY) {
540                         se->delete_nodes();
541                     } else {
542                         se->delete_nodes_preserving_shape();
543                     }
544                     ret = TRUE;
545                     break;
546                 case GDK_C:
547                 case GDK_c:
548                     if (MOD__SHIFT_ONLY) {
549                         se->set_node_type(Inkscape::NodePath::NODE_CUSP);
550                         ret = TRUE;
551                     }
552                     break;
553                 case GDK_S:
554                 case GDK_s:
555                     if (MOD__SHIFT_ONLY) {
556                         se->set_node_type(Inkscape::NodePath::NODE_SMOOTH);
557                         ret = TRUE;
558                     }
559                     break;
560                 case GDK_A:
561                 case GDK_a:
562                     if (MOD__SHIFT_ONLY) {
563                         se->set_node_type(Inkscape::NodePath::NODE_AUTO);
564                         ret = TRUE;
565                     }
566                     break;
567                 case GDK_Y:
568                 case GDK_y:
569                     if (MOD__SHIFT_ONLY) {
570                         se->set_node_type(Inkscape::NodePath::NODE_SYMM);
571                         ret = TRUE;
572                     }
573                     break;
574                 case GDK_B:
575                 case GDK_b:
576                     if (MOD__SHIFT_ONLY) {
577                         se->break_at_nodes();
578                         ret = TRUE;
579                     }
580                     break;
581                 case GDK_J:
582                 case GDK_j:
583                     if (MOD__SHIFT_ONLY) {
584                         se->join_nodes();
585                         ret = TRUE;
586                     }
587                     break;
588                 case GDK_D:
589                 case GDK_d:
590                     if (MOD__SHIFT_ONLY) {
591                         se->duplicate_nodes();
592                         ret = TRUE;
593                     }
594                     break;
595                 case GDK_L:
596                 case GDK_l:
597                     if (MOD__SHIFT_ONLY) {
598                         se->set_type_of_segments(NR_LINETO);
599                         ret = TRUE;
600                     }
601                     break;
602                 case GDK_U:
603                 case GDK_u:
604                     if (MOD__SHIFT_ONLY) {
605                         se->set_type_of_segments(NR_CURVETO);
606                         ret = TRUE;
607                     }
608                     break;
609                 case GDK_R:
610                 case GDK_r:
611                     if (MOD__SHIFT_ONLY) {
612                         // FIXME: add top panel button
613                         sp_selected_path_reverse(desktop);
614                         ret = TRUE;
615                     }
616                     break;
617                 case GDK_x:
618                 case GDK_X:
619                     if (MOD__ALT_ONLY) {
620                         desktop->setToolboxFocusTo ("altx-nodes");
621                         ret = TRUE;
622                     }
623                     break;
624                 case GDK_Left: // move selection left
625                 case GDK_KP_Left:
626                 case GDK_KP_4:
627                     if (!MOD__CTRL) { // not ctrl
628                         gint mul = 1 + gobble_key_events(
629                             get_group0_keyval(&event->key), 0); // with any mask
630                         if (MOD__ALT) { // alt
631                             if (MOD__SHIFT) se->move_nodes_screen(desktop, mul*-10, 0); // shift
632                             else se->move_nodes_screen(desktop, mul*-1, 0); // no shift
633                         }
634                         else { // no alt
635                             if (MOD__SHIFT) se->move_nodes(mul*-10*nudge, 0); // shift
636                             else se->move_nodes(mul*-nudge, 0); // no shift
637                         }
638                         ret = TRUE;
639                     }
640                     break;
641                 case GDK_Up: // move selection up
642                 case GDK_KP_Up:
643                 case GDK_KP_8:
644                     if (!MOD__CTRL) { // not ctrl
645                         gint mul = 1 + gobble_key_events(
646                             get_group0_keyval(&event->key), 0); // with any mask
647                         if (MOD__ALT) { // alt
648                             if (MOD__SHIFT) se->move_nodes_screen(desktop, 0, mul*10); // shift
649                             else se->move_nodes_screen(desktop, 0, mul*1); // no shift
650                         }
651                         else { // no alt
652                             if (MOD__SHIFT) se->move_nodes(0, mul*10*nudge); // shift
653                             else se->move_nodes(0, mul*nudge); // no shift
654                         }
655                         ret = TRUE;
656                     }
657                     break;
658                 case GDK_Right: // move selection right
659                 case GDK_KP_Right:
660                 case GDK_KP_6:
661                     if (!MOD__CTRL) { // not ctrl
662                         gint mul = 1 + gobble_key_events(
663                             get_group0_keyval(&event->key), 0); // with any mask
664                         if (MOD__ALT) { // alt
665                             if (MOD__SHIFT) se->move_nodes_screen(desktop, mul*10, 0); // shift
666                             else se->move_nodes_screen(desktop, mul*1, 0); // no shift
667                         }
668                         else { // no alt
669                             if (MOD__SHIFT) se->move_nodes(mul*10*nudge, 0); // shift
670                             else se->move_nodes(mul*nudge, 0); // no shift
671                         }
672                         ret = TRUE;
673                     }
674                     break;
675                 case GDK_Down: // move selection down
676                 case GDK_KP_Down:
677                 case GDK_KP_2:
678                     if (!MOD__CTRL) { // not ctrl
679                         gint mul = 1 + gobble_key_events(
680                             get_group0_keyval(&event->key), 0); // with any mask
681                         if (MOD__ALT) { // alt
682                             if (MOD__SHIFT) se->move_nodes_screen(desktop, 0, mul*-10); // shift
683                             else se->move_nodes_screen(desktop, 0, mul*-1); // no shift
684                         }
685                         else { // no alt
686                             if (MOD__SHIFT) se->move_nodes(0, mul*-10*nudge); // shift
687                             else se->move_nodes(0, mul*-nudge); // no shift
688                         }
689                         ret = TRUE;
690                     }
691                     break;
692                 case GDK_Escape:
693                 {
694                     Geom::OptRect const b = Inkscape::Rubberband::get(desktop)->getRectangle();
695                     if (b) {
696                         Inkscape::Rubberband::get(desktop)->stop();
697                         nc->current_state = SP_NODE_CONTEXT_INACTIVE;
698                         nc->rb_escaped = true;
699                     } else {
700                         if (se->has_selection()) {
701                             se->deselect();
702                         } else {
703                             sp_desktop_selection(desktop)->clear();
704                         }
705                     }
706                     ret = TRUE;
707                     break;
708                 }
710                 case GDK_bracketleft:
711                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
712                         if (nc->leftctrl)
713                             se->rotate_nodes (M_PI/snaps, -1, false);
714                         if (nc->rightctrl)
715                             se->rotate_nodes (M_PI/snaps, 1, false);
716                     } else if ( MOD__ALT && !MOD__CTRL ) {
717                         if (nc->leftalt && nc->rightalt)
718                             se->rotate_nodes (1, 0, true);
719                         else {
720                             if (nc->leftalt)
721                                 se->rotate_nodes (1, -1, true);
722                             if (nc->rightalt)
723                                 se->rotate_nodes (1, 1, true);
724                         }
725                     } else if ( snaps != 0 ) {
726                         se->rotate_nodes (M_PI/snaps, 0, false);
727                     }
728                     ret = TRUE;
729                     break;
730                 case GDK_bracketright:
731                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
732                         if (nc->leftctrl)
733                             se->rotate_nodes (-M_PI/snaps, -1, false);
734                         if (nc->rightctrl)
735                             se->rotate_nodes (-M_PI/snaps, 1, false);
736                     } else if ( MOD__ALT && !MOD__CTRL ) {
737                         if (nc->leftalt && nc->rightalt)
738                             se->rotate_nodes (-1, 0, true);
739                         else {
740                             if (nc->leftalt)
741                                 se->rotate_nodes (-1, -1, true);
742                             if (nc->rightalt)
743                                 se->rotate_nodes (-1, 1, true);
744                         }
745                     } else if ( snaps != 0 ) {
746                         se->rotate_nodes (-M_PI/snaps, 0, false);
747                     }
748                     ret = TRUE;
749                     break;
750                 case GDK_less:
751                 case GDK_comma:
752                     if (MOD__CTRL) {
753                         if (nc->leftctrl)
754                             se->scale_nodes(-offset, -1);
755                         if (nc->rightctrl)
756                             se->scale_nodes(-offset, 1);
757                     } else if (MOD__ALT) {
758                         if (nc->leftalt && nc->rightalt)
759                             se->scale_nodes_screen (-1, 0);
760                         else {
761                             if (nc->leftalt)
762                                 se->scale_nodes_screen (-1, -1);
763                             if (nc->rightalt)
764                                 se->scale_nodes_screen (-1, 1);
765                         }
766                     } else {
767                         se->scale_nodes (-offset, 0);
768                     }
769                     ret = TRUE;
770                     break;
771                 case GDK_greater:
772                 case GDK_period:
773                     if (MOD__CTRL) {
774                         if (nc->leftctrl)
775                             se->scale_nodes (offset, -1);
776                         if (nc->rightctrl)
777                             se->scale_nodes (offset, 1);
778                     } else if (MOD__ALT) {
779                         if (nc->leftalt && nc->rightalt)
780                             se->scale_nodes_screen (1, 0);
781                         else {
782                             if (nc->leftalt)
783                                 se->scale_nodes_screen (1, -1);
784                             if (nc->rightalt)
785                                 se->scale_nodes_screen (1, 1);
786                         }
787                     } else {
788                         se->scale_nodes (offset, 0);
789                     }
790                     ret = TRUE;
791                     break;
793                 case GDK_Alt_L:
794                     nc->leftalt = TRUE;
795                     sp_node_context_show_modifier_tip(event_context, event);
796                     break;
797                 case GDK_Alt_R:
798                     nc->rightalt = TRUE;
799                     sp_node_context_show_modifier_tip(event_context, event);
800                     break;
801                 case GDK_Control_L:
802                     nc->leftctrl = TRUE;
803                     sp_node_context_show_modifier_tip(event_context, event);
804                     break;
805                 case GDK_Control_R:
806                     nc->rightctrl = TRUE;
807                     sp_node_context_show_modifier_tip(event_context, event);
808                     break;
809                 case GDK_Shift_L:
810                 case GDK_Shift_R:
811                 case GDK_Meta_L:
812                 case GDK_Meta_R:
813                     sp_node_context_show_modifier_tip(event_context, event);
814                     break;
815                 default:
816                     ret = node_key(event);
817                     break;
818             }
819             break;
820         case GDK_KEY_RELEASE:
821             switch (get_group0_keyval(&event->key)) {
822                 case GDK_Alt_L:
823                     nc->leftalt = FALSE;
824                     event_context->defaultMessageContext()->clear();
825                     break;
826                 case GDK_Alt_R:
827                     nc->rightalt = FALSE;
828                     event_context->defaultMessageContext()->clear();
829                     break;
830                 case GDK_Control_L:
831                     nc->leftctrl = FALSE;
832                     event_context->defaultMessageContext()->clear();
833                     break;
834                 case GDK_Control_R:
835                     nc->rightctrl = FALSE;
836                     event_context->defaultMessageContext()->clear();
837                     break;
838                 case GDK_Shift_L:
839                 case GDK_Shift_R:
840                 case GDK_Meta_L:
841                 case GDK_Meta_R:
842                     event_context->defaultMessageContext()->clear();
843                     break;
844             }
845             break;
846         default:
847             break;
848     }
850     if (!ret) {
851         if (((SPEventContextClass *) parent_class)->root_handler)
852             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
853     }
855     return ret;
859 /*
860   Local Variables:
861   mode:c++
862   c-file-style:"stroustrup"
863   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
864   indent-tabs-mode:nil
865   fill-column:99
866   End:
867 */
868 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :