Code

a4a51f676a6636f55596882cfbb356ef803dd37c
[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;
108     
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         nc->grab_node = -1;
138         sp_nodepath_destroy(nc->nodepath);
139         nc->nodepath = NULL;
140     }
142     if (ec->shape_knot_holder) {
143         sp_knot_holder_destroy(ec->shape_knot_holder);
144         ec->shape_knot_holder = NULL;
145     }
147     if (nc->_node_message_context) {
148         delete nc->_node_message_context;
149     }
151     G_OBJECT_CLASS(parent_class)->dispose(object);
154 static void
155 sp_node_context_setup(SPEventContext *ec)
157     SPNodeContext *nc = SP_NODE_CONTEXT(ec);
159     if (((SPEventContextClass *) parent_class)->setup)
160         ((SPEventContextClass *) parent_class)->setup(ec);
162     nc->sel_changed_connection.disconnect();
163     nc->sel_changed_connection = sp_desktop_selection(ec->desktop)->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
165     Inkscape::Selection *selection = sp_desktop_selection(ec->desktop);
166     SPItem *item = selection->singleItem();
168     nc->grab_node = -1;
169     nc->nodepath = NULL;
170     ec->shape_knot_holder = NULL;
172     nc->rb_escaped = false;
174     nc->cursor_drag = false;
176     nc->added_node = false;
178     nc->current_state = SP_NODE_CONTEXT_INACTIVE;
180     if (item) {
181         nc->nodepath = sp_nodepath_new(ec->desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
182         if ( nc->nodepath) {
183             //point pack to parent in case nodepath is deleted
184             nc->nodepath->nodeContext = nc;
185         }
186         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
188         if (nc->nodepath || ec->shape_knot_holder) {
189             // setting listener
190             Inkscape::XML::Node *repr;
191             if (ec->shape_knot_holder)
192                 repr = ec->shape_knot_holder->repr;
193             else
194                 repr = SP_OBJECT_REPR(item);
195             if (repr) {
196                 Inkscape::GC::anchor(repr);
197                 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
198             }
199         }
200     }
202     if (prefs_get_int_attribute("tools.nodes", "selcue", 0) != 0) {
203         ec->enableSelectionCue();
204     }
206     if (prefs_get_int_attribute("tools.nodes", "gradientdrag", 0) != 0) {
207         ec->enableGrDrag();
208     }
210     nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
211     sp_nodepath_update_statusbar(nc->nodepath);
214 /**
215 \brief  Callback that processes the "changed" signal on the selection;
216 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
217 */
218 void
219 sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
221     SPNodeContext *nc = SP_NODE_CONTEXT(data);
222     SPEventContext *ec = SP_EVENT_CONTEXT(nc);
224     Inkscape::XML::Node *old_repr = NULL;
226     if (nc->nodepath) {
227         old_repr = nc->nodepath->repr;
228         nc->grab_node = -1;
229         sp_nodepath_destroy(nc->nodepath);
230         nc->nodepath = NULL;
231     }
233     if (ec->shape_knot_holder) {
234         old_repr = ec->shape_knot_holder->repr;
235         sp_knot_holder_destroy(ec->shape_knot_holder);
236     }
238     if (old_repr) { // remove old listener
239         sp_repr_remove_listener_by_data(old_repr, ec);
240         Inkscape::GC::release(old_repr);
241     }
243     SPItem *item = selection->singleItem();
245     SPDesktop *desktop = selection->desktop();
246     nc->grab_node = -1;
247     nc->nodepath = NULL;
248     ec->shape_knot_holder = NULL;
249     if (item) {
250         nc->nodepath = sp_nodepath_new(desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
251         if (nc->nodepath) {
252             nc->nodepath->nodeContext = nc;
253         }
254         ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
256         if (nc->nodepath || ec->shape_knot_holder) {
257             // setting new listener
258             Inkscape::XML::Node *repr;
259             if (ec->shape_knot_holder)
260                 repr = ec->shape_knot_holder->repr;
261             else
262                 repr = SP_OBJECT_REPR(item);
263             if (repr) {
264                 Inkscape::GC::anchor(repr);
265                 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
266             }
267         }
268     }
269     sp_nodepath_update_statusbar(nc->nodepath);
272 /**
273 \brief  Regenerates nodepath when the item's repr was change outside of node edit
274 (e.g. by undo, or xml editor, or edited in another view). The item is assumed to be the same
275 (otherwise sp_node_context_selection_changed() would have been called), so repr and listeners
276 are not changed.
277 */
278 void
279 sp_nodepath_update_from_item(SPNodeContext *nc, SPItem *item)
281     g_assert(nc);
282     SPEventContext *ec = ((SPEventContext *) nc);
284     SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(SP_EVENT_CONTEXT(nc));
285     g_assert(desktop);
287     if (nc->nodepath) {
288         nc->grab_node = -1;
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 = 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                 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
558                 desktop->updateNow();
559                 ret = TRUE;
560             }
561             break;
562         case GDK_MOTION_NOTIFY:
563             if (event->motion.state & GDK_BUTTON1_MASK) {
565                 if ( event_context->within_tolerance
566                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
567                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
568                     break; // do not drag if we're within tolerance from origin
569                 }
571                 // The path went away while dragging; throw away any further motion
572                 // events until the mouse pointer is released.
573                 if (nc->hit && (nc->nodepath == NULL)) {                  
574                   break;
575                 }
577                 // Once the user has moved farther than tolerance from the original location
578                 // (indicating they intend to move the object, not click), then always process the
579                 // motion notify coordinates as given (no snapping back to origin)
580                 event_context->within_tolerance = false;
582                 // Once we determine what the user is doing (dragging either a node or the
583                 // selection rubberband), make sure we continue to perform that operation
584                 // until the mouse pointer is lifted.
585                 if (nc->current_state == SP_NODE_CONTEXT_INACTIVE) {
586                     if (nc->nodepath && nc->hit) {
587                         nc->current_state = SP_NODE_CONTEXT_NODE_DRAGGING;
588                     } else {
589                         nc->current_state = SP_NODE_CONTEXT_RUBBERBAND_DRAGGING;
590                     }
591                 }
593                 switch (nc->current_state) {
594                     case SP_NODE_CONTEXT_NODE_DRAGGING:
595                         {
596                             if (nc->grab_node == -1) // don't know which segment to drag
597                                 break;
599                             // We round off the extra precision in the motion coordinates provided
600                             // by some input devices (like tablets). As we'll store the coordinates
601                             // as integers in curvepoint_event we need to do this rounding before
602                             // comparing them with the last coordinates from curvepoint_event.
603                             // See bug #1593499 for details.
605                             gint x = (gint) Inkscape::round(event->motion.x);
606                             gint y = (gint) Inkscape::round(event->motion.y);
608                             // The coordinates hasn't changed since the last motion event, abort
609                             if (nc->curvepoint_event[NR::X] == x &&
610                                 nc->curvepoint_event[NR::Y] == y)
611                                 break;
613                             NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
614                                                     event->motion.y - nc->curvepoint_event[NR::Y]);
615                             NR::Point const delta_dt(desktop->w2d(delta_w));
617                             sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
618                             nc->curvepoint_event[NR::X] = x;
619                             nc->curvepoint_event[NR::Y] = y;
620                             gobble_motion_events(GDK_BUTTON1_MASK);
621                             break;
622                         }
623                     case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING:
624                         if (Inkscape::Rubberband::get()->is_started()) {
625                             NR::Point const motion_w(event->motion.x,
626                                                 event->motion.y);
627                             NR::Point const motion_dt(desktop->w2d(motion_w));
628                             Inkscape::Rubberband::get()->move(motion_dt);
629                         }
630                         break;
631                 }
633                 nc->drag = TRUE;
634                 ret = TRUE;
635             } else {
636                 if (!nc->nodepath || selection->singleItem() == NULL) {
637                     break;
638                 }
640                 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
641                                                                 NR::Point(event->motion.x, event->motion.y));
642                 bool over_stroke = false;
643                 if (item_over && nc->nodepath) {
644                     over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
645                 }
647                 if (nc->cursor_drag && !over_stroke) {
648                     event_context->cursor_shape = cursor_node_xpm;
649                     event_context->hot_x = 1;
650                     event_context->hot_y = 1;
651                     sp_event_context_update_cursor(event_context);
652                     nc->cursor_drag = false;
653                 } else if (!nc->cursor_drag && over_stroke) {
654                     event_context->cursor_shape = cursor_node_d_xpm;
655                     event_context->hot_x = 1;
656                     event_context->hot_y = 1;
657                     sp_event_context_update_cursor(event_context);
658                     nc->cursor_drag = true;
659                 }
660             }
661             break;
662         case GDK_BUTTON_RELEASE:
663             event_context->xp = event_context->yp = 0;
664             if (event->button.button == 1) {
666                 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
668                 if (nc->hit && !event_context->within_tolerance) { //drag curve
669                     if (nc->nodepath) {
670                         sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
671                     }
672                 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
673                     if (nc->nodepath) {
674                         sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
675                     }
676                 } else {
677                     if (!(nc->rb_escaped)) { // unless something was cancelled
678                         if (nc->nodepath && nc->nodepath->selected)
679                             sp_nodepath_deselect(nc->nodepath);
680                         else
681                             sp_desktop_selection(desktop)->clear();
682                     }
683                 }
684                 ret = TRUE;
685                 Inkscape::Rubberband::get()->stop();
686                 desktop->updateNow();
687                 nc->rb_escaped = false;
688                 nc->drag = FALSE;
689                 nc->hit = false;
690                 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
691                 break;
692             }
693             break;
694         case GDK_KEY_PRESS:
695             switch (get_group0_keyval(&event->key)) {
696                 case GDK_Insert:
697                 case GDK_KP_Insert:
698                     // with any modifiers
699                     sp_node_selected_add_node();
700                     ret = TRUE;
701                     break;
702                 case GDK_Delete:
703                 case GDK_KP_Delete:
704                 case GDK_BackSpace:
705                     if (MOD__CTRL_ONLY) {
706                         sp_node_selected_delete();
707                     } else {
708                         if (nc->nodepath && nc->nodepath->selected) {
709                             sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
710                         }
711                     }
712                     ret = TRUE;
713                     break;
714                 case GDK_C:
715                 case GDK_c:
716                     if (MOD__SHIFT_ONLY) {
717                         sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
718                         ret = TRUE;
719                     }
720                     break;
721                 case GDK_S:
722                 case GDK_s:
723                     if (MOD__SHIFT_ONLY) {
724                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
725                         ret = TRUE;
726                     }
727                     break;
728                 case GDK_Y:
729                 case GDK_y:
730                     if (MOD__SHIFT_ONLY) {
731                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
732                         ret = TRUE;
733                     }
734                     break;
735                 case GDK_B:
736                 case GDK_b:
737                     if (MOD__SHIFT_ONLY) {
738                         sp_node_selected_break();
739                         ret = TRUE;
740                     }
741                     break;
742                 case GDK_J:
743                 case GDK_j:
744                     if (MOD__SHIFT_ONLY) {
745                         sp_node_selected_join();
746                         ret = TRUE;
747                     }
748                     break;
749                 case GDK_D:
750                 case GDK_d:
751                     if (MOD__SHIFT_ONLY) {
752                         sp_node_selected_duplicate();
753                         ret = TRUE;
754                     }
755                     break;
756                 case GDK_L:
757                 case GDK_l:
758                     if (MOD__SHIFT_ONLY) {
759                         sp_node_selected_set_line_type(NR_LINETO);
760                         ret = TRUE;
761                     }
762                     break;
763                 case GDK_U:
764                 case GDK_u:
765                     if (MOD__SHIFT_ONLY) {
766                         sp_node_selected_set_line_type(NR_CURVETO);
767                         ret = TRUE;
768                     }
769                     break;
770                 case GDK_R:
771                 case GDK_r:
772                     if (MOD__SHIFT_ONLY) {
773                         // FIXME: add top panel button
774                         sp_selected_path_reverse();
775                         ret = TRUE;
776                     }
777                     break;
778                 case GDK_Left: // move selection left
779                 case GDK_KP_Left:
780                 case GDK_KP_4:
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_Up: // move selection up
794                 case GDK_KP_Up:
795                 case GDK_KP_8:
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_Right: // move selection right
809                 case GDK_KP_Right:
810                 case GDK_KP_6:
811                     if (!MOD__CTRL) { // not ctrl
812                         if (MOD__ALT) { // alt
813                             if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
814                             else sp_node_selected_move_screen(1, 0); // no shift
815                         }
816                         else { // no alt
817                             if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
818                             else sp_node_selected_move(nudge, 0); // no shift
819                         }
820                         ret = TRUE;
821                     }
822                     break;
823                 case GDK_Down: // move selection down
824                 case GDK_KP_Down:
825                 case GDK_KP_2:
826                     if (!MOD__CTRL) { // not ctrl
827                         if (MOD__ALT) { // alt
828                             if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
829                             else sp_node_selected_move_screen(0, -1); // no shift
830                         }
831                         else { // no alt
832                             if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
833                             else sp_node_selected_move(0, -nudge); // no shift
834                         }
835                         ret = TRUE;
836                     }
837                     break;
838                 case GDK_Escape:
839                 {
840                     NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
841                     if (b != NR::Nothing()) {
842                         Inkscape::Rubberband::get()->stop();
843                         nc->current_state = SP_NODE_CONTEXT_INACTIVE;
844                         nc->rb_escaped = true;
845                     } else {
846                         if (nc->nodepath && nc->nodepath->selected) {
847                             sp_nodepath_deselect(nc->nodepath);
848                         } else {
849                             sp_desktop_selection(desktop)->clear();
850                         }
851                     }
852                     ret = TRUE;
853                     break;
854                 }
856                 case GDK_bracketleft:
857                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
858                         if (nc->leftctrl)
859                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
860                         if (nc->rightctrl)
861                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
862                     } else if ( MOD__ALT && !MOD__CTRL ) {
863                         if (nc->leftalt && nc->rightalt)
864                             sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
865                         else {
866                             if (nc->leftalt)
867                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
868                             if (nc->rightalt)
869                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
870                         }
871                     } else if ( snaps != 0 ) {
872                         sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
873                     }
874                     ret = TRUE;
875                     break;
876                 case GDK_bracketright:
877                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
878                         if (nc->leftctrl)
879                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
880                         if (nc->rightctrl)
881                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
882                     } else if ( MOD__ALT && !MOD__CTRL ) {
883                         if (nc->leftalt && nc->rightalt)
884                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
885                         else {
886                             if (nc->leftalt)
887                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
888                             if (nc->rightalt)
889                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
890                         }
891                     } else if ( snaps != 0 ) {
892                         sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
893                     }
894                     ret = TRUE;
895                     break;
896                 case GDK_less:
897                 case GDK_comma:
898                     if (MOD__CTRL) {
899                         if (nc->leftctrl)
900                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
901                         if (nc->rightctrl)
902                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
903                     } else if (MOD__ALT) {
904                         if (nc->leftalt && nc->rightalt)
905                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
906                         else {
907                             if (nc->leftalt)
908                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
909                             if (nc->rightalt)
910                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
911                         }
912                     } else {
913                         sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
914                     }
915                     ret = TRUE;
916                     break;
917                 case GDK_greater:
918                 case GDK_period:
919                     if (MOD__CTRL) {
920                         if (nc->leftctrl)
921                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
922                         if (nc->rightctrl)
923                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
924                     } else if (MOD__ALT) {
925                         if (nc->leftalt && nc->rightalt)
926                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
927                         else {
928                             if (nc->leftalt)
929                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
930                             if (nc->rightalt)
931                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
932                         }
933                     } else {
934                         sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
935                     }
936                     ret = TRUE;
937                     break;
939                 case GDK_Alt_L:
940                     nc->leftalt = TRUE;
941                     sp_node_context_show_modifier_tip(event_context, event);
942                     break;
943                 case GDK_Alt_R:
944                     nc->rightalt = TRUE;
945                     sp_node_context_show_modifier_tip(event_context, event);
946                     break;
947                 case GDK_Control_L:
948                     nc->leftctrl = TRUE;
949                     sp_node_context_show_modifier_tip(event_context, event);
950                     break;
951                 case GDK_Control_R:
952                     nc->rightctrl = TRUE;
953                     sp_node_context_show_modifier_tip(event_context, event);
954                     break;
955                 case GDK_Shift_L:
956                 case GDK_Shift_R:
957                 case GDK_Meta_L:
958                 case GDK_Meta_R:
959                     sp_node_context_show_modifier_tip(event_context, event);
960                     break;
961                 default:
962                     ret = node_key(event);
963                     break;
964             }
965             break;
966         case GDK_KEY_RELEASE:
967             switch (get_group0_keyval(&event->key)) {
968                 case GDK_Alt_L:
969                     nc->leftalt = FALSE;
970                     event_context->defaultMessageContext()->clear();
971                     break;
972                 case GDK_Alt_R:
973                     nc->rightalt = FALSE;
974                     event_context->defaultMessageContext()->clear();
975                     break;
976                 case GDK_Control_L:
977                     nc->leftctrl = FALSE;
978                     event_context->defaultMessageContext()->clear();
979                     break;
980                 case GDK_Control_R:
981                     nc->rightctrl = FALSE;
982                     event_context->defaultMessageContext()->clear();
983                     break;
984                 case GDK_Shift_L:
985                 case GDK_Shift_R:
986                 case GDK_Meta_L:
987                 case GDK_Meta_R:
988                     event_context->defaultMessageContext()->clear();
989                     break;
990             }
991             break;
992         default:
993             break;
994     }
996     if (!ret) {
997         if (((SPEventContextClass *) parent_class)->root_handler)
998             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
999     }
1001     return ret;
1005 /*
1006   Local Variables:
1007   mode:c++
1008   c-file-style:"stroustrup"
1009   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1010   indent-tabs-mode:nil
1011   fill-column:99
1012   End:
1013 */
1014 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :