Code

6edf3d0e806a14a585a73ee4609d4829d6f87868
[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     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'));
182 /**
183  * Callback for SPKnot initialization.
184  */
185 static void sp_knot_init(SPKnot *knot)
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;
219 /**
220  * Called before SPKnot destruction.
221  */
222 static void sp_knot_dispose(GObject *object)
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     }
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)
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;
275 /**
276  * Called to handle events on knots.
277  */
278 static int sp_knot_handler(SPCanvasItem *item, GdkEvent *event, SPKnot *knot)
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;
431 /**
432  * Return new knot object.
433  */
434 SPKnot *sp_knot_new(SPDesktop *desktop, const gchar *tip)
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;
463 /**
464  * Show knot on its canvas.
465  */
466 void sp_knot_show(SPKnot *knot)
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);
474 /**
475  * Hide knot on its canvas.
476  */
477 void sp_knot_hide(SPKnot *knot)
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);
485 /**
486  * Request or set new position for knot.
487  */
488 void sp_knot_request_position(SPKnot *knot, NR::Point *p, guint state)
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     }
508 /**
509  * Return distance of point to knot's position; unused.
510  */
511 gdouble sp_knot_distance(SPKnot * knot, NR::Point *p, guint state)
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;
527 /**
528  * Move knot to new position.
529  */
530 void sp_knot_set_position(SPKnot *knot, NR::Point *p, guint state)
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);
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)
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);
565 /**
566  * Returns position of knot.
567  */
568 NR::Point sp_knot_position(SPKnot const *knot)
570     g_assert(knot != NULL);
571     g_assert(SP_IS_KNOT (knot));
573     return knot->pos;
576 /**
577  * Set flag in knot, with side effects.
578  */
579 static void sp_knot_set_flag(SPKnot *knot, guint flag, bool set)
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     }
610 /**
611  * Update knot's pixbuf and set its control state.
612  */
613 void sp_knot_update_ctrl(SPKnot *knot)
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);
630 /**
631  * Set knot control state (dragging/mouseover/normal).
632  */
633 static void sp_knot_set_ctrl_state(SPKnot *knot)
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     }
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 :