Code

2b30d067b66449620c84e5ba5baaaf83e0c8f410
[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                             // We round off the extra precision in the motion coordinates provided
592                             // by some input devices (like tablets). As we'll store the coordinates
593                             // as integers in curvepoint_event we need to do this rounding before
594                             // comparing them with the last coordinates from curvepoint_event.
595                             // See bug #1593499 for details.
597                             gint x = (gint) Inkscape::round(event->motion.x);
598                             gint y = (gint) Inkscape::round(event->motion.y);
600                             // The coordinates hasn't changed since the last motion event, abort
601                             if (nc->curvepoint_event[NR::X] == x &&
602                                 nc->curvepoint_event[NR::Y] == y)
603                                 break;
605                             NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
606                                                     event->motion.y - nc->curvepoint_event[NR::Y]);
607                             NR::Point const delta_dt(desktop->w2d(delta_w));
609                             sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
610                             nc->curvepoint_event[NR::X] = x;
611                             nc->curvepoint_event[NR::Y] = y;
612                             gobble_motion_events(GDK_BUTTON1_MASK);
613                             break;
614                         }
615                     case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING:
616                         if (Inkscape::Rubberband::get()->is_started()) {
617                             NR::Point const motion_w(event->motion.x,
618                                                 event->motion.y);
619                             NR::Point const motion_dt(desktop->w2d(motion_w));
620                             Inkscape::Rubberband::get()->move(motion_dt);
621                         }
622                         break;
623                 }
625                 nc->drag = TRUE;
626                 ret = TRUE;
627             } else {
628                 if (!nc->nodepath || selection->singleItem() == NULL) {
629                     break;
630                 }
632                 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
633                                                                 NR::Point(event->motion.x, event->motion.y));
634                 bool over_stroke = false;
635                 if (item_over && nc->nodepath) {
636                     over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
637                 }
639                 if (nc->cursor_drag && !over_stroke) {
640                     event_context->cursor_shape = cursor_node_xpm;
641                     event_context->hot_x = 1;
642                     event_context->hot_y = 1;
643                     sp_event_context_update_cursor(event_context);
644                     nc->cursor_drag = false;
645                 } else if (!nc->cursor_drag && over_stroke) {
646                     event_context->cursor_shape = cursor_node_d_xpm;
647                     event_context->hot_x = 1;
648                     event_context->hot_y = 1;
649                     sp_event_context_update_cursor(event_context);
650                     nc->cursor_drag = true;
651                 }
652             }
653             break;
654         case GDK_BUTTON_RELEASE:
655             event_context->xp = event_context->yp = 0;
656             if (event->button.button == 1) {
658                 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
660                 if (nc->hit && !event_context->within_tolerance) { //drag curve
661                     if (nc->nodepath) {
662                         sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
663                     }
664                 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
665                     if (nc->nodepath) {
666                         sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
667                     }
668                 } else {
669                     if (!(nc->rb_escaped)) { // unless something was cancelled
670                         if (nc->nodepath && nc->nodepath->selected)
671                             sp_nodepath_deselect(nc->nodepath);
672                         else
673                             sp_desktop_selection(desktop)->clear();
674                     }
675                 }
676                 ret = TRUE;
677                 Inkscape::Rubberband::get()->stop();
678                 desktop->updateNow();
679                 nc->rb_escaped = false;
680                 nc->drag = FALSE;
681                 nc->hit = false;
682                 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
683                 break;
684             }
685             break;
686         case GDK_KEY_PRESS:
687             switch (get_group0_keyval(&event->key)) {
688                 case GDK_Insert:
689                 case GDK_KP_Insert:
690                     // with any modifiers
691                     sp_node_selected_add_node();
692                     ret = TRUE;
693                     break;
694                 case GDK_Delete:
695                 case GDK_KP_Delete:
696                 case GDK_BackSpace:
697                     if (MOD__CTRL_ONLY) {
698                         sp_node_selected_delete();
699                     } else {
700                         if (nc->nodepath && nc->nodepath->selected) {
701                             sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
702                         }
703                     }
704                     ret = TRUE;
705                     break;
706                 case GDK_C:
707                 case GDK_c:
708                     if (MOD__SHIFT_ONLY) {
709                         sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
710                         ret = TRUE;
711                     }
712                     break;
713                 case GDK_S:
714                 case GDK_s:
715                     if (MOD__SHIFT_ONLY) {
716                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
717                         ret = TRUE;
718                     }
719                     break;
720                 case GDK_Y:
721                 case GDK_y:
722                     if (MOD__SHIFT_ONLY) {
723                         sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
724                         ret = TRUE;
725                     }
726                     break;
727                 case GDK_B:
728                 case GDK_b:
729                     if (MOD__SHIFT_ONLY) {
730                         sp_node_selected_break();
731                         ret = TRUE;
732                     }
733                     break;
734                 case GDK_J:
735                 case GDK_j:
736                     if (MOD__SHIFT_ONLY) {
737                         sp_node_selected_join();
738                         ret = TRUE;
739                     }
740                     break;
741                 case GDK_D:
742                 case GDK_d:
743                     if (MOD__SHIFT_ONLY) {
744                         sp_node_selected_duplicate();
745                         ret = TRUE;
746                     }
747                     break;
748                 case GDK_L:
749                 case GDK_l:
750                     if (MOD__SHIFT_ONLY) {
751                         sp_node_selected_set_line_type(NR_LINETO);
752                         ret = TRUE;
753                     }
754                     break;
755                 case GDK_U:
756                 case GDK_u:
757                     if (MOD__SHIFT_ONLY) {
758                         sp_node_selected_set_line_type(NR_CURVETO);
759                         ret = TRUE;
760                     }
761                     break;
762                 case GDK_R:
763                 case GDK_r:
764                     if (MOD__SHIFT_ONLY) {
765                         // FIXME: add top panel button
766                         sp_selected_path_reverse();
767                         ret = TRUE;
768                     }
769                     break;
770                 case GDK_Left: // move selection left
771                 case GDK_KP_Left:
772                 case GDK_KP_4:
773                     if (!MOD__CTRL) { // not ctrl
774                         if (MOD__ALT) { // alt
775                             if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
776                             else sp_node_selected_move_screen(-1, 0); // no shift
777                         }
778                         else { // no alt
779                             if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
780                             else sp_node_selected_move(-nudge, 0); // no shift
781                         }
782                         ret = TRUE;
783                     }
784                     break;
785                 case GDK_Up: // move selection up
786                 case GDK_KP_Up:
787                 case GDK_KP_8:
788                     if (!MOD__CTRL) { // not ctrl
789                         if (MOD__ALT) { // alt
790                             if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
791                             else sp_node_selected_move_screen(0, 1); // no shift
792                         }
793                         else { // no alt
794                             if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
795                             else sp_node_selected_move(0, nudge); // no shift
796                         }
797                         ret = TRUE;
798                     }
799                     break;
800                 case GDK_Right: // move selection right
801                 case GDK_KP_Right:
802                 case GDK_KP_6:
803                     if (!MOD__CTRL) { // not ctrl
804                         if (MOD__ALT) { // alt
805                             if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
806                             else sp_node_selected_move_screen(1, 0); // no shift
807                         }
808                         else { // no alt
809                             if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
810                             else sp_node_selected_move(nudge, 0); // no shift
811                         }
812                         ret = TRUE;
813                     }
814                     break;
815                 case GDK_Down: // move selection down
816                 case GDK_KP_Down:
817                 case GDK_KP_2:
818                     if (!MOD__CTRL) { // not ctrl
819                         if (MOD__ALT) { // alt
820                             if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
821                             else sp_node_selected_move_screen(0, -1); // no shift
822                         }
823                         else { // no alt
824                             if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
825                             else sp_node_selected_move(0, -nudge); // no shift
826                         }
827                         ret = TRUE;
828                     }
829                     break;
830                 case GDK_Escape:
831                 {
832                     NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
833                     if (b != NR::Nothing()) {
834                         Inkscape::Rubberband::get()->stop();
835                         nc->current_state = SP_NODE_CONTEXT_INACTIVE;
836                         nc->rb_escaped = true;
837                     } else {
838                         if (nc->nodepath && nc->nodepath->selected) {
839                             sp_nodepath_deselect(nc->nodepath);
840                         } else {
841                             sp_desktop_selection(desktop)->clear();
842                         }
843                     }
844                     ret = TRUE;
845                     break;
846                 }
848                 case GDK_bracketleft:
849                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
850                         if (nc->leftctrl)
851                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
852                         if (nc->rightctrl)
853                             sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
854                     } else if ( MOD__ALT && !MOD__CTRL ) {
855                         if (nc->leftalt && nc->rightalt)
856                             sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
857                         else {
858                             if (nc->leftalt)
859                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
860                             if (nc->rightalt)
861                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
862                         }
863                     } else if ( snaps != 0 ) {
864                         sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
865                     }
866                     ret = TRUE;
867                     break;
868                 case GDK_bracketright:
869                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
870                         if (nc->leftctrl)
871                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
872                         if (nc->rightctrl)
873                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
874                     } else if ( MOD__ALT && !MOD__CTRL ) {
875                         if (nc->leftalt && nc->rightalt)
876                             sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
877                         else {
878                             if (nc->leftalt)
879                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
880                             if (nc->rightalt)
881                                 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
882                         }
883                     } else if ( snaps != 0 ) {
884                         sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
885                     }
886                     ret = TRUE;
887                     break;
888                 case GDK_less:
889                 case GDK_comma:
890                     if (MOD__CTRL) {
891                         if (nc->leftctrl)
892                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
893                         if (nc->rightctrl)
894                             sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
895                     } else if (MOD__ALT) {
896                         if (nc->leftalt && nc->rightalt)
897                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
898                         else {
899                             if (nc->leftalt)
900                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
901                             if (nc->rightalt)
902                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
903                         }
904                     } else {
905                         sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
906                     }
907                     ret = TRUE;
908                     break;
909                 case GDK_greater:
910                 case GDK_period:
911                     if (MOD__CTRL) {
912                         if (nc->leftctrl)
913                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
914                         if (nc->rightctrl)
915                             sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
916                     } else if (MOD__ALT) {
917                         if (nc->leftalt && nc->rightalt)
918                             sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
919                         else {
920                             if (nc->leftalt)
921                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
922                             if (nc->rightalt)
923                                 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
924                         }
925                     } else {
926                         sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
927                     }
928                     ret = TRUE;
929                     break;
931                 case GDK_Alt_L:
932                     nc->leftalt = TRUE;
933                     sp_node_context_show_modifier_tip(event_context, event);
934                     break;
935                 case GDK_Alt_R:
936                     nc->rightalt = TRUE;
937                     sp_node_context_show_modifier_tip(event_context, event);
938                     break;
939                 case GDK_Control_L:
940                     nc->leftctrl = TRUE;
941                     sp_node_context_show_modifier_tip(event_context, event);
942                     break;
943                 case GDK_Control_R:
944                     nc->rightctrl = TRUE;
945                     sp_node_context_show_modifier_tip(event_context, event);
946                     break;
947                 case GDK_Shift_L:
948                 case GDK_Shift_R:
949                 case GDK_Meta_L:
950                 case GDK_Meta_R:
951                     sp_node_context_show_modifier_tip(event_context, event);
952                     break;
953                 default:
954                     ret = node_key(event);
955                     break;
956             }
957             break;
958         case GDK_KEY_RELEASE:
959             switch (get_group0_keyval(&event->key)) {
960                 case GDK_Alt_L:
961                     nc->leftalt = FALSE;
962                     event_context->defaultMessageContext()->clear();
963                     break;
964                 case GDK_Alt_R:
965                     nc->rightalt = FALSE;
966                     event_context->defaultMessageContext()->clear();
967                     break;
968                 case GDK_Control_L:
969                     nc->leftctrl = FALSE;
970                     event_context->defaultMessageContext()->clear();
971                     break;
972                 case GDK_Control_R:
973                     nc->rightctrl = FALSE;
974                     event_context->defaultMessageContext()->clear();
975                     break;
976                 case GDK_Shift_L:
977                 case GDK_Shift_R:
978                 case GDK_Meta_L:
979                 case GDK_Meta_R:
980                     event_context->defaultMessageContext()->clear();
981                     break;
982             }
983             break;
984         default:
985             break;
986     }
988     if (!ret) {
989         if (((SPEventContextClass *) parent_class)->root_handler)
990             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
991     }
993     return ret;
997 /*
998   Local Variables:
999   mode:c++
1000   c-file-style:"stroustrup"
1001   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1002   indent-tabs-mode:nil
1003   fill-column:99
1004   End:
1005 */
1006 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :