1 #define __SP_KNOT_C__
3 /** \file
4 * SPKnot implementation
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * Copyright (C) 1999-2005 authors
11 * Copyright (C) 2001-2002 Ximian, Inc.
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 */
16 #ifdef HAVE_CONFIG_H
17 # include <config.h>
18 #endif
19 #include <gdk/gdkkeysyms.h>
20 #include <glibmm/i18n.h>
21 #include "helper/sp-marshal.h"
22 #include "display/sodipodi-ctrl.h"
23 #include "desktop.h"
24 #include "desktop-handles.h"
25 #include "knot.h"
26 #include "document.h"
27 #include "prefs-utils.h"
28 #include "message-stack.h"
29 #include "message-context.h"
30 #include "event-context.h"
31 #include "sp-namedview.h"
32 #include "snap.h"
33 #include "selection.h"
36 #define KNOT_EVENT_MASK (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | \
37 GDK_POINTER_MOTION_MASK | \
38 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK)
40 static bool nograb = false;
42 static bool grabbed = FALSE;
43 static bool moved = FALSE;
45 static gint xp = 0, yp = 0; // where drag started
46 static gint tolerance = 0;
47 static bool within_tolerance = false;
49 static bool transform_escaped = false; // true iff resize or rotate was cancelled by esc.
51 enum {
52 EVENT,
53 CLICKED,
54 DOUBLECLICKED,
55 GRABBED,
56 UNGRABBED,
57 MOVED,
58 REQUEST,
59 DISTANCE,
60 LAST_SIGNAL
61 };
63 static void sp_knot_class_init(SPKnotClass *klass);
64 static void sp_knot_init(SPKnot *knot);
65 static void sp_knot_dispose(GObject *object);
67 static int sp_knot_handler(SPCanvasItem *item, GdkEvent *event, SPKnot *knot);
68 static void sp_knot_set_ctrl_state(SPKnot *knot);
70 static GObjectClass *parent_class;
71 static guint knot_signals[LAST_SIGNAL] = { 0 };
73 /**
74 * Registers SPKnot class and returns its type number.
75 */
76 GType sp_knot_get_type()
77 {
78 static GType type = 0;
79 if (!type) {
80 GTypeInfo info = {
81 sizeof(SPKnotClass),
82 NULL, /* base_init */
83 NULL, /* base_finalize */
84 (GClassInitFunc) sp_knot_class_init,
85 NULL, /* class_finalize */
86 NULL, /* class_data */
87 sizeof (SPKnot),
88 16, /* n_preallocs */
89 (GInstanceInitFunc) sp_knot_init,
90 NULL
91 };
92 type = g_type_register_static (G_TYPE_OBJECT, "SPKnot", &info, (GTypeFlags) 0);
93 }
94 return type;
95 }
97 /**
98 * SPKnot vtable initialization.
99 */
100 static void sp_knot_class_init(SPKnotClass *klass)
101 {
102 GObjectClass *object_class = (GObjectClass*)klass;
104 parent_class = (GObjectClass*) g_type_class_peek_parent(klass);
106 object_class->dispose = sp_knot_dispose;
108 knot_signals[EVENT] = g_signal_new("event",
109 G_TYPE_FROM_CLASS(klass),
110 G_SIGNAL_RUN_LAST,
111 G_STRUCT_OFFSET(SPKnotClass, event),
112 NULL, NULL,
113 sp_marshal_BOOLEAN__POINTER,
114 G_TYPE_BOOLEAN, 1,
115 GDK_TYPE_EVENT);
117 knot_signals[CLICKED] = g_signal_new("clicked",
118 G_TYPE_FROM_CLASS(klass),
119 G_SIGNAL_RUN_FIRST,
120 G_STRUCT_OFFSET(SPKnotClass, clicked),
121 NULL, NULL,
122 sp_marshal_NONE__UINT,
123 G_TYPE_NONE, 1,
124 G_TYPE_UINT);
126 knot_signals[DOUBLECLICKED] = g_signal_new("doubleclicked",
127 G_TYPE_FROM_CLASS(klass),
128 G_SIGNAL_RUN_FIRST,
129 G_STRUCT_OFFSET(SPKnotClass, doubleclicked),
130 NULL, NULL,
131 sp_marshal_NONE__UINT,
132 G_TYPE_NONE, 1,
133 G_TYPE_UINT);
135 knot_signals[GRABBED] = g_signal_new("grabbed",
136 G_TYPE_FROM_CLASS(klass),
137 G_SIGNAL_RUN_FIRST,
138 G_STRUCT_OFFSET(SPKnotClass, grabbed),
139 NULL, NULL,
140 sp_marshal_NONE__UINT,
141 G_TYPE_NONE, 1,
142 G_TYPE_UINT);
144 knot_signals[UNGRABBED] = g_signal_new("ungrabbed",
145 G_TYPE_FROM_CLASS(klass),
146 G_SIGNAL_RUN_FIRST,
147 G_STRUCT_OFFSET(SPKnotClass, ungrabbed),
148 NULL, NULL,
149 sp_marshal_NONE__UINT,
150 G_TYPE_NONE, 1,
151 G_TYPE_UINT);
153 knot_signals[MOVED] = g_signal_new("moved",
154 G_TYPE_FROM_CLASS(klass),
155 G_SIGNAL_RUN_FIRST,
156 G_STRUCT_OFFSET(SPKnotClass, moved),
157 NULL, NULL,
158 sp_marshal_NONE__POINTER_UINT,
159 G_TYPE_NONE, 2,
160 G_TYPE_POINTER, G_TYPE_UINT);
162 knot_signals[REQUEST] = g_signal_new("request",
163 G_TYPE_FROM_CLASS(klass),
164 G_SIGNAL_RUN_LAST,
165 G_STRUCT_OFFSET(SPKnotClass, request),
166 NULL, NULL,
167 sp_marshal_BOOLEAN__POINTER_UINT,
168 G_TYPE_BOOLEAN, 2,
169 G_TYPE_POINTER, G_TYPE_UINT);
171 knot_signals[DISTANCE] = g_signal_new("distance",
172 G_TYPE_FROM_CLASS(klass),
173 G_SIGNAL_RUN_LAST,
174 G_STRUCT_OFFSET(SPKnotClass, distance),
175 NULL, NULL,
176 sp_marshal_DOUBLE__POINTER_UINT,
177 G_TYPE_DOUBLE, 2,
178 G_TYPE_POINTER, G_TYPE_UINT);
180 const gchar *nograbenv = getenv("INKSCAPE_NO_GRAB");
181 nograb = (nograbenv && *nograbenv && (*nograbenv != '0'));
182 }
184 /**
185 * Callback for SPKnot initialization.
186 */
187 static void sp_knot_init(SPKnot *knot)
188 {
189 knot->desktop = NULL;
190 knot->item = NULL;
191 knot->flags = 0;
193 knot->size = 8;
194 knot->pos = NR::Point(0, 0);
195 knot->grabbed_rel_pos = NR::Point(0, 0);
196 knot->anchor = GTK_ANCHOR_CENTER;
197 knot->shape = SP_KNOT_SHAPE_SQUARE;
198 knot->mode = SP_KNOT_MODE_XOR;
199 knot->tip = NULL;
200 knot->_event_handler_id = 0;
201 knot->pressure = 0;
203 knot->fill[SP_KNOT_STATE_NORMAL] = 0xffffff00;
204 knot->fill[SP_KNOT_STATE_MOUSEOVER] = 0xff0000ff;
205 knot->fill[SP_KNOT_STATE_DRAGGING] = 0x0000ffff;
207 knot->stroke[SP_KNOT_STATE_NORMAL] = 0x01000000;
208 knot->stroke[SP_KNOT_STATE_MOUSEOVER] = 0x01000000;
209 knot->stroke[SP_KNOT_STATE_DRAGGING] = 0x01000000;
211 knot->image[SP_KNOT_STATE_NORMAL] = NULL;
212 knot->image[SP_KNOT_STATE_MOUSEOVER] = NULL;
213 knot->image[SP_KNOT_STATE_DRAGGING] = NULL;
215 knot->cursor[SP_KNOT_STATE_NORMAL] = NULL;
216 knot->cursor[SP_KNOT_STATE_MOUSEOVER] = NULL;
217 knot->cursor[SP_KNOT_STATE_DRAGGING] = NULL;
219 knot->saved_cursor = NULL;
220 knot->pixbuf = NULL;
221 }
223 /**
224 * Called before SPKnot destruction.
225 */
226 static void sp_knot_dispose(GObject *object)
227 {
228 SPKnot *knot = (SPKnot *) object;
230 if ((knot->flags & SP_KNOT_GRABBED) && gdk_pointer_is_grabbed ()) {
231 // This happens e.g. when deleting a node in node tool while dragging it
232 gdk_pointer_ungrab (GDK_CURRENT_TIME);
233 }
235 if (knot->_event_handler_id > 0)
236 {
237 g_signal_handler_disconnect(G_OBJECT (knot->item), knot->_event_handler_id);
238 knot->_event_handler_id = 0;
239 }
241 if (knot->item) {
242 gtk_object_destroy (GTK_OBJECT (knot->item));
243 knot->item = NULL;
244 }
246 for (gint i = 0; i < SP_KNOT_VISIBLE_STATES; i++) {
247 if (knot->cursor[i]) {
248 gdk_cursor_unref(knot->cursor[i]);
249 knot->cursor[i] = NULL;
250 }
251 }
253 if (knot->tip) {
254 g_free(knot->tip);
255 knot->tip = NULL;
256 }
258 if (parent_class->dispose) {
259 (*parent_class->dispose) (object);
260 }
261 }
263 /**
264 * Update knot for dragging and tell canvas an item was grabbed.
265 */
266 void sp_knot_start_dragging(SPKnot *knot, NR::Point p, gint x, gint y, guint32 etime)
267 {
268 // save drag origin
269 xp = x;
270 yp = y;
271 within_tolerance = true;
273 knot->grabbed_rel_pos = p - knot->pos;
274 knot->drag_origin = knot->pos;
275 if (!nograb) {
276 sp_canvas_item_grab(knot->item,
277 KNOT_EVENT_MASK,
278 knot->cursor[SP_KNOT_STATE_DRAGGING],
279 etime);
280 }
281 sp_knot_set_flag(knot, SP_KNOT_GRABBED, TRUE);
282 grabbed = TRUE;
283 }
285 /**
286 * Called to handle events on knots.
287 */
288 static int sp_knot_handler(SPCanvasItem *item, GdkEvent *event, SPKnot *knot)
289 {
290 g_assert(knot != NULL);
291 g_assert(SP_IS_KNOT(knot));
293 /* Run client universal event handler, if present */
295 gboolean consumed = FALSE;
297 g_signal_emit(knot, knot_signals[EVENT], 0, event, &consumed);
299 if (consumed) {
300 return TRUE;
301 }
303 g_object_ref(knot);
304 tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
306 switch (event->type) {
307 case GDK_2BUTTON_PRESS:
308 if (event->button.button == 1) {
309 g_signal_emit(knot, knot_signals[DOUBLECLICKED], 0, event->button.state);
311 grabbed = FALSE;
312 moved = FALSE;
313 consumed = TRUE;
314 }
315 break;
316 case GDK_BUTTON_PRESS:
317 if (event->button.button == 1) {
318 NR::Point const p = knot->desktop->w2d(NR::Point(event->button.x, event->button.y));
319 sp_knot_start_dragging(knot, p, (gint) event->button.x, (gint) event->button.y, event->button.time);
320 consumed = TRUE;
321 }
322 break;
323 case GDK_BUTTON_RELEASE:
324 if (event->button.button == 1) {
325 knot->pressure = 0;
326 if (transform_escaped) {
327 transform_escaped = false;
328 consumed = TRUE;
329 } else {
330 sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
331 if (!nograb) {
332 sp_canvas_item_ungrab(knot->item, event->button.time);
333 }
334 if (moved) {
335 sp_knot_set_flag(knot,
336 SP_KNOT_DRAGGING,
337 FALSE);
338 g_signal_emit(knot,
339 knot_signals[UNGRABBED], 0,
340 event->button.state);
341 } else {
342 g_signal_emit(knot,
343 knot_signals[CLICKED], 0,
344 event->button.state);
345 }
346 grabbed = FALSE;
347 moved = FALSE;
348 consumed = TRUE;
349 }
350 }
351 break;
352 case GDK_MOTION_NOTIFY:
353 if (grabbed) {
354 consumed = TRUE;
356 if ( within_tolerance
357 && ( abs( (gint) event->motion.x - xp ) < tolerance )
358 && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) {
359 break; // do not drag if we're within tolerance from origin
360 }
362 // Once the user has moved farther than tolerance from the original location
363 // (indicating they intend to move the object, not click), then always process the
364 // motion notify coordinates as given (no snapping back to origin)
365 within_tolerance = false;
367 if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &knot->pressure))
368 knot->pressure = CLAMP (knot->pressure, 0, 1);
369 else
370 knot->pressure = 0.5;
372 if (!moved) {
373 g_signal_emit(knot,
374 knot_signals[GRABBED], 0,
375 event->motion.state);
376 sp_knot_set_flag(knot,
377 SP_KNOT_DRAGGING,
378 TRUE);
379 }
380 NR::Point const motion_w(event->motion.x, event->motion.y);
381 NR::Point motion_dt = knot->desktop->w2d(motion_w) - knot->grabbed_rel_pos;
383 // Only snap path nodes, for which the anchor is set to GTK_ANCHOR_CENTER
384 // (according to default value as set in sp_knot_init())
385 // If we have one of the transform handles at hand, then the anchor will be
386 // set to e.g. GTK_ANCHOR_SW (see seltrans-handles.cpp). The transform handles
387 // will be snapped in seltrans.cpp, not here
388 if (knot->anchor == GTK_ANCHOR_CENTER) {
389 SnapManager const &m = knot->desktop->namedview->snap_manager;
390 SPItem *curr = knot->desktop->selection->singleItem(); //Is this really the only way to get to the item to which this knot belongs?
391 motion_dt = m.freeSnap(Inkscape::Snapper::BBOX_POINT | Inkscape::Snapper::SNAP_POINT, motion_dt, curr).getPoint();
392 }
394 sp_knot_request_position (knot, &motion_dt, event->motion.state);
395 knot->desktop->scroll_to_point (&motion_dt);
396 knot->desktop->set_coordinate_status(knot->pos); // display the coordinate of knot, not cursor - they may be different!
397 moved = TRUE;
398 }
399 break;
400 case GDK_ENTER_NOTIFY:
401 sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, TRUE);
402 sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
404 if (knot->tip) {
405 knot->desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, knot->tip);
406 }
408 grabbed = FALSE;
409 moved = FALSE;
410 consumed = TRUE;
411 break;
412 case GDK_LEAVE_NOTIFY:
413 sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE);
414 sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
416 if (knot->tip) {
417 knot->desktop->event_context->defaultMessageContext()->clear();
418 }
420 grabbed = FALSE;
421 moved = FALSE;
423 consumed = TRUE;
424 break;
425 case GDK_KEY_PRESS: // keybindings for knot
426 switch (get_group0_keyval(&event->key)) {
427 case GDK_Escape:
428 sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
429 if (!nograb) {
430 sp_canvas_item_ungrab(knot->item, event->button.time);
431 }
432 if (moved) {
433 sp_knot_set_flag(knot,
434 SP_KNOT_DRAGGING,
435 FALSE);
436 g_signal_emit(knot,
437 knot_signals[UNGRABBED], 0,
438 event->button.state);
439 sp_document_undo(sp_desktop_document(knot->desktop));
440 knot->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Node or handle drag canceled."));
441 transform_escaped = true;
442 consumed = TRUE;
443 }
444 grabbed = FALSE;
445 moved = FALSE;
446 break;
447 default:
448 consumed = FALSE;
449 break;
450 }
451 break;
452 default:
453 break;
454 }
456 g_object_unref(knot);
458 return consumed;
459 }
461 /**
462 * Return new knot object.
463 */
464 SPKnot *sp_knot_new(SPDesktop *desktop, const gchar *tip)
465 {
466 g_return_val_if_fail(desktop != NULL, NULL);
468 SPKnot * knot = (SPKnot*) g_object_new(SP_TYPE_KNOT, 0);
470 knot->desktop = desktop;
471 knot->flags = SP_KNOT_VISIBLE;
472 if (tip) {
473 knot->tip = g_strdup (tip);
474 }
476 knot->item = sp_canvas_item_new(sp_desktop_controls (desktop),
477 SP_TYPE_CTRL,
478 "anchor", GTK_ANCHOR_CENTER,
479 "size", 8.0,
480 "filled", TRUE,
481 "fill_color", 0xffffff00,
482 "stroked", TRUE,
483 "stroke_color", 0x01000000,
484 "mode", SP_KNOT_MODE_XOR,
485 NULL);
487 knot->_event_handler_id = gtk_signal_connect(GTK_OBJECT(knot->item), "event",
488 GTK_SIGNAL_FUNC(sp_knot_handler), knot);
490 return knot;
491 }
493 /**
494 * Show knot on its canvas.
495 */
496 void sp_knot_show(SPKnot *knot)
497 {
498 g_return_if_fail(knot != NULL);
499 g_return_if_fail(SP_IS_KNOT (knot));
501 sp_knot_set_flag(knot, SP_KNOT_VISIBLE, TRUE);
502 }
504 /**
505 * Hide knot on its canvas.
506 */
507 void sp_knot_hide(SPKnot *knot)
508 {
509 g_return_if_fail(knot != NULL);
510 g_return_if_fail(SP_IS_KNOT(knot));
512 sp_knot_set_flag(knot, SP_KNOT_VISIBLE, FALSE);
513 }
515 /**
516 * Request or set new position for knot.
517 */
518 void sp_knot_request_position(SPKnot *knot, NR::Point *p, guint state)
519 {
520 g_return_if_fail(knot != NULL);
521 g_return_if_fail(SP_IS_KNOT(knot));
523 gboolean done = FALSE;
525 g_signal_emit(knot,
526 knot_signals[REQUEST], 0,
527 p,
528 state,
529 &done);
531 /* If user did not complete, we simply move knot to new position */
533 if (!done) {
534 sp_knot_set_position (knot, p, state);
535 }
536 }
538 /**
539 * Return distance of point to knot's position; unused.
540 */
541 gdouble sp_knot_distance(SPKnot * knot, NR::Point *p, guint state)
542 {
543 g_return_val_if_fail(knot != NULL, 1e18);
544 g_return_val_if_fail(SP_IS_KNOT(knot), 1e18);
546 gdouble distance = NR::L2(*p - knot->pos);
548 g_signal_emit(knot,
549 knot_signals[DISTANCE], 0,
550 p,
551 state,
552 &distance);
554 return distance;
555 }
557 /**
558 * Move knot to new position.
559 */
560 void sp_knot_set_position(SPKnot *knot, NR::Point *p, guint state)
561 {
562 g_return_if_fail(knot != NULL);
563 g_return_if_fail(SP_IS_KNOT (knot));
565 knot->pos = *p;
567 if (knot->item) {
568 SP_CTRL(knot->item)->moveto (*p);
569 }
571 g_signal_emit(knot,
572 knot_signals[MOVED], 0,
573 p,
574 state);
575 }
577 /**
578 * Move knot to new position, without emitting a MOVED signal.
579 */
580 void sp_knot_moveto(SPKnot *knot, NR::Point *p)
581 {
582 g_return_if_fail(knot != NULL);
583 g_return_if_fail(SP_IS_KNOT(knot));
585 knot->pos = *p;
587 if (knot->item) {
588 SP_CTRL(knot->item)->moveto (*p);
589 }
590 }
592 /**
593 * Returns position of knot.
594 */
595 NR::Point sp_knot_position(SPKnot const *knot)
596 {
597 g_assert(knot != NULL);
598 g_assert(SP_IS_KNOT (knot));
600 return knot->pos;
601 }
603 /**
604 * Set flag in knot, with side effects.
605 */
606 void sp_knot_set_flag(SPKnot *knot, guint flag, bool set)
607 {
608 g_assert(knot != NULL);
609 g_assert(SP_IS_KNOT(knot));
611 if (set) {
612 knot->flags |= flag;
613 } else {
614 knot->flags &= ~flag;
615 }
617 switch (flag) {
618 case SP_KNOT_VISIBLE:
619 if (set) {
620 sp_canvas_item_show(knot->item);
621 } else {
622 sp_canvas_item_hide(knot->item);
623 }
624 break;
625 case SP_KNOT_MOUSEOVER:
626 case SP_KNOT_DRAGGING:
627 sp_knot_set_ctrl_state(knot);
628 break;
629 case SP_KNOT_GRABBED:
630 break;
631 default:
632 g_assert_not_reached();
633 break;
634 }
635 }
637 /**
638 * Update knot's pixbuf and set its control state.
639 */
640 void sp_knot_update_ctrl(SPKnot *knot)
641 {
642 if (!knot->item) {
643 return;
644 }
646 gtk_object_set(GTK_OBJECT(knot->item), "shape", knot->shape, NULL);
647 gtk_object_set(GTK_OBJECT(knot->item), "mode", knot->mode, NULL);
648 gtk_object_set(GTK_OBJECT(knot->item), "size", (gdouble) knot->size, NULL);
649 gtk_object_set(GTK_OBJECT(knot->item), "anchor", knot->anchor, NULL);
650 if (knot->pixbuf) {
651 gtk_object_set(GTK_OBJECT (knot->item), "pixbuf", knot->pixbuf, NULL);
652 }
654 sp_knot_set_ctrl_state(knot);
655 }
657 /**
658 * Set knot control state (dragging/mouseover/normal).
659 */
660 static void sp_knot_set_ctrl_state(SPKnot *knot)
661 {
662 if (knot->flags & SP_KNOT_DRAGGING) {
663 gtk_object_set(GTK_OBJECT (knot->item),
664 "fill_color",
665 knot->fill[SP_KNOT_STATE_DRAGGING],
666 NULL);
667 gtk_object_set(GTK_OBJECT (knot->item),
668 "stroke_color",
669 knot->stroke[SP_KNOT_STATE_DRAGGING],
670 NULL);
671 } else if (knot->flags & SP_KNOT_MOUSEOVER) {
672 gtk_object_set(GTK_OBJECT(knot->item),
673 "fill_color",
674 knot->fill[SP_KNOT_STATE_MOUSEOVER],
675 NULL);
676 gtk_object_set(GTK_OBJECT(knot->item),
677 "stroke_color",
678 knot->stroke[SP_KNOT_STATE_MOUSEOVER],
679 NULL);
680 } else {
681 gtk_object_set(GTK_OBJECT(knot->item),
682 "fill_color",
683 knot->fill[SP_KNOT_STATE_NORMAL],
684 NULL);
685 gtk_object_set(GTK_OBJECT(knot->item),
686 "stroke_color",
687 knot->stroke[SP_KNOT_STATE_NORMAL],
688 NULL);
689 }
690 }
694 /*
695 Local Variables:
696 mode:c++
697 c-file-style:"stroustrup"
698 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
699 indent-tabs-mode:nil
700 fill-column:99
701 End:
702 */
703 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :