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, except stamping code,
11 * which is Copyright (C) Masatake Yamato 2002
12 */
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
17 #include <gdk/gdkkeysyms.h>
18 #include "macros.h"
19 #include <glibmm/i18n.h>
20 #include "display/sp-canvas-util.h"
21 #include "object-edit.h"
22 #include "sp-path.h"
23 #include "path-chemistry.h"
24 #include "rubberband.h"
25 #include "desktop.h"
26 #include "desktop-handles.h"
27 #include "selection.h"
28 #include "pixmaps/cursor-node.xpm"
29 #include "message-context.h"
30 #include "node-context.h"
31 #include "pixmaps/cursor-node-d.xpm"
32 #include "prefs-utils.h"
33 #include "xml/node-event-vector.h"
34 #include "style.h"
35 #include "splivarot.h"
37 static void sp_node_context_class_init(SPNodeContextClass *klass);
38 static void sp_node_context_init(SPNodeContext *node_context);
39 static void sp_node_context_dispose(GObject *object);
41 static void sp_node_context_setup(SPEventContext *ec);
42 static gint sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event);
43 static gint sp_node_context_item_handler(SPEventContext *event_context,
44 SPItem *item, GdkEvent *event);
46 static void nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
47 gchar const *old_value, gchar const *new_value,
48 bool is_interactive, gpointer data);
50 static Inkscape::XML::NodeEventVector nodepath_repr_events = {
51 NULL, /* child_added */
52 NULL, /* child_removed */
53 nodepath_event_attr_changed,
54 NULL, /* content_changed */
55 NULL /* order_changed */
56 };
58 static SPEventContextClass *parent_class;
60 GType
61 sp_node_context_get_type()
62 {
63 static GType type = 0;
64 if (!type) {
65 GTypeInfo info = {
66 sizeof(SPNodeContextClass),
67 NULL, NULL,
68 (GClassInitFunc) sp_node_context_class_init,
69 NULL, NULL,
70 sizeof(SPNodeContext),
71 4,
72 (GInstanceInitFunc) sp_node_context_init,
73 NULL, /* value_table */
74 };
75 type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPNodeContext", &info, (GTypeFlags)0);
76 }
77 return type;
78 }
80 static void
81 sp_node_context_class_init(SPNodeContextClass *klass)
82 {
83 GObjectClass *object_class = (GObjectClass *) klass;
84 SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
86 parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass);
88 object_class->dispose = sp_node_context_dispose;
90 event_context_class->setup = sp_node_context_setup;
91 event_context_class->root_handler = sp_node_context_root_handler;
92 event_context_class->item_handler = sp_node_context_item_handler;
93 }
95 static void
96 sp_node_context_init(SPNodeContext *node_context)
97 {
98 SPEventContext *event_context = SP_EVENT_CONTEXT(node_context);
100 event_context->cursor_shape = cursor_node_xpm;
101 event_context->hot_x = 1;
102 event_context->hot_y = 1;
104 node_context->leftalt = FALSE;
105 node_context->rightalt = FALSE;
106 node_context->leftctrl = FALSE;
107 node_context->rightctrl = FALSE;
109 new (&node_context->sel_changed_connection) sigc::connection();
110 }
112 static void
113 sp_node_context_dispose(GObject *object)
114 {
115 SPNodeContext *nc = SP_NODE_CONTEXT(object);
116 SPEventContext *ec = SP_EVENT_CONTEXT(object);
118 ec->enableGrDrag(false);
120 nc->sel_changed_connection.disconnect();
121 nc->sel_changed_connection.~connection();
123 Inkscape::XML::Node *repr = NULL;
124 if (nc->nodepath) {
125 repr = nc->nodepath->repr;
126 }
127 if (!repr && ec->shape_knot_holder) {
128 repr = ec->shape_knot_holder->repr;
129 }
131 if (repr) {
132 sp_repr_remove_listener_by_data(repr, ec);
133 Inkscape::GC::release(repr);
134 }
136 if (nc->nodepath) {
137 sp_nodepath_destroy(nc->nodepath);
138 nc->nodepath = NULL;
139 }
141 if (ec->shape_knot_holder) {
142 sp_knot_holder_destroy(ec->shape_knot_holder);
143 ec->shape_knot_holder = NULL;
144 }
146 if (nc->_node_message_context) {
147 delete nc->_node_message_context;
148 }
150 G_OBJECT_CLASS(parent_class)->dispose(object);
151 }
153 static void
154 sp_node_context_setup(SPEventContext *ec)
155 {
156 SPNodeContext *nc = SP_NODE_CONTEXT(ec);
158 if (((SPEventContextClass *) parent_class)->setup)
159 ((SPEventContextClass *) parent_class)->setup(ec);
161 nc->sel_changed_connection.disconnect();
162 nc->sel_changed_connection = sp_desktop_selection(ec->desktop)->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
164 Inkscape::Selection *selection = sp_desktop_selection(ec->desktop);
165 SPItem *item = selection->singleItem();
167 nc->nodepath = NULL;
168 ec->shape_knot_holder = NULL;
170 nc->rb_escaped = false;
172 nc->cursor_drag = false;
174 nc->added_node = false;
176 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
178 if (item) {
179 nc->nodepath = sp_nodepath_new(ec->desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
180 if ( nc->nodepath) {
181 //point pack to parent in case nodepath is deleted
182 nc->nodepath->nodeContext = nc;
183 }
184 ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
186 if (nc->nodepath || ec->shape_knot_holder) {
187 // setting listener
188 Inkscape::XML::Node *repr;
189 if (ec->shape_knot_holder)
190 repr = ec->shape_knot_holder->repr;
191 else
192 repr = SP_OBJECT_REPR(item);
193 if (repr) {
194 Inkscape::GC::anchor(repr);
195 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
196 }
197 }
198 }
200 if (prefs_get_int_attribute("tools.nodes", "selcue", 0) != 0) {
201 ec->enableSelectionCue();
202 }
204 if (prefs_get_int_attribute("tools.nodes", "gradientdrag", 0) != 0) {
205 ec->enableGrDrag();
206 }
208 nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
209 sp_nodepath_update_statusbar(nc->nodepath);
210 }
212 /**
213 \brief Callback that processes the "changed" signal on the selection;
214 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
215 */
216 void
217 sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
218 {
219 SPNodeContext *nc = SP_NODE_CONTEXT(data);
220 SPEventContext *ec = SP_EVENT_CONTEXT(nc);
222 Inkscape::XML::Node *old_repr = NULL;
224 if (nc->nodepath) {
225 old_repr = nc->nodepath->repr;
226 sp_nodepath_destroy(nc->nodepath);
227 nc->nodepath = NULL;
228 }
230 if (ec->shape_knot_holder) {
231 old_repr = ec->shape_knot_holder->repr;
232 sp_knot_holder_destroy(ec->shape_knot_holder);
233 }
235 if (old_repr) { // remove old listener
236 sp_repr_remove_listener_by_data(old_repr, ec);
237 Inkscape::GC::release(old_repr);
238 }
240 SPItem *item = selection->singleItem();
242 SPDesktop *desktop = selection->desktop();
243 nc->nodepath = NULL;
244 ec->shape_knot_holder = NULL;
245 if (item) {
246 nc->nodepath = sp_nodepath_new(desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
247 if (nc->nodepath) {
248 nc->nodepath->nodeContext = nc;
249 }
250 ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
252 if (nc->nodepath || ec->shape_knot_holder) {
253 // setting new listener
254 Inkscape::XML::Node *repr;
255 if (ec->shape_knot_holder)
256 repr = ec->shape_knot_holder->repr;
257 else
258 repr = SP_OBJECT_REPR(item);
259 if (repr) {
260 Inkscape::GC::anchor(repr);
261 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
262 }
263 }
264 }
265 sp_nodepath_update_statusbar(nc->nodepath);
266 }
268 /**
269 \brief Regenerates nodepath when the item's repr was change outside of node edit
270 (e.g. by undo, or xml editor, or edited in another view). The item is assumed to be the same
271 (otherwise sp_node_context_selection_changed() would have been called), so repr and listeners
272 are not changed.
273 */
274 void
275 sp_nodepath_update_from_item(SPNodeContext *nc, SPItem *item)
276 {
277 g_assert(nc);
278 SPEventContext *ec = ((SPEventContext *) nc);
280 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(SP_EVENT_CONTEXT(nc));
281 g_assert(desktop);
283 if (nc->nodepath) {
284 sp_nodepath_destroy(nc->nodepath);
285 nc->nodepath = NULL;
286 }
288 if (ec->shape_knot_holder) {
289 sp_knot_holder_destroy(ec->shape_knot_holder);
290 ec->shape_knot_holder = NULL;
291 }
293 Inkscape::Selection *selection = sp_desktop_selection(desktop);
294 item = selection->singleItem();
296 if (item) {
297 nc->nodepath = sp_nodepath_new(desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
298 if (nc->nodepath) {
299 nc->nodepath->nodeContext = nc;
300 }
301 ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
302 }
303 sp_nodepath_update_statusbar(nc->nodepath);
304 }
306 /**
307 \brief Callback that is fired whenever an attribute of the selected item (which we have in the nodepath) changes
308 */
309 static void
310 nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
311 gchar const *old_value, gchar const *new_value,
312 bool is_interactive, gpointer data)
313 {
314 SPItem *item = NULL;
315 gboolean changed = FALSE;
317 g_assert(data);
318 SPNodeContext *nc = ((SPNodeContext *) data);
319 SPEventContext *ec = ((SPEventContext *) data);
320 g_assert(nc);
321 Inkscape::NodePath::Path *np = nc->nodepath;
322 SPKnotHolder *kh = ec->shape_knot_holder;
324 if (np) {
325 item = SP_ITEM(np->path);
326 if (!strcmp(name, "d") || !strcmp(name, "sodipodi:nodetypes")) { // With paths, we only need to act if one of the path-affecting attributes has changed.
327 changed = (np->local_change == 0);
328 if (np->local_change > 0)
329 np->local_change--;
330 }
332 } else if (kh) {
333 item = SP_ITEM(kh->item);
334 changed = !(kh->local_change);
335 kh->local_change = FALSE;
336 }
338 if (np && changed) {
339 GList *saved = NULL;
340 SPDesktop *desktop = np->desktop;
341 g_assert(desktop);
342 Inkscape::Selection *selection = desktop->selection;
343 g_assert(selection);
345 saved = save_nodepath_selection(nc->nodepath);
346 sp_nodepath_update_from_item(nc, item);
347 if (nc->nodepath && saved) restore_nodepath_selection(nc->nodepath, saved);
349 } else if (kh && changed) {
350 sp_nodepath_update_from_item(nc, item);
351 }
353 sp_nodepath_update_statusbar(nc->nodepath);
354 }
356 void
357 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
358 {
359 sp_event_show_modifier_tip
360 (event_context->defaultMessageContext(), event,
361 _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
362 _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
363 _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
364 }
366 bool
367 sp_node_context_is_over_stroke (SPNodeContext *nc, SPItem *item, NR::Point event_p, bool remember)
368 {
369 SPDesktop *desktop = SP_EVENT_CONTEXT (nc)->desktop;
371 //Translate click point into proper coord system
372 nc->curvepoint_doc = desktop->w2d(event_p);
373 nc->curvepoint_doc *= sp_item_dt2i_affine(item);
374 nc->curvepoint_doc *= sp_item_i2doc_affine(item);
376 sp_nodepath_ensure_livarot_path(nc->nodepath);
377 NR::Maybe<Path::cut_position> position = get_nearest_position_on_Path(nc->nodepath->livarot_path, nc->curvepoint_doc);
378 NR::Point nearest = get_point_on_Path(nc->nodepath->livarot_path, position.assume().piece, position.assume().t);
379 NR::Point delta = nearest - nc->curvepoint_doc;
381 delta = desktop->d2w(delta);
383 double stroke_tolerance =
384 (SP_OBJECT_STYLE (item)->stroke.type != SP_PAINT_TYPE_NONE?
385 desktop->current_zoom() *
386 SP_OBJECT_STYLE (item)->stroke_width.computed *
387 sp_item_i2d_affine (item).expansion() * 0.5
388 : 0.0)
389 + (double) SP_EVENT_CONTEXT(nc)->tolerance;
391 bool close = (NR::L2 (delta) < stroke_tolerance);
393 if (remember && close) {
394 nc->curvepoint_event[NR::X] = (gint) event_p [NR::X];
395 nc->curvepoint_event[NR::Y] = (gint) event_p [NR::Y];
396 nc->hit = true;
397 nc->grab_t = position.assume().t;
398 nc->grab_node = sp_nodepath_get_node_by_index(position.assume().piece);
399 }
401 return close;
402 }
405 static gint
406 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
407 {
408 gint ret = FALSE;
410 SPDesktop *desktop = event_context->desktop;
411 Inkscape::Selection *selection = sp_desktop_selection (desktop);
413 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
415 switch (event->type) {
416 case GDK_2BUTTON_PRESS:
417 case GDK_BUTTON_RELEASE:
418 if (event->button.button == 1) {
419 if (!nc->drag) {
421 // find out clicked item, disregarding groups, honoring Alt
422 SPItem *item_clicked = sp_event_context_find_item (desktop,
423 NR::Point(event->button.x, event->button.y),
424 (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
425 // find out if we're over the selected item, disregarding groups
426 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
427 NR::Point(event->button.x, event->button.y));
429 bool over_stroke = false;
430 if (item_over && nc->nodepath) {
431 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), false);
432 }
434 if (over_stroke || nc->added_node) {
435 switch (event->type) {
436 case GDK_BUTTON_RELEASE:
437 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
438 //add a node
439 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
440 } else {
441 if (nc->added_node) { // we just received double click, ignore release
442 nc->added_node = false;
443 break;
444 }
445 //select the segment
446 if (event->button.state & GDK_SHIFT_MASK) {
447 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, true);
448 } else {
449 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
450 }
451 desktop->updateNow();
452 }
453 break;
454 case GDK_2BUTTON_PRESS:
455 //add a node
456 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
457 nc->added_node = true;
458 break;
459 default:
460 break;
461 }
462 } else if (event->button.state & GDK_SHIFT_MASK) {
463 selection->toggle(item_clicked);
464 desktop->updateNow();
465 } else {
466 selection->set(item_clicked);
467 desktop->updateNow();
468 }
470 ret = TRUE;
471 }
472 break;
473 }
474 break;
475 case GDK_BUTTON_PRESS:
476 if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK)) {
477 // save drag origin
478 event_context->xp = (gint) event->button.x;
479 event_context->yp = (gint) event->button.y;
480 event_context->within_tolerance = true;
481 nc->hit = false;
483 if (!nc->drag) {
484 // find out if we're over the selected item, disregarding groups
485 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
486 NR::Point(event->button.x, event->button.y));
488 if (nc->nodepath && selection->single() && item_over) {
490 // save drag origin
491 bool over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), true);
492 //only dragging curves
493 if (over_stroke) {
494 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
495 ret = TRUE;
496 } else {
497 break;
498 }
499 } else {
500 break;
501 }
503 ret = TRUE;
504 }
505 break;
506 }
507 break;
508 default:
509 break;
510 }
512 if (!ret) {
513 if (((SPEventContextClass *) parent_class)->item_handler)
514 ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
515 }
517 return ret;
518 }
520 static gint
521 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
522 {
523 SPDesktop *desktop = event_context->desktop;
524 Inkscape::Selection *selection = sp_desktop_selection (desktop);
526 // fixme: nc->nodepath can potentially become NULL after retrieving nc.
527 // A general method for handling this possibility should be created.
528 // For now, the number of checks for a NULL nc->nodepath have been
529 // increased, both here and in the called sp_nodepath_* functions.
531 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
532 double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
533 event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
534 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
535 double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
537 gint ret = FALSE;
539 switch (event->type) {
540 case GDK_BUTTON_PRESS:
541 if (event->button.button == 1) {
542 // save drag origin
543 event_context->xp = (gint) event->button.x;
544 event_context->yp = (gint) event->button.y;
545 event_context->within_tolerance = true;
546 nc->hit = false;
548 NR::Point const button_w(event->button.x,
549 event->button.y);
550 NR::Point const button_dt(desktop->w2d(button_w));
551 Inkscape::Rubberband::get()->start(desktop, button_dt);
552 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
553 desktop->updateNow();
554 ret = TRUE;
555 }
556 break;
557 case GDK_MOTION_NOTIFY:
558 if (event->motion.state & GDK_BUTTON1_MASK) {
560 if ( event_context->within_tolerance
561 && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
562 && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
563 break; // do not drag if we're within tolerance from origin
564 }
566 // The path went away while dragging; throw away any further motion
567 // events until the mouse pointer is released.
568 if (nc->hit && (nc->nodepath == NULL)) {
569 break;
570 }
572 // Once the user has moved farther than tolerance from the original location
573 // (indicating they intend to move the object, not click), then always process the
574 // motion notify coordinates as given (no snapping back to origin)
575 event_context->within_tolerance = false;
577 // Once we determine what the user is doing (dragging either a node or the
578 // selection rubberband), make sure we continue to perform that operation
579 // until the mouse pointer is lifted.
580 if (nc->current_state == SP_NODE_CONTEXT_INACTIVE) {
581 if (nc->nodepath && nc->hit) {
582 nc->current_state = SP_NODE_CONTEXT_NODE_DRAGGING;
583 } else {
584 nc->current_state = SP_NODE_CONTEXT_RUBBERBAND_DRAGGING;
585 }
586 }
588 switch (nc->current_state) {
589 case SP_NODE_CONTEXT_NODE_DRAGGING:
590 {
591 NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
592 event->motion.y - nc->curvepoint_event[NR::Y]);
593 NR::Point const delta_dt(desktop->w2d(delta_w));
594 sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
595 nc->curvepoint_event[NR::X] = (gint) event->motion.x;
596 nc->curvepoint_event[NR::Y] = (gint) event->motion.y;
597 gobble_motion_events(GDK_BUTTON1_MASK);
598 break;
599 }
600 case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING:
601 if (Inkscape::Rubberband::get()->is_started()) {
602 NR::Point const motion_w(event->motion.x,
603 event->motion.y);
604 NR::Point const motion_dt(desktop->w2d(motion_w));
605 Inkscape::Rubberband::get()->move(motion_dt);
606 }
607 break;
608 }
610 nc->drag = TRUE;
611 ret = TRUE;
612 } else {
613 if (!nc->nodepath || selection->singleItem() == NULL) {
614 break;
615 }
617 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
618 NR::Point(event->motion.x, event->motion.y));
619 bool over_stroke = false;
620 if (item_over && nc->nodepath) {
621 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
622 }
624 if (nc->cursor_drag && !over_stroke) {
625 event_context->cursor_shape = cursor_node_xpm;
626 event_context->hot_x = 1;
627 event_context->hot_y = 1;
628 sp_event_context_update_cursor(event_context);
629 nc->cursor_drag = false;
630 } else if (!nc->cursor_drag && over_stroke) {
631 event_context->cursor_shape = cursor_node_d_xpm;
632 event_context->hot_x = 1;
633 event_context->hot_y = 1;
634 sp_event_context_update_cursor(event_context);
635 nc->cursor_drag = true;
636 }
637 }
638 break;
639 case GDK_BUTTON_RELEASE:
640 event_context->xp = event_context->yp = 0;
641 if (event->button.button == 1) {
643 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
645 if (nc->hit && !event_context->within_tolerance) { //drag curve
646 if (nc->nodepath) {
647 sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
648 }
649 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
650 if (nc->nodepath) {
651 sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
652 }
653 } else {
654 if (!(nc->rb_escaped)) { // unless something was cancelled
655 if (nc->nodepath && nc->nodepath->selected)
656 sp_nodepath_deselect(nc->nodepath);
657 else
658 sp_desktop_selection(desktop)->clear();
659 }
660 }
661 ret = TRUE;
662 Inkscape::Rubberband::get()->stop();
663 desktop->updateNow();
664 nc->rb_escaped = false;
665 nc->drag = FALSE;
666 nc->hit = false;
667 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
668 break;
669 }
670 break;
671 case GDK_KEY_PRESS:
672 switch (get_group0_keyval(&event->key)) {
673 case GDK_Insert:
674 case GDK_KP_Insert:
675 // with any modifiers
676 sp_node_selected_add_node();
677 ret = TRUE;
678 break;
679 case GDK_Delete:
680 case GDK_KP_Delete:
681 case GDK_BackSpace:
682 if (MOD__CTRL_ONLY) {
683 sp_node_selected_delete();
684 } else {
685 if (nc->nodepath && nc->nodepath->selected) {
686 sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
687 }
688 }
689 ret = TRUE;
690 break;
691 case GDK_C:
692 case GDK_c:
693 if (MOD__SHIFT_ONLY) {
694 sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
695 ret = TRUE;
696 }
697 break;
698 case GDK_S:
699 case GDK_s:
700 if (MOD__SHIFT_ONLY) {
701 sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
702 ret = TRUE;
703 }
704 break;
705 case GDK_Y:
706 case GDK_y:
707 if (MOD__SHIFT_ONLY) {
708 sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
709 ret = TRUE;
710 }
711 break;
712 case GDK_B:
713 case GDK_b:
714 if (MOD__SHIFT_ONLY) {
715 sp_node_selected_break();
716 ret = TRUE;
717 }
718 break;
719 case GDK_J:
720 case GDK_j:
721 if (MOD__SHIFT_ONLY) {
722 sp_node_selected_join();
723 ret = TRUE;
724 }
725 break;
726 case GDK_D:
727 case GDK_d:
728 if (MOD__SHIFT_ONLY) {
729 sp_node_selected_duplicate();
730 ret = TRUE;
731 }
732 break;
733 case GDK_L:
734 case GDK_l:
735 if (MOD__SHIFT_ONLY) {
736 sp_node_selected_set_line_type(NR_LINETO);
737 ret = TRUE;
738 }
739 break;
740 case GDK_U:
741 case GDK_u:
742 if (MOD__SHIFT_ONLY) {
743 sp_node_selected_set_line_type(NR_CURVETO);
744 ret = TRUE;
745 }
746 break;
747 case GDK_R:
748 case GDK_r:
749 if (MOD__SHIFT_ONLY) {
750 // FIXME: add top panel button
751 sp_selected_path_reverse();
752 ret = TRUE;
753 }
754 break;
755 case GDK_Left: // move selection left
756 case GDK_KP_Left:
757 case GDK_KP_4:
758 if (!MOD__CTRL) { // not ctrl
759 if (MOD__ALT) { // alt
760 if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
761 else sp_node_selected_move_screen(-1, 0); // no shift
762 }
763 else { // no alt
764 if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
765 else sp_node_selected_move(-nudge, 0); // no shift
766 }
767 ret = TRUE;
768 }
769 break;
770 case GDK_Up: // move selection up
771 case GDK_KP_Up:
772 case GDK_KP_8:
773 if (!MOD__CTRL) { // not ctrl
774 if (MOD__ALT) { // alt
775 if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
776 else sp_node_selected_move_screen(0, 1); // no shift
777 }
778 else { // no alt
779 if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
780 else sp_node_selected_move(0, nudge); // no shift
781 }
782 ret = TRUE;
783 }
784 break;
785 case GDK_Right: // move selection right
786 case GDK_KP_Right:
787 case GDK_KP_6:
788 if (!MOD__CTRL) { // not ctrl
789 if (MOD__ALT) { // alt
790 if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
791 else sp_node_selected_move_screen(1, 0); // no shift
792 }
793 else { // no alt
794 if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
795 else sp_node_selected_move(nudge, 0); // no shift
796 }
797 ret = TRUE;
798 }
799 break;
800 case GDK_Down: // move selection down
801 case GDK_KP_Down:
802 case GDK_KP_2:
803 if (!MOD__CTRL) { // not ctrl
804 if (MOD__ALT) { // alt
805 if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
806 else sp_node_selected_move_screen(0, -1); // no shift
807 }
808 else { // no alt
809 if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
810 else sp_node_selected_move(0, -nudge); // no shift
811 }
812 ret = TRUE;
813 }
814 break;
815 case GDK_Escape:
816 {
817 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
818 if (b != NR::Nothing()) {
819 Inkscape::Rubberband::get()->stop();
820 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
821 nc->rb_escaped = true;
822 } else {
823 if (nc->nodepath && nc->nodepath->selected) {
824 sp_nodepath_deselect(nc->nodepath);
825 } else {
826 sp_desktop_selection(desktop)->clear();
827 }
828 }
829 ret = TRUE;
830 break;
831 }
833 case GDK_bracketleft:
834 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
835 if (nc->leftctrl)
836 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
837 if (nc->rightctrl)
838 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
839 } else if ( MOD__ALT && !MOD__CTRL ) {
840 if (nc->leftalt && nc->rightalt)
841 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
842 else {
843 if (nc->leftalt)
844 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
845 if (nc->rightalt)
846 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
847 }
848 } else if ( snaps != 0 ) {
849 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
850 }
851 ret = TRUE;
852 break;
853 case GDK_bracketright:
854 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
855 if (nc->leftctrl)
856 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
857 if (nc->rightctrl)
858 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
859 } else if ( MOD__ALT && !MOD__CTRL ) {
860 if (nc->leftalt && nc->rightalt)
861 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
862 else {
863 if (nc->leftalt)
864 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
865 if (nc->rightalt)
866 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
867 }
868 } else if ( snaps != 0 ) {
869 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
870 }
871 ret = TRUE;
872 break;
873 case GDK_less:
874 case GDK_comma:
875 if (MOD__CTRL) {
876 if (nc->leftctrl)
877 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
878 if (nc->rightctrl)
879 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
880 } else if (MOD__ALT) {
881 if (nc->leftalt && nc->rightalt)
882 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
883 else {
884 if (nc->leftalt)
885 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
886 if (nc->rightalt)
887 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
888 }
889 } else {
890 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
891 }
892 ret = TRUE;
893 break;
894 case GDK_greater:
895 case GDK_period:
896 if (MOD__CTRL) {
897 if (nc->leftctrl)
898 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
899 if (nc->rightctrl)
900 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
901 } else if (MOD__ALT) {
902 if (nc->leftalt && nc->rightalt)
903 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
904 else {
905 if (nc->leftalt)
906 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
907 if (nc->rightalt)
908 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
909 }
910 } else {
911 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
912 }
913 ret = TRUE;
914 break;
916 case GDK_Alt_L:
917 nc->leftalt = TRUE;
918 sp_node_context_show_modifier_tip(event_context, event);
919 break;
920 case GDK_Alt_R:
921 nc->rightalt = TRUE;
922 sp_node_context_show_modifier_tip(event_context, event);
923 break;
924 case GDK_Control_L:
925 nc->leftctrl = TRUE;
926 sp_node_context_show_modifier_tip(event_context, event);
927 break;
928 case GDK_Control_R:
929 nc->rightctrl = TRUE;
930 sp_node_context_show_modifier_tip(event_context, event);
931 break;
932 case GDK_Shift_L:
933 case GDK_Shift_R:
934 case GDK_Meta_L:
935 case GDK_Meta_R:
936 sp_node_context_show_modifier_tip(event_context, event);
937 break;
938 default:
939 ret = node_key(event);
940 break;
941 }
942 break;
943 case GDK_KEY_RELEASE:
944 switch (get_group0_keyval(&event->key)) {
945 case GDK_Alt_L:
946 nc->leftalt = FALSE;
947 event_context->defaultMessageContext()->clear();
948 break;
949 case GDK_Alt_R:
950 nc->rightalt = FALSE;
951 event_context->defaultMessageContext()->clear();
952 break;
953 case GDK_Control_L:
954 nc->leftctrl = FALSE;
955 event_context->defaultMessageContext()->clear();
956 break;
957 case GDK_Control_R:
958 nc->rightctrl = FALSE;
959 event_context->defaultMessageContext()->clear();
960 break;
961 case GDK_Shift_L:
962 case GDK_Shift_R:
963 case GDK_Meta_L:
964 case GDK_Meta_R:
965 event_context->defaultMessageContext()->clear();
966 break;
967 }
968 break;
969 default:
970 break;
971 }
973 if (!ret) {
974 if (((SPEventContextClass *) parent_class)->root_handler)
975 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
976 }
978 return ret;
979 }
982 /*
983 Local Variables:
984 mode:c++
985 c-file-style:"stroustrup"
986 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
987 indent-tabs-mode:nil
988 fill-column:99
989 End:
990 */
991 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :