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 nc->grab_node = -1;
138 sp_nodepath_destroy(nc->nodepath);
139 nc->nodepath = NULL;
140 }
142 if (ec->shape_knot_holder) {
143 sp_knot_holder_destroy(ec->shape_knot_holder);
144 ec->shape_knot_holder = NULL;
145 }
147 if (nc->_node_message_context) {
148 delete nc->_node_message_context;
149 }
151 G_OBJECT_CLASS(parent_class)->dispose(object);
152 }
154 static void
155 sp_node_context_setup(SPEventContext *ec)
156 {
157 SPNodeContext *nc = SP_NODE_CONTEXT(ec);
159 if (((SPEventContextClass *) parent_class)->setup)
160 ((SPEventContextClass *) parent_class)->setup(ec);
162 nc->sel_changed_connection.disconnect();
163 nc->sel_changed_connection = sp_desktop_selection(ec->desktop)->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc));
165 Inkscape::Selection *selection = sp_desktop_selection(ec->desktop);
166 SPItem *item = selection->singleItem();
168 nc->grab_node = -1;
169 nc->nodepath = NULL;
170 ec->shape_knot_holder = NULL;
172 nc->rb_escaped = false;
174 nc->cursor_drag = false;
176 nc->added_node = false;
178 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
180 if (item) {
181 nc->nodepath = sp_nodepath_new(ec->desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
182 if ( nc->nodepath) {
183 //point pack to parent in case nodepath is deleted
184 nc->nodepath->nodeContext = nc;
185 }
186 ec->shape_knot_holder = sp_item_knot_holder(item, ec->desktop);
188 if (nc->nodepath || ec->shape_knot_holder) {
189 // setting listener
190 Inkscape::XML::Node *repr;
191 if (ec->shape_knot_holder)
192 repr = ec->shape_knot_holder->repr;
193 else
194 repr = SP_OBJECT_REPR(item);
195 if (repr) {
196 Inkscape::GC::anchor(repr);
197 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
198 }
199 }
200 }
202 if (prefs_get_int_attribute("tools.nodes", "selcue", 0) != 0) {
203 ec->enableSelectionCue();
204 }
206 if (prefs_get_int_attribute("tools.nodes", "gradientdrag", 0) != 0) {
207 ec->enableGrDrag();
208 }
210 nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
211 sp_nodepath_update_statusbar(nc->nodepath);
212 }
214 /**
215 \brief Callback that processes the "changed" signal on the selection;
216 destroys old and creates new nodepath and reassigns listeners to the new selected item's repr
217 */
218 void
219 sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data)
220 {
221 SPNodeContext *nc = SP_NODE_CONTEXT(data);
222 SPEventContext *ec = SP_EVENT_CONTEXT(nc);
224 Inkscape::XML::Node *old_repr = NULL;
226 if (nc->nodepath) {
227 old_repr = nc->nodepath->repr;
228 nc->grab_node = -1;
229 sp_nodepath_destroy(nc->nodepath);
230 nc->nodepath = NULL;
231 }
233 if (ec->shape_knot_holder) {
234 old_repr = ec->shape_knot_holder->repr;
235 sp_knot_holder_destroy(ec->shape_knot_holder);
236 }
238 if (old_repr) { // remove old listener
239 sp_repr_remove_listener_by_data(old_repr, ec);
240 Inkscape::GC::release(old_repr);
241 }
243 SPItem *item = selection->singleItem();
245 SPDesktop *desktop = selection->desktop();
246 nc->grab_node = -1;
247 nc->nodepath = NULL;
248 ec->shape_knot_holder = NULL;
249 if (item) {
250 nc->nodepath = sp_nodepath_new(desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
251 if (nc->nodepath) {
252 nc->nodepath->nodeContext = nc;
253 }
254 ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
256 if (nc->nodepath || ec->shape_knot_holder) {
257 // setting new listener
258 Inkscape::XML::Node *repr;
259 if (ec->shape_knot_holder)
260 repr = ec->shape_knot_holder->repr;
261 else
262 repr = SP_OBJECT_REPR(item);
263 if (repr) {
264 Inkscape::GC::anchor(repr);
265 sp_repr_add_listener(repr, &nodepath_repr_events, ec);
266 }
267 }
268 }
269 sp_nodepath_update_statusbar(nc->nodepath);
270 }
272 /**
273 \brief Regenerates nodepath when the item's repr was change outside of node edit
274 (e.g. by undo, or xml editor, or edited in another view). The item is assumed to be the same
275 (otherwise sp_node_context_selection_changed() would have been called), so repr and listeners
276 are not changed.
277 */
278 void
279 sp_nodepath_update_from_item(SPNodeContext *nc, SPItem *item)
280 {
281 g_assert(nc);
282 SPEventContext *ec = ((SPEventContext *) nc);
284 SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(SP_EVENT_CONTEXT(nc));
285 g_assert(desktop);
287 if (nc->nodepath) {
288 nc->grab_node = -1;
289 sp_nodepath_destroy(nc->nodepath);
290 nc->nodepath = NULL;
291 }
293 if (ec->shape_knot_holder) {
294 sp_knot_holder_destroy(ec->shape_knot_holder);
295 ec->shape_knot_holder = NULL;
296 }
298 Inkscape::Selection *selection = sp_desktop_selection(desktop);
299 item = selection->singleItem();
301 if (item) {
302 nc->nodepath = sp_nodepath_new(desktop, item, (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0));
303 if (nc->nodepath) {
304 nc->nodepath->nodeContext = nc;
305 }
306 ec->shape_knot_holder = sp_item_knot_holder(item, desktop);
307 }
308 sp_nodepath_update_statusbar(nc->nodepath);
309 }
311 /**
312 \brief Callback that is fired whenever an attribute of the selected item (which we have in the nodepath) changes
313 */
314 static void
315 nodepath_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name,
316 gchar const *old_value, gchar const *new_value,
317 bool is_interactive, gpointer data)
318 {
319 SPItem *item = NULL;
320 gboolean changed = FALSE;
322 g_assert(data);
323 SPNodeContext *nc = ((SPNodeContext *) data);
324 SPEventContext *ec = ((SPEventContext *) data);
325 g_assert(nc);
326 Inkscape::NodePath::Path *np = nc->nodepath;
327 SPKnotHolder *kh = ec->shape_knot_holder;
329 if (np) {
330 item = SP_ITEM(np->path);
331 if (!strcmp(name, "d") || !strcmp(name, "sodipodi:nodetypes")) { // With paths, we only need to act if one of the path-affecting attributes has changed.
332 changed = (np->local_change == 0);
333 if (np->local_change > 0)
334 np->local_change--;
335 }
337 } else if (kh) {
338 item = SP_ITEM(kh->item);
339 changed = !(kh->local_change);
340 kh->local_change = FALSE;
341 }
343 if (np && changed) {
344 GList *saved = NULL;
345 SPDesktop *desktop = np->desktop;
346 g_assert(desktop);
347 Inkscape::Selection *selection = desktop->selection;
348 g_assert(selection);
350 saved = save_nodepath_selection(nc->nodepath);
351 sp_nodepath_update_from_item(nc, item);
352 if (nc->nodepath && saved) restore_nodepath_selection(nc->nodepath, saved);
354 } else if (kh && changed) {
355 sp_nodepath_update_from_item(nc, item);
356 }
358 sp_nodepath_update_statusbar(nc->nodepath);
359 }
361 void
362 sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event)
363 {
364 sp_event_show_modifier_tip
365 (event_context->defaultMessageContext(), event,
366 _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"),
367 _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"),
368 _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles"));
369 }
371 bool
372 sp_node_context_is_over_stroke (SPNodeContext *nc, SPItem *item, NR::Point event_p, bool remember)
373 {
374 SPDesktop *desktop = SP_EVENT_CONTEXT (nc)->desktop;
376 //Translate click point into proper coord system
377 nc->curvepoint_doc = desktop->w2d(event_p);
378 nc->curvepoint_doc *= sp_item_dt2i_affine(item);
379 nc->curvepoint_doc *= sp_item_i2doc_affine(item);
381 sp_nodepath_ensure_livarot_path(nc->nodepath);
382 NR::Maybe<Path::cut_position> position = get_nearest_position_on_Path(nc->nodepath->livarot_path, nc->curvepoint_doc);
383 NR::Point nearest = get_point_on_Path(nc->nodepath->livarot_path, position.assume().piece, position.assume().t);
384 NR::Point delta = nearest - nc->curvepoint_doc;
386 delta = desktop->d2w(delta);
388 double stroke_tolerance =
389 (SP_OBJECT_STYLE (item)->stroke.type != SP_PAINT_TYPE_NONE?
390 desktop->current_zoom() *
391 SP_OBJECT_STYLE (item)->stroke_width.computed *
392 sp_item_i2d_affine (item).expansion() * 0.5
393 : 0.0)
394 + (double) SP_EVENT_CONTEXT(nc)->tolerance;
396 bool close = (NR::L2 (delta) < stroke_tolerance);
398 if (remember && close) {
399 nc->curvepoint_event[NR::X] = (gint) event_p [NR::X];
400 nc->curvepoint_event[NR::Y] = (gint) event_p [NR::Y];
401 nc->hit = true;
402 nc->grab_t = position.assume().t;
403 nc->grab_node = position.assume().piece;
404 }
406 return close;
407 }
410 static gint
411 sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
412 {
413 gint ret = FALSE;
415 SPDesktop *desktop = event_context->desktop;
416 Inkscape::Selection *selection = sp_desktop_selection (desktop);
418 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
420 switch (event->type) {
421 case GDK_2BUTTON_PRESS:
422 case GDK_BUTTON_RELEASE:
423 if (event->button.button == 1) {
424 if (!nc->drag) {
426 // find out clicked item, disregarding groups, honoring Alt
427 SPItem *item_clicked = sp_event_context_find_item (desktop,
428 NR::Point(event->button.x, event->button.y),
429 (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
430 // find out if we're over the selected item, disregarding groups
431 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
432 NR::Point(event->button.x, event->button.y));
434 bool over_stroke = false;
435 if (item_over && nc->nodepath) {
436 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), false);
437 }
439 if (over_stroke || nc->added_node) {
440 switch (event->type) {
441 case GDK_BUTTON_RELEASE:
442 if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
443 //add a node
444 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
445 } else {
446 if (nc->added_node) { // we just received double click, ignore release
447 nc->added_node = false;
448 break;
449 }
450 //select the segment
451 if (event->button.state & GDK_SHIFT_MASK) {
452 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, true);
453 } else {
454 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
455 }
456 desktop->updateNow();
457 }
458 break;
459 case GDK_2BUTTON_PRESS:
460 //add a node
461 sp_nodepath_add_node_near_point(nc->nodepath, nc->curvepoint_doc);
462 nc->added_node = true;
463 break;
464 default:
465 break;
466 }
467 } else if (event->button.state & GDK_SHIFT_MASK) {
468 selection->toggle(item_clicked);
469 desktop->updateNow();
470 } else {
471 selection->set(item_clicked);
472 desktop->updateNow();
473 }
475 ret = TRUE;
476 }
477 break;
478 }
479 break;
480 case GDK_BUTTON_PRESS:
481 if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK)) {
482 // save drag origin
483 event_context->xp = (gint) event->button.x;
484 event_context->yp = (gint) event->button.y;
485 event_context->within_tolerance = true;
486 nc->hit = false;
488 if (!nc->drag) {
489 // find out if we're over the selected item, disregarding groups
490 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
491 NR::Point(event->button.x, event->button.y));
493 if (nc->nodepath && selection->single() && item_over) {
495 // save drag origin
496 bool over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->button.x, event->button.y), true);
497 //only dragging curves
498 if (over_stroke) {
499 sp_nodepath_select_segment_near_point(nc->nodepath, nc->curvepoint_doc, false);
500 ret = TRUE;
501 } else {
502 break;
503 }
504 } else {
505 break;
506 }
508 ret = TRUE;
509 }
510 break;
511 }
512 break;
513 default:
514 break;
515 }
517 if (!ret) {
518 if (((SPEventContextClass *) parent_class)->item_handler)
519 ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
520 }
522 return ret;
523 }
525 static gint
526 sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
527 {
528 SPDesktop *desktop = event_context->desktop;
529 Inkscape::Selection *selection = sp_desktop_selection (desktop);
531 // fixme: nc->nodepath can potentially become NULL after retrieving nc.
532 // A general method for handling this possibility should be created.
533 // For now, the number of checks for a NULL nc->nodepath have been
534 // increased, both here and in the called sp_nodepath_* functions.
536 SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
537 double const nudge = prefs_get_double_attribute_limited("options.nudgedistance", "value", 2, 0, 1000); // in px
538 event_context->tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100); // read every time, to make prefs changes really live
539 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
540 double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
542 gint ret = FALSE;
544 switch (event->type) {
545 case GDK_BUTTON_PRESS:
546 if (event->button.button == 1) {
547 // save drag origin
548 event_context->xp = (gint) event->button.x;
549 event_context->yp = (gint) event->button.y;
550 event_context->within_tolerance = true;
551 nc->hit = false;
553 NR::Point const button_w(event->button.x,
554 event->button.y);
555 NR::Point const button_dt(desktop->w2d(button_w));
556 Inkscape::Rubberband::get()->start(desktop, button_dt);
557 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
558 desktop->updateNow();
559 ret = TRUE;
560 }
561 break;
562 case GDK_MOTION_NOTIFY:
563 if (event->motion.state & GDK_BUTTON1_MASK) {
565 if ( event_context->within_tolerance
566 && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance )
567 && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) {
568 break; // do not drag if we're within tolerance from origin
569 }
571 // The path went away while dragging; throw away any further motion
572 // events until the mouse pointer is released.
573 if (nc->hit && (nc->nodepath == NULL)) {
574 break;
575 }
577 // Once the user has moved farther than tolerance from the original location
578 // (indicating they intend to move the object, not click), then always process the
579 // motion notify coordinates as given (no snapping back to origin)
580 event_context->within_tolerance = false;
582 // Once we determine what the user is doing (dragging either a node or the
583 // selection rubberband), make sure we continue to perform that operation
584 // until the mouse pointer is lifted.
585 if (nc->current_state == SP_NODE_CONTEXT_INACTIVE) {
586 if (nc->nodepath && nc->hit) {
587 nc->current_state = SP_NODE_CONTEXT_NODE_DRAGGING;
588 } else {
589 nc->current_state = SP_NODE_CONTEXT_RUBBERBAND_DRAGGING;
590 }
591 }
593 switch (nc->current_state) {
594 case SP_NODE_CONTEXT_NODE_DRAGGING:
595 {
596 if (nc->grab_node == -1) // don't know which segment to drag
597 break;
599 // We round off the extra precision in the motion coordinates provided
600 // by some input devices (like tablets). As we'll store the coordinates
601 // as integers in curvepoint_event we need to do this rounding before
602 // comparing them with the last coordinates from curvepoint_event.
603 // See bug #1593499 for details.
605 gint x = (gint) Inkscape::round(event->motion.x);
606 gint y = (gint) Inkscape::round(event->motion.y);
608 // The coordinates hasn't changed since the last motion event, abort
609 if (nc->curvepoint_event[NR::X] == x &&
610 nc->curvepoint_event[NR::Y] == y)
611 break;
613 NR::Point const delta_w(event->motion.x - nc->curvepoint_event[NR::X],
614 event->motion.y - nc->curvepoint_event[NR::Y]);
615 NR::Point const delta_dt(desktop->w2d(delta_w));
617 sp_nodepath_curve_drag (nc->grab_node, nc->grab_t, delta_dt);
618 nc->curvepoint_event[NR::X] = x;
619 nc->curvepoint_event[NR::Y] = y;
620 gobble_motion_events(GDK_BUTTON1_MASK);
621 break;
622 }
623 case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING:
624 if (Inkscape::Rubberband::get()->is_started()) {
625 NR::Point const motion_w(event->motion.x,
626 event->motion.y);
627 NR::Point const motion_dt(desktop->w2d(motion_w));
628 Inkscape::Rubberband::get()->move(motion_dt);
629 }
630 break;
631 }
633 nc->drag = TRUE;
634 ret = TRUE;
635 } else {
636 if (!nc->nodepath || selection->singleItem() == NULL) {
637 break;
638 }
640 SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
641 NR::Point(event->motion.x, event->motion.y));
642 bool over_stroke = false;
643 if (item_over && nc->nodepath) {
644 over_stroke = sp_node_context_is_over_stroke (nc, item_over, NR::Point(event->motion.x, event->motion.y), false);
645 }
647 if (nc->cursor_drag && !over_stroke) {
648 event_context->cursor_shape = cursor_node_xpm;
649 event_context->hot_x = 1;
650 event_context->hot_y = 1;
651 sp_event_context_update_cursor(event_context);
652 nc->cursor_drag = false;
653 } else if (!nc->cursor_drag && over_stroke) {
654 event_context->cursor_shape = cursor_node_d_xpm;
655 event_context->hot_x = 1;
656 event_context->hot_y = 1;
657 sp_event_context_update_cursor(event_context);
658 nc->cursor_drag = true;
659 }
660 }
661 break;
662 case GDK_BUTTON_RELEASE:
663 event_context->xp = event_context->yp = 0;
664 if (event->button.button == 1) {
666 NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
668 if (nc->hit && !event_context->within_tolerance) { //drag curve
669 if (nc->nodepath) {
670 sp_nodepath_update_repr (nc->nodepath, _("Drag curve"));
671 }
672 } else if (b != NR::Nothing() && !event_context->within_tolerance) { // drag to select
673 if (nc->nodepath) {
674 sp_nodepath_select_rect(nc->nodepath, b.assume(), event->button.state & GDK_SHIFT_MASK);
675 }
676 } else {
677 if (!(nc->rb_escaped)) { // unless something was cancelled
678 if (nc->nodepath && nc->nodepath->selected)
679 sp_nodepath_deselect(nc->nodepath);
680 else
681 sp_desktop_selection(desktop)->clear();
682 }
683 }
684 ret = TRUE;
685 Inkscape::Rubberband::get()->stop();
686 desktop->updateNow();
687 nc->rb_escaped = false;
688 nc->drag = FALSE;
689 nc->hit = false;
690 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
691 break;
692 }
693 break;
694 case GDK_KEY_PRESS:
695 switch (get_group0_keyval(&event->key)) {
696 case GDK_Insert:
697 case GDK_KP_Insert:
698 // with any modifiers
699 sp_node_selected_add_node();
700 ret = TRUE;
701 break;
702 case GDK_Delete:
703 case GDK_KP_Delete:
704 case GDK_BackSpace:
705 if (MOD__CTRL_ONLY) {
706 sp_node_selected_delete();
707 } else {
708 if (nc->nodepath && nc->nodepath->selected) {
709 sp_node_delete_preserve(g_list_copy(nc->nodepath->selected));
710 }
711 }
712 ret = TRUE;
713 break;
714 case GDK_C:
715 case GDK_c:
716 if (MOD__SHIFT_ONLY) {
717 sp_node_selected_set_type(Inkscape::NodePath::NODE_CUSP);
718 ret = TRUE;
719 }
720 break;
721 case GDK_S:
722 case GDK_s:
723 if (MOD__SHIFT_ONLY) {
724 sp_node_selected_set_type(Inkscape::NodePath::NODE_SMOOTH);
725 ret = TRUE;
726 }
727 break;
728 case GDK_Y:
729 case GDK_y:
730 if (MOD__SHIFT_ONLY) {
731 sp_node_selected_set_type(Inkscape::NodePath::NODE_SYMM);
732 ret = TRUE;
733 }
734 break;
735 case GDK_B:
736 case GDK_b:
737 if (MOD__SHIFT_ONLY) {
738 sp_node_selected_break();
739 ret = TRUE;
740 }
741 break;
742 case GDK_J:
743 case GDK_j:
744 if (MOD__SHIFT_ONLY) {
745 sp_node_selected_join();
746 ret = TRUE;
747 }
748 break;
749 case GDK_D:
750 case GDK_d:
751 if (MOD__SHIFT_ONLY) {
752 sp_node_selected_duplicate();
753 ret = TRUE;
754 }
755 break;
756 case GDK_L:
757 case GDK_l:
758 if (MOD__SHIFT_ONLY) {
759 sp_node_selected_set_line_type(NR_LINETO);
760 ret = TRUE;
761 }
762 break;
763 case GDK_U:
764 case GDK_u:
765 if (MOD__SHIFT_ONLY) {
766 sp_node_selected_set_line_type(NR_CURVETO);
767 ret = TRUE;
768 }
769 break;
770 case GDK_R:
771 case GDK_r:
772 if (MOD__SHIFT_ONLY) {
773 // FIXME: add top panel button
774 sp_selected_path_reverse();
775 ret = TRUE;
776 }
777 break;
778 case GDK_Left: // move selection left
779 case GDK_KP_Left:
780 case GDK_KP_4:
781 if (!MOD__CTRL) { // not ctrl
782 if (MOD__ALT) { // alt
783 if (MOD__SHIFT) sp_node_selected_move_screen(-10, 0); // shift
784 else sp_node_selected_move_screen(-1, 0); // no shift
785 }
786 else { // no alt
787 if (MOD__SHIFT) sp_node_selected_move(-10*nudge, 0); // shift
788 else sp_node_selected_move(-nudge, 0); // no shift
789 }
790 ret = TRUE;
791 }
792 break;
793 case GDK_Up: // move selection up
794 case GDK_KP_Up:
795 case GDK_KP_8:
796 if (!MOD__CTRL) { // not ctrl
797 if (MOD__ALT) { // alt
798 if (MOD__SHIFT) sp_node_selected_move_screen(0, 10); // shift
799 else sp_node_selected_move_screen(0, 1); // no shift
800 }
801 else { // no alt
802 if (MOD__SHIFT) sp_node_selected_move(0, 10*nudge); // shift
803 else sp_node_selected_move(0, nudge); // no shift
804 }
805 ret = TRUE;
806 }
807 break;
808 case GDK_Right: // move selection right
809 case GDK_KP_Right:
810 case GDK_KP_6:
811 if (!MOD__CTRL) { // not ctrl
812 if (MOD__ALT) { // alt
813 if (MOD__SHIFT) sp_node_selected_move_screen(10, 0); // shift
814 else sp_node_selected_move_screen(1, 0); // no shift
815 }
816 else { // no alt
817 if (MOD__SHIFT) sp_node_selected_move(10*nudge, 0); // shift
818 else sp_node_selected_move(nudge, 0); // no shift
819 }
820 ret = TRUE;
821 }
822 break;
823 case GDK_Down: // move selection down
824 case GDK_KP_Down:
825 case GDK_KP_2:
826 if (!MOD__CTRL) { // not ctrl
827 if (MOD__ALT) { // alt
828 if (MOD__SHIFT) sp_node_selected_move_screen(0, -10); // shift
829 else sp_node_selected_move_screen(0, -1); // no shift
830 }
831 else { // no alt
832 if (MOD__SHIFT) sp_node_selected_move(0, -10*nudge); // shift
833 else sp_node_selected_move(0, -nudge); // no shift
834 }
835 ret = TRUE;
836 }
837 break;
838 case GDK_Escape:
839 {
840 NR::Maybe<NR::Rect> const b = Inkscape::Rubberband::get()->getRectangle();
841 if (b != NR::Nothing()) {
842 Inkscape::Rubberband::get()->stop();
843 nc->current_state = SP_NODE_CONTEXT_INACTIVE;
844 nc->rb_escaped = true;
845 } else {
846 if (nc->nodepath && nc->nodepath->selected) {
847 sp_nodepath_deselect(nc->nodepath);
848 } else {
849 sp_desktop_selection(desktop)->clear();
850 }
851 }
852 ret = TRUE;
853 break;
854 }
856 case GDK_bracketleft:
857 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
858 if (nc->leftctrl)
859 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, -1, false);
860 if (nc->rightctrl)
861 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 1, false);
862 } else if ( MOD__ALT && !MOD__CTRL ) {
863 if (nc->leftalt && nc->rightalt)
864 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 0, true);
865 else {
866 if (nc->leftalt)
867 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, -1, true);
868 if (nc->rightalt)
869 sp_nodepath_selected_nodes_rotate (nc->nodepath, 1, 1, true);
870 }
871 } else if ( snaps != 0 ) {
872 sp_nodepath_selected_nodes_rotate (nc->nodepath, M_PI/snaps, 0, false);
873 }
874 ret = TRUE;
875 break;
876 case GDK_bracketright:
877 if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) {
878 if (nc->leftctrl)
879 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, -1, false);
880 if (nc->rightctrl)
881 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 1, false);
882 } else if ( MOD__ALT && !MOD__CTRL ) {
883 if (nc->leftalt && nc->rightalt)
884 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 0, true);
885 else {
886 if (nc->leftalt)
887 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, -1, true);
888 if (nc->rightalt)
889 sp_nodepath_selected_nodes_rotate (nc->nodepath, -1, 1, true);
890 }
891 } else if ( snaps != 0 ) {
892 sp_nodepath_selected_nodes_rotate (nc->nodepath, -M_PI/snaps, 0, false);
893 }
894 ret = TRUE;
895 break;
896 case GDK_less:
897 case GDK_comma:
898 if (MOD__CTRL) {
899 if (nc->leftctrl)
900 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, -1);
901 if (nc->rightctrl)
902 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 1);
903 } else if (MOD__ALT) {
904 if (nc->leftalt && nc->rightalt)
905 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 0);
906 else {
907 if (nc->leftalt)
908 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, -1);
909 if (nc->rightalt)
910 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, -1, 1);
911 }
912 } else {
913 sp_nodepath_selected_nodes_scale(nc->nodepath, -offset, 0);
914 }
915 ret = TRUE;
916 break;
917 case GDK_greater:
918 case GDK_period:
919 if (MOD__CTRL) {
920 if (nc->leftctrl)
921 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, -1);
922 if (nc->rightctrl)
923 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 1);
924 } else if (MOD__ALT) {
925 if (nc->leftalt && nc->rightalt)
926 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 0);
927 else {
928 if (nc->leftalt)
929 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, -1);
930 if (nc->rightalt)
931 sp_nodepath_selected_nodes_scale_screen(nc->nodepath, 1, 1);
932 }
933 } else {
934 sp_nodepath_selected_nodes_scale(nc->nodepath, offset, 0);
935 }
936 ret = TRUE;
937 break;
939 case GDK_Alt_L:
940 nc->leftalt = TRUE;
941 sp_node_context_show_modifier_tip(event_context, event);
942 break;
943 case GDK_Alt_R:
944 nc->rightalt = TRUE;
945 sp_node_context_show_modifier_tip(event_context, event);
946 break;
947 case GDK_Control_L:
948 nc->leftctrl = TRUE;
949 sp_node_context_show_modifier_tip(event_context, event);
950 break;
951 case GDK_Control_R:
952 nc->rightctrl = TRUE;
953 sp_node_context_show_modifier_tip(event_context, event);
954 break;
955 case GDK_Shift_L:
956 case GDK_Shift_R:
957 case GDK_Meta_L:
958 case GDK_Meta_R:
959 sp_node_context_show_modifier_tip(event_context, event);
960 break;
961 default:
962 ret = node_key(event);
963 break;
964 }
965 break;
966 case GDK_KEY_RELEASE:
967 switch (get_group0_keyval(&event->key)) {
968 case GDK_Alt_L:
969 nc->leftalt = FALSE;
970 event_context->defaultMessageContext()->clear();
971 break;
972 case GDK_Alt_R:
973 nc->rightalt = FALSE;
974 event_context->defaultMessageContext()->clear();
975 break;
976 case GDK_Control_L:
977 nc->leftctrl = FALSE;
978 event_context->defaultMessageContext()->clear();
979 break;
980 case GDK_Control_R:
981 nc->rightctrl = FALSE;
982 event_context->defaultMessageContext()->clear();
983 break;
984 case GDK_Shift_L:
985 case GDK_Shift_R:
986 case GDK_Meta_L:
987 case GDK_Meta_R:
988 event_context->defaultMessageContext()->clear();
989 break;
990 }
991 break;
992 default:
993 break;
994 }
996 if (!ret) {
997 if (((SPEventContextClass *) parent_class)->root_handler)
998 ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event);
999 }
1001 return ret;
1002 }
1005 /*
1006 Local Variables:
1007 mode:c++
1008 c-file-style:"stroustrup"
1009 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1010 indent-tabs-mode:nil
1011 fill-column:99
1012 End:
1013 */
1014 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :