6edf3d0e806a14a585a73ee4609d4829d6f87868
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"
33 #define KNOT_EVENT_MASK (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | \
34 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | \
35 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK)
37 static bool nograb = false;
39 static bool grabbed = FALSE;
40 static bool moved = FALSE;
42 static gint xp = 0, yp = 0; // where drag started
43 static gint tolerance = 0;
44 static bool within_tolerance = false;
46 static bool transform_escaped = false; // true iff resize or rotate was cancelled by esc.
48 enum {
49 EVENT,
50 CLICKED,
51 DOUBLECLICKED,
52 GRABBED,
53 UNGRABBED,
54 MOVED,
55 REQUEST,
56 DISTANCE,
57 LAST_SIGNAL
58 };
60 static void sp_knot_class_init(SPKnotClass *klass);
61 static void sp_knot_init(SPKnot *knot);
62 static void sp_knot_dispose(GObject *object);
64 static int sp_knot_handler(SPCanvasItem *item, GdkEvent *event, SPKnot *knot);
65 static void sp_knot_set_flag(SPKnot *knot, guint flag, bool set);
66 static void sp_knot_set_ctrl_state(SPKnot *knot);
68 static GObjectClass *parent_class;
69 static guint knot_signals[LAST_SIGNAL] = { 0 };
71 /**
72 * Registers SPKnot class and returns its type number.
73 */
74 GType sp_knot_get_type()
75 {
76 static GType type = 0;
77 if (!type) {
78 GTypeInfo info = {
79 sizeof(SPKnotClass),
80 NULL, /* base_init */
81 NULL, /* base_finalize */
82 (GClassInitFunc) sp_knot_class_init,
83 NULL, /* class_finalize */
84 NULL, /* class_data */
85 sizeof (SPKnot),
86 16, /* n_preallocs */
87 (GInstanceInitFunc) sp_knot_init,
88 NULL
89 };
90 type = g_type_register_static (G_TYPE_OBJECT, "SPKnot", &info, (GTypeFlags) 0);
91 }
92 return type;
93 }
95 /**
96 * SPKnot vtable initialization.
97 */
98 static void sp_knot_class_init(SPKnotClass *klass)
99 {
100 GObjectClass *object_class = (GObjectClass *) klass;
102 parent_class = (GObjectClass*) g_type_class_peek_parent(klass);
104 object_class->dispose = sp_knot_dispose;
106 knot_signals[EVENT] = g_signal_new("event",
107 G_TYPE_FROM_CLASS(klass),
108 G_SIGNAL_RUN_LAST,
109 G_STRUCT_OFFSET(SPKnotClass, event),
110 NULL, NULL,
111 sp_marshal_BOOLEAN__POINTER,
112 G_TYPE_BOOLEAN, 1,
113 GDK_TYPE_EVENT);
115 knot_signals[CLICKED] = g_signal_new("clicked",
116 G_TYPE_FROM_CLASS(klass),
117 G_SIGNAL_RUN_FIRST,
118 G_STRUCT_OFFSET(SPKnotClass, clicked),
119 NULL, NULL,
120 sp_marshal_NONE__UINT,
121 G_TYPE_NONE, 1,
122 G_TYPE_UINT);
124 knot_signals[DOUBLECLICKED] = g_signal_new("doubleclicked",
125 G_TYPE_FROM_CLASS(klass),
126 G_SIGNAL_RUN_FIRST,
127 G_STRUCT_OFFSET(SPKnotClass, doubleclicked),
128 NULL, NULL,
129 sp_marshal_NONE__UINT,
130 G_TYPE_NONE, 1,
131 G_TYPE_UINT);
133 knot_signals[GRABBED] = g_signal_new("grabbed",
134 G_TYPE_FROM_CLASS(klass),
135 G_SIGNAL_RUN_FIRST,
136 G_STRUCT_OFFSET(SPKnotClass, grabbed),
137 NULL, NULL,
138 sp_marshal_NONE__UINT,
139 G_TYPE_NONE, 1,
140 G_TYPE_UINT);
142 knot_signals[UNGRABBED] = g_signal_new("ungrabbed",
143 G_TYPE_FROM_CLASS(klass),
144 G_SIGNAL_RUN_FIRST,
145 G_STRUCT_OFFSET(SPKnotClass, ungrabbed),
146 NULL, NULL,
147 sp_marshal_NONE__UINT,
148 G_TYPE_NONE, 1,
149 G_TYPE_UINT);
151 knot_signals[MOVED] = g_signal_new("moved",
152 G_TYPE_FROM_CLASS(klass),
153 G_SIGNAL_RUN_FIRST,
154 G_STRUCT_OFFSET(SPKnotClass, moved),
155 NULL, NULL,
156 sp_marshal_NONE__POINTER_UINT,
157 G_TYPE_NONE, 2,
158 G_TYPE_POINTER, G_TYPE_UINT);
160 knot_signals[REQUEST] = g_signal_new("request",
161 G_TYPE_FROM_CLASS(klass),
162 G_SIGNAL_RUN_LAST,
163 G_STRUCT_OFFSET(SPKnotClass, request),
164 NULL, NULL,
165 sp_marshal_BOOLEAN__POINTER_UINT,
166 G_TYPE_BOOLEAN, 2,
167 G_TYPE_POINTER, G_TYPE_UINT);
169 knot_signals[DISTANCE] = g_signal_new("distance",
170 G_TYPE_FROM_CLASS(klass),
171 G_SIGNAL_RUN_LAST,
172 G_STRUCT_OFFSET(SPKnotClass, distance),
173 NULL, NULL,
174 sp_marshal_DOUBLE__POINTER_UINT,
175 G_TYPE_DOUBLE, 2,
176 G_TYPE_POINTER, G_TYPE_UINT);
178 const gchar *nograbenv = getenv("INKSCAPE_NO_GRAB");
179 nograb = (nograbenv && *nograbenv && (*nograbenv != '0'));
180 }
182 /**
183 * Callback for SPKnot initialization.
184 */
185 static void sp_knot_init(SPKnot *knot)
186 {
187 knot->desktop = NULL;
188 knot->item = NULL;
189 knot->flags = 0;
191 knot->size = 8;
192 knot->pos = NR::Point(0, 0);
193 knot->grabbed_rel_pos = NR::Point(0, 0);
194 knot->anchor = GTK_ANCHOR_CENTER;
195 knot->shape = SP_KNOT_SHAPE_SQUARE;
196 knot->mode = SP_KNOT_MODE_XOR;
197 knot->tip = NULL;
199 knot->fill[SP_KNOT_STATE_NORMAL] = 0xffffff00;
200 knot->fill[SP_KNOT_STATE_MOUSEOVER] = 0xff0000ff;
201 knot->fill[SP_KNOT_STATE_DRAGGING] = 0x0000ffff;
203 knot->stroke[SP_KNOT_STATE_NORMAL] = 0x01000000;
204 knot->stroke[SP_KNOT_STATE_MOUSEOVER] = 0x01000000;
205 knot->stroke[SP_KNOT_STATE_DRAGGING] = 0x01000000;
207 knot->image[SP_KNOT_STATE_NORMAL] = NULL;
208 knot->image[SP_KNOT_STATE_MOUSEOVER] = NULL;
209 knot->image[SP_KNOT_STATE_DRAGGING] = NULL;
211 knot->cursor[SP_KNOT_STATE_NORMAL] = NULL;
212 knot->cursor[SP_KNOT_STATE_MOUSEOVER] = NULL;
213 knot->cursor[SP_KNOT_STATE_DRAGGING] = NULL;
215 knot->saved_cursor = NULL;
216 knot->pixbuf = NULL;
217 }
219 /**
220 * Called before SPKnot destruction.
221 */
222 static void sp_knot_dispose(GObject *object)
223 {
224 SPKnot *knot = (SPKnot *) object;
226 if ((knot->flags & SP_KNOT_GRABBED) && gdk_pointer_is_grabbed ()) {
227 // This happens e.g. when deleting a node in node tool while dragging it
228 gdk_pointer_ungrab (GDK_CURRENT_TIME);
229 }
231 if (knot->item) {
232 gtk_object_destroy (GTK_OBJECT (knot->item));
233 knot->item = NULL;
234 }
236 for (gint i = 0; i < SP_KNOT_VISIBLE_STATES; i++) {
237 if (knot->cursor[i]) {
238 gdk_cursor_unref(knot->cursor[i]);
239 knot->cursor[i] = NULL;
240 }
241 }
243 if (knot->tip) {
244 g_free(knot->tip);
245 knot->tip = NULL;
246 }
248 if (((GObjectClass *) (parent_class))->dispose) {
249 (* ((GObjectClass *) (parent_class))->dispose) (object);
250 }
251 }
253 /**
254 * Update knot for dragging and tell canvas an item was grabbed.
255 */
256 void sp_knot_start_dragging(SPKnot *knot, NR::Point p, gint x, gint y, guint32 etime)
257 {
258 // save drag origin
259 xp = x;
260 yp = y;
261 within_tolerance = true;
263 knot->grabbed_rel_pos = p - knot->pos;
264 knot->drag_origin = knot->pos;
265 if (!nograb) {
266 sp_canvas_item_grab(knot->item,
267 KNOT_EVENT_MASK,
268 knot->cursor[SP_KNOT_STATE_DRAGGING],
269 etime);
270 }
271 sp_knot_set_flag(knot, SP_KNOT_GRABBED, TRUE);
272 grabbed = TRUE;
273 }
275 /**
276 * Called to handle events on knots.
277 */
278 static int sp_knot_handler(SPCanvasItem *item, GdkEvent *event, SPKnot *knot)
279 {
280 g_assert(knot != NULL);
281 g_assert(SP_IS_KNOT(knot));
283 tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
285 gboolean consumed = FALSE;
287 /* Run client universal event handler, if present */
289 g_signal_emit(G_OBJECT(knot), knot_signals[EVENT], 0, event, &consumed);
291 if (consumed) {
292 return TRUE;
293 }
295 switch (event->type) {
296 case GDK_2BUTTON_PRESS:
297 if (event->button.button == 1) {
298 g_signal_emit(G_OBJECT(knot), knot_signals[DOUBLECLICKED], 0, event->button.state);
300 grabbed = FALSE;
301 moved = FALSE;
302 consumed = TRUE;
303 }
304 break;
305 case GDK_BUTTON_PRESS:
306 if (event->button.button == 1) {
307 NR::Point const p = knot->desktop->w2d(NR::Point(event->button.x, event->button.y));
308 sp_knot_start_dragging(knot, p, (gint) event->button.x, (gint) event->button.y, event->button.time);
309 consumed = TRUE;
310 }
311 break;
312 case GDK_BUTTON_RELEASE:
313 if (event->button.button == 1) {
314 if (transform_escaped) {
315 transform_escaped = false;
316 consumed = TRUE;
317 } else {
318 sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
319 if (!nograb) {
320 sp_canvas_item_ungrab(knot->item, event->button.time);
321 }
322 if (moved) {
323 sp_knot_set_flag(knot,
324 SP_KNOT_DRAGGING,
325 FALSE);
326 g_signal_emit(G_OBJECT (knot),
327 knot_signals[UNGRABBED], 0,
328 event->button.state);
329 } else {
330 g_signal_emit(G_OBJECT (knot),
331 knot_signals[CLICKED], 0,
332 event->button.state);
333 }
334 grabbed = FALSE;
335 moved = FALSE;
336 consumed = TRUE;
337 }
338 }
339 break;
340 case GDK_MOTION_NOTIFY:
341 if (grabbed) {
342 consumed = TRUE;
344 if ( within_tolerance
345 && ( abs( (gint) event->motion.x - xp ) < tolerance )
346 && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) {
347 break; // do not drag if we're within tolerance from origin
348 }
350 // Once the user has moved farther than tolerance from the original location
351 // (indicating they intend to move the object, not click), then always process the
352 // motion notify coordinates as given (no snapping back to origin)
353 within_tolerance = false;
355 if (!moved) {
356 g_signal_emit(G_OBJECT (knot),
357 knot_signals[GRABBED], 0,
358 event->motion.state);
359 sp_knot_set_flag(knot,
360 SP_KNOT_DRAGGING,
361 TRUE);
362 }
363 NR::Point const motion_w(event->motion.x, event->motion.y);
364 NR::Point const motion_dt = knot->desktop->w2d(motion_w);
365 NR::Point p = motion_dt - knot->grabbed_rel_pos;
366 sp_knot_request_position (knot, &p, event->motion.state);
367 knot->desktop->scroll_to_point (&motion_dt);
368 moved = TRUE;
369 }
370 break;
371 case GDK_ENTER_NOTIFY:
372 sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, TRUE);
373 sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
375 if (knot->tip) {
376 knot->desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, knot->tip);
377 }
379 grabbed = FALSE;
380 moved = FALSE;
381 consumed = TRUE;
382 break;
383 case GDK_LEAVE_NOTIFY:
384 sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE);
385 sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
387 if (knot->tip) {
388 knot->desktop->event_context->defaultMessageContext()->clear();
389 }
391 grabbed = FALSE;
392 moved = FALSE;
394 consumed = TRUE;
395 break;
396 case GDK_KEY_PRESS: // keybindings for knot
397 switch (get_group0_keyval(&event->key)) {
398 case GDK_Escape:
399 sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
400 if (!nograb) {
401 sp_canvas_item_ungrab(knot->item, event->button.time);
402 }
403 if (moved) {
404 sp_knot_set_flag(knot,
405 SP_KNOT_DRAGGING,
406 FALSE);
407 g_signal_emit(G_OBJECT(knot),
408 knot_signals[UNGRABBED], 0,
409 event->button.state);
410 sp_document_undo(SP_DT_DOCUMENT(knot->desktop));
411 knot->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Node or handle drag canceled."));
412 transform_escaped = true;
413 consumed = TRUE;
414 }
415 grabbed = FALSE;
416 moved = FALSE;
417 break;
418 default:
419 consumed = FALSE;
420 break;
421 }
422 break;
423 default:
424 break;
425 }
428 return consumed;
429 }
431 /**
432 * Return new knot object.
433 */
434 SPKnot *sp_knot_new(SPDesktop *desktop, const gchar *tip)
435 {
436 g_return_val_if_fail(desktop != NULL, NULL);
438 SPKnot * knot = (SPKnot*) g_object_new(SP_TYPE_KNOT, 0);
440 knot->desktop = desktop;
441 knot->flags = SP_KNOT_VISIBLE;
442 if (tip) {
443 knot->tip = g_strdup (tip);
444 }
446 knot->item = sp_canvas_item_new(SP_DT_CONTROLS (desktop),
447 SP_TYPE_CTRL,
448 "anchor", GTK_ANCHOR_CENTER,
449 "size", 8.0,
450 "filled", TRUE,
451 "fill_color", 0xffffff00,
452 "stroked", TRUE,
453 "stroke_color", 0x01000000,
454 "mode", SP_KNOT_MODE_XOR,
455 NULL);
457 gtk_signal_connect(GTK_OBJECT(knot->item), "event",
458 GTK_SIGNAL_FUNC(sp_knot_handler), knot);
460 return knot;
461 }
463 /**
464 * Show knot on its canvas.
465 */
466 void sp_knot_show(SPKnot *knot)
467 {
468 g_return_if_fail(knot != NULL);
469 g_return_if_fail(SP_IS_KNOT (knot));
471 sp_knot_set_flag(knot, SP_KNOT_VISIBLE, TRUE);
472 }
474 /**
475 * Hide knot on its canvas.
476 */
477 void sp_knot_hide(SPKnot *knot)
478 {
479 g_return_if_fail(knot != NULL);
480 g_return_if_fail(SP_IS_KNOT(knot));
482 sp_knot_set_flag(knot, SP_KNOT_VISIBLE, FALSE);
483 }
485 /**
486 * Request or set new position for knot.
487 */
488 void sp_knot_request_position(SPKnot *knot, NR::Point *p, guint state)
489 {
490 g_return_if_fail(knot != NULL);
491 g_return_if_fail(SP_IS_KNOT(knot));
493 gboolean done = FALSE;
495 g_signal_emit(G_OBJECT (knot),
496 knot_signals[REQUEST], 0,
497 p,
498 state,
499 &done);
501 /* If user did not complete, we simply move knot to new position */
503 if (!done) {
504 sp_knot_set_position (knot, p, state);
505 }
506 }
508 /**
509 * Return distance of point to knot's position; unused.
510 */
511 gdouble sp_knot_distance(SPKnot * knot, NR::Point *p, guint state)
512 {
513 g_return_val_if_fail(knot != NULL, 1e18);
514 g_return_val_if_fail(SP_IS_KNOT(knot), 1e18);
516 gdouble distance = NR::L2(*p - knot->pos);
518 g_signal_emit(G_OBJECT(knot),
519 knot_signals[DISTANCE], 0,
520 p,
521 state,
522 &distance);
524 return distance;
525 }
527 /**
528 * Move knot to new position.
529 */
530 void sp_knot_set_position(SPKnot *knot, NR::Point *p, guint state)
531 {
532 g_return_if_fail(knot != NULL);
533 g_return_if_fail(SP_IS_KNOT (knot));
535 knot->pos = *p;
537 if (knot->item) {
538 SP_CTRL(knot->item)->moveto (*p);
539 }
541 g_signal_emit(G_OBJECT (knot),
542 knot_signals[MOVED], 0,
543 p,
544 state);
545 }
547 /**
548 * Move knot to new position, without emitting a MOVED signal. We can't be sure this artificial
549 * move has moved the knot from under cursor, but it seems likely, so we clear SP_KNOT_MOUSEOVER.
550 */
551 void sp_knot_moveto(SPKnot *knot, NR::Point *p)
552 {
553 g_return_if_fail(knot != NULL);
554 g_return_if_fail(SP_IS_KNOT(knot));
556 knot->pos = *p;
558 if (knot->item) {
559 SP_CTRL(knot->item)->moveto (*p);
560 }
562 sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE);
563 }
565 /**
566 * Returns position of knot.
567 */
568 NR::Point sp_knot_position(SPKnot const *knot)
569 {
570 g_assert(knot != NULL);
571 g_assert(SP_IS_KNOT (knot));
573 return knot->pos;
574 }
576 /**
577 * Set flag in knot, with side effects.
578 */
579 static void sp_knot_set_flag(SPKnot *knot, guint flag, bool set)
580 {
581 g_assert(knot != NULL);
582 g_assert(SP_IS_KNOT(knot));
584 if (set) {
585 knot->flags |= flag;
586 } else {
587 knot->flags &= ~flag;
588 }
590 switch (flag) {
591 case SP_KNOT_VISIBLE:
592 if (set) {
593 sp_canvas_item_show(knot->item);
594 } else {
595 sp_canvas_item_hide(knot->item);
596 }
597 break;
598 case SP_KNOT_MOUSEOVER:
599 case SP_KNOT_DRAGGING:
600 sp_knot_set_ctrl_state(knot);
601 break;
602 case SP_KNOT_GRABBED:
603 break;
604 default:
605 g_assert_not_reached();
606 break;
607 }
608 }
610 /**
611 * Update knot's pixbuf and set its control state.
612 */
613 void sp_knot_update_ctrl(SPKnot *knot)
614 {
615 if (!knot->item) {
616 return;
617 }
619 gtk_object_set(GTK_OBJECT(knot->item), "shape", knot->shape, NULL);
620 gtk_object_set(GTK_OBJECT(knot->item), "mode", knot->mode, NULL);
621 gtk_object_set(GTK_OBJECT(knot->item), "size", (gdouble) knot->size, NULL);
622 gtk_object_set(GTK_OBJECT(knot->item), "anchor", knot->anchor, NULL);
623 if (knot->pixbuf) {
624 gtk_object_set(GTK_OBJECT (knot->item), "pixbuf", knot->pixbuf, NULL);
625 }
627 sp_knot_set_ctrl_state(knot);
628 }
630 /**
631 * Set knot control state (dragging/mouseover/normal).
632 */
633 static void sp_knot_set_ctrl_state(SPKnot *knot)
634 {
635 if (knot->flags & SP_KNOT_DRAGGING) {
636 gtk_object_set(GTK_OBJECT (knot->item),
637 "fill_color",
638 knot->fill[SP_KNOT_STATE_DRAGGING],
639 NULL);
640 gtk_object_set(GTK_OBJECT (knot->item),
641 "stroke_color",
642 knot->stroke[SP_KNOT_STATE_DRAGGING],
643 NULL);
644 } else if (knot->flags & SP_KNOT_MOUSEOVER) {
645 gtk_object_set(GTK_OBJECT(knot->item),
646 "fill_color",
647 knot->fill[SP_KNOT_STATE_MOUSEOVER],
648 NULL);
649 gtk_object_set(GTK_OBJECT(knot->item),
650 "stroke_color",
651 knot->stroke[SP_KNOT_STATE_MOUSEOVER],
652 NULL);
653 } else {
654 gtk_object_set(GTK_OBJECT(knot->item),
655 "fill_color",
656 knot->fill[SP_KNOT_STATE_NORMAL],
657 NULL);
658 gtk_object_set(GTK_OBJECT(knot->item),
659 "stroke_color",
660 knot->stroke[SP_KNOT_STATE_NORMAL],
661 NULL);
662 }
663 }
667 /*
668 Local Variables:
669 mode:c++
670 c-file-style:"stroustrup"
671 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
672 indent-tabs-mode:nil
673 fill-column:99
674 End:
675 */
676 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :