Code

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