Code

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