Code

implement select next/prev as verbs
[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         sp_nodepath_destroy(nc->nodepath);
138         nc->nodepath = NULL;
139     }
141     if (ec->shape_knot_holder) {
142         sp_knot_holder_destroy(ec->shape_knot_holder);
143         ec->shape_knot_holder = NULL;
144     }
146     if (nc->_node_message_context) {
147         delete nc->_node_message_context;
148     }
150     G_OBJECT_CLASS(parent_class)->dispose(object);
153 static void
154 sp_node_context_setup(SPEventContext *ec)
156     SPNodeContext *nc = SP_NODE_CONTEXT(ec);
158     if (((SPEventContextClass *) parent_class)->setup)
159         ((SPEventContextClass *) parent_class)->setup(ec);
161     nc->sel_changed_connection.disconnect();
162     nc->sel_changed_connection = sp_desktop_selection(ec->desktop)->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
164     Inkscape::Selection *selection = sp_desktop_selection(ec->desktop);
165     SPItem *item = selection->singleItem();
167     nc->nodepath = NULL;
168     ec->shape_knot_holder = NULL;
170     nc->rb_escaped = false;
172     nc->cursor_drag = false;
174     nc->added_node = false;
176     nc->current_state = SP_NODE_CONTEXT_INACTIVE;
178     if (item) {
179         nc->nodepath = sp_nodepath_new(ec->desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
180         if ( nc->nodepath) {
181             //point pack to parent in case nodepath is deleted
182             nc->nodepath->nodeContext = nc;
183         }
184         ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
186         if (nc->nodepath || ec->shape_knot_holder) {
187             // setting listener
188             Inkscape::XML::Node *repr;
189             if (ec->shape_knot_holder)
190                 repr = ec->shape_knot_holder->repr;
191             else
192                 repr = SP_OBJECT_REPR(item);
193             if (repr) {
194                 Inkscape::GC::anchor(repr);
195                 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
196             }
197         }
198     }
200     if (prefs_get_int_attribute("tools.nodes", "selcue", 0) != 0) {
201         ec->enableSelectionCue();
202     }
204     if (prefs_get_int_attribute("tools.nodes", "gradientdrag", 0) != 0) {
205         ec->enableGrDrag();
206     }
208     nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
209     sp_nodepath_update_statusbar(nc->nodepath);
212 /**
213 \brief  Callback that processes the "changed" signal on the selection;
214 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
215 */
216 void
217 sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
219     SPNodeContext *nc = SP_NODE_CONTEXT(data);
220     SPEventContext *ec = SP_EVENT_CONTEXT(nc);
222     Inkscape::XML::Node *old_repr = NULL;
224     if (nc->nodepath) {
225         old_repr = nc->nodepath->repr;
226         sp_nodepath_destroy(nc->nodepath);
227         nc->nodepath = NULL;
228     }
230     if (ec->shape_knot_holder) {
231         old_repr = ec->shape_knot_holder->repr;
232         sp_knot_holder_destroy(ec->shape_knot_holder);
233     }
235     if (old_repr) { // remove old listener
236         sp_repr_remove_listener_by_data(old_repr, ec);
237         Inkscape::GC::release(old_repr);
238     }
240     SPItem *item = selection->singleItem();
242     SPDesktop *desktop = selection->desktop();
243     nc->nodepath = NULL;
244     ec->shape_knot_holder = NULL;
245     if (item) {
246         nc->nodepath = sp_nodepath_new(desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
247         if (nc->nodepath) {
248             nc->nodepath->nodeContext = nc;
249         }
250         ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
252         if (nc->nodepath || ec->shape_knot_holder) {
253             // setting new listener
254             Inkscape::XML::Node *repr;
255             if (ec->shape_knot_holder)
256                 repr = ec->shape_knot_holder->repr;
257             else
258                 repr = SP_OBJECT_REPR(item);
259             if (repr) {
260                 Inkscape::GC::anchor(repr);
261                 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
262             }
263         }
264     }
265     sp_nodepath_update_statusbar(nc->nodepath);
268 /**
269 \brief  Regenerates nodepath when the item's repr was change outside of node edit
270 (e.g. by undo, or xml editor, or edited in another view). The item is assumed to be the same
271 (otherwise sp_node_context_selection_changed() would have been called), so repr and listeners
272 are not changed.
273 */
274 void
275 sp_nodepath_update_from_item(SPNodeContext *nc, SPItem *item)
277     g_assert(nc);
278     SPEventContext *ec = ((SPEventContext *) nc);
280     SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(SP_EVENT_CONTEXT(nc));
281     g_assert(desktop);
283     if (nc->nodepath) {
284         sp_nodepath_destroy(nc->nodepath);
285         nc->nodepath = NULL;
286     }
288     if (ec->shape_knot_holder) {
289         sp_knot_holder_destroy(ec->shape_knot_holder);
290         ec->shape_knot_holder = NULL;
291     }
293     Inkscape::Selection *selection = sp_desktop_selection(desktop);
294     item = selection->singleItem();
296     if (item) {
297         nc->nodepath = sp_nodepath_new(desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
298         if (nc->nodepath) {
299             nc->nodepath->nodeContext = nc;
300         }
301         ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
302     }
303     sp_nodepath_update_statusbar(nc->nodepath);
306 /**
307 \brief  Callback that is fired whenever an attribute of the selected item (which we have in the nodepath) changes
308 */
309 static void
310 nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
311                             gchar const *old_value, gchar const *new_value,
312                             bool is_interactive, gpointer data)
314     SPItem *item = NULL;
315     gboolean changed = FALSE;
317     g_assert(data);
318     SPNodeContext *nc = ((SPNodeContext *) data);
319     SPEventContext *ec = ((SPEventContext *) data);
320     g_assert(nc);
321     Inkscape::NodePath::Path *np = nc->nodepath;
322     SPKnotHolder *kh = ec->shape_knot_holder;
324     if (np) {
325         item = SP_ITEM(np->path);
326         if (!strcmp(name, "d") || !strcmp(name, "sodipodi:nodetypes")) { // With paths, we only need to act if one of the path-affecting attributes has changed.
327             changed = (np->local_change == 0);
328             if (np->local_change > 0)
329                 np->local_change--;
330         }
332     } else if (kh) {
333         item = SP_ITEM(kh->item);
334         changed = !(kh->local_change);
335         kh->local_change = FALSE;
336     }
338     if (np && changed) {
339         GList *saved = NULL;
340         SPDesktop *desktop = np->desktop;
341         g_assert(desktop);
342         Inkscape::Selection *selection = desktop->selection;
343         g_assert(selection);
345         saved = save_nodepath_selection(nc->nodepath);
346         sp_nodepath_update_from_item(nc, item);
347         if (nc->nodepath && saved) restore_nodepath_selection(nc->nodepath, saved);
349     } else if (kh && changed) {
350         sp_nodepath_update_from_item(nc, item);
351     }
353     sp_nodepath_update_statusbar(nc->nodepath);
356 void
357 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
359     sp_event_show_modifier_tip
360         (event_context->defaultMessageContext(), event,
361          _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
362          _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
363          _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
366 bool
367 sp_node_context_is_over_stroke (SPNodeContext *nc, SPItem *item, NR::Point event_p, bool remember)
369     SPDesktop *desktop = SP_EVENT_CONTEXT (nc)->desktop;
371     //Translate click point into proper coord system
372     nc->curvepoint_doc = desktop->w2d(event_p);
373     nc->curvepoint_doc *= sp_item_dt2i_affine(item);
374     nc->curvepoint_doc *= sp_item_i2doc_affine(item);
376     sp_nodepath_ensure_livarot_path(nc->nodepath);
377     NR::Maybe<Path::cut_position> position = get_nearest_position_on_Path(nc->nodepath->livarot_path, nc->curvepoint_doc);
378     NR::Point nearest = get_point_on_Path(nc->nodepath->livarot_path, position.assume().piece, position.assume().t);
379     NR::Point delta = nearest - nc->curvepoint_doc;
381     delta = desktop->d2w(delta);
383     double stroke_tolerance =
384         (SP_OBJECT_STYLE (item)->stroke.type != SP_PAINT_TYPE_NONE?
385          desktop->current_zoom() *
386          SP_OBJECT_STYLE (item)->stroke_width.computed *
387          sp_item_i2d_affine (item).expansion() * 0.5
388          : 0.0)
389         + (double) SP_EVENT_CONTEXT(nc)->tolerance;
391     bool close = (NR::L2 (delta) < stroke_tolerance);
393     if (remember && close) {
394         nc->curvepoint_event[NR::X] = (gint) event_p [NR::X];
395         nc->curvepoint_event[NR::Y] = (gint) event_p [NR::Y];
396         nc->hit = true;
397         nc->grab_t = position.assume().t;
398         nc->grab_node = sp_nodepath_get_node_by_index(position.assume().piece);
399     }
401     return close;
405 static gint
406 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
408     gint ret = FALSE;
410     SPDesktop *desktop = event_context->desktop;
411     Inkscape::Selection *selection = sp_desktop_selection (desktop);
413     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
415     switch (event->type) {
416         case GDK_2BUTTON_PRESS:
417         case GDK_BUTTON_RELEASE:
418             if (event->button.button == 1) {
419                 if (!nc->drag) {
421                     // find out clicked item, disregarding groups, honoring Alt
422                     SPItem *item_clicked = sp_event_context_find_item (desktop,
423                             NR::Point(event->button.x, event->button.y),
424                             (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
425                     // find out if we're over the selected item, disregarding groups
426                     SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
427                                                                     NR::Point(event->button.x, event->button.y));
429                     bool over_stroke = false;
430                     if (item_over && nc->nodepath) {
431                         over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), false);
432                     }
434                     if (over_stroke || nc->added_node) {
435                         switch (event->type) {
436                             case GDK_BUTTON_RELEASE:
437                                 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
438                                     //add a node
439                                     sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
440                                 } else {
441                                     if (nc->added_node) { // we just received double click, ignore release
442                                         nc->added_node = false;
443                                         break;
444                                     }
445                                     //select the segment
446                                     if (event->button.state & GDK_SHIFT_MASK) {
447                                         sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, true);
448                                     } else {
449                                         sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
450                                     }
451                                     desktop->updateNow();
452                                 }
453                                 break;
454                             case GDK_2BUTTON_PRESS:
455                                 //add a node
456                                 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
457                                 nc->added_node = true;
458                                 break;
459                             default:
460                                 break;
461                         }
462                     } else if (event->button.state & GDK_SHIFT_MASK) {
463                         selection->toggle(item_clicked);
464                         desktop->updateNow();
465                     } else {
466                         selection->set(item_clicked);
467                         desktop->updateNow();
468                     }
470                     ret = TRUE;
471                 }
472                 break;
473             }
474             break;
475         case GDK_BUTTON_PRESS:
476             if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK)) {
477                 // save drag origin
478                 event_context->xp = (gint) event->button.x;
479                 event_context->yp = (gint) event->button.y;
480                 event_context->within_tolerance = true;
481                 nc->hit = false;
483                 if (!nc->drag) {
484                     // find out if we're over the selected item, disregarding groups
485                     SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
486                                                                     NR::Point(event->button.x, event->button.y));
488                         if (nc->nodepath && selection->single() && item_over) {
490                             // save drag origin
491                             bool over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), true);
492                             //only dragging curves
493                             if (over_stroke) {
494                                 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
495                                 ret = TRUE;
496                             } else {
497                                 break;
498                             }
499                         } else {
500                             break;
501                         }
503                     ret = TRUE;
504                 }
505                 break;
506             }
507             break;
508         default:
509             break;
510     }
512     if (!ret) {
513         if (((SPEventContextClass *) parent_class)->item_handler)
514             ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
515     }
517     return ret;
520 static gint
521 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
523     SPDesktop *desktop = event_context->desktop;
524     Inkscape::Selection *selection = sp_desktop_selection (desktop);
526     // fixme:  nc->nodepath can potentially become NULL after retrieving nc.
527     // A general method for handling this possibility should be created.
528     // For now, the number of checks for a NULL nc->nodepath have been
529     // increased, both here and in the called sp_nodepath_* functions.
531     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
532     double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
533     event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
534     int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
535     double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
537     gint ret = FALSE;
539     switch (event->type) {
540         case GDK_BUTTON_PRESS:
541             if (event->button.button == 1) {
542                 // save drag origin
543                 event_context->xp = (gint) event->button.x;
544                 event_context->yp = (gint) event->button.y;
545                 event_context->within_tolerance = true;
546                 nc->hit = false;
548                 NR::Point const button_w(event->button.x,
549                                          event->button.y);
550                 NR::Point const button_dt(desktop->w2d(button_w));
551                 Inkscape::Rubberband::get()->start(desktop, button_dt);
552                 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
553                 desktop->updateNow();
554                 ret = TRUE;
555             }
556             break;
557         case GDK_MOTION_NOTIFY:
558             if (event->motion.state & GDK_BUTTON1_MASK) {
560                 if ( event_context->within_tolerance
561                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
562                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
563                     break; // do not drag if we're within tolerance from origin
564                 }
566                 // The path went away while dragging; throw away any further motion
567                 // events until the mouse pointer is released.
568                 if (nc->hit && (nc->nodepath == NULL)) {                  
569                   break;
570                 }
572                 // Once the user has moved farther than tolerance from the original location
573                 // (indicating they intend to move the object, not click), then always process the
574                 // motion notify coordinates as given (no snapping back to origin)
575                 event_context->within_tolerance = false;
577                 // Once we determine what the user is doing (dragging either a node or the
578                 // selection rubberband), make sure we continue to perform that operation
579                 // until the mouse pointer is lifted.
580                 if (nc->current_state == SP_NODE_CONTEXT_INACTIVE) {
581                     if (nc->nodepath && nc->hit) {
582                         nc->current_state = SP_NODE_CONTEXT_NODE_DRAGGING;
583                     } else {
584                         nc->current_state = SP_NODE_CONTEXT_RUBBERBAND_DRAGGING;
585                     }
586                 }
588                 switch (nc->current_state) {
589                     case SP_NODE_CONTEXT_NODE_DRAGGING:
590                         {
591                             NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
592                                                 event->motion.y - nc->curvepoint_event[NR::Y]);
593                             NR::Point const delta_dt(desktop->w2d(delta_w));
594                             sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
595                             nc->curvepoint_event[NR::X] = (gint) event->motion.x;
596                             nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
597                             gobble_motion_events(GDK_BUTTON1_MASK);
598                             break;
599                         }
600                     case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING:
601                         if (Inkscape::Rubberband::get()->is_started()) {
602                             NR::Point const motion_w(event->motion.x,
603                                                 event->motion.y);
604                             NR::Point const motion_dt(desktop->w2d(motion_w));
605                             Inkscape::Rubberband::get()->move(motion_dt);
606                         }
607                         break;
608                 }
610                 nc->drag = TRUE;
611                 ret = TRUE;
612             } else {
613                 if (!nc->nodepath || selection->singleItem() == NULL) {
614                     break;
615                 }
617                 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
618                                                                 NR::Point(event->motion.x, event->motion.y));
619                 bool over_stroke = false;
620                 if (item_over && nc->nodepath) {
621                     over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
622                 }
624                 if (nc->cursor_drag && !over_stroke) {
625                     event_context->cursor_shape = cursor_node_xpm;
626                     event_context->hot_x = 1;
627                     event_context->hot_y = 1;
628                     sp_event_context_update_cursor(event_context);
629                     nc->cursor_drag = false;
630                 } else if (!nc->cursor_drag && over_stroke) {
631                     event_context->cursor_shape = cursor_node_d_xpm;
632                     event_context->hot_x = 1;
633                     event_context->hot_y = 1;
634                     sp_event_context_update_cursor(event_context);
635                     nc->cursor_drag = true;
636                 }
637             }
638             break;
639         case GDK_BUTTON_RELEASE:
640             event_context->xp = event_context->yp = 0;
641             if (event->button.button == 1) {
643                 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
645                 if (nc->hit && !event_context->within_tolerance) { //drag curve
646                     if (nc->nodepath) {
647                         sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
648                     }
649                 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
650                     if (nc->nodepath) {
651                         sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
652                     }
653                 } else {
654                     if (!(nc->rb_escaped)) { // unless something was cancelled
655                         if (nc->nodepath && nc->nodepath->selected)
656                             sp_nodepath_deselect(nc->nodepath);
657                         else
658                             sp_desktop_selection(desktop)->clear();
659                     }
660                 }
661                 ret = TRUE;
662                 Inkscape::Rubberband::get()->stop();
663                 desktop->updateNow();
664                 nc->rb_escaped = false;
665                 nc->drag = FALSE;
666                 nc->hit = false;
667                 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
668                 break;
669             }
670             break;
671         case GDK_KEY_PRESS:
672             switch (get_group0_keyval(&event->key)) {
673                 case GDK_Insert:
674                 case GDK_KP_Insert:
675                     // with any modifiers
676                     sp_node_selected_add_node();
677                     ret = TRUE;
678                     break;
679                 case GDK_Delete:
680                 case GDK_KP_Delete:
681                 case GDK_BackSpace:
682                     if (MOD__CTRL_ONLY) {
683                         sp_node_selected_delete();
684                     } else {
685                         if (nc->nodepath && nc->nodepath->selected) {
686                             sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
687                         }
688                     }
689                     ret = TRUE;
690                     break;
691                 case GDK_C:
692                 case GDK_c:
693                     if (MOD__SHIFT_ONLY) {
694                         sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
695                         ret = TRUE;
696                     }
697                     break;
698                 case GDK_S:
699                 case GDK_s:
700                     if (MOD__SHIFT_ONLY) {
701                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
702                         ret = TRUE;
703                     }
704                     break;
705                 case GDK_Y:
706                 case GDK_y:
707                     if (MOD__SHIFT_ONLY) {
708                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
709                         ret = TRUE;
710                     }
711                     break;
712                 case GDK_B:
713                 case GDK_b:
714                     if (MOD__SHIFT_ONLY) {
715                         sp_node_selected_break();
716                         ret = TRUE;
717                     }
718                     break;
719                 case GDK_J:
720                 case GDK_j:
721                     if (MOD__SHIFT_ONLY) {
722                         sp_node_selected_join();
723                         ret = TRUE;
724                     }
725                     break;
726                 case GDK_D:
727                 case GDK_d:
728                     if (MOD__SHIFT_ONLY) {
729                         sp_node_selected_duplicate();
730                         ret = TRUE;
731                     }
732                     break;
733                 case GDK_L:
734                 case GDK_l:
735                     if (MOD__SHIFT_ONLY) {
736                         sp_node_selected_set_line_type(NR_LINETO);
737                         ret = TRUE;
738                     }
739                     break;
740                 case GDK_U:
741                 case GDK_u:
742                     if (MOD__SHIFT_ONLY) {
743                         sp_node_selected_set_line_type(NR_CURVETO);
744                         ret = TRUE;
745                     }
746                     break;
747                 case GDK_R:
748                 case GDK_r:
749                     if (MOD__SHIFT_ONLY) {
750                         // FIXME: add top panel button
751                         sp_selected_path_reverse();
752                         ret = TRUE;
753                     }
754                     break;
755                 case GDK_Left: // move selection left
756                 case GDK_KP_Left:
757                 case GDK_KP_4:
758                     if (!MOD__CTRL) { // not ctrl
759                         if (MOD__ALT) { // alt
760                             if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
761                             else sp_node_selected_move_screen(-1, 0); // no shift
762                         }
763                         else { // no alt
764                             if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
765                             else sp_node_selected_move(-nudge, 0); // no shift
766                         }
767                         ret = TRUE;
768                     }
769                     break;
770                 case GDK_Up: // move selection up
771                 case GDK_KP_Up:
772                 case GDK_KP_8:
773                     if (!MOD__CTRL) { // not ctrl
774                         if (MOD__ALT) { // alt
775                             if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
776                             else sp_node_selected_move_screen(0, 1); // no shift
777                         }
778                         else { // no alt
779                             if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
780                             else sp_node_selected_move(0, nudge); // no shift
781                         }
782                         ret = TRUE;
783                     }
784                     break;
785                 case GDK_Right: // move selection right
786                 case GDK_KP_Right:
787                 case GDK_KP_6:
788                     if (!MOD__CTRL) { // not ctrl
789                         if (MOD__ALT) { // alt
790                             if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
791                             else sp_node_selected_move_screen(1, 0); // no shift
792                         }
793                         else { // no alt
794                             if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
795                             else sp_node_selected_move(nudge, 0); // no shift
796                         }
797                         ret = TRUE;
798                     }
799                     break;
800                 case GDK_Down: // move selection down
801                 case GDK_KP_Down:
802                 case GDK_KP_2:
803                     if (!MOD__CTRL) { // not ctrl
804                         if (MOD__ALT) { // alt
805                             if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
806                             else sp_node_selected_move_screen(0, -1); // no shift
807                         }
808                         else { // no alt
809                             if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
810                             else sp_node_selected_move(0, -nudge); // no shift
811                         }
812                         ret = TRUE;
813                     }
814                     break;
815                 case GDK_Escape:
816                 {
817                     NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
818                     if (b != NR::Nothing()) {
819                         Inkscape::Rubberband::get()->stop();
820                         nc->current_state = SP_NODE_CONTEXT_INACTIVE;
821                         nc->rb_escaped = true;
822                     } else {
823                         if (nc->nodepath && nc->nodepath->selected) {
824                             sp_nodepath_deselect(nc->nodepath);
825                         } else {
826                             sp_desktop_selection(desktop)->clear();
827                         }
828                     }
829                     ret = TRUE;
830                     break;
831                 }
833                 case GDK_bracketleft:
834                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
835                         if (nc->leftctrl)
836                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
837                         if (nc->rightctrl)
838                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
839                     } else if ( MOD__ALT && !MOD__CTRL ) {
840                         if (nc->leftalt && nc->rightalt)
841                             sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
842                         else {
843                             if (nc->leftalt)
844                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
845                             if (nc->rightalt)
846                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
847                         }
848                     } else if ( snaps != 0 ) {
849                         sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
850                     }
851                     ret = TRUE;
852                     break;
853                 case GDK_bracketright:
854                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
855                         if (nc->leftctrl)
856                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
857                         if (nc->rightctrl)
858                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
859                     } else if ( MOD__ALT && !MOD__CTRL ) {
860                         if (nc->leftalt && nc->rightalt)
861                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
862                         else {
863                             if (nc->leftalt)
864                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
865                             if (nc->rightalt)
866                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
867                         }
868                     } else if ( snaps != 0 ) {
869                         sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
870                     }
871                     ret = TRUE;
872                     break;
873                 case GDK_less:
874                 case GDK_comma:
875                     if (MOD__CTRL) {
876                         if (nc->leftctrl)
877                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
878                         if (nc->rightctrl)
879                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
880                     } else if (MOD__ALT) {
881                         if (nc->leftalt && nc->rightalt)
882                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
883                         else {
884                             if (nc->leftalt)
885                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
886                             if (nc->rightalt)
887                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
888                         }
889                     } else {
890                         sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
891                     }
892                     ret = TRUE;
893                     break;
894                 case GDK_greater:
895                 case GDK_period:
896                     if (MOD__CTRL) {
897                         if (nc->leftctrl)
898                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
899                         if (nc->rightctrl)
900                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
901                     } else if (MOD__ALT) {
902                         if (nc->leftalt && nc->rightalt)
903                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
904                         else {
905                             if (nc->leftalt)
906                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
907                             if (nc->rightalt)
908                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
909                         }
910                     } else {
911                         sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
912                     }
913                     ret = TRUE;
914                     break;
916                 case GDK_Alt_L:
917                     nc->leftalt = TRUE;
918                     sp_node_context_show_modifier_tip(event_context, event);
919                     break;
920                 case GDK_Alt_R:
921                     nc->rightalt = TRUE;
922                     sp_node_context_show_modifier_tip(event_context, event);
923                     break;
924                 case GDK_Control_L:
925                     nc->leftctrl = TRUE;
926                     sp_node_context_show_modifier_tip(event_context, event);
927                     break;
928                 case GDK_Control_R:
929                     nc->rightctrl = TRUE;
930                     sp_node_context_show_modifier_tip(event_context, event);
931                     break;
932                 case GDK_Shift_L:
933                 case GDK_Shift_R:
934                 case GDK_Meta_L:
935                 case GDK_Meta_R:
936                     sp_node_context_show_modifier_tip(event_context, event);
937                     break;
938                 default:
939                     ret = node_key(event);
940                     break;
941             }
942             break;
943         case GDK_KEY_RELEASE:
944             switch (get_group0_keyval(&event->key)) {
945                 case GDK_Alt_L:
946                     nc->leftalt = FALSE;
947                     event_context->defaultMessageContext()->clear();
948                     break;
949                 case GDK_Alt_R:
950                     nc->rightalt = FALSE;
951                     event_context->defaultMessageContext()->clear();
952                     break;
953                 case GDK_Control_L:
954                     nc->leftctrl = FALSE;
955                     event_context->defaultMessageContext()->clear();
956                     break;
957                 case GDK_Control_R:
958                     nc->rightctrl = FALSE;
959                     event_context->defaultMessageContext()->clear();
960                     break;
961                 case GDK_Shift_L:
962                 case GDK_Shift_R:
963                 case GDK_Meta_L:
964                 case GDK_Meta_R:
965                     event_context->defaultMessageContext()->clear();
966                     break;
967             }
968             break;
969         default:
970             break;
971     }
973     if (!ret) {
974         if (((SPEventContextClass *) parent_class)->root_handler)
975             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
976     }
978     return ret;
982 /*
983   Local Variables:
984   mode:c++
985   c-file-style:"stroustrup"
986   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
987   indent-tabs-mode:nil
988   fill-column:99
989   End:
990 */
991 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :