Code

turns out, all these synthesize_events were not necessary at all - they just fired...
[inkscape.git] / src / node-context.cpp
1 #define __SP_NODE_CONTEXT_C__
3 /*
4  * Node editing context
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   bulia byak <buliabyak@users.sf.net>
9  *
10  * This code is in public domain, except stamping code,
11  * which is Copyright (C) Masatake Yamato 2002
12  */
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
17 #include <gdk/gdkkeysyms.h>
18 #include "macros.h"
19 #include <glibmm/i18n.h>
20 #include "display/sp-canvas-util.h"
21 #include "object-edit.h"
22 #include "sp-path.h"
23 #include "path-chemistry.h"
24 #include "rubberband.h"
25 #include "desktop.h"
26 #include "desktop-handles.h"
27 #include "selection.h"
28 #include "pixmaps/cursor-node.xpm"
29 #include "message-context.h"
30 #include "node-context.h"
31 #include "pixmaps/cursor-node-d.xpm"
32 #include "prefs-utils.h"
33 #include "xml/node-event-vector.h"
34 #include "style.h"
35 #include "splivarot.h"
37 static void sp_node_context_class_init(SPNodeContextClass *klass);
38 static void sp_node_context_init(SPNodeContext *node_context);
39 static void sp_node_context_dispose(GObject *object);
41 static void sp_node_context_setup(SPEventContext *ec);
42 static gint sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event);
43 static gint sp_node_context_item_handler(SPEventContext *event_context,
44                                          SPItem *item, GdkEvent *event);
46 static void nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
47                                         gchar const *old_value, gchar const *new_value,
48                                         bool is_interactive, gpointer data);
50 static Inkscape::XML::NodeEventVector nodepath_repr_events = {
51     NULL, /* child_added */
52     NULL, /* child_removed */
53     nodepath_event_attr_changed,
54     NULL, /* content_changed */
55     NULL  /* order_changed */
56 };
58 static SPEventContextClass *parent_class;
60 static gchar *undo_label_1 = "dragcurve:1";
61 static gchar *undo_label_2 = "dragcurve:2";
62 static gchar *undo_label = undo_label_1;
64 GType
65 sp_node_context_get_type()
66 {
67     static GType type = 0;
68     if (!type) {
69         GTypeInfo info = {
70             sizeof(SPNodeContextClass),
71             NULL, NULL,
72             (GClassInitFunc) sp_node_context_class_init,
73             NULL, NULL,
74             sizeof(SPNodeContext),
75             4,
76             (GInstanceInitFunc) sp_node_context_init,
77             NULL,    /* value_table */
78         };
79         type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPNodeContext", &info, (GTypeFlags)0);
80     }
81     return type;
82 }
84 static void
85 sp_node_context_class_init(SPNodeContextClass *klass)
86 {
87     GObjectClass *object_class = (GObjectClass *) klass;
88     SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
90     parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
92     object_class->dispose = sp_node_context_dispose;
94     event_context_class->setup = sp_node_context_setup;
95     event_context_class->root_handler = sp_node_context_root_handler;
96     event_context_class->item_handler = sp_node_context_item_handler;
97 }
99 static void
100 sp_node_context_init(SPNodeContext *node_context)
102     SPEventContext *event_context = SP_EVENT_CONTEXT(node_context);
104     event_context->cursor_shape = cursor_node_xpm;
105     event_context->hot_x = 1;
106     event_context->hot_y = 1;
108     node_context->leftalt = FALSE;
109     node_context->rightalt = FALSE;
110     node_context->leftctrl = FALSE;
111     node_context->rightctrl = FALSE;
113     new (&node_context->sel_changed_connection) sigc::connection();
116 static void
117 sp_node_context_dispose(GObject *object)
119     SPNodeContext *nc = SP_NODE_CONTEXT(object);
120     SPEventContext *ec = SP_EVENT_CONTEXT(object);
122     ec->enableGrDrag(false);
124     nc->sel_changed_connection.disconnect();
125     nc->sel_changed_connection.~connection();
127     Inkscape::XML::Node *repr = NULL;
128     if (nc->nodepath) {
129         repr = nc->nodepath->repr;
130     }
131     if (!repr && ec->shape_knot_holder) {
132         repr = ec->shape_knot_holder->repr;
133     }
135     if (repr) {
136         sp_repr_remove_listener_by_data(repr, ec);
137         Inkscape::GC::release(repr);
138     }
140     if (nc->nodepath) {
141         sp_nodepath_destroy(nc->nodepath);
142         nc->nodepath = NULL;
143     }
145     if (ec->shape_knot_holder) {
146         sp_knot_holder_destroy(ec->shape_knot_holder);
147         ec->shape_knot_holder = NULL;
148     }
150     if (nc->_node_message_context) {
151         delete nc->_node_message_context;
152     }
154     G_OBJECT_CLASS(parent_class)->dispose(object);
157 static void
158 sp_node_context_setup(SPEventContext *ec)
160     SPNodeContext *nc = SP_NODE_CONTEXT(ec);
162     if (((SPEventContextClass *) parent_class)->setup)
163         ((SPEventContextClass *) parent_class)->setup(ec);
165     nc->sel_changed_connection.disconnect();
166     nc->sel_changed_connection = SP_DT_SELECTION(ec->desktop)->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
168     Inkscape::Selection *selection = SP_DT_SELECTION(ec->desktop);
169     SPItem *item = selection->singleItem();
171     nc->nodepath = NULL;
172     ec->shape_knot_holder = NULL;
174     nc->rb_escaped = false;
176     nc->cursor_drag = false;
178     nc->added_node = false;
180     if (item) {
181         nc->nodepath = sp_nodepath_new(ec->desktop, item);
182         if ( nc->nodepath) {
183             //point pack to parent in case nodepath is deleted
184             nc->nodepath->nodeContext = nc;
185         }
186         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
188         if (nc->nodepath || ec->shape_knot_holder) {
189             // setting listener
190             Inkscape::XML::Node *repr;
191             if (ec->shape_knot_holder)
192                 repr = ec->shape_knot_holder->repr;
193             else
194                 repr = SP_OBJECT_REPR(item);
195             if (repr) {
196                 Inkscape::GC::anchor(repr);
197                 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
198                 sp_repr_synthesize_events(repr, &nodepath_repr_events, ec);
199             }
200         }
201     }
203     if (prefs_get_int_attribute("tools.nodes", "selcue", 0) != 0) {
204         ec->enableSelectionCue();
205     }
207     if (prefs_get_int_attribute("tools.nodes", "gradientdrag", 0) != 0) {
208         ec->enableGrDrag();
209     }
211     nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
212     sp_nodepath_update_statusbar(nc->nodepath);
215 /**
216 \brief  Callback that processes the "changed" signal on the selection;
217 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
218 */
219 void
220 sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
222     SPNodeContext *nc = SP_NODE_CONTEXT(data);
223     SPEventContext *ec = SP_EVENT_CONTEXT(nc);
225     Inkscape::XML::Node *old_repr = NULL;
227     if (nc->nodepath) {
228         old_repr = nc->nodepath->repr;
229         sp_nodepath_destroy(nc->nodepath);
230     }
231     if (ec->shape_knot_holder) {
232         old_repr = ec->shape_knot_holder->repr;
233         sp_knot_holder_destroy(ec->shape_knot_holder);
234     }
236     if (old_repr) { // remove old listener
237         sp_repr_remove_listener_by_data(old_repr, ec);
238         Inkscape::GC::release(old_repr);
239     }
241     SPItem *item = selection->singleItem();
243     SPDesktop *desktop = selection->desktop();
244     nc->nodepath = NULL;
245     ec->shape_knot_holder = NULL;
246     if (item) {
247         nc->nodepath = sp_nodepath_new(desktop, item);
248         if (nc->nodepath) {
249             nc->nodepath->nodeContext = nc;
250         }
251         ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
253         if (nc->nodepath || ec->shape_knot_holder) {
254             // setting new listener
255             Inkscape::XML::Node *repr;
256             if (ec->shape_knot_holder)
257                 repr = ec->shape_knot_holder->repr;
258             else
259                 repr = SP_OBJECT_REPR(item);
260             if (repr) {
261                 Inkscape::GC::anchor(repr);
262                 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
263                 sp_repr_synthesize_events(repr, &nodepath_repr_events, ec);
264             }
265         }
266     }
267     sp_nodepath_update_statusbar(nc->nodepath);
270 /**
271 \brief  Regenerates nodepath when the item's repr was change outside of node edit
272 (e.g. by undo, or xml editor, or edited in another view). The item is assumed to be the same
273 (otherwise sp_node_context_selection_changed() would have been called), so repr and listeners
274 are not changed.
275 */
276 void
277 sp_nodepath_update_from_item(SPNodeContext *nc, SPItem *item)
279     g_assert(nc);
280     SPEventContext *ec = ((SPEventContext *) nc);
282     SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(SP_EVENT_CONTEXT(nc));
283     g_assert(desktop);
285     if (nc->nodepath) {
286         sp_nodepath_destroy(nc->nodepath);
287     }
289     if (ec->shape_knot_holder) {
290         sp_knot_holder_destroy(ec->shape_knot_holder);
291     }
293     Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
294     item = selection->singleItem();
296     nc->nodepath = NULL;
297     ec->shape_knot_holder = NULL;
298     if (item) {
299         nc->nodepath = sp_nodepath_new(desktop, item);
300         if (nc->nodepath) {
301             nc->nodepath->nodeContext = nc;
302         }
303         ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
304     }
305     sp_nodepath_update_statusbar(nc->nodepath);
308 /**
309 \brief  Callback that is fired whenever an attribute of the selected item (which we have in the nodepath) changes
310 */
311 static void
312 nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
313                             gchar const *old_value, gchar const *new_value,
314                             bool is_interactive, gpointer data)
316     SPItem *item = NULL;
317     char const *newd = NULL, *newtypestr = NULL;
318     gboolean changed = FALSE;
320     g_assert(data);
321     SPNodeContext *nc = ((SPNodeContext *) data);
322     SPEventContext *ec = ((SPEventContext *) data);
323     g_assert(nc);
324     Inkscape::NodePath::Path *np = nc->nodepath;
325     SPKnotHolder *kh = ec->shape_knot_holder;
327     if (np) {
328         item = SP_ITEM(np->path);
329         if (!strcmp(name, "d")) {
330             newd = new_value;
331             changed = nodepath_repr_d_changed(np, new_value);
332         } else if (!strcmp(name, "sodipodi:nodetypes")) {
333             newtypestr = new_value;
334             changed = nodepath_repr_typestr_changed(np, new_value);
335         } else {
336             return;
337             // With paths, we only need to act if one of the path-affecting attributes has changed.
338         }
339     } else if (kh) {
340         item = SP_ITEM(kh->item);
341         changed = !(kh->local_change);
342         kh->local_change = FALSE;
343     }
344     if (np && changed) {
345         GList *saved = NULL;
346         SPDesktop *desktop = np->desktop;
347         g_assert(desktop);
348         Inkscape::Selection *selection = desktop->selection;
349         g_assert(selection);
351         saved = save_nodepath_selection(nc->nodepath);
352         sp_nodepath_update_from_item(nc, item);
353         if (nc->nodepath && saved) restore_nodepath_selection(nc->nodepath, saved);
355     } else if (kh && changed) {
356         sp_nodepath_update_from_item(nc, item);
357     }
359     sp_nodepath_update_statusbar(nc->nodepath);
362 void
363 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
365     sp_event_show_modifier_tip
366         (event_context->defaultMessageContext(), event,
367          _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
368          _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
369          _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
372 bool
373 sp_node_context_is_over_stroke (SPNodeContext *nc, SPItem *item, NR::Point event_p, bool remember)
375     SPDesktop *desktop = SP_EVENT_CONTEXT (nc)->desktop;
377     //Translate click point into proper coord system
378     nc->curvepoint_doc = desktop->w2d(event_p);
379     nc->curvepoint_doc *= sp_item_dt2i_affine(item);
380     nc->curvepoint_doc *= sp_item_i2doc_affine(item);
382     NR::Maybe<Path::cut_position> position = get_nearest_position_on_Path(item, nc->curvepoint_doc);
383     NR::Point nearest = get_point_on_Path(item, position.assume().piece, position.assume().t);
384     NR::Point delta = nearest - nc->curvepoint_doc;
386     delta = desktop->d2w(delta);
388     double stroke_tolerance =
389         (SP_OBJECT_STYLE (item)->stroke.type != SP_PAINT_TYPE_NONE?
390          desktop->current_zoom() *
391          SP_OBJECT_STYLE (item)->stroke_width.computed *
392          sp_item_i2d_affine (item).expansion() * 0.5
393          : 0.0)
394         + (double) SP_EVENT_CONTEXT(nc)->tolerance;
396     bool close = (NR::L2 (delta) < stroke_tolerance);
398     if (remember && close) {
399         nc->curvepoint_event[NR::X] = (gint) event_p [NR::X];
400         nc->curvepoint_event[NR::Y] = (gint) event_p [NR::Y];
401         nc->hit = true;
402         nc->grab_t = position.assume().t;
403         nc->grab_node = sp_nodepath_get_node_by_index(position.assume().piece);
404     }
406     return close;
410 static gint
411 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
413     gint ret = FALSE;
415     SPDesktop *desktop = event_context->desktop;
416     Inkscape::Selection *selection = SP_DT_SELECTION (desktop);
418     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
420     switch (event->type) {
421         case GDK_2BUTTON_PRESS:
422         case GDK_BUTTON_RELEASE:
423             if (event->button.button == 1) {
424                 if (!nc->drag) {
426                     // find out clicked item, disregarding groups, honoring Alt
427                     SPItem *item_clicked = sp_event_context_find_item (desktop,
428                             NR::Point(event->button.x, event->button.y),
429                             (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
430                     // find out if we're over the selected item, disregarding groups
431                     SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
432                                                                     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(item_over, 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(item_over, nc->curvepoint_doc, true);
452                                     } else {
453                                         sp_nodepath_select_segment_near_point(item_over, nc->curvepoint_doc, false);
454                                     }
455                                 }
456                                 break;
457                             case GDK_2BUTTON_PRESS:
458                                 //add a node
459                                 sp_nodepath_add_node_near_point(item_over, 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(item_over, 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_DT_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, undo_label);
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->hot_x = 1;
595                     event_context->hot_y = 1;
596                     sp_event_context_update_cursor(event_context);
597                     nc->cursor_drag = false;
598                 } else if (!nc->cursor_drag && over_stroke) {
599                     event_context->cursor_shape = cursor_node_d_xpm;
600                     event_context->hot_x = 1;
601                     event_context->hot_y = 1;
602                     sp_event_context_update_cursor(event_context);
603                     nc->cursor_drag = true;
604                 }
605             }
606             break;
607         case GDK_BUTTON_RELEASE:
608             event_context->xp = event_context->yp = 0;
609             if (event->button.button == 1) {
611                 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
613                 if (nc->hit && !event_context->within_tolerance) { //drag curve
614                     if (undo_label == undo_label_1)
615                         undo_label = undo_label_2;
616                     else
617                         undo_label = undo_label_1;
618                 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
619                     if (nc->nodepath) {
620                         sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
621                     }
622                 } else {
623                     if (!(nc->rb_escaped)) { // unless something was cancelled
624                         if (nc->nodepath && nc->nodepath->selected)
625                             sp_nodepath_deselect(nc->nodepath);
626                         else
627                             SP_DT_SELECTION(desktop)->clear();
628                     }
629                 }
630                 ret = TRUE;
631                 Inkscape::Rubberband::get()->stop();
632                 nc->rb_escaped = false;
633                 nc->drag = FALSE;
634                 nc->hit = false;
635                 break;
636             }
637             break;
638         case GDK_KEY_PRESS:
639             switch (get_group0_keyval(&event->key)) {
640                 case GDK_Insert:
641                 case GDK_KP_Insert:
642                     // with any modifiers
643                     sp_node_selected_add_node();
644                     ret = TRUE;
645                     break;
646                 case GDK_Delete:
647                 case GDK_KP_Delete:
648                 case GDK_BackSpace:
649                     // with any modifiers
650                     sp_node_selected_delete();
651                     ret = TRUE;
652                     break;
653                 case GDK_C:
654                 case GDK_c:
655                     if (MOD__SHIFT_ONLY) {
656                         sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
657                         ret = TRUE;
658                     }
659                     break;
660                 case GDK_S:
661                 case GDK_s:
662                     if (MOD__SHIFT_ONLY) {
663                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
664                         ret = TRUE;
665                     }
666                     break;
667                 case GDK_Y:
668                 case GDK_y:
669                     if (MOD__SHIFT_ONLY) {
670                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
671                         ret = TRUE;
672                     }
673                     break;
674                 case GDK_B:
675                 case GDK_b:
676                     if (MOD__SHIFT_ONLY) {
677                         sp_node_selected_break();
678                         ret = TRUE;
679                     }
680                     break;
681                 case GDK_J:
682                 case GDK_j:
683                     if (MOD__SHIFT_ONLY) {
684                         sp_node_selected_join();
685                         ret = TRUE;
686                     }
687                     break;
688                 case GDK_D:
689                 case GDK_d:
690                     if (MOD__SHIFT_ONLY) {
691                         sp_node_selected_duplicate();
692                         ret = TRUE;
693                     }
694                     break;
695                 case GDK_L:
696                 case GDK_l:
697                     if (MOD__SHIFT_ONLY) {
698                         sp_node_selected_set_line_type(NR_LINETO);
699                         ret = TRUE;
700                     }
701                     break;
702                 case GDK_U:
703                 case GDK_u:
704                     if (MOD__SHIFT_ONLY) {
705                         sp_node_selected_set_line_type(NR_CURVETO);
706                         ret = TRUE;
707                     }
708                     break;
709                 case GDK_R:
710                 case GDK_r:
711                     if (MOD__SHIFT_ONLY) {
712                         // FIXME: add top panel button
713                         sp_selected_path_reverse();
714                         ret = TRUE;
715                     }
716                     break;
717                 case GDK_Left: // move selection left
718                 case GDK_KP_Left:
719                 case GDK_KP_4:
720                     if (!MOD__CTRL) { // not ctrl
721                         if (MOD__ALT) { // alt
722                             if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
723                             else sp_node_selected_move_screen(-1, 0); // no shift
724                         }
725                         else { // no alt
726                             if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
727                             else sp_node_selected_move(-nudge, 0); // no shift
728                         }
729                         ret = TRUE;
730                     }
731                     break;
732                 case GDK_Up: // move selection up
733                 case GDK_KP_Up:
734                 case GDK_KP_8:
735                     if (!MOD__CTRL) { // not ctrl
736                         if (MOD__ALT) { // alt
737                             if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
738                             else sp_node_selected_move_screen(0, 1); // no shift
739                         }
740                         else { // no alt
741                             if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
742                             else sp_node_selected_move(0, nudge); // no shift
743                         }
744                         ret = TRUE;
745                     }
746                     break;
747                 case GDK_Right: // move selection right
748                 case GDK_KP_Right:
749                 case GDK_KP_6:
750                     if (!MOD__CTRL) { // not ctrl
751                         if (MOD__ALT) { // alt
752                             if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
753                             else sp_node_selected_move_screen(1, 0); // no shift
754                         }
755                         else { // no alt
756                             if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
757                             else sp_node_selected_move(nudge, 0); // no shift
758                         }
759                         ret = TRUE;
760                     }
761                     break;
762                 case GDK_Down: // move selection down
763                 case GDK_KP_Down:
764                 case GDK_KP_2:
765                     if (!MOD__CTRL) { // not ctrl
766                         if (MOD__ALT) { // alt
767                             if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
768                             else sp_node_selected_move_screen(0, -1); // no shift
769                         }
770                         else { // no alt
771                             if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
772                             else sp_node_selected_move(0, -nudge); // no shift
773                         }
774                         ret = TRUE;
775                     }
776                     break;
777                 case GDK_Tab: // Tab - cycle selection forward
778                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
779                         sp_nodepath_select_next(nc->nodepath);
780                         ret = TRUE;
781                     }
782                     break;
783                 case GDK_ISO_Left_Tab:  // Shift Tab - cycle selection backward
784                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
785                         sp_nodepath_select_prev(nc->nodepath);
786                         ret = TRUE;
787                     }
788                     break;
789                 case GDK_Escape:
790                 {
791                     NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
792                     if (b != NR::Nothing()) {
793                         Inkscape::Rubberband::get()->stop();
794                         nc->rb_escaped = true;
795                     } else {
796                         if (nc->nodepath && nc->nodepath->selected) {
797                             sp_nodepath_deselect(nc->nodepath);
798                         } else {
799                             SP_DT_SELECTION(desktop)->clear();
800                         }
801                     }
802                     ret = TRUE;
803                     break;
804                 }
806                 case GDK_bracketleft:
807                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
808                         if (nc->leftctrl)
809                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
810                         if (nc->rightctrl)
811                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
812                     } else if ( MOD__ALT && !MOD__CTRL ) {
813                         if (nc->leftalt && nc->rightalt)
814                             sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
815                         else {
816                             if (nc->leftalt)
817                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
818                             if (nc->rightalt)
819                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
820                         }
821                     } else if ( snaps != 0 ) {
822                         sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
823                     }
824                     ret = TRUE;
825                     break;
826                 case GDK_bracketright:
827                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
828                         if (nc->leftctrl)
829                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
830                         if (nc->rightctrl)
831                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
832                     } else if ( MOD__ALT && !MOD__CTRL ) {
833                         if (nc->leftalt && nc->rightalt)
834                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
835                         else {
836                             if (nc->leftalt)
837                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
838                             if (nc->rightalt)
839                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
840                         }
841                     } else if ( snaps != 0 ) {
842                         sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
843                     }
844                     ret = TRUE;
845                     break;
846                 case GDK_less:
847                 case GDK_comma:
848                     if (MOD__CTRL) {
849                         if (nc->leftctrl)
850                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
851                         if (nc->rightctrl)
852                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
853                     } else if (MOD__ALT) {
854                         if (nc->leftalt && nc->rightalt)
855                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
856                         else {
857                             if (nc->leftalt)
858                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
859                             if (nc->rightalt)
860                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
861                         }
862                     } else {
863                         sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
864                     }
865                     ret = TRUE;
866                     break;
867                 case GDK_greater:
868                 case GDK_period:
869                     if (MOD__CTRL) {
870                         if (nc->leftctrl)
871                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
872                         if (nc->rightctrl)
873                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
874                     } else if (MOD__ALT) {
875                         if (nc->leftalt && nc->rightalt)
876                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
877                         else {
878                             if (nc->leftalt)
879                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
880                             if (nc->rightalt)
881                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
882                         }
883                     } else {
884                         sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
885                     }
886                     ret = TRUE;
887                     break;
889                 case GDK_Alt_L:
890                     nc->leftalt = TRUE;
891                     sp_node_context_show_modifier_tip(event_context, event);
892                     break;
893                 case GDK_Alt_R:
894                     nc->rightalt = TRUE;
895                     sp_node_context_show_modifier_tip(event_context, event);
896                     break;
897                 case GDK_Control_L:
898                     nc->leftctrl = TRUE;
899                     sp_node_context_show_modifier_tip(event_context, event);
900                     break;
901                 case GDK_Control_R:
902                     nc->rightctrl = TRUE;
903                     sp_node_context_show_modifier_tip(event_context, event);
904                     break;
905                 case GDK_Shift_L:
906                 case GDK_Shift_R:
907                 case GDK_Meta_L:
908                 case GDK_Meta_R:
909                     sp_node_context_show_modifier_tip(event_context, event);
910                     break;
911                 default:
912                     ret = node_key(event);
913                     break;
914             }
915             break;
916         case GDK_KEY_RELEASE:
917             switch (get_group0_keyval(&event->key)) {
918                 case GDK_Alt_L:
919                     nc->leftalt = FALSE;
920                     event_context->defaultMessageContext()->clear();
921                     break;
922                 case GDK_Alt_R:
923                     nc->rightalt = FALSE;
924                     event_context->defaultMessageContext()->clear();
925                     break;
926                 case GDK_Control_L:
927                     nc->leftctrl = FALSE;
928                     event_context->defaultMessageContext()->clear();
929                     break;
930                 case GDK_Control_R:
931                     nc->rightctrl = FALSE;
932                     event_context->defaultMessageContext()->clear();
933                     break;
934                 case GDK_Shift_L:
935                 case GDK_Shift_R:
936                 case GDK_Meta_L:
937                 case GDK_Meta_R:
938                     event_context->defaultMessageContext()->clear();
939                     break;
940             }
941             break;
942         default:
943             break;
944     }
946     if (!ret) {
947         if (((SPEventContextClass *) parent_class)->root_handler)
948             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
949     }
951     return ret;
955 /*
956   Local Variables:
957   mode:c++
958   c-file-style:"stroustrup"
959   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
960   indent-tabs-mode:nil
961   fill-column:99
962   End:
963 */
964 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :