Code

* src/Makefile_insert, src/Makefile.am, src/svg/Makefile_insert,
[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                                 }
456                                 break;
457                             case GDK_2BUTTON_PRESS:
458                                 //add a node
459                                 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
460                                 nc->added_node = true;
461                                 break;
462                             default:
463                                 break;
464                         }
465                     } else if (event->button.state & GDK_SHIFT_MASK) {
466                         selection->toggle(item_clicked);
467                     } else {
468                         selection->set(item_clicked);
469                     }
471                     ret = TRUE;
472                 }
473                 break;
474             }
475             break;
476         case GDK_BUTTON_PRESS:
477             if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK)) {
478                 // save drag origin
479                 event_context->xp = (gint) event->button.x;
480                 event_context->yp = (gint) event->button.y;
481                 event_context->within_tolerance = true;
482                 nc->hit = false;
484                 if (!nc->drag) {
485                     // find out if we're over the selected item, disregarding groups
486                     SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
487                                                                     NR::Point(event->button.x, event->button.y));
489                         if (nc->nodepath && selection->single() && item_over) {
491                             // save drag origin
492                             bool over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), true);
493                             //only dragging curves
494                             if (over_stroke) {
495                                 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
496                                 ret = TRUE;
497                             } else {
498                                 break;
499                             }
500                         } else {
501                             break;
502                         }
504                     ret = TRUE;
505                 }
506                 break;
507             }
508             break;
509         default:
510             break;
511     }
513     if (!ret) {
514         if (((SPEventContextClass *) parent_class)->item_handler)
515             ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
516     }
518     return ret;
521 static gint
522 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
524     SPDesktop *desktop = event_context->desktop;
525     Inkscape::Selection *selection = sp_desktop_selection (desktop);
527     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
528     double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
529     event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
530     int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
531     double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
533     gint ret = FALSE;
535     switch (event->type) {
536         case GDK_BUTTON_PRESS:
537             if (event->button.button == 1) {
538                 // save drag origin
539                 event_context->xp = (gint) event->button.x;
540                 event_context->yp = (gint) event->button.y;
541                 event_context->within_tolerance = true;
542                 nc->hit = false;
544                 NR::Point const button_w(event->button.x,
545                                          event->button.y);
546                 NR::Point const button_dt(desktop->w2d(button_w));
547                 Inkscape::Rubberband::get()->start(desktop, button_dt);
548                 ret = TRUE;
549             }
550             break;
551         case GDK_MOTION_NOTIFY:
552             if (event->motion.state & GDK_BUTTON1_MASK) {
554                 if ( event_context->within_tolerance
555                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
556                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
557                     break; // do not drag if we're within tolerance from origin
558                 }
559                 // Once the user has moved farther than tolerance from the original location
560                 // (indicating they intend to move the object, not click), then always process the
561                 // motion notify coordinates as given (no snapping back to origin)
562                 event_context->within_tolerance = false;
564                 if (nc->nodepath && nc->hit) {
565                     NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
566                                          event->motion.y - nc->curvepoint_event[NR::Y]);
567                     NR::Point const delta_dt(desktop->w2d(delta_w));
568                     sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
569                     nc->curvepoint_event[NR::X] = (gint) event->motion.x;
570                     nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
571                     gobble_motion_events(GDK_BUTTON1_MASK);
572                 } else {
573                     NR::Point const motion_w(event->motion.x,
574                                          event->motion.y);
575                     NR::Point const motion_dt(desktop->w2d(motion_w));
576                     Inkscape::Rubberband::get()->move(motion_dt);
577                 }
578                 nc->drag = TRUE;
579                 ret = TRUE;
580             } else {
581                 if (!nc->nodepath || selection->singleItem() == NULL) {
582                     break;
583                 }
585                 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
586                                                                 NR::Point(event->motion.x, event->motion.y));
587                 bool over_stroke = false;
588                 if (item_over && nc->nodepath) {
589                     over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
590                 }
592                 if (nc->cursor_drag && !over_stroke) {
593                     event_context->cursor_shape = cursor_node_xpm;
594                     event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
595                             -1,
596                             cursor_node_pixbuf,
597                             FALSE,
598                             NULL);  
599                     event_context->hot_x = 1;
600                     event_context->hot_y = 1;
601                     sp_event_context_update_cursor(event_context);
602                     nc->cursor_drag = false;
603                 } else if (!nc->cursor_drag && over_stroke) {
604                     event_context->cursor_shape = cursor_node_d_xpm;
605                     event_context->cursor_pixbuf = gdk_pixbuf_new_from_inline(
606                             -1,
607                             cursor_node_d_pixbuf,
608                             FALSE,
609                             NULL);  
610                     event_context->hot_x = 1;
611                     event_context->hot_y = 1;
612                     sp_event_context_update_cursor(event_context);
613                     nc->cursor_drag = true;
614                 }
615             }
616             break;
617         case GDK_BUTTON_RELEASE:
618             event_context->xp = event_context->yp = 0;
619             if (event->button.button == 1) {
621                 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
623                 if (nc->hit && !event_context->within_tolerance) { //drag curve
624                     sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
625                 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
626                     if (nc->nodepath) {
627                         sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
628                     }
629                 } else {
630                     if (!(nc->rb_escaped)) { // unless something was cancelled
631                         if (nc->nodepath && nc->nodepath->selected)
632                             sp_nodepath_deselect(nc->nodepath);
633                         else
634                             sp_desktop_selection(desktop)->clear();
635                     }
636                 }
637                 ret = TRUE;
638                 Inkscape::Rubberband::get()->stop();
639                 nc->rb_escaped = false;
640                 nc->drag = FALSE;
641                 nc->hit = false;
642                 break;
643             }
644             break;
645         case GDK_KEY_PRESS:
646             switch (get_group0_keyval(&event->key)) {
647                 case GDK_Insert:
648                 case GDK_KP_Insert:
649                     // with any modifiers
650                     sp_node_selected_add_node();
651                     ret = TRUE;
652                     break;
653                 case GDK_Delete:
654                 case GDK_KP_Delete:
655                 case GDK_BackSpace:
656                     if (MOD__CTRL_ONLY) {
657                         sp_node_selected_delete();
658                     } else {
659                         if (nc->nodepath && nc->nodepath->selected) {
660                             sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
661                         }
662                     }
663                     ret = TRUE;
664                     break;
665                 case GDK_C:
666                 case GDK_c:
667                     if (MOD__SHIFT_ONLY) {
668                         sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
669                         ret = TRUE;
670                     }
671                     break;
672                 case GDK_S:
673                 case GDK_s:
674                     if (MOD__SHIFT_ONLY) {
675                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
676                         ret = TRUE;
677                     }
678                     break;
679                 case GDK_Y:
680                 case GDK_y:
681                     if (MOD__SHIFT_ONLY) {
682                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
683                         ret = TRUE;
684                     }
685                     break;
686                 case GDK_B:
687                 case GDK_b:
688                     if (MOD__SHIFT_ONLY) {
689                         sp_node_selected_break();
690                         ret = TRUE;
691                     }
692                     break;
693                 case GDK_J:
694                 case GDK_j:
695                     if (MOD__SHIFT_ONLY) {
696                         sp_node_selected_join();
697                         ret = TRUE;
698                     }
699                     break;
700                 case GDK_D:
701                 case GDK_d:
702                     if (MOD__SHIFT_ONLY) {
703                         sp_node_selected_duplicate();
704                         ret = TRUE;
705                     }
706                     break;
707                 case GDK_L:
708                 case GDK_l:
709                     if (MOD__SHIFT_ONLY) {
710                         sp_node_selected_set_line_type(NR_LINETO);
711                         ret = TRUE;
712                     }
713                     break;
714                 case GDK_U:
715                 case GDK_u:
716                     if (MOD__SHIFT_ONLY) {
717                         sp_node_selected_set_line_type(NR_CURVETO);
718                         ret = TRUE;
719                     }
720                     break;
721                 case GDK_R:
722                 case GDK_r:
723                     if (MOD__SHIFT_ONLY) {
724                         // FIXME: add top panel button
725                         sp_selected_path_reverse();
726                         ret = TRUE;
727                     }
728                     break;
729                 case GDK_Left: // move selection left
730                 case GDK_KP_Left:
731                 case GDK_KP_4:
732                     if (!MOD__CTRL) { // not ctrl
733                         if (MOD__ALT) { // alt
734                             if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
735                             else sp_node_selected_move_screen(-1, 0); // no shift
736                         }
737                         else { // no alt
738                             if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
739                             else sp_node_selected_move(-nudge, 0); // no shift
740                         }
741                         ret = TRUE;
742                     }
743                     break;
744                 case GDK_Up: // move selection up
745                 case GDK_KP_Up:
746                 case GDK_KP_8:
747                     if (!MOD__CTRL) { // not ctrl
748                         if (MOD__ALT) { // alt
749                             if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
750                             else sp_node_selected_move_screen(0, 1); // no shift
751                         }
752                         else { // no alt
753                             if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
754                             else sp_node_selected_move(0, nudge); // no shift
755                         }
756                         ret = TRUE;
757                     }
758                     break;
759                 case GDK_Right: // move selection right
760                 case GDK_KP_Right:
761                 case GDK_KP_6:
762                     if (!MOD__CTRL) { // not ctrl
763                         if (MOD__ALT) { // alt
764                             if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
765                             else sp_node_selected_move_screen(1, 0); // no shift
766                         }
767                         else { // no alt
768                             if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
769                             else sp_node_selected_move(nudge, 0); // no shift
770                         }
771                         ret = TRUE;
772                     }
773                     break;
774                 case GDK_Down: // move selection down
775                 case GDK_KP_Down:
776                 case GDK_KP_2:
777                     if (!MOD__CTRL) { // not ctrl
778                         if (MOD__ALT) { // alt
779                             if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
780                             else sp_node_selected_move_screen(0, -1); // no shift
781                         }
782                         else { // no alt
783                             if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
784                             else sp_node_selected_move(0, -nudge); // no shift
785                         }
786                         ret = TRUE;
787                     }
788                     break;
789                 case GDK_Tab: // Tab - cycle selection forward
790                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
791                         sp_nodepath_select_next(nc->nodepath);
792                         ret = TRUE;
793                     }
794                     break;
795                 case GDK_ISO_Left_Tab:  // Shift Tab - cycle selection backward
796                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
797                         sp_nodepath_select_prev(nc->nodepath);
798                         ret = TRUE;
799                     }
800                     break;
801                 case GDK_Escape:
802                 {
803                     NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
804                     if (b != NR::Nothing()) {
805                         Inkscape::Rubberband::get()->stop();
806                         nc->rb_escaped = true;
807                     } else {
808                         if (nc->nodepath && nc->nodepath->selected) {
809                             sp_nodepath_deselect(nc->nodepath);
810                         } else {
811                             sp_desktop_selection(desktop)->clear();
812                         }
813                     }
814                     ret = TRUE;
815                     break;
816                 }
818                 case GDK_bracketleft:
819                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
820                         if (nc->leftctrl)
821                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
822                         if (nc->rightctrl)
823                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
824                     } else if ( MOD__ALT && !MOD__CTRL ) {
825                         if (nc->leftalt && nc->rightalt)
826                             sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
827                         else {
828                             if (nc->leftalt)
829                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
830                             if (nc->rightalt)
831                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
832                         }
833                     } else if ( snaps != 0 ) {
834                         sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
835                     }
836                     ret = TRUE;
837                     break;
838                 case GDK_bracketright:
839                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
840                         if (nc->leftctrl)
841                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
842                         if (nc->rightctrl)
843                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
844                     } else if ( MOD__ALT && !MOD__CTRL ) {
845                         if (nc->leftalt && nc->rightalt)
846                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
847                         else {
848                             if (nc->leftalt)
849                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
850                             if (nc->rightalt)
851                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
852                         }
853                     } else if ( snaps != 0 ) {
854                         sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
855                     }
856                     ret = TRUE;
857                     break;
858                 case GDK_less:
859                 case GDK_comma:
860                     if (MOD__CTRL) {
861                         if (nc->leftctrl)
862                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
863                         if (nc->rightctrl)
864                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
865                     } else if (MOD__ALT) {
866                         if (nc->leftalt && nc->rightalt)
867                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
868                         else {
869                             if (nc->leftalt)
870                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
871                             if (nc->rightalt)
872                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
873                         }
874                     } else {
875                         sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
876                     }
877                     ret = TRUE;
878                     break;
879                 case GDK_greater:
880                 case GDK_period:
881                     if (MOD__CTRL) {
882                         if (nc->leftctrl)
883                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
884                         if (nc->rightctrl)
885                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
886                     } else if (MOD__ALT) {
887                         if (nc->leftalt && nc->rightalt)
888                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
889                         else {
890                             if (nc->leftalt)
891                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
892                             if (nc->rightalt)
893                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
894                         }
895                     } else {
896                         sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
897                     }
898                     ret = TRUE;
899                     break;
901                 case GDK_Alt_L:
902                     nc->leftalt = TRUE;
903                     sp_node_context_show_modifier_tip(event_context, event);
904                     break;
905                 case GDK_Alt_R:
906                     nc->rightalt = TRUE;
907                     sp_node_context_show_modifier_tip(event_context, event);
908                     break;
909                 case GDK_Control_L:
910                     nc->leftctrl = TRUE;
911                     sp_node_context_show_modifier_tip(event_context, event);
912                     break;
913                 case GDK_Control_R:
914                     nc->rightctrl = TRUE;
915                     sp_node_context_show_modifier_tip(event_context, event);
916                     break;
917                 case GDK_Shift_L:
918                 case GDK_Shift_R:
919                 case GDK_Meta_L:
920                 case GDK_Meta_R:
921                     sp_node_context_show_modifier_tip(event_context, event);
922                     break;
923                 default:
924                     ret = node_key(event);
925                     break;
926             }
927             break;
928         case GDK_KEY_RELEASE:
929             switch (get_group0_keyval(&event->key)) {
930                 case GDK_Alt_L:
931                     nc->leftalt = FALSE;
932                     event_context->defaultMessageContext()->clear();
933                     break;
934                 case GDK_Alt_R:
935                     nc->rightalt = FALSE;
936                     event_context->defaultMessageContext()->clear();
937                     break;
938                 case GDK_Control_L:
939                     nc->leftctrl = FALSE;
940                     event_context->defaultMessageContext()->clear();
941                     break;
942                 case GDK_Control_R:
943                     nc->rightctrl = FALSE;
944                     event_context->defaultMessageContext()->clear();
945                     break;
946                 case GDK_Shift_L:
947                 case GDK_Shift_R:
948                 case GDK_Meta_L:
949                 case GDK_Meta_R:
950                     event_context->defaultMessageContext()->clear();
951                     break;
952             }
953             break;
954         default:
955             break;
956     }
958     if (!ret) {
959         if (((SPEventContextClass *) parent_class)->root_handler)
960             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
961     }
963     return ret;
967 /*
968   Local Variables:
969   mode:c++
970   c-file-style:"stroustrup"
971   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
972   indent-tabs-mode:nil
973   fill-column:99
974   End:
975 */
976 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :