Code

forced redraw of canvas upon completion of zooming or upon starting rubber band selec...
[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                 desktop->updateNow();
552                 ret = TRUE;
553             }
554             break;
555         case GDK_MOTION_NOTIFY:
556             if (event->motion.state & GDK_BUTTON1_MASK) {
558                 if ( event_context->within_tolerance
559                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
560                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
561                     break; // do not drag if we're within tolerance from origin
562                 }
563                 // Once the user has moved farther than tolerance from the original location
564                 // (indicating they intend to move the object, not click), then always process the
565                 // motion notify coordinates as given (no snapping back to origin)
566                 event_context->within_tolerance = false;
568                 if (nc->nodepath && nc->hit) {
569                     NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
570                                          event->motion.y - nc->curvepoint_event[NR::Y]);
571                     NR::Point const delta_dt(desktop->w2d(delta_w));
572                     sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
573                     nc->curvepoint_event[NR::X] = (gint) event->motion.x;
574                     nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
575                     gobble_motion_events(GDK_BUTTON1_MASK);
576                 } else {
577                     NR::Point const motion_w(event->motion.x,
578                                          event->motion.y);
579                     NR::Point const motion_dt(desktop->w2d(motion_w));
580                     Inkscape::Rubberband::get()->move(motion_dt);
581                 }
582                 nc->drag = TRUE;
583                 ret = TRUE;
584             } else {
585                 if (!nc->nodepath || selection->singleItem() == NULL) {
586                     break;
587                 }
589                 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
590                                                                 NR::Point(event->motion.x, event->motion.y));
591                 bool over_stroke = false;
592                 if (item_over && nc->nodepath) {
593                     over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
594                 }
596                 if (nc->cursor_drag && !over_stroke) {
597                     event_context->cursor_shape = cursor_node_xpm;
598                     event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
599                             -1,
600                             cursor_node_pixbuf,
601                             FALSE,
602                             NULL);  
603                     event_context->hot_x = 1;
604                     event_context->hot_y = 1;
605                     sp_event_context_update_cursor(event_context);
606                     nc->cursor_drag = false;
607                 } else if (!nc->cursor_drag && over_stroke) {
608                     event_context->cursor_shape = cursor_node_d_xpm;
609                     event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
610                             -1,
611                             cursor_node_d_pixbuf,
612                             FALSE,
613                             NULL);  
614                     event_context->hot_x = 1;
615                     event_context->hot_y = 1;
616                     sp_event_context_update_cursor(event_context);
617                     nc->cursor_drag = true;
618                 }
619             }
620             break;
621         case GDK_BUTTON_RELEASE:
622             event_context->xp = event_context->yp = 0;
623             if (event->button.button == 1) {
625                 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
627                 if (nc->hit && !event_context->within_tolerance) { //drag curve
628                     sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
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 :