Code

Add flashing outline of path when hovering over it. (coded in 20 minutes so perhaps...
[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;
109 static void
110 sp_node_context_dispose(GObject *object)
112     SPNodeContext *nc = SP_NODE_CONTEXT(object);
113     SPEventContext *ec = SP_EVENT_CONTEXT(object);
115     ec->enableGrDrag(false);
117     nc->sel_changed_connection.disconnect();
118     nc->sel_changed_connection.~connection();
120     delete nc->shape_editor;
122     if (nc->_node_message_context) {
123         delete nc->_node_message_context;
124     }
126     G_OBJECT_CLASS(parent_class)->dispose(object);
129 static void
130 sp_node_context_setup(SPEventContext *ec)
132     SPNodeContext *nc = SP_NODE_CONTEXT(ec);
134     if (((SPEventContextClass *) parent_class)->setup)
135         ((SPEventContextClass *) parent_class)->setup(ec);
137     nc->sel_changed_connection.disconnect();
138     nc->sel_changed_connection = sp_desktop_selection(ec->desktop)->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
140     Inkscape::Selection *selection = sp_desktop_selection(ec->desktop);
141     SPItem *item = selection->singleItem();
143     nc->shape_editor = new ShapeEditor(ec->desktop);
145     nc->rb_escaped = false;
147     nc->cursor_drag = false;
149     nc->added_node = false;
151     nc->current_state = SP_NODE_CONTEXT_INACTIVE;
153     if (item) {
154         nc->shape_editor->set_item(item);
155     }
157     if (prefs_get_int_attribute("tools.nodes", "selcue", 0) != 0) {
158         ec->enableSelectionCue();
159     }
161     if (prefs_get_int_attribute("tools.nodes", "gradientdrag", 0) != 0) {
162         ec->enableGrDrag();
163     }
165     ec->desktop->emitToolSubselectionChanged(NULL); // sets the coord entry fields to inactive
167     nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
169     nc->shape_editor->update_statusbar();
172 /**
173 \brief  Callback that processes the "changed" signal on the selection;
174 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
175 */
176 void
177 sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
179     SPNodeContext *nc = SP_NODE_CONTEXT(data);
181     // TODO: update ShapeEditorsCollective instead
182     nc->shape_editor->unset_item();
183     SPItem *item = selection->singleItem(); 
184     nc->shape_editor->set_item(item);
186     nc->shape_editor->update_statusbar();
189 void
190 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
192     sp_event_show_modifier_tip
193         (event_context->defaultMessageContext(), event,
194          _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
195          _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
196          _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
200 static gint
201 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
203     gint ret = FALSE;
204     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
206     if (nc->flashed_item != item) {
207         // we entered a new item
208         nc->flashed_item = item;
209         SPDesktop *desktop = event_context->desktop;
210         if (nc->flash_tempitem) {
211             desktop->remove_temporary_canvasitem(nc->flash_tempitem);
212         }
214         if (SP_IS_PATH(item)) {
215         // This should be put somewhere else under the name of "generate helperpath" or something. Because basically this is copied of code from nodepath...
216             SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(item));
217             SPCurve *flash_curve = sp_curve_copy(curve_new);
218             sp_curve_transform(flash_curve, sp_item_i2d_affine(item) );
219             SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
220             sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), 0xff0000ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
221             sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
222             sp_canvas_item_show(canvasitem);
223             sp_curve_unref(flash_curve);
224             nc->flash_tempitem = desktop->add_temporary_canvasitem (canvasitem, 1000);
225         }
226     }
228     if (((SPEventContextClass *) parent_class)->item_handler)
229         ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
231     return ret;
234 static gint
235 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
237     SPDesktop *desktop = event_context->desktop;
238     Inkscape::Selection *selection = sp_desktop_selection (desktop);
240     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
241     double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
242     event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
243     int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
244     double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
246     gint ret = FALSE;
247     switch (event->type) {
248         case GDK_BUTTON_PRESS:
249             if (event->button.button == 1 && !event_context->space_panning) {
250                 // save drag origin
251                 event_context->xp = (gint) event->button.x;
252                 event_context->yp = (gint) event->button.y;
253                 event_context->within_tolerance = true;
254                 nc->shape_editor->cancel_hit();
256                 if (!(event->button.state & GDK_SHIFT_MASK)) {
257                     if (!nc->drag) {
258                         if (nc->shape_editor->has_nodepath() && selection->single() /* && item_over */) {
259                             // save drag origin
260                             bool over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), true);
261                             //only dragging curves
262                             if (over_stroke) {
263                                 ret = TRUE;
264                                 break;
265                             }
266                         }
267                     }
268                 }
269                 NR::Point const button_w(event->button.x,
270                                          event->button.y);
271                 NR::Point const button_dt(desktop->w2d(button_w));
272                 Inkscape::Rubberband::get()->start(desktop, button_dt);
273                 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
274                 desktop->updateNow();
275                 ret = TRUE;
276             }
277             break;
278         case GDK_MOTION_NOTIFY:
279             if (event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
281                 if ( event_context->within_tolerance
282                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
283                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
284                     break; // do not drag if we're within tolerance from origin
285                 }
287                 // The path went away while dragging; throw away any further motion
288                 // events until the mouse pointer is released.
289                 
290                 if (nc->shape_editor->hits_curve() && !nc->shape_editor->has_nodepath()) {
291                   break;
292                 }
294                 // Once the user has moved farther than tolerance from the original location
295                 // (indicating they intend to move the object, not click), then always process the
296                 // motion notify coordinates as given (no snapping back to origin)
297                 event_context->within_tolerance = false;
299                 // Once we determine what the user is doing (dragging either a node or the
300                 // selection rubberband), make sure we continue to perform that operation
301                 // until the mouse pointer is lifted.
302                 if (nc->current_state == SP_NODE_CONTEXT_INACTIVE) {
303                     if (nc->shape_editor->hits_curve() && nc->shape_editor->has_nodepath()) {
304                         nc->current_state = SP_NODE_CONTEXT_NODE_DRAGGING;
305                     } else {
306                         nc->current_state = SP_NODE_CONTEXT_RUBBERBAND_DRAGGING;
307                     }
308                 }
310                 switch (nc->current_state) {
311                     case SP_NODE_CONTEXT_NODE_DRAGGING:
312                         {
313                             nc->shape_editor->curve_drag (event->motion.x, event->motion.y);
315                             gobble_motion_events(GDK_BUTTON1_MASK);
316                             break;
317                         }
318                     case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING:
319                         if (Inkscape::Rubberband::get()->is_started()) {
320                             NR::Point const motion_w(event->motion.x,
321                                                 event->motion.y);
322                             NR::Point const motion_dt(desktop->w2d(motion_w));
323                             Inkscape::Rubberband::get()->move(motion_dt);
324                         }
325                         break;
326                 }
328                 nc->drag = TRUE;
329                 ret = TRUE;
330             } else {
331                 if (!nc->shape_editor->has_nodepath() || selection->singleItem() == NULL) {
332                     break;
333                 }
335                 bool over_stroke = false;
336                 over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->motion.x, event->motion.y), false);
338                 if (nc->cursor_drag && !over_stroke) {
339                     event_context->cursor_shape = cursor_node_xpm;
340                     event_context->hot_x = 1;
341                     event_context->hot_y = 1;
342                     sp_event_context_update_cursor(event_context);
343                     nc->cursor_drag = false;
344                 } else if (!nc->cursor_drag && over_stroke) {
345                     event_context->cursor_shape = cursor_node_d_xpm;
346                     event_context->hot_x = 1;
347                     event_context->hot_y = 1;
348                     sp_event_context_update_cursor(event_context);
349                     nc->cursor_drag = true;
350                 }
351             }
352             break;
354         case GDK_2BUTTON_PRESS:
355         case GDK_BUTTON_RELEASE:
356             if ( (event->button.button == 1) && (!nc->drag) && !event_context->space_panning) {
357                 // find out clicked item, disregarding groups, honoring Alt
358                 SPItem *item_clicked = sp_event_context_find_item (desktop,
359                         NR::Point(event->button.x, event->button.y),
360                         (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
362                 event_context->xp = event_context->yp = 0;
364                 bool over_stroke = false;
365                 if (nc->shape_editor->has_nodepath()) {
366                     over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), false);
367                 }
369                 if (item_clicked || over_stroke) {
370                     if (over_stroke || nc->added_node) {
371                         switch (event->type) {
372                             case GDK_BUTTON_RELEASE:
373                                 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
374                                     //add a node
375                                     nc->shape_editor->add_node_near_point();
376                                 } else {
377                                     if (nc->added_node) { // we just received double click, ignore release
378                                         nc->added_node = false;
379                                         break;
380                                     }
381                                     //select the segment
382                                     if (event->button.state & GDK_SHIFT_MASK) {
383                                         nc->shape_editor->select_segment_near_point(true);
384                                     } else {
385                                         nc->shape_editor->select_segment_near_point(false);
386                                     }
387                                     desktop->updateNow();
388                                 }
389                                 break;
390                             case GDK_2BUTTON_PRESS:
391                                 //add a node
392                                 nc->shape_editor->add_node_near_point();
393                                 nc->added_node = true;
394                                 break;
395                             default:
396                                 break;
397                         }
398                     } else if (event->button.state & GDK_SHIFT_MASK) {
399                         selection->toggle(item_clicked);
400                         desktop->updateNow();
401                     } else {
402                         selection->set(item_clicked);
403                         desktop->updateNow();
404                     }
405                     Inkscape::Rubberband::get()->stop();
406                     ret = TRUE;
407                     break;
408                 }
409             } 
410             if (event->type == GDK_BUTTON_RELEASE) {
411                 event_context->xp = event_context->yp = 0;
412                 if (event->button.button == 1) {
413                     NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
415                     if (nc->shape_editor->hits_curve() && !event_context->within_tolerance) { //drag curve
416                         nc->shape_editor->finish_drag();
417                     } else if (b && !event_context->within_tolerance) { // drag to select
418                         nc->shape_editor->select_rect(*b, event->button.state & GDK_SHIFT_MASK);
419                     } else {
420                         if (!(nc->rb_escaped)) { // unless something was cancelled
421                             if (nc->shape_editor->has_selection())
422                                 nc->shape_editor->deselect();
423                             else
424                                 sp_desktop_selection(desktop)->clear();
425                         }
426                     }
427                     ret = TRUE;
428                     Inkscape::Rubberband::get()->stop();
429                     desktop->updateNow();
430                     nc->rb_escaped = false;
431                     nc->drag = FALSE;
432                     nc->shape_editor->cancel_hit();
433                     nc->current_state = SP_NODE_CONTEXT_INACTIVE;
434                 }
435             }
436             break;
437         case GDK_KEY_PRESS:
438             switch (get_group0_keyval(&event->key)) {
439                 case GDK_Insert:
440                 case GDK_KP_Insert:
441                     // with any modifiers
442                     nc->shape_editor->add_node();
443                     ret = TRUE;
444                     break;
445                 case GDK_Delete:
446                 case GDK_KP_Delete:
447                 case GDK_BackSpace:
448                     if (MOD__CTRL_ONLY) {
449                         nc->shape_editor->delete_nodes();
450                     } else {
451                         nc->shape_editor->delete_nodes_preserving_shape();
452                     }
453                     ret = TRUE;
454                     break;
455                 case GDK_C:
456                 case GDK_c:
457                     if (MOD__SHIFT_ONLY) {
458                         nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_CUSP);
459                         ret = TRUE;
460                     }
461                     break;
462                 case GDK_S:
463                 case GDK_s:
464                     if (MOD__SHIFT_ONLY) {
465                         nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_SMOOTH);
466                         ret = TRUE;
467                     }
468                     break;
469                 case GDK_Y:
470                 case GDK_y:
471                     if (MOD__SHIFT_ONLY) {
472                         nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_SYMM);
473                         ret = TRUE;
474                     }
475                     break;
476                 case GDK_B:
477                 case GDK_b:
478                     if (MOD__SHIFT_ONLY) {
479                         nc->shape_editor->break_at_nodes();
480                         ret = TRUE;
481                     }
482                     break;
483                 case GDK_J:
484                 case GDK_j:
485                     if (MOD__SHIFT_ONLY) {
486                         nc->shape_editor->join_nodes();
487                         ret = TRUE;
488                     }
489                     break;
490                 case GDK_D:
491                 case GDK_d:
492                     if (MOD__SHIFT_ONLY) {
493                         nc->shape_editor->duplicate_nodes();
494                         ret = TRUE;
495                     }
496                     break;
497                 case GDK_L:
498                 case GDK_l:
499                     if (MOD__SHIFT_ONLY) {
500                         nc->shape_editor->set_type_of_segments(NR_LINETO);
501                         ret = TRUE;
502                     }
503                     break;
504                 case GDK_U:
505                 case GDK_u:
506                     if (MOD__SHIFT_ONLY) {
507                         nc->shape_editor->set_type_of_segments(NR_CURVETO);
508                         ret = TRUE;
509                     }
510                     break;
511                 case GDK_R:
512                 case GDK_r:
513                     if (MOD__SHIFT_ONLY) {
514                         // FIXME: add top panel button
515                         sp_selected_path_reverse();
516                         ret = TRUE;
517                     }
518                     break;
519                 case GDK_x:
520                 case GDK_X:
521                     if (MOD__ALT_ONLY) {
522                         desktop->setToolboxFocusTo ("altx-nodes");
523                         ret = TRUE;
524                     }
525                     break;
526                 case GDK_Left: // move selection left
527                 case GDK_KP_Left:
528                 case GDK_KP_4:
529                     if (!MOD__CTRL) { // not ctrl
530                         gint mul = 1 + gobble_key_events(
531                             get_group0_keyval(&event->key), 0); // with any mask
532                         if (MOD__ALT) { // alt
533                             if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(mul*-10, 0); // shift
534                             else nc->shape_editor->move_nodes_screen(mul*-1, 0); // no shift
535                         }
536                         else { // no alt
537                             if (MOD__SHIFT) nc->shape_editor->move_nodes(mul*-10*nudge, 0); // shift
538                             else nc->shape_editor->move_nodes(mul*-nudge, 0); // no shift
539                         }
540                         ret = TRUE;
541                     }
542                     break;
543                 case GDK_Up: // move selection up
544                 case GDK_KP_Up:
545                 case GDK_KP_8:
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(0, mul*10); // shift
551                             else nc->shape_editor->move_nodes_screen(0, mul*1); // no shift
552                         }
553                         else { // no alt
554                             if (MOD__SHIFT) nc->shape_editor->move_nodes(0, mul*10*nudge); // shift
555                             else nc->shape_editor->move_nodes(0, mul*nudge); // no shift
556                         }
557                         ret = TRUE;
558                     }
559                     break;
560                 case GDK_Right: // move selection right
561                 case GDK_KP_Right:
562                 case GDK_KP_6:
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(mul*10, 0); // shift
568                             else nc->shape_editor->move_nodes_screen(mul*1, 0); // no shift
569                         }
570                         else { // no alt
571                             if (MOD__SHIFT) nc->shape_editor->move_nodes(mul*10*nudge, 0); // shift
572                             else nc->shape_editor->move_nodes(mul*nudge, 0); // no shift
573                         }
574                         ret = TRUE;
575                     }
576                     break;
577                 case GDK_Down: // move selection down
578                 case GDK_KP_Down:
579                 case GDK_KP_2:
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(0, mul*-10); // shift
585                             else nc->shape_editor->move_nodes_screen(0, mul*-1); // no shift
586                         }
587                         else { // no alt
588                             if (MOD__SHIFT) nc->shape_editor->move_nodes(0, mul*-10*nudge); // shift
589                             else nc->shape_editor->move_nodes(0, mul*-nudge); // no shift
590                         }
591                         ret = TRUE;
592                     }
593                     break;
594                 case GDK_Escape:
595                 {
596                     NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
597                     if (b) {
598                         Inkscape::Rubberband::get()->stop();
599                         nc->current_state = SP_NODE_CONTEXT_INACTIVE;
600                         nc->rb_escaped = true;
601                     } else {
602                         if (nc->shape_editor->has_selection()) {
603                             nc->shape_editor->deselect();
604                         } else {
605                             sp_desktop_selection(desktop)->clear();
606                         }
607                     }
608                     ret = TRUE;
609                     break;
610                 }
612                 case GDK_bracketleft:
613                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
614                         if (nc->leftctrl)
615                             nc->shape_editor->rotate_nodes (M_PI/snaps, -1, false);
616                         if (nc->rightctrl)
617                             nc->shape_editor->rotate_nodes (M_PI/snaps, 1, false);
618                     } else if ( MOD__ALT && !MOD__CTRL ) {
619                         if (nc->leftalt && nc->rightalt)
620                             nc->shape_editor->rotate_nodes (1, 0, true);
621                         else {
622                             if (nc->leftalt)
623                                 nc->shape_editor->rotate_nodes (1, -1, true);
624                             if (nc->rightalt)
625                                 nc->shape_editor->rotate_nodes (1, 1, true);
626                         }
627                     } else if ( snaps != 0 ) {
628                         nc->shape_editor->rotate_nodes (M_PI/snaps, 0, false);
629                     }
630                     ret = TRUE;
631                     break;
632                 case GDK_bracketright:
633                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
634                         if (nc->leftctrl)
635                             nc->shape_editor->rotate_nodes (-M_PI/snaps, -1, false);
636                         if (nc->rightctrl)
637                             nc->shape_editor->rotate_nodes (-M_PI/snaps, 1, false);
638                     } else if ( MOD__ALT && !MOD__CTRL ) {
639                         if (nc->leftalt && nc->rightalt)
640                             nc->shape_editor->rotate_nodes (-1, 0, true);
641                         else {
642                             if (nc->leftalt)
643                                 nc->shape_editor->rotate_nodes (-1, -1, true);
644                             if (nc->rightalt)
645                                 nc->shape_editor->rotate_nodes (-1, 1, true);
646                         }
647                     } else if ( snaps != 0 ) {
648                         nc->shape_editor->rotate_nodes (-M_PI/snaps, 0, false);
649                     }
650                     ret = TRUE;
651                     break;
652                 case GDK_less:
653                 case GDK_comma:
654                     if (MOD__CTRL) {
655                         if (nc->leftctrl)
656                             nc->shape_editor->scale_nodes(-offset, -1);
657                         if (nc->rightctrl)
658                             nc->shape_editor->scale_nodes(-offset, 1);
659                     } else if (MOD__ALT) {
660                         if (nc->leftalt && nc->rightalt)
661                             nc->shape_editor->scale_nodes_screen (-1, 0);
662                         else {
663                             if (nc->leftalt)
664                                 nc->shape_editor->scale_nodes_screen (-1, -1);
665                             if (nc->rightalt)
666                                 nc->shape_editor->scale_nodes_screen (-1, 1);
667                         }
668                     } else {
669                         nc->shape_editor->scale_nodes (-offset, 0);
670                     }
671                     ret = TRUE;
672                     break;
673                 case GDK_greater:
674                 case GDK_period:
675                     if (MOD__CTRL) {
676                         if (nc->leftctrl)
677                             nc->shape_editor->scale_nodes (offset, -1);
678                         if (nc->rightctrl)
679                             nc->shape_editor->scale_nodes (offset, 1);
680                     } else if (MOD__ALT) {
681                         if (nc->leftalt && nc->rightalt)
682                             nc->shape_editor->scale_nodes_screen (1, 0);
683                         else {
684                             if (nc->leftalt)
685                                 nc->shape_editor->scale_nodes_screen (1, -1);
686                             if (nc->rightalt)
687                                 nc->shape_editor->scale_nodes_screen (1, 1);
688                         }
689                     } else {
690                         nc->shape_editor->scale_nodes (offset, 0);
691                     }
692                     ret = TRUE;
693                     break;
695                 case GDK_Alt_L:
696                     nc->leftalt = TRUE;
697                     sp_node_context_show_modifier_tip(event_context, event);
698                     break;
699                 case GDK_Alt_R:
700                     nc->rightalt = TRUE;
701                     sp_node_context_show_modifier_tip(event_context, event);
702                     break;
703                 case GDK_Control_L:
704                     nc->leftctrl = TRUE;
705                     sp_node_context_show_modifier_tip(event_context, event);
706                     break;
707                 case GDK_Control_R:
708                     nc->rightctrl = TRUE;
709                     sp_node_context_show_modifier_tip(event_context, event);
710                     break;
711                 case GDK_Shift_L:
712                 case GDK_Shift_R:
713                 case GDK_Meta_L:
714                 case GDK_Meta_R:
715                     sp_node_context_show_modifier_tip(event_context, event);
716                     break;
717                 default:
718                     ret = node_key(event);
719                     break;
720             }
721             break;
722         case GDK_KEY_RELEASE:
723             switch (get_group0_keyval(&event->key)) {
724                 case GDK_Alt_L:
725                     nc->leftalt = FALSE;
726                     event_context->defaultMessageContext()->clear();
727                     break;
728                 case GDK_Alt_R:
729                     nc->rightalt = FALSE;
730                     event_context->defaultMessageContext()->clear();
731                     break;
732                 case GDK_Control_L:
733                     nc->leftctrl = FALSE;
734                     event_context->defaultMessageContext()->clear();
735                     break;
736                 case GDK_Control_R:
737                     nc->rightctrl = FALSE;
738                     event_context->defaultMessageContext()->clear();
739                     break;
740                 case GDK_Shift_L:
741                 case GDK_Shift_R:
742                 case GDK_Meta_L:
743                 case GDK_Meta_R:
744                     event_context->defaultMessageContext()->clear();
745                     break;
746             }
747             break;
748         default:
749             break;
750     }
752     if (!ret) {
753         if (((SPEventContextClass *) parent_class)->root_handler)
754             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
755     }
757     return ret;
761 /*
762   Local Variables:
763   mode:c++
764   c-file-style:"stroustrup"
765   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
766   indent-tabs-mode:nil
767   fill-column:99
768   End:
769 */
770 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :