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