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