Code

Remove warnings
[inkscape.git] / src / node-context.cpp
1 #define __SP_NODE_CONTEXT_C__
3 /*
4  * Node editing context
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   bulia byak <buliabyak@users.sf.net>
9  *
10  * This code is in public domain, except stamping code,
11  * which is Copyright (C) Masatake Yamato 2002
12  */
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
17 #include <gdk/gdkkeysyms.h>
18 #include "macros.h"
19 #include <glibmm/i18n.h>
20 #include "display/sp-canvas-util.h"
21 #include "object-edit.h"
22 #include "sp-path.h"
23 #include "path-chemistry.h"
24 #include "rubberband.h"
25 #include "desktop.h"
26 #include "desktop-handles.h"
27 #include "selection.h"
28 #include "pixmaps/cursor-node.xpm"
29 #include "message-context.h"
30 #include "node-context.h"
31 #include "pixmaps/cursor-node-d.xpm"
32 #include "prefs-utils.h"
33 #include "xml/node-event-vector.h"
34 #include "style.h"
35 #include "splivarot.h"
37 static void sp_node_context_class_init(SPNodeContextClass *klass);
38 static void sp_node_context_init(SPNodeContext *node_context);
39 static void sp_node_context_dispose(GObject *object);
41 static void sp_node_context_setup(SPEventContext *ec);
42 static gint sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event);
43 static gint sp_node_context_item_handler(SPEventContext *event_context,
44                                          SPItem *item, GdkEvent *event);
46 static void nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
47                                         gchar const *old_value, gchar const *new_value,
48                                         bool is_interactive, gpointer data);
50 static Inkscape::XML::NodeEventVector nodepath_repr_events = {
51     NULL, /* child_added */
52     NULL, /* child_removed */
53     nodepath_event_attr_changed,
54     NULL, /* content_changed */
55     NULL  /* order_changed */
56 };
58 static SPEventContextClass *parent_class;
60 GType
61 sp_node_context_get_type()
62 {
63     static GType type = 0;
64     if (!type) {
65         GTypeInfo info = {
66             sizeof(SPNodeContextClass),
67             NULL, NULL,
68             (GClassInitFunc) sp_node_context_class_init,
69             NULL, NULL,
70             sizeof(SPNodeContext),
71             4,
72             (GInstanceInitFunc) sp_node_context_init,
73             NULL,    /* value_table */
74         };
75         type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPNodeContext", &info, (GTypeFlags)0);
76     }
77     return type;
78 }
80 static void
81 sp_node_context_class_init(SPNodeContextClass *klass)
82 {
83     GObjectClass *object_class = (GObjectClass *) klass;
84     SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
86     parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
88     object_class->dispose = sp_node_context_dispose;
90     event_context_class->setup = sp_node_context_setup;
91     event_context_class->root_handler = sp_node_context_root_handler;
92     event_context_class->item_handler = sp_node_context_item_handler;
93 }
95 static void
96 sp_node_context_init(SPNodeContext *node_context)
97 {
98     SPEventContext *event_context = SP_EVENT_CONTEXT(node_context);
100     event_context->cursor_shape = cursor_node_xpm;
101     event_context->hot_x = 1;
102     event_context->hot_y = 1;
104     node_context->leftalt = FALSE;
105     node_context->rightalt = FALSE;
106     node_context->leftctrl = FALSE;
107     node_context->rightctrl = FALSE;
109     new (&node_context->sel_changed_connection) sigc::connection();
112 static void
113 sp_node_context_dispose(GObject *object)
115     SPNodeContext *nc = SP_NODE_CONTEXT(object);
116     SPEventContext *ec = SP_EVENT_CONTEXT(object);
118     ec->enableGrDrag(false);
120     nc->sel_changed_connection.disconnect();
121     nc->sel_changed_connection.~connection();
123     Inkscape::XML::Node *repr = NULL;
124     if (nc->nodepath) {
125         repr = nc->nodepath->repr;
126     }
127     if (!repr && ec->shape_knot_holder) {
128         repr = ec->shape_knot_holder->repr;
129     }
131     if (repr) {
132         sp_repr_remove_listener_by_data(repr, ec);
133         Inkscape::GC::release(repr);
134     }
136     if (nc->nodepath) {
137         sp_nodepath_destroy(nc->nodepath);
138         nc->nodepath = NULL;
139     }
141     if (ec->shape_knot_holder) {
142         sp_knot_holder_destroy(ec->shape_knot_holder);
143         ec->shape_knot_holder = NULL;
144     }
146     if (nc->_node_message_context) {
147         delete nc->_node_message_context;
148     }
150     G_OBJECT_CLASS(parent_class)->dispose(object);
153 static void
154 sp_node_context_setup(SPEventContext *ec)
156     SPNodeContext *nc = SP_NODE_CONTEXT(ec);
158     if (((SPEventContextClass *) parent_class)->setup)
159         ((SPEventContextClass *) parent_class)->setup(ec);
161     nc->sel_changed_connection.disconnect();
162     nc->sel_changed_connection = SP_DT_SELECTION(ec->desktop)->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
164     Inkscape::Selection *selection = SP_DT_SELECTION(ec->desktop);
165     SPItem *item = selection->singleItem();
167     nc->nodepath = NULL;
168     ec->shape_knot_holder = NULL;
170     nc->rb_escaped = false;
172     nc->cursor_drag = false;
174     nc->added_node = false;
176     if (item) {
177         nc->nodepath = sp_nodepath_new(ec->desktop, item);
178         if ( nc->nodepath) {
179             //point pack to parent in case nodepath is deleted
180             nc->nodepath->nodeContext = nc;
181         }
182         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
184         if (nc->nodepath || ec->shape_knot_holder) {
185             // setting listener
186             Inkscape::XML::Node *repr;
187             if (ec->shape_knot_holder)
188                 repr = ec->shape_knot_holder->repr;
189             else
190                 repr = SP_OBJECT_REPR(item);
191             if (repr) {
192                 Inkscape::GC::anchor(repr);
193                 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
194             }
195         }
196     }
198     if (prefs_get_int_attribute("tools.nodes", "selcue", 0) != 0) {
199         ec->enableSelectionCue();
200     }
202     if (prefs_get_int_attribute("tools.nodes", "gradientdrag", 0) != 0) {
203         ec->enableGrDrag();
204     }
206     nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
207     sp_nodepath_update_statusbar(nc->nodepath);
210 /**
211 \brief  Callback that processes the "changed" signal on the selection;
212 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
213 */
214 void
215 sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
217     SPNodeContext *nc = SP_NODE_CONTEXT(data);
218     SPEventContext *ec = SP_EVENT_CONTEXT(nc);
220     Inkscape::XML::Node *old_repr = NULL;
222     if (nc->nodepath) {
223         old_repr = nc->nodepath->repr;
224         sp_nodepath_destroy(nc->nodepath);
225         nc->nodepath = NULL;
226     }
228     if (ec->shape_knot_holder) {
229         old_repr = ec->shape_knot_holder->repr;
230         sp_knot_holder_destroy(ec->shape_knot_holder);
231     }
233     if (old_repr) { // remove old listener
234         sp_repr_remove_listener_by_data(old_repr, ec);
235         Inkscape::GC::release(old_repr);
236     }
238     SPItem *item = selection->singleItem();
240     SPDesktop *desktop = selection->desktop();
241     nc->nodepath = NULL;
242     ec->shape_knot_holder = NULL;
243     if (item) {
244         nc->nodepath = sp_nodepath_new(desktop, item);
245         if (nc->nodepath) {
246             nc->nodepath->nodeContext = nc;
247         }
248         ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
250         if (nc->nodepath || ec->shape_knot_holder) {
251             // setting new listener
252             Inkscape::XML::Node *repr;
253             if (ec->shape_knot_holder)
254                 repr = ec->shape_knot_holder->repr;
255             else
256                 repr = SP_OBJECT_REPR(item);
257             if (repr) {
258                 Inkscape::GC::anchor(repr);
259                 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
260             }
261         }
262     }
263     sp_nodepath_update_statusbar(nc->nodepath);
266 /**
267 \brief  Regenerates nodepath when the item's repr was change outside of node edit
268 (e.g. by undo, or xml editor, or edited in another view). The item is assumed to be the same
269 (otherwise sp_node_context_selection_changed() would have been called), so repr and listeners
270 are not changed.
271 */
272 void
273 sp_nodepath_update_from_item(SPNodeContext *nc, SPItem *item)
275     g_assert(nc);
276     SPEventContext *ec = ((SPEventContext *) nc);
278     SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(SP_EVENT_CONTEXT(nc));
279     g_assert(desktop);
281     if (nc->nodepath) {
282         sp_nodepath_destroy(nc->nodepath);
283         nc->nodepath = NULL;
284     }
286     if (ec->shape_knot_holder) {
287         sp_knot_holder_destroy(ec->shape_knot_holder);
288         ec->shape_knot_holder = NULL;
289     }
291     Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
292     item = selection->singleItem();
294     if (item) {
295         nc->nodepath = sp_nodepath_new(desktop, item);
296         if (nc->nodepath) {
297             nc->nodepath->nodeContext = nc;
298         }
299         ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
300     }
301     sp_nodepath_update_statusbar(nc->nodepath);
304 /**
305 \brief  Callback that is fired whenever an attribute of the selected item (which we have in the nodepath) changes
306 */
307 static void
308 nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
309                             gchar const *old_value, gchar const *new_value,
310                             bool is_interactive, gpointer data)
312     SPItem *item = NULL;
313     gboolean changed = FALSE;
315     g_assert(data);
316     SPNodeContext *nc = ((SPNodeContext *) data);
317     SPEventContext *ec = ((SPEventContext *) data);
318     g_assert(nc);
319     Inkscape::NodePath::Path *np = nc->nodepath;
320     SPKnotHolder *kh = ec->shape_knot_holder;
322     if (np) {
323         item = SP_ITEM(np->path);
324         if (!strcmp(name, "d") || !strcmp(name, "sodipodi:nodetypes")) { // With paths, we only need to act if one of the path-affecting attributes has changed.
325             changed = (np->local_change == 0);
326             if (np->local_change > 0)
327                 np->local_change--;
328         }
330     } else if (kh) {
331         item = SP_ITEM(kh->item);
332         changed = !(kh->local_change);
333         kh->local_change = FALSE;
334     }
336     if (np && changed) {
337         GList *saved = NULL;
338         SPDesktop *desktop = np->desktop;
339         g_assert(desktop);
340         Inkscape::Selection *selection = desktop->selection;
341         g_assert(selection);
343         saved = save_nodepath_selection(nc->nodepath);
344         sp_nodepath_update_from_item(nc, item);
345         if (nc->nodepath && saved) restore_nodepath_selection(nc->nodepath, saved);
347     } else if (kh && changed) {
348         sp_nodepath_update_from_item(nc, item);
349     }
351     sp_nodepath_update_statusbar(nc->nodepath);
354 void
355 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
357     sp_event_show_modifier_tip
358         (event_context->defaultMessageContext(), event,
359          _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
360          _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
361          _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
364 bool
365 sp_node_context_is_over_stroke (SPNodeContext *nc, SPItem *item, NR::Point event_p, bool remember)
367     SPDesktop *desktop = SP_EVENT_CONTEXT (nc)->desktop;
369     //Translate click point into proper coord system
370     nc->curvepoint_doc = desktop->w2d(event_p);
371     nc->curvepoint_doc *= sp_item_dt2i_affine(item);
372     nc->curvepoint_doc *= sp_item_i2doc_affine(item);
374     NR::Maybe<Path::cut_position> position = get_nearest_position_on_Path(nc->nodepath->livarot_path, nc->curvepoint_doc);
375     NR::Point nearest = get_point_on_Path(nc->nodepath->livarot_path, position.assume().piece, position.assume().t);
376     NR::Point delta = nearest - nc->curvepoint_doc;
378     delta = desktop->d2w(delta);
380     double stroke_tolerance =
381         (SP_OBJECT_STYLE (item)->stroke.type != SP_PAINT_TYPE_NONE?
382          desktop->current_zoom() *
383          SP_OBJECT_STYLE (item)->stroke_width.computed *
384          sp_item_i2d_affine (item).expansion() * 0.5
385          : 0.0)
386         + (double) SP_EVENT_CONTEXT(nc)->tolerance;
388     bool close = (NR::L2 (delta) < stroke_tolerance);
390     if (remember && close) {
391         nc->curvepoint_event[NR::X] = (gint) event_p [NR::X];
392         nc->curvepoint_event[NR::Y] = (gint) event_p [NR::Y];
393         nc->hit = true;
394         nc->grab_t = position.assume().t;
395         nc->grab_node = sp_nodepath_get_node_by_index(position.assume().piece);
396     }
398     return close;
402 static gint
403 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
405     gint ret = FALSE;
407     SPDesktop *desktop = event_context->desktop;
408     Inkscape::Selection *selection = SP_DT_SELECTION (desktop);
410     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
412     switch (event->type) {
413         case GDK_2BUTTON_PRESS:
414         case GDK_BUTTON_RELEASE:
415             if (event->button.button == 1) {
416                 if (!nc->drag) {
418                     // find out clicked item, disregarding groups, honoring Alt
419                     SPItem *item_clicked = sp_event_context_find_item (desktop,
420                             NR::Point(event->button.x, event->button.y),
421                             (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
422                     // find out if we're over the selected item, disregarding groups
423                     SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
424                                                                     NR::Point(event->button.x, event->button.y));
426                     bool over_stroke = false;
427                     if (item_over && nc->nodepath) {
428                         over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), false);
429                     }
431                     if (over_stroke || nc->added_node) {
432                         switch (event->type) {
433                             case GDK_BUTTON_RELEASE:
434                                 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
435                                     //add a node
436                                     sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
437                                 } else {
438                                     if (nc->added_node) { // we just received double click, ignore release
439                                         nc->added_node = false;
440                                         break;
441                                     }
442                                     //select the segment
443                                     if (event->button.state & GDK_SHIFT_MASK) {
444                                         sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, true);
445                                     } else {
446                                         sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
447                                     }
448                                 }
449                                 break;
450                             case GDK_2BUTTON_PRESS:
451                                 //add a node
452                                 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
453                                 nc->added_node = true;
454                                 break;
455                             default:
456                                 break;
457                         }
458                     } else if (event->button.state & GDK_SHIFT_MASK) {
459                         selection->toggle(item_clicked);
460                     } else {
461                         selection->set(item_clicked);
462                     }
464                     ret = TRUE;
465                 }
466                 break;
467             }
468             break;
469         case GDK_BUTTON_PRESS:
470             if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK)) {
471                 // save drag origin
472                 event_context->xp = (gint) event->button.x;
473                 event_context->yp = (gint) event->button.y;
474                 event_context->within_tolerance = true;
475                 nc->hit = false;
477                 if (!nc->drag) {
478                     // find out if we're over the selected item, disregarding groups
479                     SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
480                                                                     NR::Point(event->button.x, event->button.y));
482                         if (nc->nodepath && selection->single() && item_over) {
484                             // save drag origin
485                             bool over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), true);
486                             //only dragging curves
487                             if (over_stroke) {
488                                 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
489                                 ret = TRUE;
490                             } else {
491                                 break;
492                             }
493                         } else {
494                             break;
495                         }
497                     ret = TRUE;
498                 }
499                 break;
500             }
501             break;
502         default:
503             break;
504     }
506     if (!ret) {
507         if (((SPEventContextClass *) parent_class)->item_handler)
508             ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
509     }
511     return ret;
514 static gint
515 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
517     SPDesktop *desktop = event_context->desktop;
518     Inkscape::Selection *selection = SP_DT_SELECTION (desktop);
520     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
521     double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
522     event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
523     int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
524     double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
526     gint ret = FALSE;
528     switch (event->type) {
529         case GDK_BUTTON_PRESS:
530             if (event->button.button == 1) {
531                 // save drag origin
532                 event_context->xp = (gint) event->button.x;
533                 event_context->yp = (gint) event->button.y;
534                 event_context->within_tolerance = true;
535                 nc->hit = false;
537                 NR::Point const button_w(event->button.x,
538                                          event->button.y);
539                 NR::Point const button_dt(desktop->w2d(button_w));
540                 Inkscape::Rubberband::get()->start(desktop, button_dt);
541                 ret = TRUE;
542             }
543             break;
544         case GDK_MOTION_NOTIFY:
545             if (event->motion.state & GDK_BUTTON1_MASK) {
547                 if ( event_context->within_tolerance
548                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
549                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
550                     break; // do not drag if we're within tolerance from origin
551                 }
552                 // Once the user has moved farther than tolerance from the original location
553                 // (indicating they intend to move the object, not click), then always process the
554                 // motion notify coordinates as given (no snapping back to origin)
555                 event_context->within_tolerance = false;
557                 if (nc->nodepath && nc->hit) {
558                     NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
559                                          event->motion.y - nc->curvepoint_event[NR::Y]);
560                     NR::Point const delta_dt(desktop->w2d(delta_w));
561                     sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
562                     nc->curvepoint_event[NR::X] = (gint) event->motion.x;
563                     nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
564                     gobble_motion_events(GDK_BUTTON1_MASK);
565                 } else {
566                     NR::Point const motion_w(event->motion.x,
567                                          event->motion.y);
568                     NR::Point const motion_dt(desktop->w2d(motion_w));
569                     Inkscape::Rubberband::get()->move(motion_dt);
570                 }
571                 nc->drag = TRUE;
572                 ret = TRUE;
573             } else {
574                 if (!nc->nodepath || selection->singleItem() == NULL) {
575                     break;
576                 }
578                 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
579                                                                 NR::Point(event->motion.x, event->motion.y));
580                 bool over_stroke = false;
581                 if (item_over && nc->nodepath) {
582                     over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
583                 }
585                 if (nc->cursor_drag && !over_stroke) {
586                     event_context->cursor_shape = cursor_node_xpm;
587                     event_context->hot_x = 1;
588                     event_context->hot_y = 1;
589                     sp_event_context_update_cursor(event_context);
590                     nc->cursor_drag = false;
591                 } else if (!nc->cursor_drag && over_stroke) {
592                     event_context->cursor_shape = cursor_node_d_xpm;
593                     event_context->hot_x = 1;
594                     event_context->hot_y = 1;
595                     sp_event_context_update_cursor(event_context);
596                     nc->cursor_drag = true;
597                 }
598             }
599             break;
600         case GDK_BUTTON_RELEASE:
601             event_context->xp = event_context->yp = 0;
602             if (event->button.button == 1) {
604                 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
606                 if (nc->hit && !event_context->within_tolerance) { //drag curve
607                     sp_nodepath_update_repr (nc->nodepath);
608                 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
609                     if (nc->nodepath) {
610                         sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
611                     }
612                 } else {
613                     if (!(nc->rb_escaped)) { // unless something was cancelled
614                         if (nc->nodepath && nc->nodepath->selected)
615                             sp_nodepath_deselect(nc->nodepath);
616                         else
617                             SP_DT_SELECTION(desktop)->clear();
618                     }
619                 }
620                 ret = TRUE;
621                 Inkscape::Rubberband::get()->stop();
622                 nc->rb_escaped = false;
623                 nc->drag = FALSE;
624                 nc->hit = false;
625                 break;
626             }
627             break;
628         case GDK_KEY_PRESS:
629             switch (get_group0_keyval(&event->key)) {
630                 case GDK_Insert:
631                 case GDK_KP_Insert:
632                     // with any modifiers
633                     sp_node_selected_add_node();
634                     ret = TRUE;
635                     break;
636                 case GDK_Delete:
637                 case GDK_KP_Delete:
638                 case GDK_BackSpace:
639                     // with any modifiers
640                     sp_node_selected_delete();
641                     ret = TRUE;
642                     break;
643                 case GDK_C:
644                 case GDK_c:
645                     if (MOD__SHIFT_ONLY) {
646                         sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
647                         ret = TRUE;
648                     }
649                     break;
650                 case GDK_S:
651                 case GDK_s:
652                     if (MOD__SHIFT_ONLY) {
653                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
654                         ret = TRUE;
655                     }
656                     break;
657                 case GDK_Y:
658                 case GDK_y:
659                     if (MOD__SHIFT_ONLY) {
660                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
661                         ret = TRUE;
662                     }
663                     break;
664                 case GDK_B:
665                 case GDK_b:
666                     if (MOD__SHIFT_ONLY) {
667                         sp_node_selected_break();
668                         ret = TRUE;
669                     }
670                     break;
671                 case GDK_J:
672                 case GDK_j:
673                     if (MOD__SHIFT_ONLY) {
674                         sp_node_selected_join();
675                         ret = TRUE;
676                     }
677                     break;
678                 case GDK_D:
679                 case GDK_d:
680                     if (MOD__SHIFT_ONLY) {
681                         sp_node_selected_duplicate();
682                         ret = TRUE;
683                     }
684                     break;
685                 case GDK_L:
686                 case GDK_l:
687                     if (MOD__SHIFT_ONLY) {
688                         sp_node_selected_set_line_type(NR_LINETO);
689                         ret = TRUE;
690                     }
691                     break;
692                 case GDK_U:
693                 case GDK_u:
694                     if (MOD__SHIFT_ONLY) {
695                         sp_node_selected_set_line_type(NR_CURVETO);
696                         ret = TRUE;
697                     }
698                     break;
699                 case GDK_R:
700                 case GDK_r:
701                     if (MOD__SHIFT_ONLY) {
702                         // FIXME: add top panel button
703                         sp_selected_path_reverse();
704                         ret = TRUE;
705                     }
706                     break;
707                 case GDK_Left: // move selection left
708                 case GDK_KP_Left:
709                 case GDK_KP_4:
710                     if (!MOD__CTRL) { // not ctrl
711                         if (MOD__ALT) { // alt
712                             if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
713                             else sp_node_selected_move_screen(-1, 0); // no shift
714                         }
715                         else { // no alt
716                             if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
717                             else sp_node_selected_move(-nudge, 0); // no shift
718                         }
719                         ret = TRUE;
720                     }
721                     break;
722                 case GDK_Up: // move selection up
723                 case GDK_KP_Up:
724                 case GDK_KP_8:
725                     if (!MOD__CTRL) { // not ctrl
726                         if (MOD__ALT) { // alt
727                             if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
728                             else sp_node_selected_move_screen(0, 1); // no shift
729                         }
730                         else { // no alt
731                             if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
732                             else sp_node_selected_move(0, nudge); // no shift
733                         }
734                         ret = TRUE;
735                     }
736                     break;
737                 case GDK_Right: // move selection right
738                 case GDK_KP_Right:
739                 case GDK_KP_6:
740                     if (!MOD__CTRL) { // not ctrl
741                         if (MOD__ALT) { // alt
742                             if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
743                             else sp_node_selected_move_screen(1, 0); // no shift
744                         }
745                         else { // no alt
746                             if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
747                             else sp_node_selected_move(nudge, 0); // no shift
748                         }
749                         ret = TRUE;
750                     }
751                     break;
752                 case GDK_Down: // move selection down
753                 case GDK_KP_Down:
754                 case GDK_KP_2:
755                     if (!MOD__CTRL) { // not ctrl
756                         if (MOD__ALT) { // alt
757                             if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
758                             else sp_node_selected_move_screen(0, -1); // no shift
759                         }
760                         else { // no alt
761                             if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
762                             else sp_node_selected_move(0, -nudge); // no shift
763                         }
764                         ret = TRUE;
765                     }
766                     break;
767                 case GDK_Tab: // Tab - cycle selection forward
768                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
769                         sp_nodepath_select_next(nc->nodepath);
770                         ret = TRUE;
771                     }
772                     break;
773                 case GDK_ISO_Left_Tab:  // Shift Tab - cycle selection backward
774                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
775                         sp_nodepath_select_prev(nc->nodepath);
776                         ret = TRUE;
777                     }
778                     break;
779                 case GDK_Escape:
780                 {
781                     NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
782                     if (b != NR::Nothing()) {
783                         Inkscape::Rubberband::get()->stop();
784                         nc->rb_escaped = true;
785                     } else {
786                         if (nc->nodepath && nc->nodepath->selected) {
787                             sp_nodepath_deselect(nc->nodepath);
788                         } else {
789                             SP_DT_SELECTION(desktop)->clear();
790                         }
791                     }
792                     ret = TRUE;
793                     break;
794                 }
796                 case GDK_bracketleft:
797                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
798                         if (nc->leftctrl)
799                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
800                         if (nc->rightctrl)
801                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
802                     } else if ( MOD__ALT && !MOD__CTRL ) {
803                         if (nc->leftalt && nc->rightalt)
804                             sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
805                         else {
806                             if (nc->leftalt)
807                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
808                             if (nc->rightalt)
809                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
810                         }
811                     } else if ( snaps != 0 ) {
812                         sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
813                     }
814                     ret = TRUE;
815                     break;
816                 case GDK_bracketright:
817                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
818                         if (nc->leftctrl)
819                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
820                         if (nc->rightctrl)
821                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
822                     } else if ( MOD__ALT && !MOD__CTRL ) {
823                         if (nc->leftalt && nc->rightalt)
824                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
825                         else {
826                             if (nc->leftalt)
827                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
828                             if (nc->rightalt)
829                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
830                         }
831                     } else if ( snaps != 0 ) {
832                         sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
833                     }
834                     ret = TRUE;
835                     break;
836                 case GDK_less:
837                 case GDK_comma:
838                     if (MOD__CTRL) {
839                         if (nc->leftctrl)
840                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
841                         if (nc->rightctrl)
842                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
843                     } else if (MOD__ALT) {
844                         if (nc->leftalt && nc->rightalt)
845                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
846                         else {
847                             if (nc->leftalt)
848                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
849                             if (nc->rightalt)
850                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
851                         }
852                     } else {
853                         sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
854                     }
855                     ret = TRUE;
856                     break;
857                 case GDK_greater:
858                 case GDK_period:
859                     if (MOD__CTRL) {
860                         if (nc->leftctrl)
861                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
862                         if (nc->rightctrl)
863                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
864                     } else if (MOD__ALT) {
865                         if (nc->leftalt && nc->rightalt)
866                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
867                         else {
868                             if (nc->leftalt)
869                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
870                             if (nc->rightalt)
871                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
872                         }
873                     } else {
874                         sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
875                     }
876                     ret = TRUE;
877                     break;
879                 case GDK_Alt_L:
880                     nc->leftalt = TRUE;
881                     sp_node_context_show_modifier_tip(event_context, event);
882                     break;
883                 case GDK_Alt_R:
884                     nc->rightalt = TRUE;
885                     sp_node_context_show_modifier_tip(event_context, event);
886                     break;
887                 case GDK_Control_L:
888                     nc->leftctrl = TRUE;
889                     sp_node_context_show_modifier_tip(event_context, event);
890                     break;
891                 case GDK_Control_R:
892                     nc->rightctrl = TRUE;
893                     sp_node_context_show_modifier_tip(event_context, event);
894                     break;
895                 case GDK_Shift_L:
896                 case GDK_Shift_R:
897                 case GDK_Meta_L:
898                 case GDK_Meta_R:
899                     sp_node_context_show_modifier_tip(event_context, event);
900                     break;
901                 default:
902                     ret = node_key(event);
903                     break;
904             }
905             break;
906         case GDK_KEY_RELEASE:
907             switch (get_group0_keyval(&event->key)) {
908                 case GDK_Alt_L:
909                     nc->leftalt = FALSE;
910                     event_context->defaultMessageContext()->clear();
911                     break;
912                 case GDK_Alt_R:
913                     nc->rightalt = FALSE;
914                     event_context->defaultMessageContext()->clear();
915                     break;
916                 case GDK_Control_L:
917                     nc->leftctrl = FALSE;
918                     event_context->defaultMessageContext()->clear();
919                     break;
920                 case GDK_Control_R:
921                     nc->rightctrl = FALSE;
922                     event_context->defaultMessageContext()->clear();
923                     break;
924                 case GDK_Shift_L:
925                 case GDK_Shift_R:
926                 case GDK_Meta_L:
927                 case GDK_Meta_R:
928                     event_context->defaultMessageContext()->clear();
929                     break;
930             }
931             break;
932         default:
933             break;
934     }
936     if (!ret) {
937         if (((SPEventContextClass *) parent_class)->root_handler)
938             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
939     }
941     return ret;
945 /*
946   Local Variables:
947   mode:c++
948   c-file-style:"stroustrup"
949   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
950   indent-tabs-mode:nil
951   fill-column:99
952   End:
953 */
954 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :