Code

af567e6e4c5f0bbaa225f4006503a06448c04e0c
[inkscape.git] / src / knot.cpp
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,
50     
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,
61     
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;
115 /**
116  * SPKnot vtable initialization.
117  */
118 static void sp_knot_class_init(SPKnotClass *klass)
120     GObjectClass *object_class = (GObjectClass *) klass;
122     parent_class = (GObjectClass*) g_type_class_peek_parent(klass);
123     
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 :) */
129     
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));
150     
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));
158     
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));
166     
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));
174     
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));
182     
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));
190     
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));
198     
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));
206     
207     g_object_class_install_property(object_class,
208                                     PROP_IMAGE,
209                                     g_param_spec_pointer("image", "Image", "",
210                                                          (GParamFlags) G_PARAM_READWRITE));
211     
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));
216     
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));
221     
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));
227     
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));
233     
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));
239     
240     g_object_class_install_property(object_class,
241                                     PROP_PIXBUF,
242                                     g_param_spec_pointer("pixbuf", "Pixbuf", "",
243                                                          (GParamFlags) G_PARAM_READWRITE));
244     
245     g_object_class_install_property(object_class,
246                                     PROP_TIP,
247                                     g_param_spec_pointer("tip", "Tip", "",
248                                                          (GParamFlags) G_PARAM_READWRITE));
249     
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);
258     
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);
267     
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);
276     
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);
285     
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);
294     
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);
303     
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);
312     
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'));
326 /**
327  * Callback for SPKnot initialization.
328  */
329 static void sp_knot_init(SPKnot *knot)
331     knot->desktop = NULL;
332     knot->item = NULL;
333     knot->flags = 0;
334     
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;
342     
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;
363 /**
364  * Called before SPKnot destruction.
365  */
366 static void sp_knot_dispose(GObject *object)
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     }
397 /**
398  * Callback to set property.
399  */
400 static void sp_knot_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
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     }
496     
497     sp_knot_update_ctrl(knot);
500 /// Not reached.
501 static void sp_knot_get_property(GObject *, guint, GValue *, GParamSpec *)
503     g_assert_not_reached();
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)
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;
528 /**
529  * Called to handle events on knots.
530  */
531 static int sp_knot_handler(SPCanvasItem *item, GdkEvent *event, SPKnot *knot)
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;
596                 
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                 }
602                 
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; 
607                 
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;
646             
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;
684 /**
685  * Return new knot object.
686  */
687 SPKnot *sp_knot_new(SPDesktop *desktop, const gchar *tip)
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;
716 /**
717  * Show knot on its canvas.
718  */
719 void sp_knot_show(SPKnot *knot)
721     g_return_if_fail(knot != NULL);
722     g_return_if_fail(SP_IS_KNOT (knot));
723     
724     sp_knot_set_flag(knot, SP_KNOT_VISIBLE, TRUE);
727 /**
728  * Hide knot on its canvas.
729  */
730 void sp_knot_hide(SPKnot *knot)
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);
738 /**
739  * Request or set new position for knot.
740  */
741 void sp_knot_request_position(SPKnot *knot, NR::Point *p, guint state)
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     }
761 /**
762  * Return distance of point to knot's position; unused.
763  */
764 gdouble sp_knot_distance(SPKnot * knot, NR::Point *p, guint state)
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);
776     
777     return distance;
780 /**
781  * Move knot to new position.
782  */
783 void sp_knot_set_position(SPKnot *knot, NR::Point *p, guint state)
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);
800 /**
801  * Move knot to new position, without emitting a MOVED signal.
802  */
803 void sp_knot_moveto(SPKnot *knot, NR::Point *p)
805     g_return_if_fail(knot != NULL);
806     g_return_if_fail(SP_IS_KNOT(knot));
808     knot->pos = *p;
809     
810     if (knot->item) {
811         SP_CTRL(knot->item)->moveto (*p);
812     }
815 /**
816  * Returns position of knot.
817  */
818 NR::Point sp_knot_position(SPKnot const *knot)
820     g_assert(knot != NULL);
821     g_assert(SP_IS_KNOT (knot));
822     
823     return knot->pos;
826 /**
827  * Set flag in knot, with side effects.
828  */
829 static void sp_knot_set_flag(SPKnot *knot, guint flag, bool set)
831     g_assert(knot != NULL);
832     g_assert(SP_IS_KNOT(knot));
833     
834     if (set) {
835         knot->flags |= flag;
836     } else {
837         knot->flags &= ~flag;
838     }
839     
840     switch (flag) {
841         case SP_KNOT_VISIBLE:
842             if (set) {
843                 sp_canvas_item_show(knot->item);
844             } else {
845                 sp_canvas_item_hide(knot->item);
846             }
847             break;
848         case SP_KNOT_MOUSEOVER:
849         case SP_KNOT_DRAGGING:
850             sp_knot_set_ctrl_state(knot);
851             break;
852         case SP_KNOT_GRABBED:
853             break;
854         default:
855             g_assert_not_reached();
856             break;
857     }
860 /**
861  * Update knot's pixbuf and set its control state.
862  */
863 static void sp_knot_update_ctrl(SPKnot *knot)
865     if (!knot->item) {
866         return;
867     }
868     
869     gtk_object_set(GTK_OBJECT(knot->item), "shape", knot->shape, NULL);
870     gtk_object_set(GTK_OBJECT(knot->item), "mode", knot->mode, NULL);
871     gtk_object_set(GTK_OBJECT(knot->item), "size", (gdouble) knot->size, NULL);
872     gtk_object_set(GTK_OBJECT(knot->item), "anchor", knot->anchor, NULL);
873     if (knot->pixbuf) {
874         gtk_object_set(GTK_OBJECT (knot->item), "pixbuf", knot->pixbuf, NULL);
875     }
877     sp_knot_set_ctrl_state(knot);
880 /**
881  * Set knot control state (dragging/mouseover/normal).
882  */
883 static void sp_knot_set_ctrl_state(SPKnot *knot)
885     if (knot->flags & SP_KNOT_DRAGGING) {
886         gtk_object_set(GTK_OBJECT (knot->item),
887                        "fill_color",
888                        knot->fill[SP_KNOT_STATE_DRAGGING],
889                        NULL);
890         gtk_object_set(GTK_OBJECT (knot->item),
891                        "stroke_color",
892                        knot->stroke[SP_KNOT_STATE_DRAGGING],
893                        NULL);
894     } else if (knot->flags & SP_KNOT_MOUSEOVER) {
895         gtk_object_set(GTK_OBJECT(knot->item),
896                        "fill_color",
897                        knot->fill[SP_KNOT_STATE_MOUSEOVER],
898                        NULL);
899         gtk_object_set(GTK_OBJECT(knot->item),
900                        "stroke_color",
901                        knot->stroke[SP_KNOT_STATE_MOUSEOVER],
902                        NULL);
903     } else {
904         gtk_object_set(GTK_OBJECT(knot->item),
905                        "fill_color",
906                         knot->fill[SP_KNOT_STATE_NORMAL],
907                        NULL);
908         gtk_object_set(GTK_OBJECT(knot->item),
909                        "stroke_color",
910                        knot->stroke[SP_KNOT_STATE_NORMAL],
911                        NULL);
912     }
917 /*
918   Local Variables:
919   mode:c++
920   c-file-style:"stroustrup"
921   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
922   indent-tabs-mode:nil
923   fill-column:99
924   End:
925 */
926 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :