Code

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