Code

First dependency on ziptool
[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     char const *newd = NULL, *newtypestr = NULL;
314     gboolean changed = FALSE;
316     g_assert(data);
317     SPNodeContext *nc = ((SPNodeContext *) data);
318     SPEventContext *ec = ((SPEventContext *) data);
319     g_assert(nc);
320     Inkscape::NodePath::Path *np = nc->nodepath;
321     SPKnotHolder *kh = ec->shape_knot_holder;
323     if (np) {
324         item = SP_ITEM(np->path);
325         if (!strcmp(name, "d")) {
326             newd = new_value;
327             changed = nodepath_repr_d_changed(np, new_value);
328         } else if (!strcmp(name, "sodipodi:nodetypes")) {
329             newtypestr = new_value;
330             changed = nodepath_repr_typestr_changed(np, new_value);
331         } else {
332             return;
333             // With paths, we only need to act if one of the path-affecting attributes has changed.
334         }
335     } else if (kh) {
336         item = SP_ITEM(kh->item);
337         changed = !(kh->local_change);
338         kh->local_change = FALSE;
339     }
340     if (np && changed) {
341         GList *saved = NULL;
342         SPDesktop *desktop = np->desktop;
343         g_assert(desktop);
344         Inkscape::Selection *selection = desktop->selection;
345         g_assert(selection);
347         saved = save_nodepath_selection(nc->nodepath);
348         sp_nodepath_update_from_item(nc, item);
349         if (nc->nodepath && saved) restore_nodepath_selection(nc->nodepath, saved);
351     } else if (kh && changed) {
352         sp_nodepath_update_from_item(nc, item);
353     }
355     sp_nodepath_update_statusbar(nc->nodepath);
358 void
359 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
361     sp_event_show_modifier_tip
362         (event_context->defaultMessageContext(), event,
363          _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
364          _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
365          _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
368 bool
369 sp_node_context_is_over_stroke (SPNodeContext *nc, SPItem *item, NR::Point event_p, bool remember)
371     SPDesktop *desktop = SP_EVENT_CONTEXT (nc)->desktop;
373     //Translate click point into proper coord system
374     nc->curvepoint_doc = desktop->w2d(event_p);
375     nc->curvepoint_doc *= sp_item_dt2i_affine(item);
376     nc->curvepoint_doc *= sp_item_i2doc_affine(item);
378     NR::Maybe<Path::cut_position> position = get_nearest_position_on_Path(nc->nodepath->livarot_path, nc->curvepoint_doc);
379     NR::Point nearest = get_point_on_Path(nc->nodepath->livarot_path, position.assume().piece, position.assume().t);
380     NR::Point delta = nearest - nc->curvepoint_doc;
382     delta = desktop->d2w(delta);
384     double stroke_tolerance =
385         (SP_OBJECT_STYLE (item)->stroke.type != SP_PAINT_TYPE_NONE?
386          desktop->current_zoom() *
387          SP_OBJECT_STYLE (item)->stroke_width.computed *
388          sp_item_i2d_affine (item).expansion() * 0.5
389          : 0.0)
390         + (double) SP_EVENT_CONTEXT(nc)->tolerance;
392     bool close = (NR::L2 (delta) < stroke_tolerance);
394     if (remember && close) {
395         nc->curvepoint_event[NR::X] = (gint) event_p [NR::X];
396         nc->curvepoint_event[NR::Y] = (gint) event_p [NR::Y];
397         nc->hit = true;
398         nc->grab_t = position.assume().t;
399         nc->grab_node = sp_nodepath_get_node_by_index(position.assume().piece);
400     }
402     return close;
406 static gint
407 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
409     gint ret = FALSE;
411     SPDesktop *desktop = event_context->desktop;
412     Inkscape::Selection *selection = SP_DT_SELECTION (desktop);
414     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
416     switch (event->type) {
417         case GDK_2BUTTON_PRESS:
418         case GDK_BUTTON_RELEASE:
419             if (event->button.button == 1) {
420                 if (!nc->drag) {
422                     // find out clicked item, disregarding groups, honoring Alt
423                     SPItem *item_clicked = sp_event_context_find_item (desktop,
424                             NR::Point(event->button.x, event->button.y),
425                             (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
426                     // find out if we're over the selected item, disregarding groups
427                     SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
428                                                                     NR::Point(event->button.x, event->button.y));
430                     bool over_stroke = false;
431                     if (item_over && nc->nodepath) {
432                         over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), false);
433                     }
435                     if (over_stroke || nc->added_node) {
436                         switch (event->type) {
437                             case GDK_BUTTON_RELEASE:
438                                 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
439                                     //add a node
440                                     sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
441                                 } else {
442                                     if (nc->added_node) { // we just received double click, ignore release
443                                         nc->added_node = false;
444                                         break;
445                                     }
446                                     //select the segment
447                                     if (event->button.state & GDK_SHIFT_MASK) {
448                                         sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, true);
449                                     } else {
450                                         sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
451                                     }
452                                 }
453                                 break;
454                             case GDK_2BUTTON_PRESS:
455                                 //add a node
456                                 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
457                                 nc->added_node = true;
458                                 break;
459                             default:
460                                 break;
461                         }
462                     } else if (event->button.state & GDK_SHIFT_MASK) {
463                         selection->toggle(item_clicked);
464                     } else {
465                         selection->set(item_clicked);
466                     }
468                     ret = TRUE;
469                 }
470                 break;
471             }
472             break;
473         case GDK_BUTTON_PRESS:
474             if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK)) {
475                 // save drag origin
476                 event_context->xp = (gint) event->button.x;
477                 event_context->yp = (gint) event->button.y;
478                 event_context->within_tolerance = true;
479                 nc->hit = false;
481                 if (!nc->drag) {
482                     // find out if we're over the selected item, disregarding groups
483                     SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
484                                                                     NR::Point(event->button.x, event->button.y));
486                         if (nc->nodepath && selection->single() && item_over) {
488                             // save drag origin
489                             bool over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), true);
490                             //only dragging curves
491                             if (over_stroke) {
492                                 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
493                                 ret = TRUE;
494                             } else {
495                                 break;
496                             }
497                         } else {
498                             break;
499                         }
501                     ret = TRUE;
502                 }
503                 break;
504             }
505             break;
506         default:
507             break;
508     }
510     if (!ret) {
511         if (((SPEventContextClass *) parent_class)->item_handler)
512             ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
513     }
515     return ret;
518 static gint
519 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
521     SPDesktop *desktop = event_context->desktop;
522     Inkscape::Selection *selection = SP_DT_SELECTION (desktop);
524     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
525     double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
526     event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
527     int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
528     double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
530     gint ret = FALSE;
532     switch (event->type) {
533         case GDK_BUTTON_PRESS:
534             if (event->button.button == 1) {
535                 // save drag origin
536                 event_context->xp = (gint) event->button.x;
537                 event_context->yp = (gint) event->button.y;
538                 event_context->within_tolerance = true;
539                 nc->hit = false;
541                 NR::Point const button_w(event->button.x,
542                                          event->button.y);
543                 NR::Point const button_dt(desktop->w2d(button_w));
544                 Inkscape::Rubberband::get()->start(desktop, button_dt);
545                 ret = TRUE;
546             }
547             break;
548         case GDK_MOTION_NOTIFY:
549             if (event->motion.state & GDK_BUTTON1_MASK) {
551                 if ( event_context->within_tolerance
552                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
553                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
554                     break; // do not drag if we're within tolerance from origin
555                 }
556                 // Once the user has moved farther than tolerance from the original location
557                 // (indicating they intend to move the object, not click), then always process the
558                 // motion notify coordinates as given (no snapping back to origin)
559                 event_context->within_tolerance = false;
561                 if (nc->nodepath && nc->hit) {
562                     NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
563                                          event->motion.y - nc->curvepoint_event[NR::Y]);
564                     NR::Point const delta_dt(desktop->w2d(delta_w));
565                     sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
566                     nc->curvepoint_event[NR::X] = (gint) event->motion.x;
567                     nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
568                     gobble_motion_events(GDK_BUTTON1_MASK);
569                 } else {
570                     NR::Point const motion_w(event->motion.x,
571                                          event->motion.y);
572                     NR::Point const motion_dt(desktop->w2d(motion_w));
573                     Inkscape::Rubberband::get()->move(motion_dt);
574                 }
575                 nc->drag = TRUE;
576                 ret = TRUE;
577             } else {
578                 if (!nc->nodepath || selection->singleItem() == NULL) {
579                     break;
580                 }
582                 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
583                                                                 NR::Point(event->motion.x, event->motion.y));
584                 bool over_stroke = false;
585                 if (item_over && nc->nodepath) {
586                     over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
587                 }
589                 if (nc->cursor_drag && !over_stroke) {
590                     event_context->cursor_shape = cursor_node_xpm;
591                     event_context->hot_x = 1;
592                     event_context->hot_y = 1;
593                     sp_event_context_update_cursor(event_context);
594                     nc->cursor_drag = false;
595                 } else if (!nc->cursor_drag && over_stroke) {
596                     event_context->cursor_shape = cursor_node_d_xpm;
597                     event_context->hot_x = 1;
598                     event_context->hot_y = 1;
599                     sp_event_context_update_cursor(event_context);
600                     nc->cursor_drag = true;
601                 }
602             }
603             break;
604         case GDK_BUTTON_RELEASE:
605             event_context->xp = event_context->yp = 0;
606             if (event->button.button == 1) {
608                 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
610                 if (nc->hit && !event_context->within_tolerance) { //drag curve
611                     sp_nodepath_update_repr (nc->nodepath);
612                 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
613                     if (nc->nodepath) {
614                         sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
615                     }
616                 } else {
617                     if (!(nc->rb_escaped)) { // unless something was cancelled
618                         if (nc->nodepath && nc->nodepath->selected)
619                             sp_nodepath_deselect(nc->nodepath);
620                         else
621                             SP_DT_SELECTION(desktop)->clear();
622                     }
623                 }
624                 ret = TRUE;
625                 Inkscape::Rubberband::get()->stop();
626                 nc->rb_escaped = false;
627                 nc->drag = FALSE;
628                 nc->hit = false;
629                 break;
630             }
631             break;
632         case GDK_KEY_PRESS:
633             switch (get_group0_keyval(&event->key)) {
634                 case GDK_Insert:
635                 case GDK_KP_Insert:
636                     // with any modifiers
637                     sp_node_selected_add_node();
638                     ret = TRUE;
639                     break;
640                 case GDK_Delete:
641                 case GDK_KP_Delete:
642                 case GDK_BackSpace:
643                     // with any modifiers
644                     sp_node_selected_delete();
645                     ret = TRUE;
646                     break;
647                 case GDK_C:
648                 case GDK_c:
649                     if (MOD__SHIFT_ONLY) {
650                         sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
651                         ret = TRUE;
652                     }
653                     break;
654                 case GDK_S:
655                 case GDK_s:
656                     if (MOD__SHIFT_ONLY) {
657                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
658                         ret = TRUE;
659                     }
660                     break;
661                 case GDK_Y:
662                 case GDK_y:
663                     if (MOD__SHIFT_ONLY) {
664                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
665                         ret = TRUE;
666                     }
667                     break;
668                 case GDK_B:
669                 case GDK_b:
670                     if (MOD__SHIFT_ONLY) {
671                         sp_node_selected_break();
672                         ret = TRUE;
673                     }
674                     break;
675                 case GDK_J:
676                 case GDK_j:
677                     if (MOD__SHIFT_ONLY) {
678                         sp_node_selected_join();
679                         ret = TRUE;
680                     }
681                     break;
682                 case GDK_D:
683                 case GDK_d:
684                     if (MOD__SHIFT_ONLY) {
685                         sp_node_selected_duplicate();
686                         ret = TRUE;
687                     }
688                     break;
689                 case GDK_L:
690                 case GDK_l:
691                     if (MOD__SHIFT_ONLY) {
692                         sp_node_selected_set_line_type(NR_LINETO);
693                         ret = TRUE;
694                     }
695                     break;
696                 case GDK_U:
697                 case GDK_u:
698                     if (MOD__SHIFT_ONLY) {
699                         sp_node_selected_set_line_type(NR_CURVETO);
700                         ret = TRUE;
701                     }
702                     break;
703                 case GDK_R:
704                 case GDK_r:
705                     if (MOD__SHIFT_ONLY) {
706                         // FIXME: add top panel button
707                         sp_selected_path_reverse();
708                         ret = TRUE;
709                     }
710                     break;
711                 case GDK_Left: // move selection left
712                 case GDK_KP_Left:
713                 case GDK_KP_4:
714                     if (!MOD__CTRL) { // not ctrl
715                         if (MOD__ALT) { // alt
716                             if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
717                             else sp_node_selected_move_screen(-1, 0); // no shift
718                         }
719                         else { // no alt
720                             if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
721                             else sp_node_selected_move(-nudge, 0); // no shift
722                         }
723                         ret = TRUE;
724                     }
725                     break;
726                 case GDK_Up: // move selection up
727                 case GDK_KP_Up:
728                 case GDK_KP_8:
729                     if (!MOD__CTRL) { // not ctrl
730                         if (MOD__ALT) { // alt
731                             if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
732                             else sp_node_selected_move_screen(0, 1); // no shift
733                         }
734                         else { // no alt
735                             if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
736                             else sp_node_selected_move(0, nudge); // no shift
737                         }
738                         ret = TRUE;
739                     }
740                     break;
741                 case GDK_Right: // move selection right
742                 case GDK_KP_Right:
743                 case GDK_KP_6:
744                     if (!MOD__CTRL) { // not ctrl
745                         if (MOD__ALT) { // alt
746                             if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
747                             else sp_node_selected_move_screen(1, 0); // no shift
748                         }
749                         else { // no alt
750                             if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
751                             else sp_node_selected_move(nudge, 0); // no shift
752                         }
753                         ret = TRUE;
754                     }
755                     break;
756                 case GDK_Down: // move selection down
757                 case GDK_KP_Down:
758                 case GDK_KP_2:
759                     if (!MOD__CTRL) { // not ctrl
760                         if (MOD__ALT) { // alt
761                             if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
762                             else sp_node_selected_move_screen(0, -1); // no shift
763                         }
764                         else { // no alt
765                             if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
766                             else sp_node_selected_move(0, -nudge); // no shift
767                         }
768                         ret = TRUE;
769                     }
770                     break;
771                 case GDK_Tab: // Tab - cycle selection forward
772                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
773                         sp_nodepath_select_next(nc->nodepath);
774                         ret = TRUE;
775                     }
776                     break;
777                 case GDK_ISO_Left_Tab:  // Shift Tab - cycle selection backward
778                     if (!(MOD__CTRL_ONLY || (MOD__CTRL && MOD__SHIFT))) {
779                         sp_nodepath_select_prev(nc->nodepath);
780                         ret = TRUE;
781                     }
782                     break;
783                 case GDK_Escape:
784                 {
785                     NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
786                     if (b != NR::Nothing()) {
787                         Inkscape::Rubberband::get()->stop();
788                         nc->rb_escaped = true;
789                     } else {
790                         if (nc->nodepath && nc->nodepath->selected) {
791                             sp_nodepath_deselect(nc->nodepath);
792                         } else {
793                             SP_DT_SELECTION(desktop)->clear();
794                         }
795                     }
796                     ret = TRUE;
797                     break;
798                 }
800                 case GDK_bracketleft:
801                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
802                         if (nc->leftctrl)
803                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
804                         if (nc->rightctrl)
805                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
806                     } else if ( MOD__ALT && !MOD__CTRL ) {
807                         if (nc->leftalt && nc->rightalt)
808                             sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
809                         else {
810                             if (nc->leftalt)
811                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
812                             if (nc->rightalt)
813                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
814                         }
815                     } else if ( snaps != 0 ) {
816                         sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
817                     }
818                     ret = TRUE;
819                     break;
820                 case GDK_bracketright:
821                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
822                         if (nc->leftctrl)
823                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
824                         if (nc->rightctrl)
825                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
826                     } else if ( MOD__ALT && !MOD__CTRL ) {
827                         if (nc->leftalt && nc->rightalt)
828                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
829                         else {
830                             if (nc->leftalt)
831                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
832                             if (nc->rightalt)
833                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
834                         }
835                     } else if ( snaps != 0 ) {
836                         sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
837                     }
838                     ret = TRUE;
839                     break;
840                 case GDK_less:
841                 case GDK_comma:
842                     if (MOD__CTRL) {
843                         if (nc->leftctrl)
844                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
845                         if (nc->rightctrl)
846                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
847                     } else if (MOD__ALT) {
848                         if (nc->leftalt && nc->rightalt)
849                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
850                         else {
851                             if (nc->leftalt)
852                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
853                             if (nc->rightalt)
854                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
855                         }
856                     } else {
857                         sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
858                     }
859                     ret = TRUE;
860                     break;
861                 case GDK_greater:
862                 case GDK_period:
863                     if (MOD__CTRL) {
864                         if (nc->leftctrl)
865                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
866                         if (nc->rightctrl)
867                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
868                     } else if (MOD__ALT) {
869                         if (nc->leftalt && nc->rightalt)
870                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
871                         else {
872                             if (nc->leftalt)
873                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
874                             if (nc->rightalt)
875                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
876                         }
877                     } else {
878                         sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
879                     }
880                     ret = TRUE;
881                     break;
883                 case GDK_Alt_L:
884                     nc->leftalt = TRUE;
885                     sp_node_context_show_modifier_tip(event_context, event);
886                     break;
887                 case GDK_Alt_R:
888                     nc->rightalt = TRUE;
889                     sp_node_context_show_modifier_tip(event_context, event);
890                     break;
891                 case GDK_Control_L:
892                     nc->leftctrl = TRUE;
893                     sp_node_context_show_modifier_tip(event_context, event);
894                     break;
895                 case GDK_Control_R:
896                     nc->rightctrl = TRUE;
897                     sp_node_context_show_modifier_tip(event_context, event);
898                     break;
899                 case GDK_Shift_L:
900                 case GDK_Shift_R:
901                 case GDK_Meta_L:
902                 case GDK_Meta_R:
903                     sp_node_context_show_modifier_tip(event_context, event);
904                     break;
905                 default:
906                     ret = node_key(event);
907                     break;
908             }
909             break;
910         case GDK_KEY_RELEASE:
911             switch (get_group0_keyval(&event->key)) {
912                 case GDK_Alt_L:
913                     nc->leftalt = FALSE;
914                     event_context->defaultMessageContext()->clear();
915                     break;
916                 case GDK_Alt_R:
917                     nc->rightalt = FALSE;
918                     event_context->defaultMessageContext()->clear();
919                     break;
920                 case GDK_Control_L:
921                     nc->leftctrl = FALSE;
922                     event_context->defaultMessageContext()->clear();
923                     break;
924                 case GDK_Control_R:
925                     nc->rightctrl = FALSE;
926                     event_context->defaultMessageContext()->clear();
927                     break;
928                 case GDK_Shift_L:
929                 case GDK_Shift_R:
930                 case GDK_Meta_L:
931                 case GDK_Meta_R:
932                     event_context->defaultMessageContext()->clear();
933                     break;
934             }
935             break;
936         default:
937             break;
938     }
940     if (!ret) {
941         if (((SPEventContextClass *) parent_class)->root_handler)
942             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
943     }
945     return ret;
949 /*
950   Local Variables:
951   mode:c++
952   c-file-style:"stroustrup"
953   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
954   indent-tabs-mode:nil
955   fill-column:99
956   End:
957 */
958 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :