Code

Committed double code because of the hurry to let you use the axonom-snapping stuff.
[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     sp_nodepath_ensure_livarot_path(nc->nodepath);
375     NR::Maybe<Path::cut_position> position = get_nearest_position_on_Path(nc->nodepath->livarot_path, nc->curvepoint_doc);
376     NR::Point nearest = get_point_on_Path(nc->nodepath->livarot_path, position.assume().piece, position.assume().t);
377     NR::Point delta = nearest - nc->curvepoint_doc;
379     delta = desktop->d2w(delta);
381     double stroke_tolerance =
382         (SP_OBJECT_STYLE (item)->stroke.type != SP_PAINT_TYPE_NONE?
383          desktop->current_zoom() *
384          SP_OBJECT_STYLE (item)->stroke_width.computed *
385          sp_item_i2d_affine (item).expansion() * 0.5
386          : 0.0)
387         + (double) SP_EVENT_CONTEXT(nc)->tolerance;
389     bool close = (NR::L2 (delta) < stroke_tolerance);
391     if (remember && close) {
392         nc->curvepoint_event[NR::X] = (gint) event_p [NR::X];
393         nc->curvepoint_event[NR::Y] = (gint) event_p [NR::Y];
394         nc->hit = true;
395         nc->grab_t = position.assume().t;
396         nc->grab_node = sp_nodepath_get_node_by_index(position.assume().piece);
397     }
399     return close;
403 static gint
404 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
406     gint ret = FALSE;
408     SPDesktop *desktop = event_context->desktop;
409     Inkscape::Selection *selection = sp_desktop_selection (desktop);
411     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
413     switch (event->type) {
414         case GDK_2BUTTON_PRESS:
415         case GDK_BUTTON_RELEASE:
416             if (event->button.button == 1) {
417                 if (!nc->drag) {
419                     // find out clicked item, disregarding groups, honoring Alt
420                     SPItem *item_clicked = sp_event_context_find_item (desktop,
421                             NR::Point(event->button.x, event->button.y),
422                             (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
423                     // find out if we're over the selected item, disregarding groups
424                     SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
425                                                                     NR::Point(event->button.x, event->button.y));
427                     bool over_stroke = false;
428                     if (item_over && nc->nodepath) {
429                         over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), false);
430                     }
432                     if (over_stroke || nc->added_node) {
433                         switch (event->type) {
434                             case GDK_BUTTON_RELEASE:
435                                 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
436                                     //add a node
437                                     sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
438                                 } else {
439                                     if (nc->added_node) { // we just received double click, ignore release
440                                         nc->added_node = false;
441                                         break;
442                                     }
443                                     //select the segment
444                                     if (event->button.state & GDK_SHIFT_MASK) {
445                                         sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, true);
446                                     } else {
447                                         sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
448                                     }
449                                     desktop->updateNow();
450                                 }
451                                 break;
452                             case GDK_2BUTTON_PRESS:
453                                 //add a node
454                                 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
455                                 nc->added_node = true;
456                                 break;
457                             default:
458                                 break;
459                         }
460                     } else if (event->button.state & GDK_SHIFT_MASK) {
461                         selection->toggle(item_clicked);
462                         desktop->updateNow();
463                     } else {
464                         selection->set(item_clicked);
465                         desktop->updateNow();
466                     }
468                     ret = TRUE;
469                 }
470                 break;
471             }
472             break;
473         case GDK_BUTTON_PRESS:
474             if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK)) {
475                 // save drag origin
476                 event_context->xp = (gint) event->button.x;
477                 event_context->yp = (gint) event->button.y;
478                 event_context->within_tolerance = true;
479                 nc->hit = false;
481                 if (!nc->drag) {
482                     // find out if we're over the selected item, disregarding groups
483                     SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
484                                                                     NR::Point(event->button.x, event->button.y));
486                         if (nc->nodepath && selection->single() && item_over) {
488                             // save drag origin
489                             bool over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), true);
490                             //only dragging curves
491                             if (over_stroke) {
492                                 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
493                                 ret = TRUE;
494                             } else {
495                                 break;
496                             }
497                         } else {
498                             break;
499                         }
501                     ret = TRUE;
502                 }
503                 break;
504             }
505             break;
506         default:
507             break;
508     }
510     if (!ret) {
511         if (((SPEventContextClass *) parent_class)->item_handler)
512             ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
513     }
515     return ret;
518 static gint
519 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
521     SPDesktop *desktop = event_context->desktop;
522     Inkscape::Selection *selection = sp_desktop_selection (desktop);
524     // fixme:  nc->nodepath can potentially become NULL after retrieving nc.
525     // A general method for handling this possibility should be created.
526     // For now, the number of checks for a NULL nc->nodepath have been
527     // increased, both here and in the called sp_nodepath_* functions.
529     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
530     double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
531     event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
532     int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
533     double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
535     gint ret = FALSE;
537     switch (event->type) {
538         case GDK_BUTTON_PRESS:
539             if (event->button.button == 1) {
540                 // save drag origin
541                 event_context->xp = (gint) event->button.x;
542                 event_context->yp = (gint) event->button.y;
543                 event_context->within_tolerance = true;
544                 nc->hit = false;
546                 NR::Point const button_w(event->button.x,
547                                          event->button.y);
548                 NR::Point const button_dt(desktop->w2d(button_w));
549                 Inkscape::Rubberband::get()->start(desktop, button_dt);
550                 desktop->updateNow();
551                 ret = TRUE;
552             }
553             break;
554         case GDK_MOTION_NOTIFY:
555             if (event->motion.state & GDK_BUTTON1_MASK) {
557                 if ( event_context->within_tolerance
558                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
559                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
560                     break; // do not drag if we're within tolerance from origin
561                 }
562                 
563                 // The path went away while dragging; throw away any further motion
564                 // events until the mouse pointer is released.
565                 if (nc->hit && (nc->nodepath == NULL)) {                  
566                   break;
567                 }
568                 
569                 // Once the user has moved farther than tolerance from the original location
570                 // (indicating they intend to move the object, not click), then always process the
571                 // motion notify coordinates as given (no snapping back to origin)
572                 event_context->within_tolerance = false;
574                 if (nc->nodepath && nc->hit) {
575                     NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
576                                          event->motion.y - nc->curvepoint_event[NR::Y]);
577                     NR::Point const delta_dt(desktop->w2d(delta_w));
578                     sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
579                     nc->curvepoint_event[NR::X] = (gint) event->motion.x;
580                     nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
581                     gobble_motion_events(GDK_BUTTON1_MASK);
582                 } else {
583                     if (Inkscape::Rubberband::get()->is_started()) {
584                         NR::Point const motion_w(event->motion.x,
585                                             event->motion.y);
586                         NR::Point const motion_dt(desktop->w2d(motion_w));
587                         Inkscape::Rubberband::get()->move(motion_dt);
588                     }
589                 }
590                 nc->drag = TRUE;
591                 ret = TRUE;
592             } else {
593                 if (!nc->nodepath || selection->singleItem() == NULL) {
594                     break;
595                 }
597                 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
598                                                                 NR::Point(event->motion.x, event->motion.y));
599                 bool over_stroke = false;
600                 if (item_over && nc->nodepath) {
601                     over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
602                 }
604                 if (nc->cursor_drag && !over_stroke) {
605                     event_context->cursor_shape = cursor_node_xpm;
606                     event_context->hot_x = 1;
607                     event_context->hot_y = 1;
608                     sp_event_context_update_cursor(event_context);
609                     nc->cursor_drag = false;
610                 } else if (!nc->cursor_drag && over_stroke) {
611                     event_context->cursor_shape = cursor_node_d_xpm;
612                     event_context->hot_x = 1;
613                     event_context->hot_y = 1;
614                     sp_event_context_update_cursor(event_context);
615                     nc->cursor_drag = true;
616                 }
617             }
618             break;
619         case GDK_BUTTON_RELEASE:
620             event_context->xp = event_context->yp = 0;
621             if (event->button.button == 1) {
623                 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
625                 if (nc->hit && !event_context->within_tolerance) { //drag curve
626                     if (nc->nodepath) {
627                         sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
628                     }
629                 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
630                     if (nc->nodepath) {
631                         sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
632                     }
633                 } else {
634                     if (!(nc->rb_escaped)) { // unless something was cancelled
635                         if (nc->nodepath && nc->nodepath->selected)
636                             sp_nodepath_deselect(nc->nodepath);
637                         else
638                             sp_desktop_selection(desktop)->clear();
639                     }
640                 }
641                 ret = TRUE;
642                 Inkscape::Rubberband::get()->stop();
643                 desktop->updateNow();
644                 nc->rb_escaped = false;
645                 nc->drag = FALSE;
646                 nc->hit = false;
647                 break;
648             }
649             break;
650         case GDK_KEY_PRESS:
651             switch (get_group0_keyval(&event->key)) {
652                 case GDK_Insert:
653                 case GDK_KP_Insert:
654                     // with any modifiers
655                     sp_node_selected_add_node();
656                     ret = TRUE;
657                     break;
658                 case GDK_Delete:
659                 case GDK_KP_Delete:
660                 case GDK_BackSpace:
661                     if (MOD__CTRL_ONLY) {
662                         sp_node_selected_delete();
663                     } else {
664                         if (nc->nodepath && nc->nodepath->selected) {
665                             sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
666                         }
667                     }
668                     ret = TRUE;
669                     break;
670                 case GDK_C:
671                 case GDK_c:
672                     if (MOD__SHIFT_ONLY) {
673                         sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
674                         ret = TRUE;
675                     }
676                     break;
677                 case GDK_S:
678                 case GDK_s:
679                     if (MOD__SHIFT_ONLY) {
680                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
681                         ret = TRUE;
682                     }
683                     break;
684                 case GDK_Y:
685                 case GDK_y:
686                     if (MOD__SHIFT_ONLY) {
687                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
688                         ret = TRUE;
689                     }
690                     break;
691                 case GDK_B:
692                 case GDK_b:
693                     if (MOD__SHIFT_ONLY) {
694                         sp_node_selected_break();
695                         ret = TRUE;
696                     }
697                     break;
698                 case GDK_J:
699                 case GDK_j:
700                     if (MOD__SHIFT_ONLY) {
701                         sp_node_selected_join();
702                         ret = TRUE;
703                     }
704                     break;
705                 case GDK_D:
706                 case GDK_d:
707                     if (MOD__SHIFT_ONLY) {
708                         sp_node_selected_duplicate();
709                         ret = TRUE;
710                     }
711                     break;
712                 case GDK_L:
713                 case GDK_l:
714                     if (MOD__SHIFT_ONLY) {
715                         sp_node_selected_set_line_type(NR_LINETO);
716                         ret = TRUE;
717                     }
718                     break;
719                 case GDK_U:
720                 case GDK_u:
721                     if (MOD__SHIFT_ONLY) {
722                         sp_node_selected_set_line_type(NR_CURVETO);
723                         ret = TRUE;
724                     }
725                     break;
726                 case GDK_R:
727                 case GDK_r:
728                     if (MOD__SHIFT_ONLY) {
729                         // FIXME: add top panel button
730                         sp_selected_path_reverse();
731                         ret = TRUE;
732                     }
733                     break;
734                 case GDK_Left: // move selection left
735                 case GDK_KP_Left:
736                 case GDK_KP_4:
737                     if (!MOD__CTRL) { // not ctrl
738                         if (MOD__ALT) { // alt
739                             if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
740                             else sp_node_selected_move_screen(-1, 0); // no shift
741                         }
742                         else { // no alt
743                             if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
744                             else sp_node_selected_move(-nudge, 0); // no shift
745                         }
746                         ret = TRUE;
747                     }
748                     break;
749                 case GDK_Up: // move selection up
750                 case GDK_KP_Up:
751                 case GDK_KP_8:
752                     if (!MOD__CTRL) { // not ctrl
753                         if (MOD__ALT) { // alt
754                             if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
755                             else sp_node_selected_move_screen(0, 1); // no shift
756                         }
757                         else { // no alt
758                             if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
759                             else sp_node_selected_move(0, nudge); // no shift
760                         }
761                         ret = TRUE;
762                     }
763                     break;
764                 case GDK_Right: // move selection right
765                 case GDK_KP_Right:
766                 case GDK_KP_6:
767                     if (!MOD__CTRL) { // not ctrl
768                         if (MOD__ALT) { // alt
769                             if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
770                             else sp_node_selected_move_screen(1, 0); // no shift
771                         }
772                         else { // no alt
773                             if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
774                             else sp_node_selected_move(nudge, 0); // no shift
775                         }
776                         ret = TRUE;
777                     }
778                     break;
779                 case GDK_Down: // move selection down
780                 case GDK_KP_Down:
781                 case GDK_KP_2:
782                     if (!MOD__CTRL) { // not ctrl
783                         if (MOD__ALT) { // alt
784                             if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
785                             else sp_node_selected_move_screen(0, -1); // no shift
786                         }
787                         else { // no alt
788                             if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
789                             else sp_node_selected_move(0, -nudge); // no shift
790                         }
791                         ret = TRUE;
792                     }
793                     break;
794                 case GDK_Tab: // Tab - cycle selection forward
795                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
796                         sp_nodepath_select_next(nc->nodepath);
797                         ret = TRUE;
798                     }
799                     break;
800                 case GDK_ISO_Left_Tab:  // Shift Tab - cycle selection backward
801                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
802                         sp_nodepath_select_prev(nc->nodepath);
803                         ret = TRUE;
804                     }
805                     break;
806                 case GDK_Escape:
807                 {
808                     NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
809                     if (b != NR::Nothing()) {
810                         Inkscape::Rubberband::get()->stop();
811                         nc->rb_escaped = true;
812                     } else {
813                         if (nc->nodepath && nc->nodepath->selected) {
814                             sp_nodepath_deselect(nc->nodepath);
815                         } else {
816                             sp_desktop_selection(desktop)->clear();
817                         }
818                     }
819                     ret = TRUE;
820                     break;
821                 }
823                 case GDK_bracketleft:
824                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
825                         if (nc->leftctrl)
826                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
827                         if (nc->rightctrl)
828                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
829                     } else if ( MOD__ALT && !MOD__CTRL ) {
830                         if (nc->leftalt && nc->rightalt)
831                             sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
832                         else {
833                             if (nc->leftalt)
834                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
835                             if (nc->rightalt)
836                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
837                         }
838                     } else if ( snaps != 0 ) {
839                         sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
840                     }
841                     ret = TRUE;
842                     break;
843                 case GDK_bracketright:
844                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
845                         if (nc->leftctrl)
846                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
847                         if (nc->rightctrl)
848                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
849                     } else if ( MOD__ALT && !MOD__CTRL ) {
850                         if (nc->leftalt && nc->rightalt)
851                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
852                         else {
853                             if (nc->leftalt)
854                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
855                             if (nc->rightalt)
856                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
857                         }
858                     } else if ( snaps != 0 ) {
859                         sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
860                     }
861                     ret = TRUE;
862                     break;
863                 case GDK_less:
864                 case GDK_comma:
865                     if (MOD__CTRL) {
866                         if (nc->leftctrl)
867                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
868                         if (nc->rightctrl)
869                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
870                     } else if (MOD__ALT) {
871                         if (nc->leftalt && nc->rightalt)
872                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
873                         else {
874                             if (nc->leftalt)
875                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
876                             if (nc->rightalt)
877                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
878                         }
879                     } else {
880                         sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
881                     }
882                     ret = TRUE;
883                     break;
884                 case GDK_greater:
885                 case GDK_period:
886                     if (MOD__CTRL) {
887                         if (nc->leftctrl)
888                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
889                         if (nc->rightctrl)
890                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
891                     } else if (MOD__ALT) {
892                         if (nc->leftalt && nc->rightalt)
893                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
894                         else {
895                             if (nc->leftalt)
896                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
897                             if (nc->rightalt)
898                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
899                         }
900                     } else {
901                         sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
902                     }
903                     ret = TRUE;
904                     break;
906                 case GDK_Alt_L:
907                     nc->leftalt = TRUE;
908                     sp_node_context_show_modifier_tip(event_context, event);
909                     break;
910                 case GDK_Alt_R:
911                     nc->rightalt = TRUE;
912                     sp_node_context_show_modifier_tip(event_context, event);
913                     break;
914                 case GDK_Control_L:
915                     nc->leftctrl = TRUE;
916                     sp_node_context_show_modifier_tip(event_context, event);
917                     break;
918                 case GDK_Control_R:
919                     nc->rightctrl = TRUE;
920                     sp_node_context_show_modifier_tip(event_context, event);
921                     break;
922                 case GDK_Shift_L:
923                 case GDK_Shift_R:
924                 case GDK_Meta_L:
925                 case GDK_Meta_R:
926                     sp_node_context_show_modifier_tip(event_context, event);
927                     break;
928                 default:
929                     ret = node_key(event);
930                     break;
931             }
932             break;
933         case GDK_KEY_RELEASE:
934             switch (get_group0_keyval(&event->key)) {
935                 case GDK_Alt_L:
936                     nc->leftalt = FALSE;
937                     event_context->defaultMessageContext()->clear();
938                     break;
939                 case GDK_Alt_R:
940                     nc->rightalt = FALSE;
941                     event_context->defaultMessageContext()->clear();
942                     break;
943                 case GDK_Control_L:
944                     nc->leftctrl = FALSE;
945                     event_context->defaultMessageContext()->clear();
946                     break;
947                 case GDK_Control_R:
948                     nc->rightctrl = FALSE;
949                     event_context->defaultMessageContext()->clear();
950                     break;
951                 case GDK_Shift_L:
952                 case GDK_Shift_R:
953                 case GDK_Meta_L:
954                 case GDK_Meta_R:
955                     event_context->defaultMessageContext()->clear();
956                     break;
957             }
958             break;
959         default:
960             break;
961     }
963     if (!ret) {
964         if (((SPEventContextClass *) parent_class)->root_handler)
965             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
966     }
968     return ret;
972 /*
973   Local Variables:
974   mode:c++
975   c-file-style:"stroustrup"
976   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
977   indent-tabs-mode:nil
978   fill-column:99
979   End:
980 */
981 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :