Code

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