Code

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