Code

Second step: try to make helper curves respond faster (don't recreate/delete the...
[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     // TODO: should this be here?
125     SPItem *item = sp_desktop_selection(ec->desktop)->singleItem();
126     if (item && SP_IS_LPE_ITEM(item)) {
127         sp_lpe_item_remove_temporary_canvasitems(SP_LPE_ITEM(item), ec->desktop);
128     }
130     delete nc->shape_editor;
132     if (nc->_node_message_context) {
133         delete nc->_node_message_context;
134     }
136     G_OBJECT_CLASS(parent_class)->dispose(object);
139 static void
140 sp_node_context_setup(SPEventContext *ec)
142     SPNodeContext *nc = SP_NODE_CONTEXT(ec);
144     if (((SPEventContextClass *) parent_class)->setup)
145         ((SPEventContextClass *) parent_class)->setup(ec);
147     Inkscape::Selection *selection = sp_desktop_selection (ec->desktop);
148     nc->sel_changed_connection.disconnect();
149     nc->sel_changed_connection =
150         selection->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
152     SPItem *item = selection->singleItem();
154     nc->shape_editor = new ShapeEditor(ec->desktop);
156     nc->rb_escaped = false;
158     nc->cursor_drag = false;
160     nc->added_node = false;
162     nc->current_state = SP_NODE_CONTEXT_INACTIVE;
164     if (item) {
165         nc->shape_editor->set_item(item);
166     }
168     if (prefs_get_int_attribute("tools.nodes", "selcue", 0) != 0) {
169         ec->enableSelectionCue();
170     }
172     if (prefs_get_int_attribute("tools.nodes", "gradientdrag", 0) != 0) {
173         ec->enableGrDrag();
174     }
176     ec->desktop->emitToolSubselectionChanged(NULL); // sets the coord entry fields to inactive
178     nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
180     nc->shape_editor->update_statusbar();
183 static void
184 sp_node_context_flash_path(SPEventContext *event_context, SPItem *item, guint timeout) {
185     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
187     nc->remove_flash_counter = 3; // for some reason root_handler is called twice after each item_handler...
188     if (nc->flashed_item != item) {
189         // we entered a new item
190         nc->flashed_item = item;
191         SPDesktop *desktop = event_context->desktop;
192         if (nc->flash_tempitem) {
193             desktop->remove_temporary_canvasitem(nc->flash_tempitem);
194             nc->flash_tempitem = NULL;
195         }
197         if (SP_IS_PATH(item)) {
198             SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, SP_PATH(item));
199             nc->flash_tempitem = desktop->add_temporary_canvasitem (canvasitem, timeout);
200         }
201     }
204 /**
205 \brief  Callback that processes the "changed" signal on the selection;
206 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
207 */
208 void
209 sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
211     SPNodeContext *nc = SP_NODE_CONTEXT(data);
213     // TODO: update ShapeEditorsCollective instead
214     nc->shape_editor->unset_item();
215     SPItem *item = selection->singleItem(); 
216     nc->shape_editor->set_item(item);
217     nc->shape_editor->update_statusbar();
220 void
221 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
223     sp_event_show_modifier_tip
224         (event_context->defaultMessageContext(), event,
225          _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
226          _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
227          _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
230 static gint
231 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
233     gint ret = FALSE;
235     if (prefs_get_int_attribute ("tools.nodes", "pathflash_enabled", 0) == 1) {
236         guint timeout = prefs_get_int_attribute("tools.nodes", "pathflash_timeout", 500);
237         if (SP_IS_LPE_ITEM(item)) {
238             Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
239             if (lpe) {
240                 if (lpe->pathFlashType() == Inkscape::LivePathEffect::SUPPRESS_FLASH) {
241                     // suppressed and permanent flashes for LPE items are handled in
242                     // sp_node_context_selection_changed()
243                     return ret;
244                 }
245                 if (lpe->pathFlashType() == Inkscape::LivePathEffect::PERMANENT_FLASH) {
246                     timeout = 0;
247                 }
248             }
249         }
250         sp_node_context_flash_path(event_context, item, timeout);
251     }
253     if (((SPEventContextClass *) parent_class)->item_handler)
254         ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
256     return ret;
259 static gint
260 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
262     SPDesktop *desktop = event_context->desktop;
263     Inkscape::Selection *selection = sp_desktop_selection (desktop);
265     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
266     double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
267     event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
268     int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
269     double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
271     if ( (nc->flash_tempitem) && (nc->remove_flash_counter <= 0) ) {
272         desktop->remove_temporary_canvasitem(nc->flash_tempitem);
273         nc->flash_tempitem = NULL;
274         nc->flashed_item = NULL; // also reset this one, so the next time the same object is hovered over it shows again the highlight
275     } else {
276         nc->remove_flash_counter--;
277     }
279     gint ret = FALSE;
280     switch (event->type) {
281         case GDK_BUTTON_PRESS:
282             if (event->button.button == 1 && !event_context->space_panning) {
283                 // save drag origin
284                 event_context->xp = (gint) event->button.x;
285                 event_context->yp = (gint) event->button.y;
286                 event_context->within_tolerance = true;
287                 nc->shape_editor->cancel_hit();
289                 if (!(event->button.state & GDK_SHIFT_MASK)) {
290                     if (!nc->drag) {
291                         if (nc->shape_editor->has_nodepath() && selection->single() /* && item_over */) {
292                             // save drag origin
293                             bool over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), true);
294                             //only dragging curves
295                             if (over_stroke) {
296                                 ret = TRUE;
297                                 break;
298                             }
299                         }
300                     }
301                 }
302                 NR::Point const button_w(event->button.x,
303                                          event->button.y);
304                 NR::Point const button_dt(desktop->w2d(button_w));
305                 Inkscape::Rubberband::get()->start(desktop, button_dt);
306                 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
307                 desktop->updateNow();
308                 ret = TRUE;
309             }
310             break;
311         case GDK_MOTION_NOTIFY:
312             if (event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
314                 if ( event_context->within_tolerance
315                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
316                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
317                     break; // do not drag if we're within tolerance from origin
318                 }
320                 // The path went away while dragging; throw away any further motion
321                 // events until the mouse pointer is released.
322                 
323                 if (nc->shape_editor->hits_curve() && !nc->shape_editor->has_nodepath()) {
324                   break;
325                 }
327                 // Once the user has moved farther than tolerance from the original location
328                 // (indicating they intend to move the object, not click), then always process the
329                 // motion notify coordinates as given (no snapping back to origin)
330                 event_context->within_tolerance = false;
332                 // Once we determine what the user is doing (dragging either a node or the
333                 // selection rubberband), make sure we continue to perform that operation
334                 // until the mouse pointer is lifted.
335                 if (nc->current_state == SP_NODE_CONTEXT_INACTIVE) {
336                     if (nc->shape_editor->hits_curve() && nc->shape_editor->has_nodepath()) {
337                         nc->current_state = SP_NODE_CONTEXT_NODE_DRAGGING;
338                     } else {
339                         nc->current_state = SP_NODE_CONTEXT_RUBBERBAND_DRAGGING;
340                     }
341                 }
343                 switch (nc->current_state) {
344                     case SP_NODE_CONTEXT_NODE_DRAGGING:
345                         {
346                             nc->shape_editor->curve_drag (event->motion.x, event->motion.y);
348                             gobble_motion_events(GDK_BUTTON1_MASK);
349                             break;
350                         }
351                     case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING:
352                         if (Inkscape::Rubberband::get()->is_started()) {
353                             NR::Point const motion_w(event->motion.x,
354                                                 event->motion.y);
355                             NR::Point const motion_dt(desktop->w2d(motion_w));
356                             Inkscape::Rubberband::get()->move(motion_dt);
357                         }
358                         break;
359                 }
361                 nc->drag = TRUE;
362                 ret = TRUE;
363             } else {
364                 if (!nc->shape_editor->has_nodepath() || selection->singleItem() == NULL) {
365                     break;
366                 }
368                 bool over_stroke = false;
369                 over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->motion.x, event->motion.y), false);
371                 if (nc->cursor_drag && !over_stroke) {
372                     event_context->cursor_shape = cursor_node_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 = false;
377                 } else if (!nc->cursor_drag && over_stroke) {
378                     event_context->cursor_shape = cursor_node_d_xpm;
379                     event_context->hot_x = 1;
380                     event_context->hot_y = 1;
381                     sp_event_context_update_cursor(event_context);
382                     nc->cursor_drag = true;
383                 }
384             }
385             break;
387         case GDK_2BUTTON_PRESS:
388         case GDK_BUTTON_RELEASE:
389             if ( (event->button.button == 1) && (!nc->drag) && !event_context->space_panning) {
390                 // find out clicked item, disregarding groups, honoring Alt
391                 SPItem *item_clicked = sp_event_context_find_item (desktop,
392                         NR::Point(event->button.x, event->button.y),
393                         (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
395                 event_context->xp = event_context->yp = 0;
397                 bool over_stroke = false;
398                 if (nc->shape_editor->has_nodepath()) {
399                     over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), false);
400                 }
402                 if (item_clicked || over_stroke) {
403                     if (over_stroke || nc->added_node) {
404                         switch (event->type) {
405                             case GDK_BUTTON_RELEASE:
406                                 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
407                                     //add a node
408                                     nc->shape_editor->add_node_near_point();
409                                 } else {
410                                     if (nc->added_node) { // we just received double click, ignore release
411                                         nc->added_node = false;
412                                         break;
413                                     }
414                                     //select the segment
415                                     if (event->button.state & GDK_SHIFT_MASK) {
416                                         nc->shape_editor->select_segment_near_point(true);
417                                     } else {
418                                         nc->shape_editor->select_segment_near_point(false);
419                                     }
420                                     desktop->updateNow();
421                                 }
422                                 break;
423                             case GDK_2BUTTON_PRESS:
424                                 //add a node
425                                 nc->shape_editor->add_node_near_point();
426                                 nc->added_node = true;
427                                 break;
428                             default:
429                                 break;
430                         }
431                     } else if (event->button.state & GDK_SHIFT_MASK) {
432                         selection->toggle(item_clicked);
433                         desktop->updateNow();
434                     } else {
435                         selection->set(item_clicked);
436                         desktop->updateNow();
437                     }
438                     Inkscape::Rubberband::get()->stop();
439                     ret = TRUE;
440                     break;
441                 }
442             } 
443             if (event->type == GDK_BUTTON_RELEASE) {
444                 event_context->xp = event_context->yp = 0;
445                 if (event->button.button == 1) {
446                     NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
448                     if (nc->shape_editor->hits_curve() && !event_context->within_tolerance) { //drag curve
449                         nc->shape_editor->finish_drag();
450                     } else if (b && !event_context->within_tolerance) { // drag to select
451                         nc->shape_editor->select_rect(*b, event->button.state & GDK_SHIFT_MASK);
452                     } else {
453                         if (!(nc->rb_escaped)) { // unless something was cancelled
454                             if (nc->shape_editor->has_selection())
455                                 nc->shape_editor->deselect();
456                             else
457                                 sp_desktop_selection(desktop)->clear();
458                         }
459                     }
460                     ret = TRUE;
461                     Inkscape::Rubberband::get()->stop();
462                     desktop->updateNow();
463                     nc->rb_escaped = false;
464                     nc->drag = FALSE;
465                     nc->shape_editor->cancel_hit();
466                     nc->current_state = SP_NODE_CONTEXT_INACTIVE;
467                 }
468             }
469             break;
470         case GDK_KEY_PRESS:
471             switch (get_group0_keyval(&event->key)) {
472                 case GDK_Insert:
473                 case GDK_KP_Insert:
474                     // with any modifiers
475                     nc->shape_editor->add_node();
476                     ret = TRUE;
477                     break;
478                 case GDK_Delete:
479                 case GDK_KP_Delete:
480                 case GDK_BackSpace:
481                     if (MOD__CTRL_ONLY) {
482                         nc->shape_editor->delete_nodes();
483                     } else {
484                         nc->shape_editor->delete_nodes_preserving_shape();
485                     }
486                     ret = TRUE;
487                     break;
488                 case GDK_C:
489                 case GDK_c:
490                     if (MOD__SHIFT_ONLY) {
491                         nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_CUSP);
492                         ret = TRUE;
493                     }
494                     break;
495                 case GDK_S:
496                 case GDK_s:
497                     if (MOD__SHIFT_ONLY) {
498                         nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_SMOOTH);
499                         ret = TRUE;
500                     }
501                     break;
502                 case GDK_Y:
503                 case GDK_y:
504                     if (MOD__SHIFT_ONLY) {
505                         nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_SYMM);
506                         ret = TRUE;
507                     }
508                     break;
509                 case GDK_B:
510                 case GDK_b:
511                     if (MOD__SHIFT_ONLY) {
512                         nc->shape_editor->break_at_nodes();
513                         ret = TRUE;
514                     }
515                     break;
516                 case GDK_J:
517                 case GDK_j:
518                     if (MOD__SHIFT_ONLY) {
519                         nc->shape_editor->join_nodes();
520                         ret = TRUE;
521                     }
522                     break;
523                 case GDK_D:
524                 case GDK_d:
525                     if (MOD__SHIFT_ONLY) {
526                         nc->shape_editor->duplicate_nodes();
527                         ret = TRUE;
528                     }
529                     break;
530                 case GDK_L:
531                 case GDK_l:
532                     if (MOD__SHIFT_ONLY) {
533                         nc->shape_editor->set_type_of_segments(NR_LINETO);
534                         ret = TRUE;
535                     }
536                     break;
537                 case GDK_U:
538                 case GDK_u:
539                     if (MOD__SHIFT_ONLY) {
540                         nc->shape_editor->set_type_of_segments(NR_CURVETO);
541                         ret = TRUE;
542                     }
543                     break;
544                 case GDK_R:
545                 case GDK_r:
546                     if (MOD__SHIFT_ONLY) {
547                         // FIXME: add top panel button
548                         sp_selected_path_reverse();
549                         ret = TRUE;
550                     }
551                     break;
552                 case GDK_x:
553                 case GDK_X:
554                     if (MOD__ALT_ONLY) {
555                         desktop->setToolboxFocusTo ("altx-nodes");
556                         ret = TRUE;
557                     }
558                     break;
559                 case GDK_Left: // move selection left
560                 case GDK_KP_Left:
561                 case GDK_KP_4:
562                     if (!MOD__CTRL) { // not ctrl
563                         gint mul = 1 + gobble_key_events(
564                             get_group0_keyval(&event->key), 0); // with any mask
565                         if (MOD__ALT) { // alt
566                             if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(mul*-10, 0); // shift
567                             else nc->shape_editor->move_nodes_screen(mul*-1, 0); // no shift
568                         }
569                         else { // no alt
570                             if (MOD__SHIFT) nc->shape_editor->move_nodes(mul*-10*nudge, 0); // shift
571                             else nc->shape_editor->move_nodes(mul*-nudge, 0); // no shift
572                         }
573                         ret = TRUE;
574                     }
575                     break;
576                 case GDK_Up: // move selection up
577                 case GDK_KP_Up:
578                 case GDK_KP_8:
579                     if (!MOD__CTRL) { // not ctrl
580                         gint mul = 1 + gobble_key_events(
581                             get_group0_keyval(&event->key), 0); // with any mask
582                         if (MOD__ALT) { // alt
583                             if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(0, mul*10); // shift
584                             else nc->shape_editor->move_nodes_screen(0, mul*1); // no shift
585                         }
586                         else { // no alt
587                             if (MOD__SHIFT) nc->shape_editor->move_nodes(0, mul*10*nudge); // shift
588                             else nc->shape_editor->move_nodes(0, mul*nudge); // no shift
589                         }
590                         ret = TRUE;
591                     }
592                     break;
593                 case GDK_Right: // move selection right
594                 case GDK_KP_Right:
595                 case GDK_KP_6:
596                     if (!MOD__CTRL) { // not ctrl
597                         gint mul = 1 + gobble_key_events(
598                             get_group0_keyval(&event->key), 0); // with any mask
599                         if (MOD__ALT) { // alt
600                             if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(mul*10, 0); // shift
601                             else nc->shape_editor->move_nodes_screen(mul*1, 0); // no shift
602                         }
603                         else { // no alt
604                             if (MOD__SHIFT) nc->shape_editor->move_nodes(mul*10*nudge, 0); // shift
605                             else nc->shape_editor->move_nodes(mul*nudge, 0); // no shift
606                         }
607                         ret = TRUE;
608                     }
609                     break;
610                 case GDK_Down: // move selection down
611                 case GDK_KP_Down:
612                 case GDK_KP_2:
613                     if (!MOD__CTRL) { // not ctrl
614                         gint mul = 1 + gobble_key_events(
615                             get_group0_keyval(&event->key), 0); // with any mask
616                         if (MOD__ALT) { // alt
617                             if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(0, mul*-10); // shift
618                             else nc->shape_editor->move_nodes_screen(0, mul*-1); // no shift
619                         }
620                         else { // no alt
621                             if (MOD__SHIFT) nc->shape_editor->move_nodes(0, mul*-10*nudge); // shift
622                             else nc->shape_editor->move_nodes(0, mul*-nudge); // no shift
623                         }
624                         ret = TRUE;
625                     }
626                     break;
627                 case GDK_Escape:
628                 {
629                     NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
630                     if (b) {
631                         Inkscape::Rubberband::get()->stop();
632                         nc->current_state = SP_NODE_CONTEXT_INACTIVE;
633                         nc->rb_escaped = true;
634                     } else {
635                         if (nc->shape_editor->has_selection()) {
636                             nc->shape_editor->deselect();
637                         } else {
638                             sp_desktop_selection(desktop)->clear();
639                         }
640                     }
641                     ret = TRUE;
642                     break;
643                 }
645                 case GDK_bracketleft:
646                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
647                         if (nc->leftctrl)
648                             nc->shape_editor->rotate_nodes (M_PI/snaps, -1, false);
649                         if (nc->rightctrl)
650                             nc->shape_editor->rotate_nodes (M_PI/snaps, 1, false);
651                     } else if ( MOD__ALT && !MOD__CTRL ) {
652                         if (nc->leftalt && nc->rightalt)
653                             nc->shape_editor->rotate_nodes (1, 0, true);
654                         else {
655                             if (nc->leftalt)
656                                 nc->shape_editor->rotate_nodes (1, -1, true);
657                             if (nc->rightalt)
658                                 nc->shape_editor->rotate_nodes (1, 1, true);
659                         }
660                     } else if ( snaps != 0 ) {
661                         nc->shape_editor->rotate_nodes (M_PI/snaps, 0, false);
662                     }
663                     ret = TRUE;
664                     break;
665                 case GDK_bracketright:
666                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
667                         if (nc->leftctrl)
668                             nc->shape_editor->rotate_nodes (-M_PI/snaps, -1, false);
669                         if (nc->rightctrl)
670                             nc->shape_editor->rotate_nodes (-M_PI/snaps, 1, false);
671                     } else if ( MOD__ALT && !MOD__CTRL ) {
672                         if (nc->leftalt && nc->rightalt)
673                             nc->shape_editor->rotate_nodes (-1, 0, true);
674                         else {
675                             if (nc->leftalt)
676                                 nc->shape_editor->rotate_nodes (-1, -1, true);
677                             if (nc->rightalt)
678                                 nc->shape_editor->rotate_nodes (-1, 1, true);
679                         }
680                     } else if ( snaps != 0 ) {
681                         nc->shape_editor->rotate_nodes (-M_PI/snaps, 0, false);
682                     }
683                     ret = TRUE;
684                     break;
685                 case GDK_less:
686                 case GDK_comma:
687                     if (MOD__CTRL) {
688                         if (nc->leftctrl)
689                             nc->shape_editor->scale_nodes(-offset, -1);
690                         if (nc->rightctrl)
691                             nc->shape_editor->scale_nodes(-offset, 1);
692                     } else if (MOD__ALT) {
693                         if (nc->leftalt && nc->rightalt)
694                             nc->shape_editor->scale_nodes_screen (-1, 0);
695                         else {
696                             if (nc->leftalt)
697                                 nc->shape_editor->scale_nodes_screen (-1, -1);
698                             if (nc->rightalt)
699                                 nc->shape_editor->scale_nodes_screen (-1, 1);
700                         }
701                     } else {
702                         nc->shape_editor->scale_nodes (-offset, 0);
703                     }
704                     ret = TRUE;
705                     break;
706                 case GDK_greater:
707                 case GDK_period:
708                     if (MOD__CTRL) {
709                         if (nc->leftctrl)
710                             nc->shape_editor->scale_nodes (offset, -1);
711                         if (nc->rightctrl)
712                             nc->shape_editor->scale_nodes (offset, 1);
713                     } else if (MOD__ALT) {
714                         if (nc->leftalt && nc->rightalt)
715                             nc->shape_editor->scale_nodes_screen (1, 0);
716                         else {
717                             if (nc->leftalt)
718                                 nc->shape_editor->scale_nodes_screen (1, -1);
719                             if (nc->rightalt)
720                                 nc->shape_editor->scale_nodes_screen (1, 1);
721                         }
722                     } else {
723                         nc->shape_editor->scale_nodes (offset, 0);
724                     }
725                     ret = TRUE;
726                     break;
728                 case GDK_Alt_L:
729                     nc->leftalt = TRUE;
730                     sp_node_context_show_modifier_tip(event_context, event);
731                     break;
732                 case GDK_Alt_R:
733                     nc->rightalt = TRUE;
734                     sp_node_context_show_modifier_tip(event_context, event);
735                     break;
736                 case GDK_Control_L:
737                     nc->leftctrl = TRUE;
738                     sp_node_context_show_modifier_tip(event_context, event);
739                     break;
740                 case GDK_Control_R:
741                     nc->rightctrl = TRUE;
742                     sp_node_context_show_modifier_tip(event_context, event);
743                     break;
744                 case GDK_Shift_L:
745                 case GDK_Shift_R:
746                 case GDK_Meta_L:
747                 case GDK_Meta_R:
748                     sp_node_context_show_modifier_tip(event_context, event);
749                     break;
750                 default:
751                     ret = node_key(event);
752                     break;
753             }
754             break;
755         case GDK_KEY_RELEASE:
756             switch (get_group0_keyval(&event->key)) {
757                 case GDK_Alt_L:
758                     nc->leftalt = FALSE;
759                     event_context->defaultMessageContext()->clear();
760                     break;
761                 case GDK_Alt_R:
762                     nc->rightalt = FALSE;
763                     event_context->defaultMessageContext()->clear();
764                     break;
765                 case GDK_Control_L:
766                     nc->leftctrl = FALSE;
767                     event_context->defaultMessageContext()->clear();
768                     break;
769                 case GDK_Control_R:
770                     nc->rightctrl = FALSE;
771                     event_context->defaultMessageContext()->clear();
772                     break;
773                 case GDK_Shift_L:
774                 case GDK_Shift_R:
775                 case GDK_Meta_L:
776                 case GDK_Meta_R:
777                     event_context->defaultMessageContext()->clear();
778                     break;
779             }
780             break;
781         default:
782             break;
783     }
785     if (!ret) {
786         if (((SPEventContextClass *) parent_class)->root_handler)
787             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
788     }
790     return ret;
794 /*
795   Local Variables:
796   mode:c++
797   c-file-style:"stroustrup"
798   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
799   indent-tabs-mode:nil
800   fill-column:99
801   End:
802 */
803 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :