Code

revert 11172, synthesized events are used to update controls spinbuttons
[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, except stamping code,
11  * which is Copyright (C) Masatake Yamato 2002
12  */
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
17 #include <gdk/gdkkeysyms.h>
18 #include "macros.h"
19 #include <glibmm/i18n.h>
20 #include "display/sp-canvas-util.h"
21 #include "object-edit.h"
22 #include "sp-path.h"
23 #include "path-chemistry.h"
24 #include "rubberband.h"
25 #include "desktop.h"
26 #include "desktop-handles.h"
27 #include "selection.h"
28 #include "pixmaps/cursor-node.xpm"
29 #include "message-context.h"
30 #include "node-context.h"
31 #include "pixmaps/cursor-node-d.xpm"
32 #include "prefs-utils.h"
33 #include "xml/node-event-vector.h"
34 #include "style.h"
35 #include "splivarot.h"
37 static void sp_node_context_class_init(SPNodeContextClass *klass);
38 static void sp_node_context_init(SPNodeContext *node_context);
39 static void sp_node_context_dispose(GObject *object);
41 static void sp_node_context_setup(SPEventContext *ec);
42 static gint sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event);
43 static gint sp_node_context_item_handler(SPEventContext *event_context,
44                                          SPItem *item, GdkEvent *event);
46 static void nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
47                                         gchar const *old_value, gchar const *new_value,
48                                         bool is_interactive, gpointer data);
50 static Inkscape::XML::NodeEventVector nodepath_repr_events = {
51     NULL, /* child_added */
52     NULL, /* child_removed */
53     nodepath_event_attr_changed,
54     NULL, /* content_changed */
55     NULL  /* order_changed */
56 };
58 static SPEventContextClass *parent_class;
60 GType
61 sp_node_context_get_type()
62 {
63     static GType type = 0;
64     if (!type) {
65         GTypeInfo info = {
66             sizeof(SPNodeContextClass),
67             NULL, NULL,
68             (GClassInitFunc) sp_node_context_class_init,
69             NULL, NULL,
70             sizeof(SPNodeContext),
71             4,
72             (GInstanceInitFunc) sp_node_context_init,
73             NULL,    /* value_table */
74         };
75         type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPNodeContext", &info, (GTypeFlags)0);
76     }
77     return type;
78 }
80 static void
81 sp_node_context_class_init(SPNodeContextClass *klass)
82 {
83     GObjectClass *object_class = (GObjectClass *) klass;
84     SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
86     parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
88     object_class->dispose = sp_node_context_dispose;
90     event_context_class->setup = sp_node_context_setup;
91     event_context_class->root_handler = sp_node_context_root_handler;
92     event_context_class->item_handler = sp_node_context_item_handler;
93 }
95 static void
96 sp_node_context_init(SPNodeContext *node_context)
97 {
98     SPEventContext *event_context = SP_EVENT_CONTEXT(node_context);
100     event_context->cursor_shape = cursor_node_xpm;
101     event_context->hot_x = 1;
102     event_context->hot_y = 1;
104     node_context->leftalt = FALSE;
105     node_context->rightalt = FALSE;
106     node_context->leftctrl = FALSE;
107     node_context->rightctrl = FALSE;
109     new (&node_context->sel_changed_connection) sigc::connection();
112 static void
113 sp_node_context_dispose(GObject *object)
115     SPNodeContext *nc = SP_NODE_CONTEXT(object);
116     SPEventContext *ec = SP_EVENT_CONTEXT(object);
118     ec->enableGrDrag(false);
120     nc->sel_changed_connection.disconnect();
121     nc->sel_changed_connection.~connection();
123     Inkscape::XML::Node *repr = NULL;
124     if (nc->nodepath) {
125         repr = nc->nodepath->repr;
126     }
127     if (!repr && ec->shape_knot_holder) {
128         repr = ec->shape_knot_holder->repr;
129     }
131     if (repr) {
132         sp_repr_remove_listener_by_data(repr, ec);
133         Inkscape::GC::release(repr);
134     }
136     if (nc->nodepath) {
137         sp_nodepath_destroy(nc->nodepath);
138         nc->nodepath = NULL;
139     }
141     if (ec->shape_knot_holder) {
142         sp_knot_holder_destroy(ec->shape_knot_holder);
143         ec->shape_knot_holder = NULL;
144     }
146     if (nc->_node_message_context) {
147         delete nc->_node_message_context;
148     }
150     G_OBJECT_CLASS(parent_class)->dispose(object);
153 static void
154 sp_node_context_setup(SPEventContext *ec)
156     SPNodeContext *nc = SP_NODE_CONTEXT(ec);
158     if (((SPEventContextClass *) parent_class)->setup)
159         ((SPEventContextClass *) parent_class)->setup(ec);
161     nc->sel_changed_connection.disconnect();
162     nc->sel_changed_connection = sp_desktop_selection(ec->desktop)->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
164     Inkscape::Selection *selection = sp_desktop_selection(ec->desktop);
165     SPItem *item = selection->singleItem();
167     nc->nodepath = NULL;
168     ec->shape_knot_holder = NULL;
170     nc->rb_escaped = false;
172     nc->cursor_drag = false;
174     nc->added_node = false;
176     if (item) {
177         nc->nodepath = sp_nodepath_new(ec->desktop, item);
178         if ( nc->nodepath) {
179             //point pack to parent in case nodepath is deleted
180             nc->nodepath->nodeContext = nc;
181         }
182         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
184         if (nc->nodepath || ec->shape_knot_holder) {
185             // setting listener
186             Inkscape::XML::Node *repr;
187             if (ec->shape_knot_holder)
188                 repr = ec->shape_knot_holder->repr;
189             else
190                 repr = SP_OBJECT_REPR(item);
191             if (repr) {
192                 Inkscape::GC::anchor(repr);
193                 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
194             }
195         }
196     }
198     if (prefs_get_int_attribute("tools.nodes", "selcue", 0) != 0) {
199         ec->enableSelectionCue();
200     }
202     if (prefs_get_int_attribute("tools.nodes", "gradientdrag", 0) != 0) {
203         ec->enableGrDrag();
204     }
206     nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
207     sp_nodepath_update_statusbar(nc->nodepath);
210 /**
211 \brief  Callback that processes the "changed" signal on the selection;
212 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
213 */
214 void
215 sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
217     SPNodeContext *nc = SP_NODE_CONTEXT(data);
218     SPEventContext *ec = SP_EVENT_CONTEXT(nc);
220     Inkscape::XML::Node *old_repr = NULL;
222     if (nc->nodepath) {
223         old_repr = nc->nodepath->repr;
224         sp_nodepath_destroy(nc->nodepath);
225         nc->nodepath = NULL;
226     }
228     if (ec->shape_knot_holder) {
229         old_repr = ec->shape_knot_holder->repr;
230         sp_knot_holder_destroy(ec->shape_knot_holder);
231     }
233     if (old_repr) { // remove old listener
234         sp_repr_remove_listener_by_data(old_repr, ec);
235         Inkscape::GC::release(old_repr);
236     }
238     SPItem *item = selection->singleItem();
240     SPDesktop *desktop = selection->desktop();
241     nc->nodepath = NULL;
242     ec->shape_knot_holder = NULL;
243     if (item) {
244         nc->nodepath = sp_nodepath_new(desktop, item);
245         if (nc->nodepath) {
246             nc->nodepath->nodeContext = nc;
247         }
248         ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
250         if (nc->nodepath || ec->shape_knot_holder) {
251             // setting new listener
252             Inkscape::XML::Node *repr;
253             if (ec->shape_knot_holder)
254                 repr = ec->shape_knot_holder->repr;
255             else
256                 repr = SP_OBJECT_REPR(item);
257             if (repr) {
258                 Inkscape::GC::anchor(repr);
259                 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
260             }
261         }
262     }
263     sp_nodepath_update_statusbar(nc->nodepath);
266 /**
267 \brief  Regenerates nodepath when the item's repr was change outside of node edit
268 (e.g. by undo, or xml editor, or edited in another view). The item is assumed to be the same
269 (otherwise sp_node_context_selection_changed() would have been called), so repr and listeners
270 are not changed.
271 */
272 void
273 sp_nodepath_update_from_item(SPNodeContext *nc, SPItem *item)
275     g_assert(nc);
276     SPEventContext *ec = ((SPEventContext *) nc);
278     SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(SP_EVENT_CONTEXT(nc));
279     g_assert(desktop);
281     if (nc->nodepath) {
282         sp_nodepath_destroy(nc->nodepath);
283         nc->nodepath = NULL;
284     }
286     if (ec->shape_knot_holder) {
287         sp_knot_holder_destroy(ec->shape_knot_holder);
288         ec->shape_knot_holder = NULL;
289     }
291     Inkscape::Selection *selection = sp_desktop_selection(desktop);
292     item = selection->singleItem();
294     if (item) {
295         nc->nodepath = sp_nodepath_new(desktop, item);
296         if (nc->nodepath) {
297             nc->nodepath->nodeContext = nc;
298         }
299         ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
300     }
301     sp_nodepath_update_statusbar(nc->nodepath);
304 /**
305 \brief  Callback that is fired whenever an attribute of the selected item (which we have in the nodepath) changes
306 */
307 static void
308 nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
309                             gchar const *old_value, gchar const *new_value,
310                             bool is_interactive, gpointer data)
312     SPItem *item = NULL;
313     gboolean changed = FALSE;
315     g_assert(data);
316     SPNodeContext *nc = ((SPNodeContext *) data);
317     SPEventContext *ec = ((SPEventContext *) data);
318     g_assert(nc);
319     Inkscape::NodePath::Path *np = nc->nodepath;
320     SPKnotHolder *kh = ec->shape_knot_holder;
322     if (np) {
323         item = SP_ITEM(np->path);
324         if (!strcmp(name, "d") || !strcmp(name, "sodipodi:nodetypes")) { // With paths, we only need to act if one of the path-affecting attributes has changed.
325             changed = (np->local_change == 0);
326             if (np->local_change > 0)
327                 np->local_change--;
328         }
330     } else if (kh) {
331         item = SP_ITEM(kh->item);
332         changed = !(kh->local_change);
333         kh->local_change = FALSE;
334     }
336     if (np && changed) {
337         GList *saved = NULL;
338         SPDesktop *desktop = np->desktop;
339         g_assert(desktop);
340         Inkscape::Selection *selection = desktop->selection;
341         g_assert(selection);
343         saved = save_nodepath_selection(nc->nodepath);
344         sp_nodepath_update_from_item(nc, item);
345         if (nc->nodepath && saved) restore_nodepath_selection(nc->nodepath, saved);
347     } else if (kh && changed) {
348         sp_nodepath_update_from_item(nc, item);
349     }
351     sp_nodepath_update_statusbar(nc->nodepath);
354 void
355 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
357     sp_event_show_modifier_tip
358         (event_context->defaultMessageContext(), event,
359          _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
360          _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
361          _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
364 bool
365 sp_node_context_is_over_stroke (SPNodeContext *nc, SPItem *item, NR::Point event_p, bool remember)
367     SPDesktop *desktop = SP_EVENT_CONTEXT (nc)->desktop;
369     //Translate click point into proper coord system
370     nc->curvepoint_doc = desktop->w2d(event_p);
371     nc->curvepoint_doc *= sp_item_dt2i_affine(item);
372     nc->curvepoint_doc *= sp_item_i2doc_affine(item);
374     NR::Maybe<Path::cut_position> position = get_nearest_position_on_Path(nc->nodepath->livarot_path, nc->curvepoint_doc);
375     NR::Point nearest = get_point_on_Path(nc->nodepath->livarot_path, position.assume().piece, position.assume().t);
376     NR::Point delta = nearest - nc->curvepoint_doc;
378     delta = desktop->d2w(delta);
380     double stroke_tolerance =
381         (SP_OBJECT_STYLE (item)->stroke.type != SP_PAINT_TYPE_NONE?
382          desktop->current_zoom() *
383          SP_OBJECT_STYLE (item)->stroke_width.computed *
384          sp_item_i2d_affine (item).expansion() * 0.5
385          : 0.0)
386         + (double) SP_EVENT_CONTEXT(nc)->tolerance;
388     bool close = (NR::L2 (delta) < stroke_tolerance);
390     if (remember && close) {
391         nc->curvepoint_event[NR::X] = (gint) event_p [NR::X];
392         nc->curvepoint_event[NR::Y] = (gint) event_p [NR::Y];
393         nc->hit = true;
394         nc->grab_t = position.assume().t;
395         nc->grab_node = sp_nodepath_get_node_by_index(position.assume().piece);
396     }
398     return close;
402 static gint
403 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
405     gint ret = FALSE;
407     SPDesktop *desktop = event_context->desktop;
408     Inkscape::Selection *selection = sp_desktop_selection (desktop);
410     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
412     switch (event->type) {
413         case GDK_2BUTTON_PRESS:
414         case GDK_BUTTON_RELEASE:
415             if (event->button.button == 1) {
416                 if (!nc->drag) {
418                     // find out clicked item, disregarding groups, honoring Alt
419                     SPItem *item_clicked = sp_event_context_find_item (desktop,
420                             NR::Point(event->button.x, event->button.y),
421                             (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
422                     // find out if we're over the selected item, disregarding groups
423                     SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
424                                                                     NR::Point(event->button.x, event->button.y));
426                     bool over_stroke = false;
427                     if (item_over && nc->nodepath) {
428                         over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), false);
429                     }
431                     if (over_stroke || nc->added_node) {
432                         switch (event->type) {
433                             case GDK_BUTTON_RELEASE:
434                                 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
435                                     //add a node
436                                     sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
437                                 } else {
438                                     if (nc->added_node) { // we just received double click, ignore release
439                                         nc->added_node = false;
440                                         break;
441                                     }
442                                     //select the segment
443                                     if (event->button.state & GDK_SHIFT_MASK) {
444                                         sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, true);
445                                     } else {
446                                         sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
447                                     }
448                                 }
449                                 break;
450                             case GDK_2BUTTON_PRESS:
451                                 //add a node
452                                 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
453                                 nc->added_node = true;
454                                 break;
455                             default:
456                                 break;
457                         }
458                     } else if (event->button.state & GDK_SHIFT_MASK) {
459                         selection->toggle(item_clicked);
460                     } else {
461                         selection->set(item_clicked);
462                     }
464                     ret = TRUE;
465                 }
466                 break;
467             }
468             break;
469         case GDK_BUTTON_PRESS:
470             if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK)) {
471                 // save drag origin
472                 event_context->xp = (gint) event->button.x;
473                 event_context->yp = (gint) event->button.y;
474                 event_context->within_tolerance = true;
475                 nc->hit = false;
477                 if (!nc->drag) {
478                     // find out if we're over the selected item, disregarding groups
479                     SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
480                                                                     NR::Point(event->button.x, event->button.y));
482                         if (nc->nodepath && selection->single() && item_over) {
484                             // save drag origin
485                             bool over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), true);
486                             //only dragging curves
487                             if (over_stroke) {
488                                 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
489                                 ret = TRUE;
490                             } else {
491                                 break;
492                             }
493                         } else {
494                             break;
495                         }
497                     ret = TRUE;
498                 }
499                 break;
500             }
501             break;
502         default:
503             break;
504     }
506     if (!ret) {
507         if (((SPEventContextClass *) parent_class)->item_handler)
508             ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
509     }
511     return ret;
514 static gint
515 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
517     SPDesktop *desktop = event_context->desktop;
518     Inkscape::Selection *selection = sp_desktop_selection (desktop);
520     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
521     double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
522     event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
523     int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
524     double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
526     gint ret = FALSE;
528     switch (event->type) {
529         case GDK_BUTTON_PRESS:
530             if (event->button.button == 1) {
531                 // save drag origin
532                 event_context->xp = (gint) event->button.x;
533                 event_context->yp = (gint) event->button.y;
534                 event_context->within_tolerance = true;
535                 nc->hit = false;
537                 NR::Point const button_w(event->button.x,
538                                          event->button.y);
539                 NR::Point const button_dt(desktop->w2d(button_w));
540                 Inkscape::Rubberband::get()->start(desktop, button_dt);
541                 ret = TRUE;
542             }
543             break;
544         case GDK_MOTION_NOTIFY:
545             if (event->motion.state & GDK_BUTTON1_MASK) {
547                 if ( event_context->within_tolerance
548                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
549                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
550                     break; // do not drag if we're within tolerance from origin
551                 }
552                 // Once the user has moved farther than tolerance from the original location
553                 // (indicating they intend to move the object, not click), then always process the
554                 // motion notify coordinates as given (no snapping back to origin)
555                 event_context->within_tolerance = false;
557                 if (nc->nodepath && nc->hit) {
558                     NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
559                                          event->motion.y - nc->curvepoint_event[NR::Y]);
560                     NR::Point const delta_dt(desktop->w2d(delta_w));
561                     sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
562                     nc->curvepoint_event[NR::X] = (gint) event->motion.x;
563                     nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
564                     gobble_motion_events(GDK_BUTTON1_MASK);
565                 } else {
566                     NR::Point const motion_w(event->motion.x,
567                                          event->motion.y);
568                     NR::Point const motion_dt(desktop->w2d(motion_w));
569                     Inkscape::Rubberband::get()->move(motion_dt);
570                 }
571                 nc->drag = TRUE;
572                 ret = TRUE;
573             } else {
574                 if (!nc->nodepath || selection->singleItem() == NULL) {
575                     break;
576                 }
578                 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
579                                                                 NR::Point(event->motion.x, event->motion.y));
580                 bool over_stroke = false;
581                 if (item_over && nc->nodepath) {
582                     over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
583                 }
585                 if (nc->cursor_drag && !over_stroke) {
586                     event_context->cursor_shape = cursor_node_xpm;
587                     event_context->hot_x = 1;
588                     event_context->hot_y = 1;
589                     sp_event_context_update_cursor(event_context);
590                     nc->cursor_drag = false;
591                 } else if (!nc->cursor_drag && over_stroke) {
592                     event_context->cursor_shape = cursor_node_d_xpm;
593                     event_context->hot_x = 1;
594                     event_context->hot_y = 1;
595                     sp_event_context_update_cursor(event_context);
596                     nc->cursor_drag = true;
597                 }
598             }
599             break;
600         case GDK_BUTTON_RELEASE:
601             event_context->xp = event_context->yp = 0;
602             if (event->button.button == 1) {
604                 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
606                 if (nc->hit && !event_context->within_tolerance) { //drag curve
607                     sp_nodepath_update_repr (nc->nodepath);
608                 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
609                     if (nc->nodepath) {
610                         sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
611                     }
612                 } else {
613                     if (!(nc->rb_escaped)) { // unless something was cancelled
614                         if (nc->nodepath && nc->nodepath->selected)
615                             sp_nodepath_deselect(nc->nodepath);
616                         else
617                             sp_desktop_selection(desktop)->clear();
618                     }
619                 }
620                 ret = TRUE;
621                 Inkscape::Rubberband::get()->stop();
622                 nc->rb_escaped = false;
623                 nc->drag = FALSE;
624                 nc->hit = false;
625                 break;
626             }
627             break;
628         case GDK_KEY_PRESS:
629             switch (get_group0_keyval(&event->key)) {
630                 case GDK_Insert:
631                 case GDK_KP_Insert:
632                     // with any modifiers
633                     sp_node_selected_add_node();
634                     ret = TRUE;
635                     break;
636                 case GDK_Delete:
637                 case GDK_KP_Delete:
638                 case GDK_BackSpace:
639                     if (MOD__CTRL_ONLY) {
640                         sp_node_selected_delete();
641                     } else {
642                         sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
643                     }
644                     ret = TRUE;
645                     break;
646                 case GDK_C:
647                 case GDK_c:
648                     if (MOD__SHIFT_ONLY) {
649                         sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
650                         ret = TRUE;
651                     }
652                     break;
653                 case GDK_S:
654                 case GDK_s:
655                     if (MOD__SHIFT_ONLY) {
656                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
657                         ret = TRUE;
658                     }
659                     break;
660                 case GDK_Y:
661                 case GDK_y:
662                     if (MOD__SHIFT_ONLY) {
663                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
664                         ret = TRUE;
665                     }
666                     break;
667                 case GDK_B:
668                 case GDK_b:
669                     if (MOD__SHIFT_ONLY) {
670                         sp_node_selected_break();
671                         ret = TRUE;
672                     }
673                     break;
674                 case GDK_J:
675                 case GDK_j:
676                     if (MOD__SHIFT_ONLY) {
677                         sp_node_selected_join();
678                         ret = TRUE;
679                     }
680                     break;
681                 case GDK_D:
682                 case GDK_d:
683                     if (MOD__SHIFT_ONLY) {
684                         sp_node_selected_duplicate();
685                         ret = TRUE;
686                     }
687                     break;
688                 case GDK_L:
689                 case GDK_l:
690                     if (MOD__SHIFT_ONLY) {
691                         sp_node_selected_set_line_type(NR_LINETO);
692                         ret = TRUE;
693                     }
694                     break;
695                 case GDK_U:
696                 case GDK_u:
697                     if (MOD__SHIFT_ONLY) {
698                         sp_node_selected_set_line_type(NR_CURVETO);
699                         ret = TRUE;
700                     }
701                     break;
702                 case GDK_R:
703                 case GDK_r:
704                     if (MOD__SHIFT_ONLY) {
705                         // FIXME: add top panel button
706                         sp_selected_path_reverse();
707                         ret = TRUE;
708                     }
709                     break;
710                 case GDK_Left: // move selection left
711                 case GDK_KP_Left:
712                 case GDK_KP_4:
713                     if (!MOD__CTRL) { // not ctrl
714                         if (MOD__ALT) { // alt
715                             if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
716                             else sp_node_selected_move_screen(-1, 0); // no shift
717                         }
718                         else { // no alt
719                             if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
720                             else sp_node_selected_move(-nudge, 0); // no shift
721                         }
722                         ret = TRUE;
723                     }
724                     break;
725                 case GDK_Up: // move selection up
726                 case GDK_KP_Up:
727                 case GDK_KP_8:
728                     if (!MOD__CTRL) { // not ctrl
729                         if (MOD__ALT) { // alt
730                             if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
731                             else sp_node_selected_move_screen(0, 1); // no shift
732                         }
733                         else { // no alt
734                             if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
735                             else sp_node_selected_move(0, nudge); // no shift
736                         }
737                         ret = TRUE;
738                     }
739                     break;
740                 case GDK_Right: // move selection right
741                 case GDK_KP_Right:
742                 case GDK_KP_6:
743                     if (!MOD__CTRL) { // not ctrl
744                         if (MOD__ALT) { // alt
745                             if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
746                             else sp_node_selected_move_screen(1, 0); // no shift
747                         }
748                         else { // no alt
749                             if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
750                             else sp_node_selected_move(nudge, 0); // no shift
751                         }
752                         ret = TRUE;
753                     }
754                     break;
755                 case GDK_Down: // move selection down
756                 case GDK_KP_Down:
757                 case GDK_KP_2:
758                     if (!MOD__CTRL) { // not ctrl
759                         if (MOD__ALT) { // alt
760                             if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
761                             else sp_node_selected_move_screen(0, -1); // no shift
762                         }
763                         else { // no alt
764                             if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
765                             else sp_node_selected_move(0, -nudge); // no shift
766                         }
767                         ret = TRUE;
768                     }
769                     break;
770                 case GDK_Tab: // Tab - cycle selection forward
771                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
772                         sp_nodepath_select_next(nc->nodepath);
773                         ret = TRUE;
774                     }
775                     break;
776                 case GDK_ISO_Left_Tab:  // Shift Tab - cycle selection backward
777                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
778                         sp_nodepath_select_prev(nc->nodepath);
779                         ret = TRUE;
780                     }
781                     break;
782                 case GDK_Escape:
783                 {
784                     NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
785                     if (b != NR::Nothing()) {
786                         Inkscape::Rubberband::get()->stop();
787                         nc->rb_escaped = true;
788                     } else {
789                         if (nc->nodepath && nc->nodepath->selected) {
790                             sp_nodepath_deselect(nc->nodepath);
791                         } else {
792                             sp_desktop_selection(desktop)->clear();
793                         }
794                     }
795                     ret = TRUE;
796                     break;
797                 }
799                 case GDK_bracketleft:
800                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
801                         if (nc->leftctrl)
802                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
803                         if (nc->rightctrl)
804                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
805                     } else if ( MOD__ALT && !MOD__CTRL ) {
806                         if (nc->leftalt && nc->rightalt)
807                             sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
808                         else {
809                             if (nc->leftalt)
810                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
811                             if (nc->rightalt)
812                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
813                         }
814                     } else if ( snaps != 0 ) {
815                         sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
816                     }
817                     ret = TRUE;
818                     break;
819                 case GDK_bracketright:
820                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
821                         if (nc->leftctrl)
822                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
823                         if (nc->rightctrl)
824                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
825                     } else if ( MOD__ALT && !MOD__CTRL ) {
826                         if (nc->leftalt && nc->rightalt)
827                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
828                         else {
829                             if (nc->leftalt)
830                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
831                             if (nc->rightalt)
832                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
833                         }
834                     } else if ( snaps != 0 ) {
835                         sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
836                     }
837                     ret = TRUE;
838                     break;
839                 case GDK_less:
840                 case GDK_comma:
841                     if (MOD__CTRL) {
842                         if (nc->leftctrl)
843                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
844                         if (nc->rightctrl)
845                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
846                     } else if (MOD__ALT) {
847                         if (nc->leftalt && nc->rightalt)
848                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
849                         else {
850                             if (nc->leftalt)
851                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
852                             if (nc->rightalt)
853                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
854                         }
855                     } else {
856                         sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
857                     }
858                     ret = TRUE;
859                     break;
860                 case GDK_greater:
861                 case GDK_period:
862                     if (MOD__CTRL) {
863                         if (nc->leftctrl)
864                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
865                         if (nc->rightctrl)
866                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
867                     } else if (MOD__ALT) {
868                         if (nc->leftalt && nc->rightalt)
869                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
870                         else {
871                             if (nc->leftalt)
872                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
873                             if (nc->rightalt)
874                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
875                         }
876                     } else {
877                         sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
878                     }
879                     ret = TRUE;
880                     break;
882                 case GDK_Alt_L:
883                     nc->leftalt = TRUE;
884                     sp_node_context_show_modifier_tip(event_context, event);
885                     break;
886                 case GDK_Alt_R:
887                     nc->rightalt = TRUE;
888                     sp_node_context_show_modifier_tip(event_context, event);
889                     break;
890                 case GDK_Control_L:
891                     nc->leftctrl = TRUE;
892                     sp_node_context_show_modifier_tip(event_context, event);
893                     break;
894                 case GDK_Control_R:
895                     nc->rightctrl = TRUE;
896                     sp_node_context_show_modifier_tip(event_context, event);
897                     break;
898                 case GDK_Shift_L:
899                 case GDK_Shift_R:
900                 case GDK_Meta_L:
901                 case GDK_Meta_R:
902                     sp_node_context_show_modifier_tip(event_context, event);
903                     break;
904                 default:
905                     ret = node_key(event);
906                     break;
907             }
908             break;
909         case GDK_KEY_RELEASE:
910             switch (get_group0_keyval(&event->key)) {
911                 case GDK_Alt_L:
912                     nc->leftalt = FALSE;
913                     event_context->defaultMessageContext()->clear();
914                     break;
915                 case GDK_Alt_R:
916                     nc->rightalt = FALSE;
917                     event_context->defaultMessageContext()->clear();
918                     break;
919                 case GDK_Control_L:
920                     nc->leftctrl = FALSE;
921                     event_context->defaultMessageContext()->clear();
922                     break;
923                 case GDK_Control_R:
924                     nc->rightctrl = FALSE;
925                     event_context->defaultMessageContext()->clear();
926                     break;
927                 case GDK_Shift_L:
928                 case GDK_Shift_R:
929                 case GDK_Meta_L:
930                 case GDK_Meta_R:
931                     event_context->defaultMessageContext()->clear();
932                     break;
933             }
934             break;
935         default:
936             break;
937     }
939     if (!ret) {
940         if (((SPEventContextClass *) parent_class)->root_handler)
941             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
942     }
944     return ret;
948 /*
949   Local Variables:
950   mode:c++
951   c-file-style:"stroustrup"
952   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
953   indent-tabs-mode:nil
954   fill-column:99
955   End:
956 */
957 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :