Code

New crosshairs cursor for geometry context
[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
11  */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16 #include <cstring>
17 #include <string>
18 #include <gdk/gdkkeysyms.h>
19 #include "macros.h"
20 #include <glibmm/i18n.h>
21 #include "display/sp-canvas-util.h"
22 #include "object-edit.h"
23 #include "sp-path.h"
24 #include "path-chemistry.h"
25 #include "rubberband.h"
26 #include "desktop.h"
27 #include "desktop-handles.h"
28 #include "selection.h"
29 #include "pixmaps/cursor-node.xpm"
30 #include "message-context.h"
31 #include "node-context.h"
32 #include "pixmaps/cursor-node-d.xpm"
33 #include "prefs-utils.h"
34 #include "xml/node-event-vector.h"
35 #include "style.h"
36 #include "splivarot.h"
37 #include "shape-editor.h"
38 #include "live_effects/effect.h"
40 #include "sp-lpe-item.h"
42 // needed for flash nodepath upon mouseover:
43 #include "display/canvas-bpath.h"
44 #include "display/curve.h"
46 static void sp_node_context_class_init(SPNodeContextClass *klass);
47 static void sp_node_context_init(SPNodeContext *node_context);
48 static void sp_node_context_dispose(GObject *object);
50 static void sp_node_context_setup(SPEventContext *ec);
51 static gint sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event);
52 static gint sp_node_context_item_handler(SPEventContext *event_context,
53                                          SPItem *item, GdkEvent *event);
55 static SPEventContextClass *parent_class;
57 GType
58 sp_node_context_get_type()
59 {
60     static GType type = 0;
61     if (!type) {
62         GTypeInfo info = {
63             sizeof(SPNodeContextClass),
64             NULL, NULL,
65             (GClassInitFunc) sp_node_context_class_init,
66             NULL, NULL,
67             sizeof(SPNodeContext),
68             4,
69             (GInstanceInitFunc) sp_node_context_init,
70             NULL,    /* value_table */
71         };
72         type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPNodeContext", &info, (GTypeFlags)0);
73     }
74     return type;
75 }
77 static void
78 sp_node_context_class_init(SPNodeContextClass *klass)
79 {
80     GObjectClass *object_class = (GObjectClass *) klass;
81     SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
83     parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
85     object_class->dispose = sp_node_context_dispose;
87     event_context_class->setup = sp_node_context_setup;
88     event_context_class->root_handler = sp_node_context_root_handler;
89     event_context_class->item_handler = sp_node_context_item_handler;
90 }
92 static void
93 sp_node_context_init(SPNodeContext *node_context)
94 {
95     SPEventContext *event_context = SP_EVENT_CONTEXT(node_context);
97     event_context->cursor_shape = cursor_node_xpm;
98     event_context->hot_x = 1;
99     event_context->hot_y = 1;
101     node_context->leftalt = FALSE;
102     node_context->rightalt = FALSE;
103     node_context->leftctrl = FALSE;
104     node_context->rightctrl = FALSE;
105     
106     new (&node_context->sel_changed_connection) sigc::connection();
108     node_context->flash_tempitem = NULL;
109     node_context->flashed_item = NULL;
110     node_context->remove_flash_counter = 0;
113 static void
114 sp_node_context_dispose(GObject *object)
116     SPNodeContext *nc = SP_NODE_CONTEXT(object);
117     SPEventContext *ec = SP_EVENT_CONTEXT(object);
119     ec->enableGrDrag(false);
121     nc->sel_changed_connection.disconnect();
122     nc->sel_changed_connection.~connection();
124     delete nc->shape_editor;
126     if (nc->_node_message_context) {
127         delete nc->_node_message_context;
128     }
130     G_OBJECT_CLASS(parent_class)->dispose(object);
133 static void
134 sp_node_context_setup(SPEventContext *ec)
136     SPNodeContext *nc = SP_NODE_CONTEXT(ec);
138     if (((SPEventContextClass *) parent_class)->setup)
139         ((SPEventContextClass *) parent_class)->setup(ec);
141     Inkscape::Selection *selection = sp_desktop_selection (ec->desktop);
142     nc->sel_changed_connection.disconnect();
143     nc->sel_changed_connection =
144         selection->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
146     SPItem *item = selection->singleItem();
148     nc->shape_editor = new ShapeEditor(ec->desktop);
150     nc->rb_escaped = false;
152     nc->cursor_drag = false;
154     nc->added_node = false;
156     nc->current_state = SP_NODE_CONTEXT_INACTIVE;
158     if (item) {
159         nc->shape_editor->set_item(item, SH_NODEPATH);
160         nc->shape_editor->set_item(item, SH_KNOTHOLDER);
161     }
163     if (prefs_get_int_attribute("tools.nodes", "selcue", 0) != 0) {
164         ec->enableSelectionCue();
165     }
167     if (prefs_get_int_attribute("tools.nodes", "gradientdrag", 0) != 0) {
168         ec->enableGrDrag();
169     }
171     ec->desktop->emitToolSubselectionChanged(NULL); // sets the coord entry fields to inactive
173     nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
175     nc->shape_editor->update_statusbar();
178 static void
179 sp_node_context_flash_path(SPEventContext *event_context, SPItem *item, guint timeout) {
180     g_print ("-----> sp_node_context_flash_path(): TODO: generate the helper path!!\n");
181     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
183     nc->remove_flash_counter = 3; // for some reason root_handler is called twice after each item_handler...
184     if (nc->flashed_item != item) {
185         // we entered a new item
186         nc->flashed_item = item;
187         SPDesktop *desktop = event_context->desktop;
188         if (nc->flash_tempitem) {
189             desktop->remove_temporary_canvasitem(nc->flash_tempitem);
190             nc->flash_tempitem = NULL;
191         }
193         if (SP_IS_PATH(item)) {
194             SPCanvasItem *canvasitem = sp_nodepath_helperpath_from_path(desktop, SP_PATH(item));
195             nc->flash_tempitem = desktop->add_temporary_canvasitem (canvasitem, timeout);
196         }
197     }
200 /**
201 \brief  Callback that processes the "changed" signal on the selection;
202 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
203 */
204 void
205 sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
207     SPNodeContext *nc = SP_NODE_CONTEXT(data);
209     // TODO: update ShapeEditorsCollective instead
210     nc->shape_editor->unset_item(SH_NODEPATH);
211     nc->shape_editor->unset_item(SH_KNOTHOLDER);
212     SPItem *item = selection->singleItem(); 
213     nc->shape_editor->set_item(item, SH_NODEPATH);
214     nc->shape_editor->set_item(item, SH_KNOTHOLDER);
215     nc->shape_editor->update_statusbar();
218 void
219 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
221     sp_event_show_modifier_tip
222         (event_context->defaultMessageContext(), event,
223          _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
224          _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
225          _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
228 static gint
229 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
231     gint ret = FALSE;
233     if (prefs_get_int_attribute ("tools.nodes", "pathflash_enabled", 0) == 1) {
234         guint timeout = prefs_get_int_attribute("tools.nodes", "pathflash_timeout", 500);
235         if (SP_IS_LPE_ITEM(item)) {
236             Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
237             if (lpe && (lpe->providesOwnFlashPaths() ||
238                         lpe->pathFlashType() == Inkscape::LivePathEffect::SUPPRESS_FLASH)) {
239                 // path should be suppressed or permanent; this is handled in
240                 // sp_node_context_selection_changed()
241                 return ret;
242             }
243         }
244         sp_node_context_flash_path(event_context, item, timeout);
245     }
247     if (((SPEventContextClass *) parent_class)->item_handler)
248         ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
250     return ret;
253 static gint
254 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
256     SPDesktop *desktop = event_context->desktop;
257     Inkscape::Selection *selection = sp_desktop_selection (desktop);
259     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
260     double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
261     event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
262     int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
263     double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
265     if ( (nc->flash_tempitem) && (nc->remove_flash_counter <= 0) ) {
266         desktop->remove_temporary_canvasitem(nc->flash_tempitem);
267         nc->flash_tempitem = NULL;
268         nc->flashed_item = NULL; // also reset this one, so the next time the same object is hovered over it shows again the highlight
269     } else {
270         nc->remove_flash_counter--;
271     }
273     gint ret = FALSE;
274     switch (event->type) {
275         case GDK_BUTTON_PRESS:
276             if (event->button.button == 1 && !event_context->space_panning) {
277                 // save drag origin
278                 event_context->xp = (gint) event->button.x;
279                 event_context->yp = (gint) event->button.y;
280                 event_context->within_tolerance = true;
281                 nc->shape_editor->cancel_hit();
283                 if (!(event->button.state & GDK_SHIFT_MASK)) {
284                     if (!nc->drag) {
285                         if (nc->shape_editor->has_nodepath() && selection->single() /* && item_over */) {
286                             // save drag origin
287                             bool over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), true);
288                             //only dragging curves
289                             if (over_stroke) {
290                                 ret = TRUE;
291                                 break;
292                             }
293                         }
294                     }
295                 }
296                 NR::Point const button_w(event->button.x,
297                                          event->button.y);
298                 NR::Point const button_dt(desktop->w2d(button_w));
299                 Inkscape::Rubberband::get(desktop)->start(desktop, button_dt);
300                 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
301                 desktop->updateNow();
302                 ret = TRUE;
303             }
304             break;
305         case GDK_MOTION_NOTIFY:
306             if (event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
308                 if ( event_context->within_tolerance
309                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
310                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
311                     break; // do not drag if we're within tolerance from origin
312                 }
314                 // The path went away while dragging; throw away any further motion
315                 // events until the mouse pointer is released.
316                 
317                 if (nc->shape_editor->hits_curve() && !nc->shape_editor->has_nodepath()) {
318                   break;
319                 }
321                 // Once the user has moved farther than tolerance from the original location
322                 // (indicating they intend to move the object, not click), then always process the
323                 // motion notify coordinates as given (no snapping back to origin)
324                 event_context->within_tolerance = false;
326                 // Once we determine what the user is doing (dragging either a node or the
327                 // selection rubberband), make sure we continue to perform that operation
328                 // until the mouse pointer is lifted.
329                 if (nc->current_state == SP_NODE_CONTEXT_INACTIVE) {
330                     if (nc->shape_editor->hits_curve() && nc->shape_editor->has_nodepath()) {
331                         nc->current_state = SP_NODE_CONTEXT_NODE_DRAGGING;
332                     } else {
333                         nc->current_state = SP_NODE_CONTEXT_RUBBERBAND_DRAGGING;
334                     }
335                 }
337                 switch (nc->current_state) {
338                     case SP_NODE_CONTEXT_NODE_DRAGGING:
339                         {
340                             nc->shape_editor->curve_drag (event->motion.x, event->motion.y);
342                             gobble_motion_events(GDK_BUTTON1_MASK);
343                             break;
344                         }
345                     case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING:
346                         if (Inkscape::Rubberband::get(desktop)->is_started()) {
347                             NR::Point const motion_w(event->motion.x,
348                                                 event->motion.y);
349                             NR::Point const motion_dt(desktop->w2d(motion_w));
350                             Inkscape::Rubberband::get(desktop)->move(motion_dt);
351                         }
352                         break;
353                 }
355                 nc->drag = TRUE;
356                 ret = TRUE;
357             } else {
358                 if (!nc->shape_editor->has_nodepath() || selection->singleItem() == NULL) {
359                     break;
360                 }
362                 bool over_stroke = false;
363                 over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->motion.x, event->motion.y), false);
365                 if (nc->cursor_drag && !over_stroke) {
366                     event_context->cursor_shape = cursor_node_xpm;
367                     event_context->hot_x = 1;
368                     event_context->hot_y = 1;
369                     sp_event_context_update_cursor(event_context);
370                     nc->cursor_drag = false;
371                 } else if (!nc->cursor_drag && over_stroke) {
372                     event_context->cursor_shape = cursor_node_d_xpm;
373                     event_context->hot_x = 1;
374                     event_context->hot_y = 1;
375                     sp_event_context_update_cursor(event_context);
376                     nc->cursor_drag = true;
377                 }
378             }
379             break;
381         case GDK_2BUTTON_PRESS:
382         case GDK_BUTTON_RELEASE:
383             if ( (event->button.button == 1) && (!nc->drag) && !event_context->space_panning) {
384                 // find out clicked item, disregarding groups, honoring Alt
385                 SPItem *item_clicked = sp_event_context_find_item (desktop,
386                         NR::Point(event->button.x, event->button.y),
387                         (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
389                 event_context->xp = event_context->yp = 0;
391                 bool over_stroke = false;
392                 if (nc->shape_editor->has_nodepath()) {
393                     over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), false);
394                 }
396                 if (item_clicked || over_stroke) {
397                     if (over_stroke || nc->added_node) {
398                         switch (event->type) {
399                             case GDK_BUTTON_RELEASE:
400                                 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
401                                     //add a node
402                                     nc->shape_editor->add_node_near_point();
403                                 } else {
404                                     if (nc->added_node) { // we just received double click, ignore release
405                                         nc->added_node = false;
406                                         break;
407                                     }
408                                     //select the segment
409                                     if (event->button.state & GDK_SHIFT_MASK) {
410                                         nc->shape_editor->select_segment_near_point(true);
411                                     } else {
412                                         nc->shape_editor->select_segment_near_point(false);
413                                     }
414                                     desktop->updateNow();
415                                 }
416                                 break;
417                             case GDK_2BUTTON_PRESS:
418                                 //add a node
419                                 nc->shape_editor->add_node_near_point();
420                                 nc->added_node = true;
421                                 break;
422                             default:
423                                 break;
424                         }
425                     } else if (event->button.state & GDK_SHIFT_MASK) {
426                         selection->toggle(item_clicked);
427                         desktop->updateNow();
428                     } else {
429                         selection->set(item_clicked);
430                         desktop->updateNow();
431                     }
432                     Inkscape::Rubberband::get(desktop)->stop();
433                     ret = TRUE;
434                     break;
435                 }
436             } 
437             if (event->type == GDK_BUTTON_RELEASE) {
438                 event_context->xp = event_context->yp = 0;
439                 if (event->button.button == 1) {
440                     boost::optional<NR::Rect> b = Inkscape::Rubberband::get(desktop)->getRectangle();
442                     if (nc->shape_editor->hits_curve() && !event_context->within_tolerance) { //drag curve
443                         nc->shape_editor->finish_drag();
444                     } else if (b && !event_context->within_tolerance) { // drag to select
445                         nc->shape_editor->select_rect(*b, event->button.state & GDK_SHIFT_MASK);
446                     } else {
447                         if (!(nc->rb_escaped)) { // unless something was cancelled
448                             if (nc->shape_editor->has_selection())
449                                 nc->shape_editor->deselect();
450                             else
451                                 sp_desktop_selection(desktop)->clear();
452                         }
453                     }
454                     ret = TRUE;
455                     Inkscape::Rubberband::get(desktop)->stop();
456                     desktop->updateNow();
457                     nc->rb_escaped = false;
458                     nc->drag = FALSE;
459                     nc->shape_editor->cancel_hit();
460                     nc->current_state = SP_NODE_CONTEXT_INACTIVE;
461                 }
462             }
463             break;
464         case GDK_KEY_PRESS:
465             switch (get_group0_keyval(&event->key)) {
466                 case GDK_Insert:
467                 case GDK_KP_Insert:
468                     // with any modifiers
469                     nc->shape_editor->add_node();
470                     ret = TRUE;
471                     break;
472                 case GDK_Delete:
473                 case GDK_KP_Delete:
474                 case GDK_BackSpace:
475                     if (MOD__CTRL_ONLY) {
476                         nc->shape_editor->delete_nodes();
477                     } else {
478                         nc->shape_editor->delete_nodes_preserving_shape();
479                     }
480                     ret = TRUE;
481                     break;
482                 case GDK_C:
483                 case GDK_c:
484                     if (MOD__SHIFT_ONLY) {
485                         nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_CUSP);
486                         ret = TRUE;
487                     }
488                     break;
489                 case GDK_S:
490                 case GDK_s:
491                     if (MOD__SHIFT_ONLY) {
492                         nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_SMOOTH);
493                         ret = TRUE;
494                     }
495                     break;
496                 case GDK_Y:
497                 case GDK_y:
498                     if (MOD__SHIFT_ONLY) {
499                         nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_SYMM);
500                         ret = TRUE;
501                     }
502                     break;
503                 case GDK_B:
504                 case GDK_b:
505                     if (MOD__SHIFT_ONLY) {
506                         nc->shape_editor->break_at_nodes();
507                         ret = TRUE;
508                     }
509                     break;
510                 case GDK_J:
511                 case GDK_j:
512                     if (MOD__SHIFT_ONLY) {
513                         nc->shape_editor->join_nodes();
514                         ret = TRUE;
515                     }
516                     break;
517                 case GDK_D:
518                 case GDK_d:
519                     if (MOD__SHIFT_ONLY) {
520                         nc->shape_editor->duplicate_nodes();
521                         ret = TRUE;
522                     }
523                     break;
524                 case GDK_L:
525                 case GDK_l:
526                     if (MOD__SHIFT_ONLY) {
527                         nc->shape_editor->set_type_of_segments(NR_LINETO);
528                         ret = TRUE;
529                     }
530                     break;
531                 case GDK_U:
532                 case GDK_u:
533                     if (MOD__SHIFT_ONLY) {
534                         nc->shape_editor->set_type_of_segments(NR_CURVETO);
535                         ret = TRUE;
536                     }
537                     break;
538                 case GDK_R:
539                 case GDK_r:
540                     if (MOD__SHIFT_ONLY) {
541                         // FIXME: add top panel button
542                         sp_selected_path_reverse(desktop);
543                         ret = TRUE;
544                     }
545                     break;
546                 case GDK_x:
547                 case GDK_X:
548                     if (MOD__ALT_ONLY) {
549                         desktop->setToolboxFocusTo ("altx-nodes");
550                         ret = TRUE;
551                     }
552                     break;
553                 case GDK_Left: // move selection left
554                 case GDK_KP_Left:
555                 case GDK_KP_4:
556                     if (!MOD__CTRL) { // not ctrl
557                         gint mul = 1 + gobble_key_events(
558                             get_group0_keyval(&event->key), 0); // with any mask
559                         if (MOD__ALT) { // alt
560                             if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(desktop, mul*-10, 0); // shift
561                             else nc->shape_editor->move_nodes_screen(desktop, mul*-1, 0); // no shift
562                         }
563                         else { // no alt
564                             if (MOD__SHIFT) nc->shape_editor->move_nodes(mul*-10*nudge, 0); // shift
565                             else nc->shape_editor->move_nodes(mul*-nudge, 0); // no shift
566                         }
567                         ret = TRUE;
568                     }
569                     break;
570                 case GDK_Up: // move selection up
571                 case GDK_KP_Up:
572                 case GDK_KP_8:
573                     if (!MOD__CTRL) { // not ctrl
574                         gint mul = 1 + gobble_key_events(
575                             get_group0_keyval(&event->key), 0); // with any mask
576                         if (MOD__ALT) { // alt
577                             if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(desktop, 0, mul*10); // shift
578                             else nc->shape_editor->move_nodes_screen(desktop, 0, mul*1); // no shift
579                         }
580                         else { // no alt
581                             if (MOD__SHIFT) nc->shape_editor->move_nodes(0, mul*10*nudge); // shift
582                             else nc->shape_editor->move_nodes(0, mul*nudge); // no shift
583                         }
584                         ret = TRUE;
585                     }
586                     break;
587                 case GDK_Right: // move selection right
588                 case GDK_KP_Right:
589                 case GDK_KP_6:
590                     if (!MOD__CTRL) { // not ctrl
591                         gint mul = 1 + gobble_key_events(
592                             get_group0_keyval(&event->key), 0); // with any mask
593                         if (MOD__ALT) { // alt
594                             if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(desktop, mul*10, 0); // shift
595                             else nc->shape_editor->move_nodes_screen(desktop, mul*1, 0); // no shift
596                         }
597                         else { // no alt
598                             if (MOD__SHIFT) nc->shape_editor->move_nodes(mul*10*nudge, 0); // shift
599                             else nc->shape_editor->move_nodes(mul*nudge, 0); // no shift
600                         }
601                         ret = TRUE;
602                     }
603                     break;
604                 case GDK_Down: // move selection down
605                 case GDK_KP_Down:
606                 case GDK_KP_2:
607                     if (!MOD__CTRL) { // not ctrl
608                         gint mul = 1 + gobble_key_events(
609                             get_group0_keyval(&event->key), 0); // with any mask
610                         if (MOD__ALT) { // alt
611                             if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(desktop, 0, mul*-10); // shift
612                             else nc->shape_editor->move_nodes_screen(desktop, 0, mul*-1); // no shift
613                         }
614                         else { // no alt
615                             if (MOD__SHIFT) nc->shape_editor->move_nodes(0, mul*-10*nudge); // shift
616                             else nc->shape_editor->move_nodes(0, mul*-nudge); // no shift
617                         }
618                         ret = TRUE;
619                     }
620                     break;
621                 case GDK_Escape:
622                 {
623                     boost::optional<NR::Rect> const b = Inkscape::Rubberband::get(desktop)->getRectangle();
624                     if (b) {
625                         Inkscape::Rubberband::get(desktop)->stop();
626                         nc->current_state = SP_NODE_CONTEXT_INACTIVE;
627                         nc->rb_escaped = true;
628                     } else {
629                         if (nc->shape_editor->has_selection()) {
630                             nc->shape_editor->deselect();
631                         } else {
632                             sp_desktop_selection(desktop)->clear();
633                         }
634                     }
635                     ret = TRUE;
636                     break;
637                 }
639                 case GDK_bracketleft:
640                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
641                         if (nc->leftctrl)
642                             nc->shape_editor->rotate_nodes (M_PI/snaps, -1, false);
643                         if (nc->rightctrl)
644                             nc->shape_editor->rotate_nodes (M_PI/snaps, 1, false);
645                     } else if ( MOD__ALT && !MOD__CTRL ) {
646                         if (nc->leftalt && nc->rightalt)
647                             nc->shape_editor->rotate_nodes (1, 0, true);
648                         else {
649                             if (nc->leftalt)
650                                 nc->shape_editor->rotate_nodes (1, -1, true);
651                             if (nc->rightalt)
652                                 nc->shape_editor->rotate_nodes (1, 1, true);
653                         }
654                     } else if ( snaps != 0 ) {
655                         nc->shape_editor->rotate_nodes (M_PI/snaps, 0, false);
656                     }
657                     ret = TRUE;
658                     break;
659                 case GDK_bracketright:
660                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
661                         if (nc->leftctrl)
662                             nc->shape_editor->rotate_nodes (-M_PI/snaps, -1, false);
663                         if (nc->rightctrl)
664                             nc->shape_editor->rotate_nodes (-M_PI/snaps, 1, false);
665                     } else if ( MOD__ALT && !MOD__CTRL ) {
666                         if (nc->leftalt && nc->rightalt)
667                             nc->shape_editor->rotate_nodes (-1, 0, true);
668                         else {
669                             if (nc->leftalt)
670                                 nc->shape_editor->rotate_nodes (-1, -1, true);
671                             if (nc->rightalt)
672                                 nc->shape_editor->rotate_nodes (-1, 1, true);
673                         }
674                     } else if ( snaps != 0 ) {
675                         nc->shape_editor->rotate_nodes (-M_PI/snaps, 0, false);
676                     }
677                     ret = TRUE;
678                     break;
679                 case GDK_less:
680                 case GDK_comma:
681                     if (MOD__CTRL) {
682                         if (nc->leftctrl)
683                             nc->shape_editor->scale_nodes(-offset, -1);
684                         if (nc->rightctrl)
685                             nc->shape_editor->scale_nodes(-offset, 1);
686                     } else if (MOD__ALT) {
687                         if (nc->leftalt && nc->rightalt)
688                             nc->shape_editor->scale_nodes_screen (-1, 0);
689                         else {
690                             if (nc->leftalt)
691                                 nc->shape_editor->scale_nodes_screen (-1, -1);
692                             if (nc->rightalt)
693                                 nc->shape_editor->scale_nodes_screen (-1, 1);
694                         }
695                     } else {
696                         nc->shape_editor->scale_nodes (-offset, 0);
697                     }
698                     ret = TRUE;
699                     break;
700                 case GDK_greater:
701                 case GDK_period:
702                     if (MOD__CTRL) {
703                         if (nc->leftctrl)
704                             nc->shape_editor->scale_nodes (offset, -1);
705                         if (nc->rightctrl)
706                             nc->shape_editor->scale_nodes (offset, 1);
707                     } else if (MOD__ALT) {
708                         if (nc->leftalt && nc->rightalt)
709                             nc->shape_editor->scale_nodes_screen (1, 0);
710                         else {
711                             if (nc->leftalt)
712                                 nc->shape_editor->scale_nodes_screen (1, -1);
713                             if (nc->rightalt)
714                                 nc->shape_editor->scale_nodes_screen (1, 1);
715                         }
716                     } else {
717                         nc->shape_editor->scale_nodes (offset, 0);
718                     }
719                     ret = TRUE;
720                     break;
722                 case GDK_Alt_L:
723                     nc->leftalt = TRUE;
724                     sp_node_context_show_modifier_tip(event_context, event);
725                     break;
726                 case GDK_Alt_R:
727                     nc->rightalt = TRUE;
728                     sp_node_context_show_modifier_tip(event_context, event);
729                     break;
730                 case GDK_Control_L:
731                     nc->leftctrl = TRUE;
732                     sp_node_context_show_modifier_tip(event_context, event);
733                     break;
734                 case GDK_Control_R:
735                     nc->rightctrl = TRUE;
736                     sp_node_context_show_modifier_tip(event_context, event);
737                     break;
738                 case GDK_Shift_L:
739                 case GDK_Shift_R:
740                 case GDK_Meta_L:
741                 case GDK_Meta_R:
742                     sp_node_context_show_modifier_tip(event_context, event);
743                     break;
744                 default:
745                     ret = node_key(event);
746                     break;
747             }
748             break;
749         case GDK_KEY_RELEASE:
750             switch (get_group0_keyval(&event->key)) {
751                 case GDK_Alt_L:
752                     nc->leftalt = FALSE;
753                     event_context->defaultMessageContext()->clear();
754                     break;
755                 case GDK_Alt_R:
756                     nc->rightalt = FALSE;
757                     event_context->defaultMessageContext()->clear();
758                     break;
759                 case GDK_Control_L:
760                     nc->leftctrl = FALSE;
761                     event_context->defaultMessageContext()->clear();
762                     break;
763                 case GDK_Control_R:
764                     nc->rightctrl = FALSE;
765                     event_context->defaultMessageContext()->clear();
766                     break;
767                 case GDK_Shift_L:
768                 case GDK_Shift_R:
769                 case GDK_Meta_L:
770                 case GDK_Meta_R:
771                     event_context->defaultMessageContext()->clear();
772                     break;
773             }
774             break;
775         default:
776             break;
777     }
779     if (!ret) {
780         if (((SPEventContextClass *) parent_class)->root_handler)
781             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
782     }
784     return ret;
788 /*
789   Local Variables:
790   mode:c++
791   c-file-style:"stroustrup"
792   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
793   indent-tabs-mode:nil
794   fill-column:99
795   End:
796 */
797 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :