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 PROP_0,
51 PROP_SIZE,
52 PROP_ANCHOR,
53 PROP_SHAPE,
54 PROP_MODE,
55 PROP_FILL, PROP_FILL_MOUSEOVER, PROP_FILL_DRAGGING,
56 PROP_STROKE, PROP_STROKE_MOUSEOVER, PROP_STROKE_DRAGGING,
57 PROP_IMAGE, PROP_IMAGE_MOUSEOVER, PROP_IMAGE_DRAGGING,
58 PROP_CURSOR, PROP_CURSOR_MOUSEOVER, PROP_CURSOR_DRAGGING,
59 PROP_PIXBUF,
60 PROP_TIP,
62 PROP_LAST
63 };
65 enum {
66 EVENT,
67 CLICKED,
68 DOUBLECLICKED,
69 GRABBED,
70 UNGRABBED,
71 MOVED,
72 REQUEST,
73 DISTANCE,
74 LAST_SIGNAL
75 };
77 static void sp_knot_class_init(SPKnotClass *klass);
78 static void sp_knot_init(SPKnot *knot);
79 static void sp_knot_dispose(GObject *object);
80 static void sp_knot_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
81 static void sp_knot_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
83 static int sp_knot_handler(SPCanvasItem *item, GdkEvent *event, SPKnot *knot);
84 static void sp_knot_set_flag(SPKnot *knot, guint flag, bool set);
85 static void sp_knot_update_ctrl(SPKnot *knot);
86 static void sp_knot_set_ctrl_state(SPKnot *knot);
88 static GObjectClass *parent_class;
89 static guint knot_signals[LAST_SIGNAL] = { 0 };
91 /**
92 * Registers SPKnot class and returns its type number.
93 */
94 GType sp_knot_get_type()
95 {
96 static GType type = 0;
97 if (!type) {
98 GTypeInfo info = {
99 sizeof(SPKnotClass),
100 NULL, /* base_init */
101 NULL, /* base_finalize */
102 (GClassInitFunc) sp_knot_class_init,
103 NULL, /* class_finalize */
104 NULL, /* class_data */
105 sizeof (SPKnot),
106 16, /* n_preallocs */
107 (GInstanceInitFunc) sp_knot_init,
108 NULL
109 };
110 type = g_type_register_static (G_TYPE_OBJECT, "SPKnot", &info, (GTypeFlags) 0);
111 }
112 return type;
113 }
115 /**
116 * SPKnot vtable initialization.
117 */
118 static void sp_knot_class_init(SPKnotClass *klass)
119 {
120 GObjectClass *object_class = (GObjectClass *) klass;
122 parent_class = (GObjectClass*) g_type_class_peek_parent(klass);
124 object_class->dispose = sp_knot_dispose;
125 object_class->set_property = sp_knot_set_property;
126 object_class->get_property = sp_knot_get_property;
128 /* Huh :) */
130 g_object_class_install_property(object_class,
131 PROP_SIZE,
132 g_param_spec_uint("size", "Size", "",
133 0,
134 0xffffffff,
135 0xff000000,
136 (GParamFlags) G_PARAM_READWRITE));
137 g_object_class_install_property(object_class,
138 PROP_ANCHOR,
139 g_param_spec_enum("anchor", "Anchor", "",
140 GTK_TYPE_ANCHOR_TYPE,
141 GTK_ANCHOR_CENTER,
142 (GParamFlags) G_PARAM_READWRITE));
143 g_object_class_install_property(object_class,
144 PROP_SHAPE,
145 g_param_spec_int("shape", "Shape", "",
146 SP_KNOT_SHAPE_SQUARE,
147 SP_KNOT_SHAPE_IMAGE,
148 SP_KNOT_SHAPE_SQUARE,
149 (GParamFlags) G_PARAM_READWRITE));
151 g_object_class_install_property(object_class,
152 PROP_MODE,
153 g_param_spec_int("mode", "Mode", "",
154 SP_KNOT_MODE_COLOR,
155 SP_KNOT_MODE_XOR,
156 SP_KNOT_MODE_XOR,
157 (GParamFlags) G_PARAM_READWRITE));
159 g_object_class_install_property(object_class,
160 PROP_FILL,
161 g_param_spec_uint("fill", "Fill", "",
162 0,
163 0xffffffff,
164 0xff000000,
165 (GParamFlags) G_PARAM_READWRITE));
167 g_object_class_install_property(object_class,
168 PROP_FILL_MOUSEOVER,
169 g_param_spec_uint("fill_mouseover", "Fill mouse over", "",
170 0,
171 0xffffffff,
172 0xff000000,
173 (GParamFlags) G_PARAM_READWRITE));
175 g_object_class_install_property(object_class,
176 PROP_FILL_DRAGGING,
177 g_param_spec_uint("fill_dragging", "Fill dragging", "",
178 0,
179 0xffffffff,
180 0xff000000,
181 (GParamFlags) G_PARAM_READWRITE));
183 g_object_class_install_property(object_class,
184 PROP_STROKE,
185 g_param_spec_uint("stroke", "Stroke", "",
186 0,
187 0xffffffff,
188 0x01000000,
189 (GParamFlags) G_PARAM_READWRITE));
191 g_object_class_install_property(object_class,
192 PROP_STROKE_MOUSEOVER,
193 g_param_spec_uint("stroke_mouseover", "Stroke mouseover", "",
194 0,
195 0xffffffff,
196 0x01000000,
197 (GParamFlags) G_PARAM_READWRITE));
199 g_object_class_install_property(object_class,
200 PROP_STROKE_DRAGGING,
201 g_param_spec_uint("stroke_dragging", "Stroke dragging", "",
202 0,
203 0xffffffff,
204 0x01000000,
205 (GParamFlags) G_PARAM_READWRITE));
207 g_object_class_install_property(object_class,
208 PROP_IMAGE,
209 g_param_spec_pointer("image", "Image", "",
210 (GParamFlags) G_PARAM_READWRITE));
212 g_object_class_install_property(object_class,
213 PROP_IMAGE_MOUSEOVER,
214 g_param_spec_pointer("image_mouseover", "Image mouseover", "",
215 (GParamFlags) G_PARAM_READWRITE));
217 g_object_class_install_property(object_class,
218 PROP_IMAGE_DRAGGING,
219 g_param_spec_pointer("image_dragging", "Image dragging", "",
220 (GParamFlags) G_PARAM_READWRITE));
222 g_object_class_install_property(object_class,
223 PROP_CURSOR,
224 g_param_spec_boxed("cursor", "Cursor", "",
225 GDK_TYPE_CURSOR,
226 (GParamFlags) G_PARAM_READWRITE));
228 g_object_class_install_property(object_class,
229 PROP_CURSOR_MOUSEOVER,
230 g_param_spec_boxed("cursor_mouseover", "Cursor mouseover", "",
231 GDK_TYPE_CURSOR,
232 (GParamFlags) G_PARAM_READWRITE));
234 g_object_class_install_property(object_class,
235 PROP_CURSOR_DRAGGING,
236 g_param_spec_boxed("cursor_dragging", "Cursor dragging", "",
237 GDK_TYPE_CURSOR,
238 (GParamFlags) G_PARAM_READWRITE));
240 g_object_class_install_property(object_class,
241 PROP_PIXBUF,
242 g_param_spec_pointer("pixbuf", "Pixbuf", "",
243 (GParamFlags) G_PARAM_READWRITE));
245 g_object_class_install_property(object_class,
246 PROP_TIP,
247 g_param_spec_pointer("tip", "Tip", "",
248 (GParamFlags) G_PARAM_READWRITE));
250 knot_signals[EVENT] = g_signal_new("event",
251 G_TYPE_FROM_CLASS(klass),
252 G_SIGNAL_RUN_LAST,
253 G_STRUCT_OFFSET(SPKnotClass, event),
254 NULL, NULL,
255 sp_marshal_BOOLEAN__POINTER,
256 G_TYPE_BOOLEAN, 1,
257 GDK_TYPE_EVENT);
259 knot_signals[CLICKED] = g_signal_new("clicked",
260 G_TYPE_FROM_CLASS(klass),
261 G_SIGNAL_RUN_FIRST,
262 G_STRUCT_OFFSET(SPKnotClass, clicked),
263 NULL, NULL,
264 sp_marshal_NONE__UINT,
265 G_TYPE_NONE, 1,
266 G_TYPE_UINT);
268 knot_signals[DOUBLECLICKED] = g_signal_new("doubleclicked",
269 G_TYPE_FROM_CLASS(klass),
270 G_SIGNAL_RUN_FIRST,
271 G_STRUCT_OFFSET(SPKnotClass, doubleclicked),
272 NULL, NULL,
273 sp_marshal_NONE__UINT,
274 G_TYPE_NONE, 1,
275 G_TYPE_UINT);
277 knot_signals[GRABBED] = g_signal_new("grabbed",
278 G_TYPE_FROM_CLASS(klass),
279 G_SIGNAL_RUN_FIRST,
280 G_STRUCT_OFFSET(SPKnotClass, grabbed),
281 NULL, NULL,
282 sp_marshal_NONE__UINT,
283 G_TYPE_NONE, 1,
284 G_TYPE_UINT);
286 knot_signals[UNGRABBED] = g_signal_new("ungrabbed",
287 G_TYPE_FROM_CLASS(klass),
288 G_SIGNAL_RUN_FIRST,
289 G_STRUCT_OFFSET(SPKnotClass, ungrabbed),
290 NULL, NULL,
291 sp_marshal_NONE__UINT,
292 G_TYPE_NONE, 1,
293 G_TYPE_UINT);
295 knot_signals[MOVED] = g_signal_new("moved",
296 G_TYPE_FROM_CLASS(klass),
297 G_SIGNAL_RUN_FIRST,
298 G_STRUCT_OFFSET(SPKnotClass, moved),
299 NULL, NULL,
300 sp_marshal_NONE__POINTER_UINT,
301 G_TYPE_NONE, 2,
302 G_TYPE_POINTER, G_TYPE_UINT);
304 knot_signals[REQUEST] = g_signal_new("request",
305 G_TYPE_FROM_CLASS(klass),
306 G_SIGNAL_RUN_LAST,
307 G_STRUCT_OFFSET(SPKnotClass, request),
308 NULL, NULL,
309 sp_marshal_BOOLEAN__POINTER_UINT,
310 G_TYPE_BOOLEAN, 2,
311 G_TYPE_POINTER, G_TYPE_UINT);
313 knot_signals[DISTANCE] = g_signal_new("distance",
314 G_TYPE_FROM_CLASS(klass),
315 G_SIGNAL_RUN_LAST,
316 G_STRUCT_OFFSET(SPKnotClass, distance),
317 NULL, NULL,
318 sp_marshal_DOUBLE__POINTER_UINT,
319 G_TYPE_DOUBLE, 2,
320 G_TYPE_POINTER, G_TYPE_UINT);
322 const gchar *nograbenv = getenv("INKSCAPE_NO_GRAB");
323 nograb = (nograbenv && *nograbenv && (*nograbenv != '0'));
324 }
326 /**
327 * Callback for SPKnot initialization.
328 */
329 static void sp_knot_init(SPKnot *knot)
330 {
331 knot->desktop = NULL;
332 knot->item = NULL;
333 knot->flags = 0;
335 knot->size = 8;
336 knot->pos = NR::Point(0, 0);
337 knot->grabbed_rel_pos = NR::Point(0, 0);
338 knot->anchor = GTK_ANCHOR_CENTER;
339 knot->shape = SP_KNOT_SHAPE_SQUARE;
340 knot->mode = SP_KNOT_MODE_XOR;
341 knot->tip = NULL;
343 knot->fill[SP_KNOT_STATE_NORMAL] = 0xffffff00;
344 knot->fill[SP_KNOT_STATE_MOUSEOVER] = 0xff0000ff;
345 knot->fill[SP_KNOT_STATE_DRAGGING] = 0x0000ffff;
347 knot->stroke[SP_KNOT_STATE_NORMAL] = 0x01000000;
348 knot->stroke[SP_KNOT_STATE_MOUSEOVER] = 0x01000000;
349 knot->stroke[SP_KNOT_STATE_DRAGGING] = 0x01000000;
351 knot->image[SP_KNOT_STATE_NORMAL] = NULL;
352 knot->image[SP_KNOT_STATE_MOUSEOVER] = NULL;
353 knot->image[SP_KNOT_STATE_DRAGGING] = NULL;
355 knot->cursor[SP_KNOT_STATE_NORMAL] = NULL;
356 knot->cursor[SP_KNOT_STATE_MOUSEOVER] = NULL;
357 knot->cursor[SP_KNOT_STATE_DRAGGING] = NULL;
359 knot->saved_cursor = NULL;
360 knot->pixbuf = NULL;
361 }
363 /**
364 * Called before SPKnot destruction.
365 */
366 static void sp_knot_dispose(GObject *object)
367 {
368 SPKnot *knot = (SPKnot *) object;
370 if ((knot->flags & SP_KNOT_GRABBED) && gdk_pointer_is_grabbed ()) {
371 // This happens e.g. when deleting a node in node tool while dragging it
372 gdk_pointer_ungrab (GDK_CURRENT_TIME);
373 }
375 if (knot->item) {
376 gtk_object_destroy (GTK_OBJECT (knot->item));
377 knot->item = NULL;
378 }
380 for (gint i = 0; i < SP_KNOT_VISIBLE_STATES; i++) {
381 if (knot->cursor[i]) {
382 gdk_cursor_unref(knot->cursor[i]);
383 knot->cursor[i] = NULL;
384 }
385 }
387 if (knot->tip) {
388 g_free(knot->tip);
389 knot->tip = NULL;
390 }
392 if (((GObjectClass *) (parent_class))->dispose) {
393 (* ((GObjectClass *) (parent_class))->dispose) (object);
394 }
395 }
397 /**
398 * Callback to set property.
399 */
400 static void sp_knot_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
401 {
402 GdkCursor *cursor;
404 SPKnot *knot = SP_KNOT(object);
406 switch (prop_id) {
407 case PROP_SIZE:
408 knot->size = g_value_get_uint(value);
409 break;
410 case PROP_ANCHOR:
411 knot->anchor = (GtkAnchorType) g_value_get_enum(value);
412 break;
413 case PROP_SHAPE:
414 knot->shape = (SPKnotShapeType) g_value_get_int(value);
415 break;
416 case PROP_MODE:
417 knot->mode = (SPKnotModeType) g_value_get_int(value);
418 break;
419 case PROP_FILL:
420 knot->fill[SP_KNOT_STATE_NORMAL] =
421 knot->fill[SP_KNOT_STATE_MOUSEOVER] =
422 knot->fill[SP_KNOT_STATE_DRAGGING] = g_value_get_uint(value);
423 break;
424 case PROP_FILL_MOUSEOVER:
425 knot->fill[SP_KNOT_STATE_MOUSEOVER] =
426 knot->fill[SP_KNOT_STATE_DRAGGING] = g_value_get_uint(value);
427 break;
428 case PROP_FILL_DRAGGING:
429 knot->fill[SP_KNOT_STATE_DRAGGING] = g_value_get_uint(value);
430 break;
431 case PROP_STROKE:
432 knot->stroke[SP_KNOT_STATE_NORMAL] =
433 knot->stroke[SP_KNOT_STATE_MOUSEOVER] =
434 knot->stroke[SP_KNOT_STATE_DRAGGING] = g_value_get_uint(value);
435 break;
436 case PROP_STROKE_MOUSEOVER:
437 knot->stroke[SP_KNOT_STATE_MOUSEOVER] =
438 knot->stroke[SP_KNOT_STATE_DRAGGING] = g_value_get_uint(value);
439 break;
440 case PROP_STROKE_DRAGGING:
441 knot->stroke[SP_KNOT_STATE_DRAGGING] = g_value_get_uint(value);
442 break;
443 case PROP_IMAGE:
444 knot->image[SP_KNOT_STATE_NORMAL] =
445 knot->image[SP_KNOT_STATE_MOUSEOVER] =
446 knot->image[SP_KNOT_STATE_DRAGGING] = (guchar*) g_value_get_pointer(value);
447 break;
448 case PROP_IMAGE_MOUSEOVER:
449 knot->image[SP_KNOT_STATE_MOUSEOVER] = (guchar*) g_value_get_pointer(value);
450 break;
451 case PROP_IMAGE_DRAGGING:
452 knot->image[SP_KNOT_STATE_DRAGGING] = (guchar*) g_value_get_pointer(value);
453 break;
454 case PROP_CURSOR:
455 cursor = (GdkCursor*) g_value_get_boxed(value);
456 for (gint i = 0; i < SP_KNOT_VISIBLE_STATES; i++) {
457 if (knot->cursor[i]) {
458 gdk_cursor_unref(knot->cursor[i]);
459 }
460 knot->cursor[i] = cursor;
461 if (cursor) {
462 gdk_cursor_ref(cursor);
463 }
464 }
465 break;
466 case PROP_CURSOR_MOUSEOVER:
467 cursor = (GdkCursor*) g_value_get_boxed(value);
468 if (knot->cursor[SP_KNOT_STATE_MOUSEOVER]) {
469 gdk_cursor_unref(knot->cursor[SP_KNOT_STATE_MOUSEOVER]);
470 }
471 knot->cursor[SP_KNOT_STATE_MOUSEOVER] = cursor;
472 if (cursor) {
473 gdk_cursor_ref(cursor);
474 }
475 break;
476 case PROP_CURSOR_DRAGGING:
477 cursor = (GdkCursor*) g_value_get_boxed(value);
478 if (knot->cursor[SP_KNOT_STATE_DRAGGING]) {
479 gdk_cursor_unref(knot->cursor[SP_KNOT_STATE_DRAGGING]);
480 }
481 knot->cursor[SP_KNOT_STATE_DRAGGING] = cursor;
482 if (cursor) {
483 gdk_cursor_ref(cursor);
484 }
485 break;
486 case PROP_PIXBUF:
487 knot->pixbuf = g_value_get_pointer(value);
488 break;
489 case PROP_TIP:
490 knot->tip = g_strdup((const gchar *) g_value_get_pointer(value));
491 break;
492 default:
493 g_assert_not_reached();
494 break;
495 }
497 sp_knot_update_ctrl(knot);
498 }
500 /// Not reached.
501 static void sp_knot_get_property(GObject *, guint, GValue *, GParamSpec *)
502 {
503 g_assert_not_reached();
504 }
506 /**
507 * Update knot for dragging and tell canvas an item was grabbed.
508 */
509 void sp_knot_start_dragging(SPKnot *knot, NR::Point p, gint x, gint y, guint32 etime)
510 {
511 // save drag origin
512 xp = x;
513 yp = y;
514 within_tolerance = true;
516 knot->grabbed_rel_pos = p - knot->pos;
517 knot->drag_origin = knot->pos;
518 if (!nograb) {
519 sp_canvas_item_grab(knot->item,
520 KNOT_EVENT_MASK,
521 knot->cursor[SP_KNOT_STATE_DRAGGING],
522 etime);
523 }
524 sp_knot_set_flag(knot, SP_KNOT_GRABBED, TRUE);
525 grabbed = TRUE;
526 }
528 /**
529 * Called to handle events on knots.
530 */
531 static int sp_knot_handler(SPCanvasItem *item, GdkEvent *event, SPKnot *knot)
532 {
533 g_assert(knot != NULL);
534 g_assert(SP_IS_KNOT(knot));
536 tolerance = prefs_get_int_attribute_limited("options.dragtolerance", "value", 0, 0, 100);
538 gboolean consumed = FALSE;
540 /* Run client universal event handler, if present */
542 g_signal_emit(G_OBJECT(knot), knot_signals[EVENT], 0, event, &consumed);
544 if (consumed) {
545 return TRUE;
546 }
548 switch (event->type) {
549 case GDK_2BUTTON_PRESS:
550 if (event->button.button == 1) {
551 g_signal_emit(G_OBJECT(knot), knot_signals[DOUBLECLICKED], 0, event->button.state);
553 grabbed = FALSE;
554 moved = FALSE;
555 consumed = TRUE;
556 }
557 break;
558 case GDK_BUTTON_PRESS:
559 if (event->button.button == 1) {
560 NR::Point const p = knot->desktop->w2d(NR::Point(event->button.x, event->button.y));
561 sp_knot_start_dragging(knot, p, (gint) event->button.x, (gint) event->button.y, event->button.time);
562 consumed = TRUE;
563 }
564 break;
565 case GDK_BUTTON_RELEASE:
566 if (event->button.button == 1) {
567 if (transform_escaped) {
568 transform_escaped = false;
569 consumed = TRUE;
570 } else {
571 sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
572 if (!nograb) {
573 sp_canvas_item_ungrab(knot->item, event->button.time);
574 }
575 if (moved) {
576 sp_knot_set_flag(knot,
577 SP_KNOT_DRAGGING,
578 FALSE);
579 g_signal_emit(G_OBJECT (knot),
580 knot_signals[UNGRABBED], 0,
581 event->button.state);
582 } else {
583 g_signal_emit(G_OBJECT (knot),
584 knot_signals[CLICKED], 0,
585 event->button.state);
586 }
587 grabbed = FALSE;
588 moved = FALSE;
589 consumed = TRUE;
590 }
591 }
592 break;
593 case GDK_MOTION_NOTIFY:
594 if (grabbed) {
595 consumed = TRUE;
597 if ( within_tolerance
598 && ( abs( (gint) event->motion.x - xp ) < tolerance )
599 && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) {
600 break; // do not drag if we're within tolerance from origin
601 }
603 // Once the user has moved farther than tolerance from the original location
604 // (indicating they intend to move the object, not click), then always process the
605 // motion notify coordinates as given (no snapping back to origin)
606 within_tolerance = false;
608 if (!moved) {
609 g_signal_emit(G_OBJECT (knot),
610 knot_signals[GRABBED], 0,
611 event->motion.state);
612 sp_knot_set_flag(knot,
613 SP_KNOT_DRAGGING,
614 TRUE);
615 }
616 NR::Point const motion_w(event->motion.x, event->motion.y);
617 NR::Point const motion_dt = knot->desktop->w2d(motion_w);
618 NR::Point p = motion_dt - knot->grabbed_rel_pos;
619 sp_knot_request_position (knot, &p, event->motion.state);
620 knot->desktop->scroll_to_point (&motion_dt);
621 moved = TRUE;
622 }
623 break;
624 case GDK_ENTER_NOTIFY:
625 sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, TRUE);
626 sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
628 if (knot->tip) {
629 knot->desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, knot->tip);
630 }
632 grabbed = FALSE;
633 moved = FALSE;
634 consumed = TRUE;
635 break;
636 case GDK_LEAVE_NOTIFY:
637 sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE);
638 sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
640 if (knot->tip) {
641 knot->desktop->event_context->defaultMessageContext()->clear();
642 }
644 grabbed = FALSE;
645 moved = FALSE;
647 consumed = TRUE;
648 break;
649 case GDK_KEY_PRESS: // keybindings for knot
650 switch (get_group0_keyval(&event->key)) {
651 case GDK_Escape:
652 sp_knot_set_flag(knot, SP_KNOT_GRABBED, FALSE);
653 if (!nograb) {
654 sp_canvas_item_ungrab(knot->item, event->button.time);
655 }
656 if (moved) {
657 sp_knot_set_flag(knot,
658 SP_KNOT_DRAGGING,
659 FALSE);
660 g_signal_emit(G_OBJECT(knot),
661 knot_signals[UNGRABBED], 0,
662 event->button.state);
663 sp_document_undo(SP_DT_DOCUMENT(knot->desktop));
664 knot->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Node or handle drag canceled."));
665 transform_escaped = true;
666 consumed = TRUE;
667 }
668 grabbed = FALSE;
669 moved = FALSE;
670 break;
671 default:
672 consumed = FALSE;
673 break;
674 }
675 break;
676 default:
677 break;
678 }
681 return consumed;
682 }
684 /**
685 * Return new knot object.
686 */
687 SPKnot *sp_knot_new(SPDesktop *desktop, const gchar *tip)
688 {
689 g_return_val_if_fail(desktop != NULL, NULL);
691 SPKnot * knot = (SPKnot*) g_object_new(SP_TYPE_KNOT, 0);
693 knot->desktop = desktop;
694 knot->flags = SP_KNOT_VISIBLE;
695 if (tip) {
696 knot->tip = g_strdup (tip);
697 }
699 knot->item = sp_canvas_item_new(SP_DT_CONTROLS (desktop),
700 SP_TYPE_CTRL,
701 "anchor", GTK_ANCHOR_CENTER,
702 "size", 8.0,
703 "filled", TRUE,
704 "fill_color", 0xffffff00,
705 "stroked", TRUE,
706 "stroke_color", 0x01000000,
707 "mode", SP_KNOT_MODE_XOR,
708 NULL);
710 gtk_signal_connect(GTK_OBJECT(knot->item), "event",
711 GTK_SIGNAL_FUNC(sp_knot_handler), knot);
713 return knot;
714 }
716 /**
717 * Show knot on its canvas.
718 */
719 void sp_knot_show(SPKnot *knot)
720 {
721 g_return_if_fail(knot != NULL);
722 g_return_if_fail(SP_IS_KNOT (knot));
724 sp_knot_set_flag(knot, SP_KNOT_VISIBLE, TRUE);
725 }
727 /**
728 * Hide knot on its canvas.
729 */
730 void sp_knot_hide(SPKnot *knot)
731 {
732 g_return_if_fail(knot != NULL);
733 g_return_if_fail(SP_IS_KNOT(knot));
735 sp_knot_set_flag(knot, SP_KNOT_VISIBLE, FALSE);
736 }
738 /**
739 * Request or set new position for knot.
740 */
741 void sp_knot_request_position(SPKnot *knot, NR::Point *p, guint state)
742 {
743 g_return_if_fail(knot != NULL);
744 g_return_if_fail(SP_IS_KNOT(knot));
746 gboolean done = FALSE;
748 g_signal_emit(G_OBJECT (knot),
749 knot_signals[REQUEST], 0,
750 p,
751 state,
752 &done);
754 /* If user did not complete, we simply move knot to new position */
756 if (!done) {
757 sp_knot_set_position (knot, p, state);
758 }
759 }
761 /**
762 * Return distance of point to knot's position; unused.
763 */
764 gdouble sp_knot_distance(SPKnot * knot, NR::Point *p, guint state)
765 {
766 g_return_val_if_fail(knot != NULL, 1e18);
767 g_return_val_if_fail(SP_IS_KNOT(knot), 1e18);
769 gdouble distance = NR::L2(*p - knot->pos);
771 g_signal_emit(G_OBJECT(knot),
772 knot_signals[DISTANCE], 0,
773 p,
774 state,
775 &distance);
777 return distance;
778 }
780 /**
781 * Move knot to new position.
782 */
783 void sp_knot_set_position(SPKnot *knot, NR::Point *p, guint state)
784 {
785 g_return_if_fail(knot != NULL);
786 g_return_if_fail(SP_IS_KNOT (knot));
788 knot->pos = *p;
790 if (knot->item) {
791 SP_CTRL(knot->item)->moveto (*p);
792 }
794 g_signal_emit(G_OBJECT (knot),
795 knot_signals[MOVED], 0,
796 p,
797 state);
798 }
800 /**
801 * Move knot to new position, without emitting a MOVED signal. We can't be sure this artificial
802 * move has moved the knot from under cursor, but it seems likely, so we clear SP_KNOT_MOUSEOVER.
803 */
804 void sp_knot_moveto(SPKnot *knot, NR::Point *p)
805 {
806 g_return_if_fail(knot != NULL);
807 g_return_if_fail(SP_IS_KNOT(knot));
809 knot->pos = *p;
811 if (knot->item) {
812 SP_CTRL(knot->item)->moveto (*p);
813 }
815 sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE);
816 }
818 /**
819 * Returns position of knot.
820 */
821 NR::Point sp_knot_position(SPKnot const *knot)
822 {
823 g_assert(knot != NULL);
824 g_assert(SP_IS_KNOT (knot));
826 return knot->pos;
827 }
829 /**
830 * Set flag in knot, with side effects.
831 */
832 static void sp_knot_set_flag(SPKnot *knot, guint flag, bool set)
833 {
834 g_assert(knot != NULL);
835 g_assert(SP_IS_KNOT(knot));
837 if (set) {
838 knot->flags |= flag;
839 } else {
840 knot->flags &= ~flag;
841 }
843 switch (flag) {
844 case SP_KNOT_VISIBLE:
845 if (set) {
846 sp_canvas_item_show(knot->item);
847 } else {
848 sp_canvas_item_hide(knot->item);
849 }
850 break;
851 case SP_KNOT_MOUSEOVER:
852 case SP_KNOT_DRAGGING:
853 sp_knot_set_ctrl_state(knot);
854 break;
855 case SP_KNOT_GRABBED:
856 break;
857 default:
858 g_assert_not_reached();
859 break;
860 }
861 }
863 /**
864 * Update knot's pixbuf and set its control state.
865 */
866 static void sp_knot_update_ctrl(SPKnot *knot)
867 {
868 if (!knot->item) {
869 return;
870 }
872 gtk_object_set(GTK_OBJECT(knot->item), "shape", knot->shape, NULL);
873 gtk_object_set(GTK_OBJECT(knot->item), "mode", knot->mode, NULL);
874 gtk_object_set(GTK_OBJECT(knot->item), "size", (gdouble) knot->size, NULL);
875 gtk_object_set(GTK_OBJECT(knot->item), "anchor", knot->anchor, NULL);
876 if (knot->pixbuf) {
877 gtk_object_set(GTK_OBJECT (knot->item), "pixbuf", knot->pixbuf, NULL);
878 }
880 sp_knot_set_ctrl_state(knot);
881 }
883 /**
884 * Set knot control state (dragging/mouseover/normal).
885 */
886 static void sp_knot_set_ctrl_state(SPKnot *knot)
887 {
888 if (knot->flags & SP_KNOT_DRAGGING) {
889 gtk_object_set(GTK_OBJECT (knot->item),
890 "fill_color",
891 knot->fill[SP_KNOT_STATE_DRAGGING],
892 NULL);
893 gtk_object_set(GTK_OBJECT (knot->item),
894 "stroke_color",
895 knot->stroke[SP_KNOT_STATE_DRAGGING],
896 NULL);
897 } else if (knot->flags & SP_KNOT_MOUSEOVER) {
898 gtk_object_set(GTK_OBJECT(knot->item),
899 "fill_color",
900 knot->fill[SP_KNOT_STATE_MOUSEOVER],
901 NULL);
902 gtk_object_set(GTK_OBJECT(knot->item),
903 "stroke_color",
904 knot->stroke[SP_KNOT_STATE_MOUSEOVER],
905 NULL);
906 } else {
907 gtk_object_set(GTK_OBJECT(knot->item),
908 "fill_color",
909 knot->fill[SP_KNOT_STATE_NORMAL],
910 NULL);
911 gtk_object_set(GTK_OBJECT(knot->item),
912 "stroke_color",
913 knot->stroke[SP_KNOT_STATE_NORMAL],
914 NULL);
915 }
916 }
920 /*
921 Local Variables:
922 mode:c++
923 c-file-style:"stroustrup"
924 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
925 indent-tabs-mode:nil
926 fill-column:99
927 End:
928 */
929 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :