Code

tweak the helperpath flashing:
[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     nc->remove_flash_counter = 3; // for some reason root_handler is called twice after each item_handler...
208     if (nc->flashed_item != item) {
209         // we entered a new item
210         nc->flashed_item = item;
211         SPDesktop *desktop = event_context->desktop;
212         if (nc->flash_tempitem) {
213             desktop->remove_temporary_canvasitem(nc->flash_tempitem);
214             nc->flash_tempitem = NULL;
215         }
217         if (SP_IS_PATH(item)) {
218         // This should be put somewhere else under the name of "generate helperpath" or something. Because basically this is copied of code from nodepath...
219             SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(item));
220             SPCurve *flash_curve = sp_curve_copy(curve_new);
221             sp_curve_transform(flash_curve, sp_item_i2d_affine(item) );
222             SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
223         // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
224         // unless we also flash the nodes...
225             sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), 0xff0000ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
226             sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
227             sp_canvas_item_show(canvasitem);
228             sp_curve_unref(flash_curve);
229             nc->flash_tempitem = desktop->add_temporary_canvasitem (canvasitem, 600);
230         }
231     }
233     if (((SPEventContextClass *) parent_class)->item_handler)
234         ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
236     return ret;
239 static gint
240 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
242     SPDesktop *desktop = event_context->desktop;
243     Inkscape::Selection *selection = sp_desktop_selection (desktop);
245     SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
246     double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
247     event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
248     int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
249     double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
251     if ( (nc->flash_tempitem) && (nc->remove_flash_counter <= 0) ) {
252         desktop->remove_temporary_canvasitem(nc->flash_tempitem);
253         nc->flash_tempitem = NULL;
254         nc->flashed_item = NULL; // also reset this one, so the next time the same object is hovered over it shows again the highlight
255     } else {
256         nc->remove_flash_counter--;
257     }
259     gint ret = FALSE;
260     switch (event->type) {
261         case GDK_BUTTON_PRESS:
262             if (event->button.button == 1 && !event_context->space_panning) {
263                 // save drag origin
264                 event_context->xp = (gint) event->button.x;
265                 event_context->yp = (gint) event->button.y;
266                 event_context->within_tolerance = true;
267                 nc->shape_editor->cancel_hit();
269                 if (!(event->button.state & GDK_SHIFT_MASK)) {
270                     if (!nc->drag) {
271                         if (nc->shape_editor->has_nodepath() && selection->single() /* && item_over */) {
272                             // save drag origin
273                             bool over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), true);
274                             //only dragging curves
275                             if (over_stroke) {
276                                 ret = TRUE;
277                                 break;
278                             }
279                         }
280                     }
281                 }
282                 NR::Point const button_w(event->button.x,
283                                          event->button.y);
284                 NR::Point const button_dt(desktop->w2d(button_w));
285                 Inkscape::Rubberband::get()->start(desktop, button_dt);
286                 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
287                 desktop->updateNow();
288                 ret = TRUE;
289             }
290             break;
291         case GDK_MOTION_NOTIFY:
292             if (event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) {
294                 if ( event_context->within_tolerance
295                      && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
296                      && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
297                     break; // do not drag if we're within tolerance from origin
298                 }
300                 // The path went away while dragging; throw away any further motion
301                 // events until the mouse pointer is released.
302                 
303                 if (nc->shape_editor->hits_curve() && !nc->shape_editor->has_nodepath()) {
304                   break;
305                 }
307                 // Once the user has moved farther than tolerance from the original location
308                 // (indicating they intend to move the object, not click), then always process the
309                 // motion notify coordinates as given (no snapping back to origin)
310                 event_context->within_tolerance = false;
312                 // Once we determine what the user is doing (dragging either a node or the
313                 // selection rubberband), make sure we continue to perform that operation
314                 // until the mouse pointer is lifted.
315                 if (nc->current_state == SP_NODE_CONTEXT_INACTIVE) {
316                     if (nc->shape_editor->hits_curve() && nc->shape_editor->has_nodepath()) {
317                         nc->current_state = SP_NODE_CONTEXT_NODE_DRAGGING;
318                     } else {
319                         nc->current_state = SP_NODE_CONTEXT_RUBBERBAND_DRAGGING;
320                     }
321                 }
323                 switch (nc->current_state) {
324                     case SP_NODE_CONTEXT_NODE_DRAGGING:
325                         {
326                             nc->shape_editor->curve_drag (event->motion.x, event->motion.y);
328                             gobble_motion_events(GDK_BUTTON1_MASK);
329                             break;
330                         }
331                     case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING:
332                         if (Inkscape::Rubberband::get()->is_started()) {
333                             NR::Point const motion_w(event->motion.x,
334                                                 event->motion.y);
335                             NR::Point const motion_dt(desktop->w2d(motion_w));
336                             Inkscape::Rubberband::get()->move(motion_dt);
337                         }
338                         break;
339                 }
341                 nc->drag = TRUE;
342                 ret = TRUE;
343             } else {
344                 if (!nc->shape_editor->has_nodepath() || selection->singleItem() == NULL) {
345                     break;
346                 }
348                 bool over_stroke = false;
349                 over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->motion.x, event->motion.y), false);
351                 if (nc->cursor_drag && !over_stroke) {
352                     event_context->cursor_shape = cursor_node_xpm;
353                     event_context->hot_x = 1;
354                     event_context->hot_y = 1;
355                     sp_event_context_update_cursor(event_context);
356                     nc->cursor_drag = false;
357                 } else if (!nc->cursor_drag && over_stroke) {
358                     event_context->cursor_shape = cursor_node_d_xpm;
359                     event_context->hot_x = 1;
360                     event_context->hot_y = 1;
361                     sp_event_context_update_cursor(event_context);
362                     nc->cursor_drag = true;
363                 }
364             }
365             break;
367         case GDK_2BUTTON_PRESS:
368         case GDK_BUTTON_RELEASE:
369             if ( (event->button.button == 1) && (!nc->drag) && !event_context->space_panning) {
370                 // find out clicked item, disregarding groups, honoring Alt
371                 SPItem *item_clicked = sp_event_context_find_item (desktop,
372                         NR::Point(event->button.x, event->button.y),
373                         (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
375                 event_context->xp = event_context->yp = 0;
377                 bool over_stroke = false;
378                 if (nc->shape_editor->has_nodepath()) {
379                     over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), false);
380                 }
382                 if (item_clicked || over_stroke) {
383                     if (over_stroke || nc->added_node) {
384                         switch (event->type) {
385                             case GDK_BUTTON_RELEASE:
386                                 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
387                                     //add a node
388                                     nc->shape_editor->add_node_near_point();
389                                 } else {
390                                     if (nc->added_node) { // we just received double click, ignore release
391                                         nc->added_node = false;
392                                         break;
393                                     }
394                                     //select the segment
395                                     if (event->button.state & GDK_SHIFT_MASK) {
396                                         nc->shape_editor->select_segment_near_point(true);
397                                     } else {
398                                         nc->shape_editor->select_segment_near_point(false);
399                                     }
400                                     desktop->updateNow();
401                                 }
402                                 break;
403                             case GDK_2BUTTON_PRESS:
404                                 //add a node
405                                 nc->shape_editor->add_node_near_point();
406                                 nc->added_node = true;
407                                 break;
408                             default:
409                                 break;
410                         }
411                     } else if (event->button.state & GDK_SHIFT_MASK) {
412                         selection->toggle(item_clicked);
413                         desktop->updateNow();
414                     } else {
415                         selection->set(item_clicked);
416                         desktop->updateNow();
417                     }
418                     Inkscape::Rubberband::get()->stop();
419                     ret = TRUE;
420                     break;
421                 }
422             } 
423             if (event->type == GDK_BUTTON_RELEASE) {
424                 event_context->xp = event_context->yp = 0;
425                 if (event->button.button == 1) {
426                     NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
428                     if (nc->shape_editor->hits_curve() && !event_context->within_tolerance) { //drag curve
429                         nc->shape_editor->finish_drag();
430                     } else if (b && !event_context->within_tolerance) { // drag to select
431                         nc->shape_editor->select_rect(*b, event->button.state & GDK_SHIFT_MASK);
432                     } else {
433                         if (!(nc->rb_escaped)) { // unless something was cancelled
434                             if (nc->shape_editor->has_selection())
435                                 nc->shape_editor->deselect();
436                             else
437                                 sp_desktop_selection(desktop)->clear();
438                         }
439                     }
440                     ret = TRUE;
441                     Inkscape::Rubberband::get()->stop();
442                     desktop->updateNow();
443                     nc->rb_escaped = false;
444                     nc->drag = FALSE;
445                     nc->shape_editor->cancel_hit();
446                     nc->current_state = SP_NODE_CONTEXT_INACTIVE;
447                 }
448             }
449             break;
450         case GDK_KEY_PRESS:
451             switch (get_group0_keyval(&event->key)) {
452                 case GDK_Insert:
453                 case GDK_KP_Insert:
454                     // with any modifiers
455                     nc->shape_editor->add_node();
456                     ret = TRUE;
457                     break;
458                 case GDK_Delete:
459                 case GDK_KP_Delete:
460                 case GDK_BackSpace:
461                     if (MOD__CTRL_ONLY) {
462                         nc->shape_editor->delete_nodes();
463                     } else {
464                         nc->shape_editor->delete_nodes_preserving_shape();
465                     }
466                     ret = TRUE;
467                     break;
468                 case GDK_C:
469                 case GDK_c:
470                     if (MOD__SHIFT_ONLY) {
471                         nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_CUSP);
472                         ret = TRUE;
473                     }
474                     break;
475                 case GDK_S:
476                 case GDK_s:
477                     if (MOD__SHIFT_ONLY) {
478                         nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_SMOOTH);
479                         ret = TRUE;
480                     }
481                     break;
482                 case GDK_Y:
483                 case GDK_y:
484                     if (MOD__SHIFT_ONLY) {
485                         nc->shape_editor->set_node_type(Inkscape::NodePath::NODE_SYMM);
486                         ret = TRUE;
487                     }
488                     break;
489                 case GDK_B:
490                 case GDK_b:
491                     if (MOD__SHIFT_ONLY) {
492                         nc->shape_editor->break_at_nodes();
493                         ret = TRUE;
494                     }
495                     break;
496                 case GDK_J:
497                 case GDK_j:
498                     if (MOD__SHIFT_ONLY) {
499                         nc->shape_editor->join_nodes();
500                         ret = TRUE;
501                     }
502                     break;
503                 case GDK_D:
504                 case GDK_d:
505                     if (MOD__SHIFT_ONLY) {
506                         nc->shape_editor->duplicate_nodes();
507                         ret = TRUE;
508                     }
509                     break;
510                 case GDK_L:
511                 case GDK_l:
512                     if (MOD__SHIFT_ONLY) {
513                         nc->shape_editor->set_type_of_segments(NR_LINETO);
514                         ret = TRUE;
515                     }
516                     break;
517                 case GDK_U:
518                 case GDK_u:
519                     if (MOD__SHIFT_ONLY) {
520                         nc->shape_editor->set_type_of_segments(NR_CURVETO);
521                         ret = TRUE;
522                     }
523                     break;
524                 case GDK_R:
525                 case GDK_r:
526                     if (MOD__SHIFT_ONLY) {
527                         // FIXME: add top panel button
528                         sp_selected_path_reverse();
529                         ret = TRUE;
530                     }
531                     break;
532                 case GDK_x:
533                 case GDK_X:
534                     if (MOD__ALT_ONLY) {
535                         desktop->setToolboxFocusTo ("altx-nodes");
536                         ret = TRUE;
537                     }
538                     break;
539                 case GDK_Left: // move selection left
540                 case GDK_KP_Left:
541                 case GDK_KP_4:
542                     if (!MOD__CTRL) { // not ctrl
543                         gint mul = 1 + gobble_key_events(
544                             get_group0_keyval(&event->key), 0); // with any mask
545                         if (MOD__ALT) { // alt
546                             if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(mul*-10, 0); // shift
547                             else nc->shape_editor->move_nodes_screen(mul*-1, 0); // no shift
548                         }
549                         else { // no alt
550                             if (MOD__SHIFT) nc->shape_editor->move_nodes(mul*-10*nudge, 0); // shift
551                             else nc->shape_editor->move_nodes(mul*-nudge, 0); // no shift
552                         }
553                         ret = TRUE;
554                     }
555                     break;
556                 case GDK_Up: // move selection up
557                 case GDK_KP_Up:
558                 case GDK_KP_8:
559                     if (!MOD__CTRL) { // not ctrl
560                         gint mul = 1 + gobble_key_events(
561                             get_group0_keyval(&event->key), 0); // with any mask
562                         if (MOD__ALT) { // alt
563                             if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(0, mul*10); // shift
564                             else nc->shape_editor->move_nodes_screen(0, mul*1); // no shift
565                         }
566                         else { // no alt
567                             if (MOD__SHIFT) nc->shape_editor->move_nodes(0, mul*10*nudge); // shift
568                             else nc->shape_editor->move_nodes(0, mul*nudge); // no shift
569                         }
570                         ret = TRUE;
571                     }
572                     break;
573                 case GDK_Right: // move selection right
574                 case GDK_KP_Right:
575                 case GDK_KP_6:
576                     if (!MOD__CTRL) { // not ctrl
577                         gint mul = 1 + gobble_key_events(
578                             get_group0_keyval(&event->key), 0); // with any mask
579                         if (MOD__ALT) { // alt
580                             if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(mul*10, 0); // shift
581                             else nc->shape_editor->move_nodes_screen(mul*1, 0); // no shift
582                         }
583                         else { // no alt
584                             if (MOD__SHIFT) nc->shape_editor->move_nodes(mul*10*nudge, 0); // shift
585                             else nc->shape_editor->move_nodes(mul*nudge, 0); // no shift
586                         }
587                         ret = TRUE;
588                     }
589                     break;
590                 case GDK_Down: // move selection down
591                 case GDK_KP_Down:
592                 case GDK_KP_2:
593                     if (!MOD__CTRL) { // not ctrl
594                         gint mul = 1 + gobble_key_events(
595                             get_group0_keyval(&event->key), 0); // with any mask
596                         if (MOD__ALT) { // alt
597                             if (MOD__SHIFT) nc->shape_editor->move_nodes_screen(0, mul*-10); // shift
598                             else nc->shape_editor->move_nodes_screen(0, mul*-1); // no shift
599                         }
600                         else { // no alt
601                             if (MOD__SHIFT) nc->shape_editor->move_nodes(0, mul*-10*nudge); // shift
602                             else nc->shape_editor->move_nodes(0, mul*-nudge); // no shift
603                         }
604                         ret = TRUE;
605                     }
606                     break;
607                 case GDK_Escape:
608                 {
609                     NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
610                     if (b) {
611                         Inkscape::Rubberband::get()->stop();
612                         nc->current_state = SP_NODE_CONTEXT_INACTIVE;
613                         nc->rb_escaped = true;
614                     } else {
615                         if (nc->shape_editor->has_selection()) {
616                             nc->shape_editor->deselect();
617                         } else {
618                             sp_desktop_selection(desktop)->clear();
619                         }
620                     }
621                     ret = TRUE;
622                     break;
623                 }
625                 case GDK_bracketleft:
626                     if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
627                         if (nc->leftctrl)
628                             nc->shape_editor->rotate_nodes (M_PI/snaps, -1, false);
629                         if (nc->rightctrl)
630                             nc->shape_editor->rotate_nodes (M_PI/snaps, 1, false);
631                     } else if ( MOD__ALT && !MOD__CTRL ) {
632                         if (nc->leftalt && nc->rightalt)
633                             nc->shape_editor->rotate_nodes (1, 0, true);
634                         else {
635                             if (nc->leftalt)
636                                 nc->shape_editor->rotate_nodes (1, -1, true);
637                             if (nc->rightalt)
638                                 nc->shape_editor->rotate_nodes (1, 1, true);
639                         }
640                     } else if ( snaps != 0 ) {
641                         nc->shape_editor->rotate_nodes (M_PI/snaps, 0, false);
642                     }
643                     ret = TRUE;
644                     break;
645                 case GDK_bracketright:
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_less:
666                 case GDK_comma:
667                     if (MOD__CTRL) {
668                         if (nc->leftctrl)
669                             nc->shape_editor->scale_nodes(-offset, -1);
670                         if (nc->rightctrl)
671                             nc->shape_editor->scale_nodes(-offset, 1);
672                     } else if (MOD__ALT) {
673                         if (nc->leftalt && nc->rightalt)
674                             nc->shape_editor->scale_nodes_screen (-1, 0);
675                         else {
676                             if (nc->leftalt)
677                                 nc->shape_editor->scale_nodes_screen (-1, -1);
678                             if (nc->rightalt)
679                                 nc->shape_editor->scale_nodes_screen (-1, 1);
680                         }
681                     } else {
682                         nc->shape_editor->scale_nodes (-offset, 0);
683                     }
684                     ret = TRUE;
685                     break;
686                 case GDK_greater:
687                 case GDK_period:
688                     if (MOD__CTRL) {
689                         if (nc->leftctrl)
690                             nc->shape_editor->scale_nodes (offset, -1);
691                         if (nc->rightctrl)
692                             nc->shape_editor->scale_nodes (offset, 1);
693                     } else if (MOD__ALT) {
694                         if (nc->leftalt && nc->rightalt)
695                             nc->shape_editor->scale_nodes_screen (1, 0);
696                         else {
697                             if (nc->leftalt)
698                                 nc->shape_editor->scale_nodes_screen (1, -1);
699                             if (nc->rightalt)
700                                 nc->shape_editor->scale_nodes_screen (1, 1);
701                         }
702                     } else {
703                         nc->shape_editor->scale_nodes (offset, 0);
704                     }
705                     ret = TRUE;
706                     break;
708                 case GDK_Alt_L:
709                     nc->leftalt = TRUE;
710                     sp_node_context_show_modifier_tip(event_context, event);
711                     break;
712                 case GDK_Alt_R:
713                     nc->rightalt = TRUE;
714                     sp_node_context_show_modifier_tip(event_context, event);
715                     break;
716                 case GDK_Control_L:
717                     nc->leftctrl = TRUE;
718                     sp_node_context_show_modifier_tip(event_context, event);
719                     break;
720                 case GDK_Control_R:
721                     nc->rightctrl = TRUE;
722                     sp_node_context_show_modifier_tip(event_context, event);
723                     break;
724                 case GDK_Shift_L:
725                 case GDK_Shift_R:
726                 case GDK_Meta_L:
727                 case GDK_Meta_R:
728                     sp_node_context_show_modifier_tip(event_context, event);
729                     break;
730                 default:
731                     ret = node_key(event);
732                     break;
733             }
734             break;
735         case GDK_KEY_RELEASE:
736             switch (get_group0_keyval(&event->key)) {
737                 case GDK_Alt_L:
738                     nc->leftalt = FALSE;
739                     event_context->defaultMessageContext()->clear();
740                     break;
741                 case GDK_Alt_R:
742                     nc->rightalt = FALSE;
743                     event_context->defaultMessageContext()->clear();
744                     break;
745                 case GDK_Control_L:
746                     nc->leftctrl = FALSE;
747                     event_context->defaultMessageContext()->clear();
748                     break;
749                 case GDK_Control_R:
750                     nc->rightctrl = FALSE;
751                     event_context->defaultMessageContext()->clear();
752                     break;
753                 case GDK_Shift_L:
754                 case GDK_Shift_R:
755                 case GDK_Meta_L:
756                 case GDK_Meta_R:
757                     event_context->defaultMessageContext()->clear();
758                     break;
759             }
760             break;
761         default:
762             break;
763     }
765     if (!ret) {
766         if (((SPEventContextClass *) parent_class)->root_handler)
767             ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
768     }
770     return ret;
774 /*
775   Local Variables:
776   mode:c++
777   c-file-style:"stroustrup"
778   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
779   indent-tabs-mode:nil
780   fill-column:99
781   End:
782 */
783 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :