Code

show handles toggle
[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, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
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, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
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, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
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                         if (nc->nodepath && nc->nodepath->selected) {
643                             sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
644                         }
645                     }
646                     ret = TRUE;
647                     break;
648                 case GDK_C:
649                 case GDK_c:
650                     if (MOD__SHIFT_ONLY) {
651                         sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
652                         ret = TRUE;
653                     }
654                     break;
655                 case GDK_S:
656                 case GDK_s:
657                     if (MOD__SHIFT_ONLY) {
658                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
659                         ret = TRUE;
660                     }
661                     break;
662                 case GDK_Y:
663                 case GDK_y:
664                     if (MOD__SHIFT_ONLY) {
665                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
666                         ret = TRUE;
667                     }
668                     break;
669                 case GDK_B:
670                 case GDK_b:
671                     if (MOD__SHIFT_ONLY) {
672                         sp_node_selected_break();
673                         ret = TRUE;
674                     }
675                     break;
676                 case GDK_J:
677                 case GDK_j:
678                     if (MOD__SHIFT_ONLY) {
679                         sp_node_selected_join();
680                         ret = TRUE;
681                     }
682                     break;
683                 case GDK_D:
684                 case GDK_d:
685                     if (MOD__SHIFT_ONLY) {
686                         sp_node_selected_duplicate();
687                         ret = TRUE;
688                     }
689                     break;
690                 case GDK_L:
691                 case GDK_l:
692                     if (MOD__SHIFT_ONLY) {
693                         sp_node_selected_set_line_type(NR_LINETO);
694                         ret = TRUE;
695                     }
696                     break;
697                 case GDK_U:
698                 case GDK_u:
699                     if (MOD__SHIFT_ONLY) {
700                         sp_node_selected_set_line_type(NR_CURVETO);
701                         ret = TRUE;
702                     }
703                     break;
704                 case GDK_R:
705                 case GDK_r:
706                     if (MOD__SHIFT_ONLY) {
707                         // FIXME: add top panel button
708                         sp_selected_path_reverse();
709                         ret = TRUE;
710                     }
711                     break;
712                 case GDK_Left: // move selection left
713                 case GDK_KP_Left:
714                 case GDK_KP_4:
715                     if (!MOD__CTRL) { // not ctrl
716                         if (MOD__ALT) { // alt
717                             if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
718                             else sp_node_selected_move_screen(-1, 0); // no shift
719                         }
720                         else { // no alt
721                             if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
722                             else sp_node_selected_move(-nudge, 0); // no shift
723                         }
724                         ret = TRUE;
725                     }
726                     break;
727                 case GDK_Up: // move selection up
728                 case GDK_KP_Up:
729                 case GDK_KP_8:
730                     if (!MOD__CTRL) { // not ctrl
731                         if (MOD__ALT) { // alt
732                             if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
733                             else sp_node_selected_move_screen(0, 1); // no shift
734                         }
735                         else { // no alt
736                             if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
737                             else sp_node_selected_move(0, nudge); // no shift
738                         }
739                         ret = TRUE;
740                     }
741                     break;
742                 case GDK_Right: // move selection right
743                 case GDK_KP_Right:
744                 case GDK_KP_6:
745                     if (!MOD__CTRL) { // not ctrl
746                         if (MOD__ALT) { // alt
747                             if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
748                             else sp_node_selected_move_screen(1, 0); // no shift
749                         }
750                         else { // no alt
751                             if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
752                             else sp_node_selected_move(nudge, 0); // no shift
753                         }
754                         ret = TRUE;
755                     }
756                     break;
757                 case GDK_Down: // move selection down
758                 case GDK_KP_Down:
759                 case GDK_KP_2:
760                     if (!MOD__CTRL) { // not ctrl
761                         if (MOD__ALT) { // alt
762                             if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
763                             else sp_node_selected_move_screen(0, -1); // no shift
764                         }
765                         else { // no alt
766                             if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
767                             else sp_node_selected_move(0, -nudge); // no shift
768                         }
769                         ret = TRUE;
770                     }
771                     break;
772                 case GDK_Tab: // Tab - cycle selection forward
773                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
774                         sp_nodepath_select_next(nc->nodepath);
775                         ret = TRUE;
776                     }
777                     break;
778                 case GDK_ISO_Left_Tab:  // Shift Tab - cycle selection backward
779                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
780                         sp_nodepath_select_prev(nc->nodepath);
781                         ret = TRUE;
782                     }
783                     break;
784                 case GDK_Escape:
785                 {
786                     NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
787                     if (b != NR::Nothing()) {
788                         Inkscape::Rubberband::get()->stop();
789                         nc->rb_escaped = true;
790                     } else {
791                         if (nc->nodepath && nc->nodepath->selected) {
792                             sp_nodepath_deselect(nc->nodepath);
793                         } else {
794                             sp_desktop_selection(desktop)->clear();
795                         }
796                     }
797                     ret = TRUE;
798                     break;
799                 }
801                 case GDK_bracketleft:
802                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
803                         if (nc->leftctrl)
804                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
805                         if (nc->rightctrl)
806                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
807                     } else if ( MOD__ALT && !MOD__CTRL ) {
808                         if (nc->leftalt && nc->rightalt)
809                             sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
810                         else {
811                             if (nc->leftalt)
812                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
813                             if (nc->rightalt)
814                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
815                         }
816                     } else if ( snaps != 0 ) {
817                         sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
818                     }
819                     ret = TRUE;
820                     break;
821                 case GDK_bracketright:
822                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
823                         if (nc->leftctrl)
824                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
825                         if (nc->rightctrl)
826                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
827                     } else if ( MOD__ALT && !MOD__CTRL ) {
828                         if (nc->leftalt && nc->rightalt)
829                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
830                         else {
831                             if (nc->leftalt)
832                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
833                             if (nc->rightalt)
834                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
835                         }
836                     } else if ( snaps != 0 ) {
837                         sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
838                     }
839                     ret = TRUE;
840                     break;
841                 case GDK_less:
842                 case GDK_comma:
843                     if (MOD__CTRL) {
844                         if (nc->leftctrl)
845                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
846                         if (nc->rightctrl)
847                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
848                     } else if (MOD__ALT) {
849                         if (nc->leftalt && nc->rightalt)
850                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
851                         else {
852                             if (nc->leftalt)
853                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
854                             if (nc->rightalt)
855                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
856                         }
857                     } else {
858                         sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
859                     }
860                     ret = TRUE;
861                     break;
862                 case GDK_greater:
863                 case GDK_period:
864                     if (MOD__CTRL) {
865                         if (nc->leftctrl)
866                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
867                         if (nc->rightctrl)
868                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
869                     } else if (MOD__ALT) {
870                         if (nc->leftalt && nc->rightalt)
871                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
872                         else {
873                             if (nc->leftalt)
874                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
875                             if (nc->rightalt)
876                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
877                         }
878                     } else {
879                         sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
880                     }
881                     ret = TRUE;
882                     break;
884                 case GDK_Alt_L:
885                     nc->leftalt = TRUE;
886                     sp_node_context_show_modifier_tip(event_context, event);
887                     break;
888                 case GDK_Alt_R:
889                     nc->rightalt = TRUE;
890                     sp_node_context_show_modifier_tip(event_context, event);
891                     break;
892                 case GDK_Control_L:
893                     nc->leftctrl = TRUE;
894                     sp_node_context_show_modifier_tip(event_context, event);
895                     break;
896                 case GDK_Control_R:
897                     nc->rightctrl = TRUE;
898                     sp_node_context_show_modifier_tip(event_context, event);
899                     break;
900                 case GDK_Shift_L:
901                 case GDK_Shift_R:
902                 case GDK_Meta_L:
903                 case GDK_Meta_R:
904                     sp_node_context_show_modifier_tip(event_context, event);
905                     break;
906                 default:
907                     ret = node_key(event);
908                     break;
909             }
910             break;
911         case GDK_KEY_RELEASE:
912             switch (get_group0_keyval(&event->key)) {
913                 case GDK_Alt_L:
914                     nc->leftalt = FALSE;
915                     event_context->defaultMessageContext()->clear();
916                     break;
917                 case GDK_Alt_R:
918                     nc->rightalt = FALSE;
919                     event_context->defaultMessageContext()->clear();
920                     break;
921                 case GDK_Control_L:
922                     nc->leftctrl = FALSE;
923                     event_context->defaultMessageContext()->clear();
924                     break;
925                 case GDK_Control_R:
926                     nc->rightctrl = FALSE;
927                     event_context->defaultMessageContext()->clear();
928                     break;
929                 case GDK_Shift_L:
930                 case GDK_Shift_R:
931                 case GDK_Meta_L:
932                 case GDK_Meta_R:
933                     event_context->defaultMessageContext()->clear();
934                     break;
935             }
936             break;
937         default:
938             break;
939     }
941     if (!ret) {
942         if (((SPEventContextClass *) parent_class)->root_handler)
943             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
944     }
946     return ret;
950 /*
951   Local Variables:
952   mode:c++
953   c-file-style:"stroustrup"
954   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
955   indent-tabs-mode:nil
956   fill-column:99
957   End:
958 */
959 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :