1 /*
2 * GooCanvas. Copyright (C) 2005 Damon Chaplin.
3 * Released under the GNU LGPL license. See COPYING for details.
4 *
5 * goocanvas.c - the main canvas widget.
6 */
7
8 /**
9 * SECTION: goocanvas
10 * @Title: GooCanvas
11 * @Short_Description: the main canvas widget.
12 *
13 * #GooCanvas is the main widget containing a number of canvas items.
14 *
15 * Here is a simple example:
16 *
17 * <informalexample><programlisting>
18 * #include <goocanvas.h>
19 *
20 * static gboolean on_rect_button_press (GooCanvasItem *view,
21 * GooCanvasItem *target,
22 * GdkEventButton *event,
23 * gpointer data);
24 *
25 * int
26 * main (int argc, char *argv[])
27 * {
28 * GtkWidget *window, *scrolled_win, *canvas;
29 * GooCanvasItem *root, *rect_item, *text_item;
30 *
31 * /* Initialize GTK+. */
32 * gtk_set_locale ();
33 * gtk_init (&argc, &argv);
34 *
35 * /* Create the window and widgets. */
36 * window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
37 * gtk_window_set_default_size (GTK_WINDOW (window), 640, 600);
38 * gtk_widget_show (window);
39 * g_signal_connect (window, "delete_event", (GtkSignalFunc) on_delete_event,
40 * NULL);
41 *
42 * scrolled_win = gtk_scrolled_window_new (NULL, NULL);
43 * gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win),
44 * GTK_SHADOW_IN);
45 * gtk_widget_show (scrolled_win);
46 * gtk_container_add (GTK_CONTAINER (window), scrolled_win);
47 *
48 * canvas = goo_canvas_new ();
49 * gtk_widget_set_size_request (canvas, 600, 450);
50 * goo_canvas_set_bounds (GOO_CANVAS (canvas), 0, 0, 1000, 1000);
51 * gtk_widget_show (canvas);
52 * gtk_container_add (GTK_CONTAINER (scrolled_win), canvas);
53 *
54 * root = goo_canvas_get_root_item (GOO_CANVAS (canvas));
55 *
56 * /* Add a few simple items. */
57 * rect_item = goo_canvas_rect_new (root, 100, 100, 400, 400,
58 * "line-width", 10.0,
59 * "radius-x", 20.0,
60 * "radius-y", 10.0,
61 * "stroke-color", "yellow",
62 * "fill-color", "red",
63 * NULL);
64 *
65 * text_item = goo_canvas_text_new (root, "Hello World", 300, 300, -1,
66 * GTK_ANCHOR_CENTER,
67 * "font", "Sans 24",
68 * NULL);
69 * goo_canvas_item_rotate (text_item, 45, 300, 300);
70 *
71 * /* Connect a signal handler for the rectangle item. */
72 * g_signal_connect (rect_item, "button_press_event",
73 * (GtkSignalFunc) on_rect_button_press, NULL);
74 *
75 * /* Pass control to the GTK+ main event loop. */
76 * gtk_main ();
77 *
78 * return 0;
79 * }
80 *
81 *
82 * /* This handles button presses in item views. We simply output a message to
83 * the console. */
84 * static gboolean
85 * on_rect_button_press (GooCanvasItem *item,
86 * GooCanvasItem *target,
87 * GdkEventButton *event,
88 * gpointer data)
89 * {
90 * g_print ("rect item received button press event\n");
91 * return TRUE;
92 * }
93 *
94 * </programlisting></informalexample>
95 */
96 #include <config.h>
97 #include <math.h>
98 #include <glib/gi18n-lib.h>
99 #include <gtk/gtk.h>
100 #include "goocanvasatk.h"
101 #include "goocanvas.h"
102 #include "goocanvasitemmodel.h"
103 #include "goocanvasitem.h"
104 #include "goocanvasgroup.h"
105 #include "goocanvasmarshal.h"
106
107
108 #define GOO_CANVAS_GET_PRIVATE(canvas) \
109 (G_TYPE_INSTANCE_GET_PRIVATE ((canvas), GOO_TYPE_CANVAS, GooCanvasPrivate))
110
111 typedef struct _GooCanvasPrivate GooCanvasPrivate;
112 struct _GooCanvasPrivate {
113 GooCanvasItem *static_root_item;
114 GooCanvasItemModel *static_root_item_model;
115 gint window_x, window_y;
116 };
117
118
119 enum {
120 PROP_0,
121
122 PROP_SCALE,
123 PROP_SCALE_X,
124 PROP_SCALE_Y,
125 PROP_ANCHOR,
126 PROP_X1,
127 PROP_Y1,
128 PROP_X2,
129 PROP_Y2,
130 PROP_AUTOMATIC_BOUNDS,
131 PROP_BOUNDS_FROM_ORIGIN,
132 PROP_BOUNDS_PADDING,
133 PROP_UNITS,
134 PROP_RESOLUTION_X,
135 PROP_RESOLUTION_Y,
136 PROP_BACKGROUND_COLOR,
137 PROP_BACKGROUND_COLOR_RGB,
138 PROP_INTEGER_LAYOUT,
139 PROP_CLEAR_BACKGROUND,
140 PROP_REDRAW_WHEN_SCROLLED
141 };
142
143 enum {
144 ITEM_CREATED,
145
146 LAST_SIGNAL
147 };
148
149
150 static guint canvas_signals[LAST_SIGNAL] = { 0 };
151
152 static void goo_canvas_dispose (GObject *object);
153 static void goo_canvas_finalize (GObject *object);
154 static void goo_canvas_realize (GtkWidget *widget);
155 static void goo_canvas_unrealize (GtkWidget *widget);
156 static void goo_canvas_map (GtkWidget *widget);
157 static void goo_canvas_style_set (GtkWidget *widget,
158 GtkStyle *old_style);
159 static void goo_canvas_size_request (GtkWidget *widget,
160 GtkRequisition *requisition);
161 static void goo_canvas_size_allocate (GtkWidget *widget,
162 GtkAllocation *allocation);
163 static void goo_canvas_set_adjustments (GooCanvas *canvas,
164 GtkAdjustment *hadj,
165 GtkAdjustment *vadj);
166 static gboolean goo_canvas_expose_event (GtkWidget *widget,
167 GdkEventExpose *event);
168 static gboolean goo_canvas_button_press (GtkWidget *widget,
169 GdkEventButton *event);
170 static gboolean goo_canvas_button_release (GtkWidget *widget,
171 GdkEventButton *event);
172 static gboolean goo_canvas_motion (GtkWidget *widget,
173 GdkEventMotion *event);
174 static gboolean goo_canvas_scroll (GtkWidget *widget,
175 GdkEventScroll *event);
176 static gboolean goo_canvas_focus (GtkWidget *widget,
177 GtkDirectionType direction);
178 static gboolean goo_canvas_key_press (GtkWidget *widget,
179 GdkEventKey *event);
180 static gboolean goo_canvas_key_release (GtkWidget *widget,
181 GdkEventKey *event);
182 static gboolean goo_canvas_crossing (GtkWidget *widget,
183 GdkEventCrossing *event);
184 static gboolean goo_canvas_focus_in (GtkWidget *widget,
185 GdkEventFocus *event);
186 static gboolean goo_canvas_focus_out (GtkWidget *widget,
187 GdkEventFocus *event);
188 static gboolean goo_canvas_grab_broken (GtkWidget *widget,
189 GdkEventGrabBroken *event);
190 static void goo_canvas_get_property (GObject *object,
191 guint prop_id,
192 GValue *value,
193 GParamSpec *pspec);
194 static void goo_canvas_set_property (GObject *object,
195 guint prop_id,
196 const GValue *value,
197 GParamSpec *pspec);
198 static void goo_canvas_remove (GtkContainer *container,
199 GtkWidget *widget);
200 static void goo_canvas_forall (GtkContainer *container,
201 gboolean include_internals,
202 GtkCallback callback,
203 gpointer callback_data);
204 static gboolean goo_canvas_query_tooltip (GtkWidget *widget,
205 gint x,
206 gint y,
207 gboolean keyboard_tip,
208 GtkTooltip *tooltip);
209
210 static void goo_canvas_set_scale_internal (GooCanvas *canvas,
211 gdouble scale_x,
212 gdouble scale_y);
213
214 static void set_item_pointer (GooCanvasItem **item,
215 GooCanvasItem *new_item);
216 static void update_pointer_item (GooCanvas *canvas,
217 GdkEvent *event);
218 static void reconfigure_canvas (GooCanvas *canvas,
219 gboolean redraw_if_needed);
220 static void goo_canvas_update_automatic_bounds (GooCanvas *canvas);
221
222 static void goo_canvas_convert_to_static_item_space (GooCanvas *canvas,
223 gdouble *x,
224 gdouble *y);
225
G_DEFINE_TYPE(GooCanvas,goo_canvas,GTK_TYPE_CONTAINER)226 G_DEFINE_TYPE (GooCanvas, goo_canvas, GTK_TYPE_CONTAINER)
227
228 /* This evaluates to TRUE if an item is still in the canvas. */
229 #define ITEM_IS_VALID(item) (goo_canvas_item_get_canvas (item))
230
231 #define GOO_CANVAS_DEFAULT_WIDTH 1000.0
232 #define GOO_CANVAS_DEFAULT_HEIGHT 1000.0
233
234 static void
235 goo_canvas_class_init (GooCanvasClass *klass)
236 {
237 GObjectClass *gobject_class = (GObjectClass*) klass;
238 GtkWidgetClass *widget_class = (GtkWidgetClass*) klass;
239 GtkContainerClass *container_class = (GtkContainerClass*) klass;
240
241 g_type_class_add_private (gobject_class, sizeof (GooCanvasPrivate));
242
243 gobject_class->dispose = goo_canvas_dispose;
244 gobject_class->finalize = goo_canvas_finalize;
245 gobject_class->get_property = goo_canvas_get_property;
246 gobject_class->set_property = goo_canvas_set_property;
247
248 widget_class->realize = goo_canvas_realize;
249 widget_class->unrealize = goo_canvas_unrealize;
250 widget_class->map = goo_canvas_map;
251 widget_class->size_request = goo_canvas_size_request;
252 widget_class->size_allocate = goo_canvas_size_allocate;
253 widget_class->style_set = goo_canvas_style_set;
254 widget_class->expose_event = goo_canvas_expose_event;
255 widget_class->button_press_event = goo_canvas_button_press;
256 widget_class->button_release_event = goo_canvas_button_release;
257 widget_class->motion_notify_event = goo_canvas_motion;
258 widget_class->scroll_event = goo_canvas_scroll;
259 widget_class->focus = goo_canvas_focus;
260 widget_class->key_press_event = goo_canvas_key_press;
261 widget_class->key_release_event = goo_canvas_key_release;
262 widget_class->enter_notify_event = goo_canvas_crossing;
263 widget_class->leave_notify_event = goo_canvas_crossing;
264 widget_class->focus_in_event = goo_canvas_focus_in;
265 widget_class->focus_out_event = goo_canvas_focus_out;
266 widget_class->grab_broken_event = goo_canvas_grab_broken;
267 widget_class->query_tooltip = goo_canvas_query_tooltip;
268
269 container_class->remove = goo_canvas_remove;
270 container_class->forall = goo_canvas_forall;
271
272 klass->set_scroll_adjustments = goo_canvas_set_adjustments;
273
274 /* Register our accessible factory, but only if accessibility is enabled. */
275 if (!ATK_IS_NO_OP_OBJECT_FACTORY (atk_registry_get_factory (atk_get_default_registry (), GTK_TYPE_WIDGET)))
276 {
277 atk_registry_set_factory_type (atk_get_default_registry (),
278 GOO_TYPE_CANVAS,
279 goo_canvas_accessible_factory_get_type ());
280 }
281
282 g_object_class_install_property (gobject_class, PROP_SCALE,
283 g_param_spec_double ("scale",
284 _("Scale"),
285 _("The magnification factor of the canvas"),
286 0.0, G_MAXDOUBLE, 1.0,
287 G_PARAM_READWRITE));
288
289 g_object_class_install_property (gobject_class, PROP_SCALE_X,
290 g_param_spec_double ("scale-x",
291 _("Scale X"),
292 _("The horizontal magnification factor of the canvas"),
293 0.0, G_MAXDOUBLE, 1.0,
294 G_PARAM_READWRITE));
295
296 g_object_class_install_property (gobject_class, PROP_SCALE_Y,
297 g_param_spec_double ("scale-y",
298 _("Scale Y"),
299 _("The vertical magnification factor of the canvas"),
300 0.0, G_MAXDOUBLE, 1.0,
301 G_PARAM_READWRITE));
302
303 g_object_class_install_property (gobject_class, PROP_ANCHOR,
304 g_param_spec_enum ("anchor",
305 _("Anchor"),
306 _("Where to place the canvas when it is smaller than the widget's allocated area"),
307 GTK_TYPE_ANCHOR_TYPE,
308 GTK_ANCHOR_NW,
309 G_PARAM_READWRITE));
310
311 g_object_class_install_property (gobject_class, PROP_X1,
312 g_param_spec_double ("x1",
313 _("X1"),
314 _("The x coordinate of the left edge of the canvas bounds, in canvas units"),
315 -G_MAXDOUBLE,
316 G_MAXDOUBLE, 0.0,
317 G_PARAM_READWRITE));
318
319 g_object_class_install_property (gobject_class, PROP_Y1,
320 g_param_spec_double ("y1",
321 _("Y1"),
322 _("The y coordinate of the top edge of the canvas bounds, in canvas units"),
323 -G_MAXDOUBLE,
324 G_MAXDOUBLE, 0.0,
325 G_PARAM_READWRITE));
326
327 g_object_class_install_property (gobject_class, PROP_X2,
328 g_param_spec_double ("x2",
329 _("X2"),
330 _("The x coordinate of the right edge of the canvas bounds, in canvas units"),
331 -G_MAXDOUBLE,
332 G_MAXDOUBLE,
333 GOO_CANVAS_DEFAULT_WIDTH,
334 G_PARAM_READWRITE));
335
336 g_object_class_install_property (gobject_class, PROP_Y2,
337 g_param_spec_double ("y2",
338 _("Y2"),
339 _("The y coordinate of the bottom edge of the canvas bounds, in canvas units"),
340 -G_MAXDOUBLE,
341 G_MAXDOUBLE,
342 GOO_CANVAS_DEFAULT_HEIGHT,
343 G_PARAM_READWRITE));
344
345
346 g_object_class_install_property (gobject_class, PROP_AUTOMATIC_BOUNDS,
347 g_param_spec_boolean ("automatic-bounds",
348 _("Automatic Bounds"),
349 _("If the bounds are automatically calculated based on the bounds of all the items in the canvas"),
350 FALSE,
351 G_PARAM_READWRITE));
352
353 g_object_class_install_property (gobject_class, PROP_BOUNDS_FROM_ORIGIN,
354 g_param_spec_boolean ("bounds-from-origin",
355 _("Bounds From Origin"),
356 _("If the automatic bounds are calculated from the origin"),
357 TRUE,
358 G_PARAM_READWRITE));
359
360 g_object_class_install_property (gobject_class, PROP_BOUNDS_PADDING,
361 g_param_spec_double ("bounds-padding",
362 _("Bounds Padding"),
363 _("The padding added to the automatic bounds"),
364 0.0, G_MAXDOUBLE, 0.0,
365 G_PARAM_READWRITE));
366
367 g_object_class_install_property (gobject_class, PROP_UNITS,
368 g_param_spec_enum ("units",
369 _("Units"),
370 _("The units to use for the canvas"),
371 GTK_TYPE_UNIT,
372 GTK_UNIT_PIXEL,
373 G_PARAM_READWRITE));
374
375 g_object_class_install_property (gobject_class, PROP_RESOLUTION_X,
376 g_param_spec_double ("resolution-x",
377 _("Resolution X"),
378 _("The horizontal resolution of the display, in dots per inch"),
379 0.0, G_MAXDOUBLE,
380 96.0,
381 G_PARAM_READWRITE));
382
383 g_object_class_install_property (gobject_class, PROP_RESOLUTION_Y,
384 g_param_spec_double ("resolution-y",
385 _("Resolution Y"),
386 _("The vertical resolution of the display, in dots per inch"),
387 0.0, G_MAXDOUBLE,
388 96.0,
389 G_PARAM_READWRITE));
390
391 /* Convenience properties - writable only. */
392 g_object_class_install_property (gobject_class, PROP_BACKGROUND_COLOR,
393 g_param_spec_string ("background-color",
394 _("Background Color"),
395 _("The color to use for the canvas background"),
396 NULL,
397 G_PARAM_WRITABLE));
398
399 g_object_class_install_property (gobject_class, PROP_BACKGROUND_COLOR_RGB,
400 g_param_spec_uint ("background-color-rgb",
401 _("Background Color RGB"),
402 _("The color to use for the canvas background, specified as a 24-bit integer value, 0xRRGGBB"),
403 0, G_MAXUINT, 0,
404 G_PARAM_WRITABLE));
405
406 g_object_class_install_property (gobject_class, PROP_INTEGER_LAYOUT,
407 g_param_spec_boolean ("integer-layout",
408 _("Integer Layout"),
409 _("If all item layout is done to the nearest integer"),
410 FALSE,
411 G_PARAM_READWRITE));
412
413 g_object_class_install_property (gobject_class, PROP_CLEAR_BACKGROUND,
414 g_param_spec_boolean ("clear-background",
415 _("Clear Background"),
416 _("If the background is cleared before the canvas is painted"),
417 TRUE,
418 G_PARAM_READWRITE));
419
420 g_object_class_install_property (gobject_class, PROP_REDRAW_WHEN_SCROLLED,
421 g_param_spec_boolean ("redraw-when-scrolled",
422 _("Redraw When Scrolled"),
423 _("If the canvas is completely redrawn when scrolled, to reduce the flicker of static items"),
424 FALSE,
425 G_PARAM_READWRITE));
426
427 /**
428 * GooCanvas::set-scroll-adjustments
429 * @canvas: the canvas.
430 * @hadjustment: the horizontal adjustment.
431 * @vadjustment: the vertical adjustment.
432 *
433 * This is used when the #GooCanvas is placed inside a #GtkScrolledWindow,
434 * to connect up the adjustments so scrolling works properly.
435 *
436 * It isn't useful for applications.
437 */
438 widget_class->set_scroll_adjustments_signal =
439 g_signal_new ("set_scroll_adjustments",
440 G_OBJECT_CLASS_TYPE (gobject_class),
441 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
442 G_STRUCT_OFFSET (GooCanvasClass, set_scroll_adjustments),
443 NULL, NULL,
444 goo_canvas_marshal_VOID__OBJECT_OBJECT,
445 G_TYPE_NONE, 2,
446 GTK_TYPE_ADJUSTMENT,
447 GTK_TYPE_ADJUSTMENT);
448
449 /* Signals. */
450
451 /**
452 * GooCanvas::item-created
453 * @canvas: the canvas.
454 * @item: the new item.
455 * @model: the item's model.
456 *
457 * This is emitted when a new canvas item is created, in model/view mode.
458 *
459 * Applications can set up signal handlers for the new items here.
460 */
461 canvas_signals[ITEM_CREATED] =
462 g_signal_new ("item-created",
463 G_TYPE_FROM_CLASS (gobject_class),
464 G_SIGNAL_RUN_LAST,
465 G_STRUCT_OFFSET (GooCanvasClass, item_created),
466 NULL, NULL,
467 goo_canvas_marshal_VOID__OBJECT_OBJECT,
468 G_TYPE_NONE, 2,
469 GOO_TYPE_CANVAS_ITEM,
470 GOO_TYPE_CANVAS_ITEM_MODEL);
471 }
472
473
474 static void
goo_canvas_init(GooCanvas * canvas)475 goo_canvas_init (GooCanvas *canvas)
476 {
477 GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
478
479 /* We set GTK_CAN_FOCUS by default, so it works as people expect.
480 Though developers can turn this off if not needed for efficiency. */
481 GTK_WIDGET_SET_FLAGS (canvas, GTK_CAN_FOCUS);
482
483 canvas->scale_x = 1.0;
484 canvas->scale_y = 1.0;
485 canvas->scale = 1.0;
486 canvas->need_update = TRUE;
487 canvas->need_entire_subtree_update = TRUE;
488 canvas->crossing_event.type = GDK_LEAVE_NOTIFY;
489 canvas->anchor = GTK_ANCHOR_NORTH_WEST;
490 canvas->clear_background = TRUE;
491 canvas->redraw_when_scrolled = FALSE;
492 canvas->before_initial_expose = TRUE;
493
494 /* Set the default bounds to a reasonable size. */
495 canvas->bounds.x1 = 0.0;
496 canvas->bounds.y1 = 0.0;
497 canvas->bounds.x2 = GOO_CANVAS_DEFAULT_WIDTH;
498 canvas->bounds.y2 = GOO_CANVAS_DEFAULT_HEIGHT;
499 canvas->automatic_bounds = FALSE;
500 canvas->bounds_from_origin = TRUE;
501 canvas->bounds_padding = 0.0;
502
503 canvas->units = GTK_UNIT_PIXEL;
504 canvas->resolution_x = 96.0;
505 canvas->resolution_y = 96.0;
506
507 /* Create our own adjustments, in case we aren't inserted into a scrolled
508 window. The accessibility code needs these. */
509 canvas->hadjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0,
510 0.0, 0.0, 0.0));
511 canvas->vadjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0,
512 0.0, 0.0, 0.0));
513
514 g_object_ref_sink (canvas->hadjustment);
515 g_object_ref_sink (canvas->vadjustment);
516
517 canvas->model_to_item = g_hash_table_new (g_direct_hash, g_direct_equal);
518
519 /* Use a simple group as the default root item, which is fine 99% of the
520 time. Apps can set their own root item if required. */
521 canvas->root_item = goo_canvas_group_new (NULL, NULL);
522 goo_canvas_item_set_canvas (canvas->root_item, canvas);
523
524 priv->static_root_item = goo_canvas_group_new (NULL, NULL);
525 goo_canvas_item_set_canvas (priv->static_root_item, canvas);
526 goo_canvas_item_set_is_static (priv->static_root_item, TRUE);
527 priv->static_root_item_model = NULL;
528
529 priv->window_x = 0;
530 priv->window_y = 0;
531 }
532
533
534 /**
535 * goo_canvas_new:
536 *
537 * Creates a new #GooCanvas widget.
538 *
539 * A #GooCanvasGroup is created automatically as the root item of the canvas,
540 * though this can be overriden with goo_canvas_set_root_item() or
541 * goo_canvas_set_root_item_model().
542 *
543 * Returns: a new #GooCanvas widget.
544 **/
545 GtkWidget*
goo_canvas_new(void)546 goo_canvas_new (void)
547 {
548 return GTK_WIDGET (g_object_new (GOO_TYPE_CANVAS, NULL));
549 }
550
551 static void
goo_canvas_dispose(GObject * object)552 goo_canvas_dispose (GObject *object)
553 {
554 GooCanvas *canvas = (GooCanvas*) object;
555 GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
556
557 if (canvas->model_to_item)
558 {
559 g_hash_table_destroy (canvas->model_to_item);
560 canvas->model_to_item = NULL;
561 }
562
563 if (canvas->root_item)
564 {
565 g_object_unref (canvas->root_item);
566 canvas->root_item = NULL;
567 }
568
569 if (canvas->root_item_model)
570 {
571 g_object_unref (canvas->root_item_model);
572 canvas->root_item_model = NULL;
573 }
574
575 if (priv->static_root_item)
576 {
577 g_object_unref (priv->static_root_item);
578 priv->static_root_item = NULL;
579 }
580
581 if (priv->static_root_item_model)
582 {
583 g_object_unref (priv->static_root_item_model);
584 priv->static_root_item_model = NULL;
585 }
586
587 if (canvas->idle_id)
588 {
589 g_source_remove (canvas->idle_id);
590 canvas->idle_id = 0;
591 }
592
593 /* Release any references we hold to items. */
594 set_item_pointer (&canvas->pointer_item, NULL);
595 set_item_pointer (&canvas->pointer_grab_item, NULL);
596 set_item_pointer (&canvas->pointer_grab_initial_item, NULL);
597 set_item_pointer (&canvas->focused_item, NULL);
598 set_item_pointer (&canvas->keyboard_grab_item, NULL);
599
600 if (canvas->hadjustment)
601 {
602 g_object_unref (canvas->hadjustment);
603 canvas->hadjustment = NULL;
604 }
605
606 if (canvas->vadjustment)
607 {
608 g_object_unref (canvas->vadjustment);
609 canvas->vadjustment = NULL;
610 }
611
612 G_OBJECT_CLASS (goo_canvas_parent_class)->dispose (object);
613 }
614
615
616 static void
goo_canvas_finalize(GObject * object)617 goo_canvas_finalize (GObject *object)
618 {
619 /*GooCanvas *canvas = (GooCanvas*) object;*/
620
621 G_OBJECT_CLASS (goo_canvas_parent_class)->finalize (object);
622 }
623
624
625 /**
626 * goo_canvas_get_default_line_width:
627 * @canvas: a #GooCanvas.
628 *
629 * Gets the default line width, which depends on the current units setting.
630 *
631 * Returns: the default line width of the canvas.
632 **/
633 gdouble
goo_canvas_get_default_line_width(GooCanvas * canvas)634 goo_canvas_get_default_line_width (GooCanvas *canvas)
635 {
636 gdouble line_width = 2.0;
637
638 if (!canvas)
639 return 2.0;
640
641 /* We use the same default as cairo when using pixels, i.e. 2 pixels.
642 For other units we use 2 points, or thereabouts. */
643 switch (canvas->units)
644 {
645 case GTK_UNIT_PIXEL:
646 line_width = 2.0;
647 break;
648 case GTK_UNIT_POINTS:
649 line_width = 2.0;
650 break;
651 case GTK_UNIT_INCH:
652 line_width = 2.0 / 72.0;
653 break;
654 case GTK_UNIT_MM:
655 line_width = 0.7;
656 break;
657 }
658
659 return line_width;
660 }
661
662
663 /**
664 * goo_canvas_create_cairo_context:
665 * @canvas: a #GooCanvas.
666 *
667 * Creates a cairo context, initialized with the default canvas settings.
668 *
669 * Returns: a new cairo context. It should be freed with cairo_destroy().
670 **/
671 cairo_t*
goo_canvas_create_cairo_context(GooCanvas * canvas)672 goo_canvas_create_cairo_context (GooCanvas *canvas)
673 {
674 cairo_t *cr;
675 cairo_surface_t *surface;
676
677 /* If the canvas is realized we can use the GDK function to create a cairo
678 context for the canvas window. Otherwise we create a small temporary
679 image surface. */
680 if (canvas && canvas->canvas_window)
681 {
682 cr = gdk_cairo_create (canvas->canvas_window);
683 }
684 else
685 {
686 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
687 cr = cairo_create (surface);
688 /* The cairo context will keep a reference to the surface so we can
689 drop our reference. */
690 cairo_surface_destroy (surface);
691 }
692
693 /* We use CAIRO_ANTIALIAS_GRAY as the default antialiasing mode, as that is
694 what is recommended when using unhinted text. */
695 cairo_set_antialias (cr, CAIRO_ANTIALIAS_GRAY);
696
697 /* Set the default line width based on the current units setting. */
698 cairo_set_line_width (cr, goo_canvas_get_default_line_width (canvas));
699
700 return cr;
701 }
702
703
704 static void
goo_canvas_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)705 goo_canvas_get_property (GObject *object,
706 guint prop_id,
707 GValue *value,
708 GParamSpec *pspec)
709 {
710 GooCanvas *canvas = (GooCanvas*) object;
711
712 switch (prop_id)
713 {
714 case PROP_SCALE:
715 g_value_set_double (value, canvas->scale);
716 break;
717 case PROP_SCALE_X:
718 g_value_set_double (value, canvas->scale_x);
719 break;
720 case PROP_SCALE_Y:
721 g_value_set_double (value, canvas->scale_y);
722 break;
723 case PROP_ANCHOR:
724 g_value_set_enum (value, canvas->anchor);
725 break;
726 case PROP_X1:
727 g_value_set_double (value, canvas->bounds.x1);
728 break;
729 case PROP_Y1:
730 g_value_set_double (value, canvas->bounds.y1);
731 break;
732 case PROP_X2:
733 g_value_set_double (value, canvas->bounds.x2);
734 break;
735 case PROP_Y2:
736 g_value_set_double (value, canvas->bounds.y2);
737 break;
738 case PROP_AUTOMATIC_BOUNDS:
739 g_value_set_boolean (value, canvas->automatic_bounds);
740 break;
741 case PROP_BOUNDS_FROM_ORIGIN:
742 g_value_set_boolean (value, canvas->bounds_from_origin);
743 break;
744 case PROP_BOUNDS_PADDING:
745 g_value_set_double (value, canvas->bounds_padding);
746 break;
747 case PROP_UNITS:
748 g_value_set_enum (value, canvas->units);
749 break;
750 case PROP_RESOLUTION_X:
751 g_value_set_double (value, canvas->resolution_x);
752 break;
753 case PROP_RESOLUTION_Y:
754 g_value_set_double (value, canvas->resolution_y);
755 break;
756 case PROP_INTEGER_LAYOUT:
757 g_value_set_boolean (value, canvas->integer_layout);
758 break;
759 case PROP_CLEAR_BACKGROUND:
760 g_value_set_boolean (value, canvas->clear_background);
761 break;
762 case PROP_REDRAW_WHEN_SCROLLED:
763 g_value_set_boolean (value, canvas->redraw_when_scrolled);
764 break;
765
766 default:
767 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
768 break;
769 }
770 }
771
772
773 static void
goo_canvas_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)774 goo_canvas_set_property (GObject *object,
775 guint prop_id,
776 const GValue *value,
777 GParamSpec *pspec)
778 {
779 GooCanvas *canvas = (GooCanvas*) object;
780 GdkColor color = { 0, 0, 0, 0, };
781 gboolean need_reconfigure = FALSE;
782 gboolean need_update_automatic_bounds = FALSE;
783 guint rgb;
784
785 switch (prop_id)
786 {
787 case PROP_SCALE:
788 goo_canvas_set_scale (canvas, g_value_get_double (value));
789 break;
790 case PROP_SCALE_X:
791 goo_canvas_set_scale_internal (canvas, g_value_get_double (value),
792 canvas->scale_y);
793 break;
794 case PROP_SCALE_Y:
795 goo_canvas_set_scale_internal (canvas, canvas->scale_x,
796 g_value_get_double (value));
797 break;
798 case PROP_ANCHOR:
799 canvas->anchor = g_value_get_enum (value);
800 need_reconfigure = TRUE;
801 break;
802 case PROP_X1:
803 canvas->bounds.x1 = g_value_get_double (value);
804 need_reconfigure = TRUE;
805 break;
806 case PROP_Y1:
807 canvas->bounds.y1 = g_value_get_double (value);
808 need_reconfigure = TRUE;
809 break;
810 case PROP_X2:
811 canvas->bounds.x2 = g_value_get_double (value);
812 need_reconfigure = TRUE;
813 break;
814 case PROP_Y2:
815 canvas->bounds.y2 = g_value_get_double (value);
816 need_reconfigure = TRUE;
817 break;
818 case PROP_AUTOMATIC_BOUNDS:
819 canvas->automatic_bounds = g_value_get_boolean (value);
820 if (canvas->automatic_bounds)
821 need_update_automatic_bounds = TRUE;
822 break;
823 case PROP_BOUNDS_FROM_ORIGIN:
824 canvas->bounds_from_origin = g_value_get_boolean (value);
825 if (canvas->automatic_bounds)
826 need_update_automatic_bounds = TRUE;
827 break;
828 case PROP_BOUNDS_PADDING:
829 canvas->bounds_padding = g_value_get_double (value);
830 if (canvas->automatic_bounds)
831 need_update_automatic_bounds = TRUE;
832 break;
833 case PROP_UNITS:
834 canvas->units = g_value_get_enum (value);
835 need_reconfigure = TRUE;
836 break;
837 case PROP_RESOLUTION_X:
838 canvas->resolution_x = g_value_get_double (value);
839 need_reconfigure = TRUE;
840 break;
841 case PROP_RESOLUTION_Y:
842 canvas->resolution_y = g_value_get_double (value);
843 need_reconfigure = TRUE;
844 break;
845 case PROP_BACKGROUND_COLOR:
846 if (!g_value_get_string (value))
847 gtk_widget_modify_base ((GtkWidget*) canvas, GTK_STATE_NORMAL, NULL);
848 else if (gdk_color_parse (g_value_get_string (value), &color))
849 gtk_widget_modify_base ((GtkWidget*) canvas, GTK_STATE_NORMAL, &color);
850 else
851 g_warning ("Unknown color: %s", g_value_get_string (value));
852 break;
853 case PROP_BACKGROUND_COLOR_RGB:
854 rgb = g_value_get_uint (value);
855 color.red = ((rgb >> 16) & 0xFF) * 257;
856 color.green = ((rgb >> 8) & 0xFF) * 257;
857 color.blue = ((rgb) & 0xFF) * 257;
858 gtk_widget_modify_base ((GtkWidget*) canvas, GTK_STATE_NORMAL, &color);
859 break;
860 case PROP_INTEGER_LAYOUT:
861 canvas->integer_layout = g_value_get_boolean (value);
862 canvas->need_entire_subtree_update = TRUE;
863 goo_canvas_request_update (canvas);
864 break;
865 case PROP_CLEAR_BACKGROUND:
866 canvas->clear_background = g_value_get_boolean (value);
867 break;
868 case PROP_REDRAW_WHEN_SCROLLED:
869 canvas->redraw_when_scrolled = g_value_get_boolean (value);
870 break;
871
872 default:
873 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
874 break;
875 }
876
877 if (need_update_automatic_bounds)
878 {
879 goo_canvas_update_automatic_bounds (canvas);
880 }
881
882 if (need_reconfigure)
883 {
884 reconfigure_canvas (canvas, FALSE);
885 gtk_widget_queue_draw (GTK_WIDGET (canvas));
886 }
887 }
888
889
890 /**
891 * goo_canvas_get_root_item_model:
892 * @canvas: a #GooCanvas.
893 *
894 * Gets the root item model of the canvas.
895 *
896 * Returns: the root item model, or %NULL if there is no root item model.
897 **/
898 GooCanvasItemModel*
goo_canvas_get_root_item_model(GooCanvas * canvas)899 goo_canvas_get_root_item_model (GooCanvas *canvas)
900 {
901 g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
902
903 return canvas->root_item_model;
904 }
905
906
907 /**
908 * goo_canvas_set_root_item_model:
909 * @canvas: a #GooCanvas.
910 * @model: a #GooCanvasItemModel.
911 *
912 * Sets the root item model of the canvas.
913 *
914 * A hierarchy of canvas items will be created, corresponding to the hierarchy
915 * of items in the model. Any current canvas items will be removed.
916 **/
917 void
goo_canvas_set_root_item_model(GooCanvas * canvas,GooCanvasItemModel * model)918 goo_canvas_set_root_item_model (GooCanvas *canvas,
919 GooCanvasItemModel *model)
920 {
921 g_return_if_fail (GOO_IS_CANVAS (canvas));
922 g_return_if_fail (GOO_IS_CANVAS_ITEM_MODEL (model));
923
924 if (canvas->root_item_model == model)
925 return;
926
927 if (canvas->root_item_model)
928 {
929 g_object_unref (canvas->root_item_model);
930 canvas->root_item_model = NULL;
931 }
932
933 if (canvas->root_item)
934 {
935 g_object_unref (canvas->root_item);
936 canvas->root_item = NULL;
937 }
938
939 if (model)
940 {
941 canvas->root_item_model = g_object_ref (model);
942
943 /* Create a hierarchy of canvas items for all the items in the model. */
944 canvas->root_item = goo_canvas_create_item (canvas, model);
945 }
946 else
947 {
948 /* The model has been reset so we go back to a default root group. */
949 canvas->root_item = goo_canvas_group_new (NULL, NULL);
950 }
951
952 goo_canvas_item_set_canvas (canvas->root_item, canvas);
953 canvas->need_update = TRUE;
954
955 #if GTK_CHECK_VERSION(2, 19, 6)
956 if (gtk_widget_get_realized (GTK_WIDGET (canvas)))
957 #else
958 if (GTK_WIDGET_REALIZED (canvas))
959 #endif
960 goo_canvas_update (canvas);
961
962 gtk_widget_queue_draw (GTK_WIDGET (canvas));
963 }
964
965
966 /**
967 * goo_canvas_get_root_item:
968 * @canvas: a #GooCanvas.
969 *
970 * Gets the root item of the canvas, usually a #GooCanvasGroup.
971 *
972 * Returns: the root item, or %NULL if there is no root item.
973 **/
974 GooCanvasItem*
goo_canvas_get_root_item(GooCanvas * canvas)975 goo_canvas_get_root_item (GooCanvas *canvas)
976 {
977 g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
978
979 return canvas->root_item;
980 }
981
982
983 /**
984 * goo_canvas_set_root_item:
985 * @canvas: a #GooCanvas.
986 * @item: the root canvas item.
987 *
988 * Sets the root item of the canvas. Any existing canvas items are removed.
989 **/
990 void
goo_canvas_set_root_item(GooCanvas * canvas,GooCanvasItem * item)991 goo_canvas_set_root_item (GooCanvas *canvas,
992 GooCanvasItem *item)
993 {
994 g_return_if_fail (GOO_IS_CANVAS (canvas));
995 g_return_if_fail (GOO_IS_CANVAS_ITEM (item));
996
997 if (canvas->root_item == item)
998 return;
999
1000 /* Remove any current model. */
1001 if (canvas->root_item_model)
1002 {
1003 g_object_unref (canvas->root_item_model);
1004 canvas->root_item_model = NULL;
1005 }
1006
1007 if (canvas->root_item)
1008 g_object_unref (canvas->root_item);
1009
1010 canvas->root_item = g_object_ref (item);
1011 goo_canvas_item_set_canvas (canvas->root_item, canvas);
1012
1013 canvas->need_update = TRUE;
1014
1015 #if GTK_CHECK_VERSION(2, 19, 6)
1016 if (gtk_widget_get_realized (GTK_WIDGET (canvas)))
1017 #else
1018 if (GTK_WIDGET_REALIZED (canvas))
1019 #endif
1020 goo_canvas_update (canvas);
1021
1022 gtk_widget_queue_draw (GTK_WIDGET (canvas));
1023 }
1024
1025
1026 /**
1027 * goo_canvas_get_static_root_item:
1028 * @canvas: a #GooCanvas.
1029 *
1030 * Gets the static root item of the canvas.
1031 *
1032 * Static items are exactly the same as ordinary canvas items, except that
1033 * they do not move or change size when the canvas is scrolled or the scale
1034 * changes.
1035 *
1036 * Static items are added to the static root item in exactly the same way that
1037 * ordinary items are added to the root item.
1038 *
1039 * Returns: the static root item, or %NULL.
1040 **/
1041 GooCanvasItem*
goo_canvas_get_static_root_item(GooCanvas * canvas)1042 goo_canvas_get_static_root_item (GooCanvas *canvas)
1043 {
1044 g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
1045
1046 return GOO_CANVAS_GET_PRIVATE (canvas)->static_root_item;
1047 }
1048
1049
1050 /**
1051 * goo_canvas_set_static_root_item:
1052 * @canvas: a #GooCanvas.
1053 * @item: the static root item.
1054 *
1055 * Sets the static root item. Any existing static items are removed.
1056 *
1057 * Static items are exactly the same as ordinary canvas items, except that
1058 * they do not move or change size when the canvas is scrolled or the scale
1059 * changes.
1060 *
1061 * Static items are added to the static root item in exactly the same way that
1062 * ordinary items are added to the root item.
1063 **/
1064 void
goo_canvas_set_static_root_item(GooCanvas * canvas,GooCanvasItem * item)1065 goo_canvas_set_static_root_item (GooCanvas *canvas,
1066 GooCanvasItem *item)
1067 {
1068 GooCanvasPrivate *priv;
1069
1070 g_return_if_fail (GOO_IS_CANVAS (canvas));
1071 g_return_if_fail (GOO_IS_CANVAS_ITEM (item));
1072
1073 priv = GOO_CANVAS_GET_PRIVATE (canvas);
1074
1075 if (priv->static_root_item == item)
1076 return;
1077
1078 /* Remove any current model. */
1079 if (priv->static_root_item_model)
1080 {
1081 g_object_unref (priv->static_root_item_model);
1082 priv->static_root_item_model = NULL;
1083 }
1084
1085 if (priv->static_root_item)
1086 g_object_unref (priv->static_root_item);
1087
1088 priv->static_root_item = g_object_ref (item);
1089 goo_canvas_item_set_canvas (priv->static_root_item, canvas);
1090 goo_canvas_item_set_is_static (priv->static_root_item, TRUE);
1091
1092 canvas->need_update = TRUE;
1093
1094 #if GTK_CHECK_VERSION(2, 19, 6)
1095 if (gtk_widget_get_realized (GTK_WIDGET (canvas)))
1096 #else
1097 if (GTK_WIDGET_REALIZED (canvas))
1098 #endif
1099 goo_canvas_update (canvas);
1100
1101 gtk_widget_queue_draw (GTK_WIDGET (canvas));
1102 }
1103
1104
1105 /**
1106 * goo_canvas_get_static_root_item_model:
1107 * @canvas: a #GooCanvas.
1108 *
1109 * Gets the static root item model of the canvas.
1110 *
1111 * Static item models are exactly the same as ordinary item models, except that
1112 * the corresponding items do not move or change size when the canvas is
1113 * scrolled or the scale changes.
1114 *
1115 * Static items models are added to the static root item model in exactly the
1116 * same way that ordinary item models are added to the root item model.
1117 *
1118 * Returns: the static root item model, or %NULL.
1119 **/
1120 GooCanvasItemModel*
goo_canvas_get_static_root_item_model(GooCanvas * canvas)1121 goo_canvas_get_static_root_item_model (GooCanvas *canvas)
1122 {
1123 g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
1124
1125 return GOO_CANVAS_GET_PRIVATE (canvas)->static_root_item_model;
1126 }
1127
1128
1129 /**
1130 * goo_canvas_set_static_root_item_model:
1131 * @canvas: a #GooCanvas.
1132 * @model: the static root item model.
1133 *
1134 * Sets the static root item model. Any existing static item models are
1135 * removed.
1136 *
1137 * Static item models are exactly the same as ordinary item models, except that
1138 * the corresponding items do not move or change size when the canvas is
1139 * scrolled or the scale changes.
1140 *
1141 * Static items models are added to the static root item model in exactly the
1142 * same way that ordinary item models are added to the root item model.
1143 **/
1144 void
goo_canvas_set_static_root_item_model(GooCanvas * canvas,GooCanvasItemModel * model)1145 goo_canvas_set_static_root_item_model (GooCanvas *canvas,
1146 GooCanvasItemModel *model)
1147 {
1148 GooCanvasPrivate *priv;
1149
1150 g_return_if_fail (GOO_IS_CANVAS (canvas));
1151 g_return_if_fail (GOO_IS_CANVAS_ITEM_MODEL (model));
1152
1153 priv = GOO_CANVAS_GET_PRIVATE (canvas);
1154
1155 if (priv->static_root_item_model == model)
1156 return;
1157
1158 if (priv->static_root_item_model)
1159 {
1160 g_object_unref (priv->static_root_item_model);
1161 priv->static_root_item_model = NULL;
1162 }
1163
1164 if (priv->static_root_item)
1165 {
1166 g_object_unref (priv->static_root_item);
1167 priv->static_root_item = NULL;
1168 }
1169
1170 if (model)
1171 {
1172 priv->static_root_item_model = g_object_ref (model);
1173
1174 /* Create a hierarchy of canvas items for all the items in the model. */
1175 priv->static_root_item = goo_canvas_create_item (canvas, model);
1176 }
1177 else
1178 {
1179 /* The model has been reset so we go back to a default root group. */
1180 priv->static_root_item = goo_canvas_group_new (NULL, NULL);
1181 }
1182
1183 goo_canvas_item_set_canvas (priv->static_root_item, canvas);
1184 goo_canvas_item_set_is_static (priv->static_root_item, TRUE);
1185 canvas->need_update = TRUE;
1186
1187 #if GTK_CHECK_VERSION(2, 19, 6)
1188 if (gtk_widget_get_realized (GTK_WIDGET (canvas)))
1189 #else
1190 if (GTK_WIDGET_REALIZED (canvas))
1191 #endif
1192 goo_canvas_update (canvas);
1193
1194 gtk_widget_queue_draw (GTK_WIDGET (canvas));
1195 }
1196
1197
1198 /**
1199 * goo_canvas_get_item:
1200 * @canvas: a #GooCanvas.
1201 * @model: a #GooCanvasItemModel.
1202 *
1203 * Gets the canvas item associated with the given #GooCanvasItemModel.
1204 * This is only useful when goo_canvas_set_root_item_model() has been used to
1205 * set a model for the canvas.
1206 *
1207 * For simple applications you can use goo_canvas_get_item() to set up
1208 * signal handlers for your items, e.g.
1209 *
1210 * <informalexample><programlisting>
1211 * item = goo_canvas_get_item (GOO_CANVAS (canvas), my_item);
1212 * g_signal_connect (item, "button_press_event",
1213 * (GtkSignalFunc) on_my_item_button_press, NULL);
1214 * </programlisting></informalexample>
1215 *
1216 * More complex applications may want to use the #GooCanvas::item-created
1217 * signal to hook up their signal handlers.
1218 *
1219 * Returns: the canvas item corresponding to the given #GooCanvasItemModel,
1220 * or %NULL if no canvas item has been created for it yet.
1221 **/
1222 GooCanvasItem*
goo_canvas_get_item(GooCanvas * canvas,GooCanvasItemModel * model)1223 goo_canvas_get_item (GooCanvas *canvas,
1224 GooCanvasItemModel *model)
1225 {
1226 GooCanvasItem *item = NULL;
1227
1228 g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
1229 g_return_val_if_fail (GOO_IS_CANVAS_ITEM_MODEL (model), NULL);
1230
1231 if (canvas->model_to_item)
1232 item = g_hash_table_lookup (canvas->model_to_item, model);
1233
1234 /* If the item model has a canvas item check it is valid. */
1235 g_return_val_if_fail (!item || GOO_IS_CANVAS_ITEM (item), NULL);
1236
1237 return item;
1238 }
1239
1240
1241 /**
1242 * goo_canvas_get_item_at:
1243 * @canvas: a #GooCanvas.
1244 * @x: the x coordinate of the point.
1245 * @y: the y coordinate of the point
1246 * @is_pointer_event: %TRUE if the "pointer-events" property of
1247 * items should be used to determine which parts of the item are tested.
1248 *
1249 * Gets the item at the given point.
1250 *
1251 * Returns: the item found at the given point, or %NULL if no item was found.
1252 **/
1253 GooCanvasItem*
goo_canvas_get_item_at(GooCanvas * canvas,gdouble x,gdouble y,gboolean is_pointer_event)1254 goo_canvas_get_item_at (GooCanvas *canvas,
1255 gdouble x,
1256 gdouble y,
1257 gboolean is_pointer_event)
1258 {
1259 GooCanvasPrivate *priv;
1260 cairo_t *cr;
1261 GooCanvasItem *result = NULL;
1262 GList *list = NULL;
1263
1264 g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
1265
1266 priv = GOO_CANVAS_GET_PRIVATE (canvas);
1267 cr = goo_canvas_create_cairo_context (canvas);
1268
1269 if (canvas->root_item)
1270 list = goo_canvas_item_get_items_at (canvas->root_item, x, y, cr,
1271 is_pointer_event, TRUE, NULL);
1272
1273 if (!list && priv->static_root_item)
1274 {
1275 gdouble static_x = x, static_y = y;
1276
1277 goo_canvas_convert_to_static_item_space (canvas, &static_x, &static_y);
1278 list = goo_canvas_item_get_items_at (priv->static_root_item,
1279 static_x, static_y, cr,
1280 is_pointer_event, TRUE, NULL);
1281 }
1282
1283 cairo_destroy (cr);
1284
1285 /* We just return the top item in the list. */
1286 if (list)
1287 result = list->data;
1288
1289 g_list_free (list);
1290
1291 return result;
1292 }
1293
1294
1295 /**
1296 * goo_canvas_get_items_at:
1297 * @canvas: a #GooCanvas.
1298 * @x: the x coordinate of the point.
1299 * @y: the y coordinate of the point
1300 * @is_pointer_event: %TRUE if the "pointer-events" property of
1301 * items should be used to determine which parts of the item are tested.
1302 *
1303 * Gets all items at the given point.
1304 *
1305 * Returns: a list of items found at the given point, with the top item at
1306 * the start of the list, or %NULL if no items were found. The list must be
1307 * freed with g_list_free().
1308 **/
1309 GList*
goo_canvas_get_items_at(GooCanvas * canvas,gdouble x,gdouble y,gboolean is_pointer_event)1310 goo_canvas_get_items_at (GooCanvas *canvas,
1311 gdouble x,
1312 gdouble y,
1313 gboolean is_pointer_event)
1314 {
1315 GooCanvasPrivate *priv;
1316 cairo_t *cr;
1317 GList *result = NULL;
1318
1319 g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
1320
1321 priv = GOO_CANVAS_GET_PRIVATE (canvas);
1322 cr = goo_canvas_create_cairo_context (canvas);
1323
1324 if (canvas->root_item)
1325 result = goo_canvas_item_get_items_at (canvas->root_item, x, y, cr,
1326 is_pointer_event, TRUE, NULL);
1327
1328 if (priv->static_root_item)
1329 {
1330 gdouble static_x = x, static_y = y;
1331
1332 goo_canvas_convert_to_static_item_space (canvas, &static_x, &static_y);
1333 result = goo_canvas_item_get_items_at (priv->static_root_item,
1334 static_x, static_y, cr,
1335 is_pointer_event, TRUE, result);
1336 }
1337
1338 cairo_destroy (cr);
1339
1340 return result;
1341 }
1342
1343
1344 static GList*
goo_canvas_get_items_in_area_recurse(GooCanvas * canvas,GooCanvasItem * item,const GooCanvasBounds * area,gboolean inside_area,gboolean allow_overlaps,gboolean include_containers,GList * found_items)1345 goo_canvas_get_items_in_area_recurse (GooCanvas *canvas,
1346 GooCanvasItem *item,
1347 const GooCanvasBounds *area,
1348 gboolean inside_area,
1349 gboolean allow_overlaps,
1350 gboolean include_containers,
1351 GList *found_items)
1352 {
1353 GooCanvasBounds bounds;
1354 gboolean completely_inside = FALSE, completely_outside = FALSE;
1355 gboolean is_container, add_item = FALSE;
1356 gint n_children, i;
1357
1358 /* First check the item/container itself. */
1359 goo_canvas_item_get_bounds (item, &bounds);
1360
1361 is_container = goo_canvas_item_is_container (item);
1362
1363 if (bounds.x1 >= area->x1 && bounds.x2 <= area->x2
1364 && bounds.y1 >= area->y1 && bounds.y2 <= area->y2)
1365 completely_inside = TRUE;
1366
1367 if (bounds.x1 > area->x2 || bounds.x2 < area->x1
1368 || bounds.y1 > area->y2 || bounds.y2 < area->y1)
1369 completely_outside = TRUE;
1370
1371 if (inside_area)
1372 {
1373 if (completely_inside
1374 || (allow_overlaps && !completely_outside))
1375 add_item = TRUE;
1376 }
1377 else
1378 {
1379 if (completely_outside
1380 || (allow_overlaps && !completely_inside))
1381 add_item = TRUE;
1382 }
1383
1384 if (add_item && (!is_container || include_containers))
1385 found_items = g_list_prepend (found_items, item);
1386
1387 /* Now check any children, if appropriate. */
1388 if ((inside_area && !completely_outside)
1389 || (!inside_area && !completely_inside))
1390 {
1391 n_children = goo_canvas_item_get_n_children (item);
1392 for (i = 0; i < n_children; i++)
1393 {
1394 GooCanvasItem *child = goo_canvas_item_get_child (item, i);
1395 found_items = goo_canvas_get_items_in_area_recurse (canvas, child,
1396 area,
1397 inside_area,
1398 allow_overlaps,
1399 include_containers,
1400 found_items);
1401 }
1402 }
1403
1404 return found_items;
1405 }
1406
1407
1408 /**
1409 * goo_canvas_get_items_in_area:
1410 * @canvas: a #GooCanvas.
1411 * @area: the area to compare with each item's bounds.
1412 * @inside_area: %TRUE if items inside @area should be returned, or %FALSE if
1413 * items outside @area should be returned.
1414 * @allow_overlaps: %TRUE if items which are partly inside and partly outside
1415 * should be returned.
1416 * @include_containers: %TRUE if containers should be checked as well as
1417 * normal items.
1418 *
1419 * Gets a list of items inside or outside a given area.
1420 *
1421 * Returns: a list of items in the given area, or %NULL if no items are found.
1422 * The list should be freed with g_list_free().
1423 **/
1424 GList*
goo_canvas_get_items_in_area(GooCanvas * canvas,const GooCanvasBounds * area,gboolean inside_area,gboolean allow_overlaps,gboolean include_containers)1425 goo_canvas_get_items_in_area (GooCanvas *canvas,
1426 const GooCanvasBounds *area,
1427 gboolean inside_area,
1428 gboolean allow_overlaps,
1429 gboolean include_containers)
1430 {
1431 g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
1432
1433 /* If no root item is set, just return NULL. */
1434 if (!canvas->root_item)
1435 return NULL;
1436
1437 return goo_canvas_get_items_in_area_recurse (canvas, canvas->root_item,
1438 area, inside_area,
1439 allow_overlaps,
1440 include_containers, NULL);
1441 }
1442
1443
1444 static void
goo_canvas_realize(GtkWidget * widget)1445 goo_canvas_realize (GtkWidget *widget)
1446 {
1447 GooCanvas *canvas;
1448 GooCanvasPrivate *priv;
1449 GdkWindowAttr attributes;
1450 gint attributes_mask;
1451 gint width_pixels, height_pixels;
1452 GList *tmp_list;
1453
1454 g_return_if_fail (GOO_IS_CANVAS (widget));
1455
1456 canvas = GOO_CANVAS (widget);
1457 priv = GOO_CANVAS_GET_PRIVATE (canvas);
1458 GTK_WIDGET_SET_FLAGS (canvas, GTK_REALIZED);
1459
1460 attributes.window_type = GDK_WINDOW_CHILD;
1461 attributes.x = widget->allocation.x;
1462 attributes.y = widget->allocation.y;
1463 attributes.width = widget->allocation.width;
1464 attributes.height = widget->allocation.height;
1465 attributes.wclass = GDK_INPUT_OUTPUT;
1466 attributes.visual = gtk_widget_get_visual (widget);
1467 attributes.colormap = gtk_widget_get_colormap (widget);
1468 attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK;
1469
1470 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
1471
1472 widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
1473 &attributes, attributes_mask);
1474 gdk_window_set_user_data (widget->window, widget);
1475
1476 /* We want to round the sizes up to the next pixel. */
1477 width_pixels = ((canvas->bounds.x2 - canvas->bounds.x1) * canvas->device_to_pixels_x) + 1;
1478 height_pixels = ((canvas->bounds.y2 - canvas->bounds.y1) * canvas->device_to_pixels_y) + 1;
1479
1480 attributes.x = canvas->hadjustment ? - canvas->hadjustment->value : 0,
1481 attributes.y = canvas->vadjustment ? - canvas->vadjustment->value : 0;
1482 attributes.width = MAX (width_pixels, widget->allocation.width);
1483 attributes.height = MAX (height_pixels, widget->allocation.height);
1484 attributes.event_mask = GDK_EXPOSURE_MASK
1485 | GDK_SCROLL_MASK
1486 | GDK_BUTTON_PRESS_MASK
1487 | GDK_BUTTON_RELEASE_MASK
1488 | GDK_POINTER_MOTION_MASK
1489 | GDK_POINTER_MOTION_HINT_MASK
1490 | GDK_KEY_PRESS_MASK
1491 | GDK_KEY_RELEASE_MASK
1492 | GDK_ENTER_NOTIFY_MASK
1493 | GDK_LEAVE_NOTIFY_MASK
1494 | GDK_FOCUS_CHANGE_MASK
1495 | gtk_widget_get_events (widget);
1496
1497 priv->window_x = attributes.x;
1498 priv->window_y = attributes.y;
1499
1500 canvas->canvas_window = gdk_window_new (widget->window,
1501 &attributes, attributes_mask);
1502 gdk_window_set_user_data (canvas->canvas_window, widget);
1503
1504 attributes.x = widget->allocation.x;
1505 attributes.y = widget->allocation.y;
1506 attributes.width = widget->allocation.width;
1507 attributes.height = widget->allocation.height;
1508 attributes.event_mask = 0;
1509
1510 canvas->tmp_window = gdk_window_new (gtk_widget_get_parent_window (widget),
1511 &attributes, attributes_mask);
1512 gdk_window_set_user_data (canvas->tmp_window, widget);
1513
1514 widget->style = gtk_style_attach (widget->style, widget->window);
1515
1516 /* Make sure the window backgrounds aren't set, to avoid flicker when
1517 scrolling (due to the delay between X clearing the background and
1518 GooCanvas painting it). */
1519 gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
1520 gdk_window_set_back_pixmap (canvas->canvas_window, NULL, FALSE);
1521 gdk_window_set_back_pixmap (canvas->tmp_window, NULL, FALSE);
1522
1523 /* Set the parent window of all the child widget items. */
1524 tmp_list = canvas->widget_items;
1525 while (tmp_list)
1526 {
1527 GooCanvasWidget *witem = tmp_list->data;
1528 tmp_list = tmp_list->next;
1529
1530 if (witem->widget)
1531 gtk_widget_set_parent_window (witem->widget, canvas->canvas_window);
1532 }
1533
1534 goo_canvas_update (GOO_CANVAS (widget));
1535 }
1536
1537
1538 static void
goo_canvas_unrealize(GtkWidget * widget)1539 goo_canvas_unrealize (GtkWidget *widget)
1540 {
1541 GooCanvas *canvas;
1542
1543 g_return_if_fail (GOO_IS_CANVAS (widget));
1544
1545 canvas = GOO_CANVAS (widget);
1546
1547 gdk_window_set_user_data (canvas->canvas_window, NULL);
1548 gdk_window_destroy (canvas->canvas_window);
1549 canvas->canvas_window = NULL;
1550
1551 gdk_window_set_user_data (canvas->tmp_window, NULL);
1552 gdk_window_destroy (canvas->tmp_window);
1553 canvas->tmp_window = NULL;
1554
1555 if (GTK_WIDGET_CLASS (goo_canvas_parent_class)->unrealize)
1556 GTK_WIDGET_CLASS (goo_canvas_parent_class)->unrealize (widget);
1557 }
1558
1559
1560 static void
goo_canvas_map(GtkWidget * widget)1561 goo_canvas_map (GtkWidget *widget)
1562 {
1563 GooCanvas *canvas;
1564 GList *tmp_list;
1565
1566 g_return_if_fail (GOO_IS_CANVAS (widget));
1567
1568 canvas = GOO_CANVAS (widget);
1569
1570 GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
1571
1572 tmp_list = canvas->widget_items;
1573 while (tmp_list)
1574 {
1575 GooCanvasWidget *witem = tmp_list->data;
1576 tmp_list = tmp_list->next;
1577
1578 #if GTK_CHECK_VERSION(2, 19, 6)
1579 if (witem->widget && gtk_widget_get_visible (witem->widget))
1580 #else
1581 if (witem->widget && GTK_WIDGET_VISIBLE (witem->widget))
1582 #endif
1583 {
1584 #if GTK_CHECK_VERSION(2, 19, 6)
1585 if (!gtk_widget_get_mapped (witem->widget))
1586 #else
1587 if (!GTK_WIDGET_MAPPED (witem->widget))
1588 #endif
1589 gtk_widget_map (witem->widget);
1590 }
1591 }
1592
1593 gdk_window_show (canvas->canvas_window);
1594 gdk_window_show (widget->window);
1595 }
1596
1597
1598 static void
goo_canvas_style_set(GtkWidget * widget,GtkStyle * old_style)1599 goo_canvas_style_set (GtkWidget *widget,
1600 GtkStyle *old_style)
1601 {
1602 if (GTK_WIDGET_CLASS (goo_canvas_parent_class)->style_set)
1603 GTK_WIDGET_CLASS (goo_canvas_parent_class)->style_set (widget, old_style);
1604
1605 #if GTK_CHECK_VERSION(2, 19, 6)
1606 if (gtk_widget_get_realized (widget))
1607 #else
1608 if (GTK_WIDGET_REALIZED (widget))
1609 #endif
1610 {
1611 /* Make sure the window backgrounds aren't set, to avoid flicker when
1612 scrolling (due to the delay between X clearing the background and
1613 GooCanvas painting it). */
1614 gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
1615 gdk_window_set_back_pixmap (GOO_CANVAS (widget)->canvas_window, NULL, FALSE);
1616 }
1617 }
1618
1619
1620 static void
goo_canvas_configure_hadjustment(GooCanvas * canvas,gint window_width)1621 goo_canvas_configure_hadjustment (GooCanvas *canvas,
1622 gint window_width)
1623 {
1624 GtkWidget *widget = GTK_WIDGET (canvas);
1625 GtkAdjustment *adj = canvas->hadjustment;
1626 gboolean changed = FALSE;
1627 gboolean value_changed = FALSE;
1628 gdouble max_value;
1629
1630 if (adj->upper != window_width)
1631 {
1632 adj->upper = window_width;
1633 changed = TRUE;
1634 }
1635
1636 if (adj->page_size != widget->allocation.width)
1637 {
1638 adj->page_size = widget->allocation.width;
1639 adj->page_increment = adj->page_size * 0.9;
1640 adj->step_increment = adj->page_size * 0.1;
1641 changed = TRUE;
1642 }
1643
1644 max_value = MAX (0.0, adj->upper - adj->page_size);
1645 if (adj->value > max_value)
1646 {
1647 adj->value = max_value;
1648 value_changed = TRUE;
1649 }
1650
1651 if (changed)
1652 gtk_adjustment_changed (adj);
1653
1654 if (value_changed)
1655 gtk_adjustment_value_changed (adj);
1656 }
1657
1658
1659 static void
goo_canvas_configure_vadjustment(GooCanvas * canvas,gint window_height)1660 goo_canvas_configure_vadjustment (GooCanvas *canvas,
1661 gint window_height)
1662 {
1663 GtkWidget *widget = GTK_WIDGET (canvas);
1664 GtkAdjustment *adj = canvas->vadjustment;
1665 gboolean changed = FALSE;
1666 gboolean value_changed = FALSE;
1667 gdouble max_value;
1668
1669 if (adj->upper != window_height)
1670 {
1671 adj->upper = window_height;
1672 changed = TRUE;
1673 }
1674
1675 if (adj->page_size != widget->allocation.height)
1676 {
1677 adj->page_size = widget->allocation.height;
1678 adj->page_increment = adj->page_size * 0.9;
1679 adj->step_increment = adj->page_size * 0.1;
1680 changed = TRUE;
1681 }
1682
1683 max_value = MAX (0.0, adj->upper - adj->page_size);
1684 if (adj->value > max_value)
1685 {
1686 adj->value = max_value;
1687 value_changed = TRUE;
1688 }
1689
1690 if (changed)
1691 gtk_adjustment_changed (adj);
1692
1693 if (value_changed)
1694 gtk_adjustment_value_changed (adj);
1695 }
1696
1697
1698 static void
recalculate_scales(GooCanvas * canvas)1699 recalculate_scales (GooCanvas *canvas)
1700 {
1701 switch (canvas->units)
1702 {
1703 case GTK_UNIT_PIXEL:
1704 canvas->device_to_pixels_x = canvas->scale_x;
1705 canvas->device_to_pixels_y = canvas->scale_y;
1706 break;
1707 case GTK_UNIT_POINTS:
1708 canvas->device_to_pixels_x = canvas->scale_x * (canvas->resolution_x / 72.0);
1709 canvas->device_to_pixels_y = canvas->scale_y * (canvas->resolution_y / 72.0);
1710 break;
1711 case GTK_UNIT_INCH:
1712 canvas->device_to_pixels_x = canvas->scale_x * canvas->resolution_x;
1713 canvas->device_to_pixels_y = canvas->scale_y * canvas->resolution_y;
1714 break;
1715 case GTK_UNIT_MM:
1716 /* There are 25.4 mm to an inch. */
1717 canvas->device_to_pixels_x = canvas->scale_x * (canvas->resolution_x / 25.4);
1718 canvas->device_to_pixels_y = canvas->scale_y * (canvas->resolution_y / 25.4);
1719 break;
1720 }
1721 }
1722
1723
1724 static void
request_static_redraw(GooCanvas * canvas,const GooCanvasBounds * bounds)1725 request_static_redraw (GooCanvas *canvas,
1726 const GooCanvasBounds *bounds)
1727 {
1728 GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
1729 GdkRectangle rect;
1730
1731 #if GTK_CHECK_VERSION (2, 19, 6)
1732 if (!gtk_widget_is_drawable (GTK_WIDGET (canvas)) || (bounds->x1 == bounds->x2))
1733 #else
1734 if (!GTK_WIDGET_DRAWABLE (canvas) || (bounds->x1 == bounds->x2))
1735 #endif
1736 return;
1737
1738 /* We subtract one from the left & top edges, in case anti-aliasing makes
1739 the drawing use an extra pixel. */
1740 rect.x = (double) bounds->x1 - priv->window_x - 1;
1741 rect.y = (double) bounds->y1 - priv->window_y - 1;
1742
1743 /* We add an extra one here for the same reason. (The other extra one is to
1744 round up to the next pixel.) And one for luck! */
1745 rect.width = (double) bounds->x2 - priv->window_x - rect.x + 2 + 1;
1746 rect.height = (double) bounds->y2 - priv->window_y - rect.y + 2 + 1;
1747
1748 gdk_window_invalidate_rect (canvas->canvas_window, &rect, FALSE);
1749 }
1750
1751
1752 /* This requests a redraw of all the toplevel static items at their current
1753 position, but redraws them at their given new position.
1754 We redraw one item at a time to avoid GTK+ merging the rectangles into
1755 one big one. */
1756 static void
redraw_static_items_at_position(GooCanvas * canvas,gint x,gint y)1757 redraw_static_items_at_position (GooCanvas *canvas,
1758 gint x,
1759 gint y)
1760 {
1761 GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
1762 GooCanvasBounds bounds;
1763 GooCanvasItem *item;
1764 gint n_children, i, window_x_copy, window_y_copy;
1765
1766 if (!priv->static_root_item)
1767 return;
1768
1769 window_x_copy = priv->window_x;
1770 window_y_copy = priv->window_y;
1771
1772 n_children = goo_canvas_item_get_n_children (priv->static_root_item);
1773 for (i = 0; i < n_children; i++)
1774 {
1775 item = goo_canvas_item_get_child (priv->static_root_item, i);
1776
1777 /* Get the bounds of all the static items, relative to the window. */
1778 goo_canvas_item_get_bounds (item, &bounds);
1779
1780 /* Request a redraw of the old position. */
1781 request_static_redraw (canvas, &bounds);
1782
1783 /* Redraw the item in its new position. */
1784 priv->window_x = x;
1785 priv->window_y = y;
1786
1787 gdk_window_process_updates (canvas->canvas_window, TRUE);
1788
1789 /* Now reset the window position. */
1790 priv->window_x = window_x_copy;
1791 priv->window_y = window_y_copy;
1792 }
1793 }
1794
1795
1796 /* This makes sure the canvas is all set up correctly, i.e. the scrollbar
1797 adjustments are set, the canvas x & y offsets are calculated, and the
1798 canvas window is sized. */
1799 static void
reconfigure_canvas(GooCanvas * canvas,gboolean redraw_if_needed)1800 reconfigure_canvas (GooCanvas *canvas,
1801 gboolean redraw_if_needed)
1802 {
1803 gint width_pixels, height_pixels;
1804 gint window_x = 0, window_y = 0, window_width, window_height;
1805 gint new_x_offset = 0, new_y_offset = 0;
1806 GtkWidget *widget;
1807
1808 widget = GTK_WIDGET (canvas);
1809
1810 /* Make sure the bounds are sane. */
1811 if (canvas->bounds.x2 < canvas->bounds.x1)
1812 canvas->bounds.x2 = canvas->bounds.x1;
1813 if (canvas->bounds.y2 < canvas->bounds.y1)
1814 canvas->bounds.y2 = canvas->bounds.y1;
1815
1816 /* Recalculate device_to_pixels_x & device_to_pixels_y. */
1817 recalculate_scales (canvas);
1818
1819 /* This is the natural size of the canvas window in pixels, rounded up to
1820 the next pixel. */
1821 width_pixels = ((canvas->bounds.x2 - canvas->bounds.x1) * canvas->device_to_pixels_x) + 1;
1822 height_pixels = ((canvas->bounds.y2 - canvas->bounds.y1) * canvas->device_to_pixels_y) + 1;
1823
1824 /* The actual window size is always at least as big as the widget's window.*/
1825 window_width = MAX (width_pixels, widget->allocation.width);
1826 window_height = MAX (height_pixels, widget->allocation.height);
1827
1828 /* If the width or height is smaller than the window, we need to calculate
1829 the canvas x & y offsets according to the anchor. */
1830 if (width_pixels < widget->allocation.width)
1831 {
1832 switch (canvas->anchor)
1833 {
1834 case GTK_ANCHOR_NORTH_WEST:
1835 case GTK_ANCHOR_WEST:
1836 case GTK_ANCHOR_SOUTH_WEST:
1837 new_x_offset = 0;
1838 break;
1839 case GTK_ANCHOR_NORTH:
1840 case GTK_ANCHOR_CENTER:
1841 case GTK_ANCHOR_SOUTH:
1842 new_x_offset = (widget->allocation.width - width_pixels) / 2;
1843 break;
1844 case GTK_ANCHOR_NORTH_EAST:
1845 case GTK_ANCHOR_EAST:
1846 case GTK_ANCHOR_SOUTH_EAST:
1847 new_x_offset = widget->allocation.width - width_pixels;
1848 break;
1849 }
1850 }
1851
1852 if (height_pixels < widget->allocation.height)
1853 {
1854 switch (canvas->anchor)
1855 {
1856 case GTK_ANCHOR_NORTH_WEST:
1857 case GTK_ANCHOR_NORTH:
1858 case GTK_ANCHOR_NORTH_EAST:
1859 new_y_offset = 0;
1860 break;
1861 case GTK_ANCHOR_WEST:
1862 case GTK_ANCHOR_CENTER:
1863 case GTK_ANCHOR_EAST:
1864 new_y_offset = (widget->allocation.height - height_pixels) / 2;
1865 break;
1866 case GTK_ANCHOR_SOUTH_WEST:
1867 case GTK_ANCHOR_SOUTH:
1868 case GTK_ANCHOR_SOUTH_EAST:
1869 new_y_offset = widget->allocation.height - height_pixels;
1870 break;
1871 }
1872 }
1873
1874 canvas->freeze_count++;
1875
1876 if (canvas->hadjustment)
1877 {
1878 goo_canvas_configure_hadjustment (canvas, window_width);
1879 window_x = - canvas->hadjustment->value;
1880 }
1881
1882 if (canvas->vadjustment)
1883 {
1884 goo_canvas_configure_vadjustment (canvas, window_height);
1885 window_y = - canvas->vadjustment->value;
1886 }
1887
1888 canvas->freeze_count--;
1889
1890 #if GTK_CHECK_VERSION (2, 19, 6)
1891 if (gtk_widget_get_realized (GTK_WIDGET (canvas)))
1892 #else
1893 if (GTK_WIDGET_REALIZED (canvas))
1894 #endif
1895 {
1896 gdk_window_move_resize (canvas->canvas_window, window_x, window_y,
1897 window_width, window_height);
1898 }
1899
1900 /* If one of the offsets has changed we have to redraw the widget. */
1901 if (canvas->canvas_x_offset != new_x_offset
1902 || canvas->canvas_y_offset != new_y_offset)
1903 {
1904 canvas->canvas_x_offset = new_x_offset;
1905 canvas->canvas_y_offset = new_y_offset;
1906
1907 if (redraw_if_needed)
1908 gtk_widget_queue_draw (GTK_WIDGET (canvas));
1909 }
1910 }
1911
1912
1913 static void
goo_canvas_size_request(GtkWidget * widget,GtkRequisition * requisition)1914 goo_canvas_size_request (GtkWidget *widget,
1915 GtkRequisition *requisition)
1916 {
1917 GList *tmp_list;
1918 GooCanvas *canvas;
1919
1920 g_return_if_fail (GOO_IS_CANVAS (widget));
1921
1922 canvas = GOO_CANVAS (widget);
1923
1924 requisition->width = 0;
1925 requisition->height = 0;
1926
1927 tmp_list = canvas->widget_items;
1928
1929 while (tmp_list)
1930 {
1931 GooCanvasWidget *witem = tmp_list->data;
1932 GtkRequisition child_requisition;
1933
1934 tmp_list = tmp_list->next;
1935
1936 if (witem->widget)
1937 gtk_widget_size_request (witem->widget, &child_requisition);
1938 }
1939 }
1940
1941
1942 static void
goo_canvas_allocate_child_widget(GooCanvas * canvas,GooCanvasWidget * witem)1943 goo_canvas_allocate_child_widget (GooCanvas *canvas,
1944 GooCanvasWidget *witem)
1945 {
1946 GooCanvasBounds bounds;
1947 GtkAllocation allocation;
1948
1949 goo_canvas_item_get_bounds ((GooCanvasItem*) witem, &bounds);
1950
1951 goo_canvas_convert_to_pixels (canvas, &bounds.x1, &bounds.y1);
1952 goo_canvas_convert_to_pixels (canvas, &bounds.x2, &bounds.y2);
1953
1954 /* Note that we only really support integers for the bounds, and we don't
1955 support scaling of a canvas with widget items in it. */
1956 allocation.x = bounds.x1;
1957 allocation.y = bounds.y1;
1958 allocation.width = bounds.x2 - allocation.x;
1959 allocation.height = bounds.y2 - allocation.y;
1960
1961 gtk_widget_size_allocate (witem->widget, &allocation);
1962 }
1963
1964
1965 static void
goo_canvas_size_allocate(GtkWidget * widget,GtkAllocation * allocation)1966 goo_canvas_size_allocate (GtkWidget *widget,
1967 GtkAllocation *allocation)
1968 {
1969 GooCanvas *canvas;
1970 GList *tmp_list;
1971
1972 g_return_if_fail (GOO_IS_CANVAS (widget));
1973
1974 canvas = GOO_CANVAS (widget);
1975
1976 widget->allocation = *allocation;
1977
1978 #if GTK_CHECK_VERSION (2, 19, 6)
1979 if (gtk_widget_get_realized (widget))
1980 #else
1981 if (GTK_WIDGET_REALIZED (widget))
1982 #endif
1983 {
1984 /* We can only allocate our children when we are realized, since we
1985 need a window to create a cairo_t which we use for layout. */
1986 tmp_list = canvas->widget_items;
1987 while (tmp_list)
1988 {
1989 GooCanvasWidget *witem = tmp_list->data;
1990 tmp_list = tmp_list->next;
1991
1992 if (witem->widget)
1993 goo_canvas_allocate_child_widget (canvas, witem);
1994 }
1995
1996 gdk_window_move_resize (widget->window,
1997 allocation->x, allocation->y,
1998 allocation->width, allocation->height);
1999 gdk_window_move_resize (canvas->tmp_window,
2000 allocation->x, allocation->y,
2001 allocation->width, allocation->height);
2002 }
2003
2004 reconfigure_canvas (canvas, TRUE);
2005 }
2006
2007
2008 static void
goo_canvas_adjustment_value_changed(GtkAdjustment * adjustment,GooCanvas * canvas)2009 goo_canvas_adjustment_value_changed (GtkAdjustment *adjustment,
2010 GooCanvas *canvas)
2011 {
2012 GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
2013 AtkObject *accessible;
2014
2015 #if GTK_CHECK_VERSION (2, 19, 6)
2016 if (!canvas->freeze_count && gtk_widget_get_realized (GTK_WIDGET(canvas)))
2017 #else
2018 if (!canvas->freeze_count && GTK_WIDGET_REALIZED (canvas))
2019 #endif
2020 {
2021 if (canvas->redraw_when_scrolled)
2022 {
2023 /* Map the temporary window to stop the canvas window being scrolled.
2024 When it is unmapped the entire canvas will be redrawn. */
2025 #if GTK_CHECK_VERSION (2, 19, 6)
2026 if (gtk_widget_get_mapped (GTK_WIDGET (canvas)))
2027 #else
2028 if (GTK_WIDGET_MAPPED (canvas))
2029 #endif
2030 gdk_window_show (canvas->tmp_window);
2031 }
2032 else
2033 {
2034 /* Redraw the area currently occupied by the static items. But
2035 draw the static items in their new position. This stops them
2036 from being "dragged" when the window is scrolled. */
2037 redraw_static_items_at_position (canvas,
2038 -canvas->hadjustment->value,
2039 -canvas->vadjustment->value);
2040
2041 /* Move the static items to the new position. */
2042 priv->window_x = -canvas->hadjustment->value;
2043 priv->window_y = -canvas->vadjustment->value;
2044 }
2045
2046 gdk_window_move (canvas->canvas_window,
2047 - canvas->hadjustment->value,
2048 - canvas->vadjustment->value);
2049
2050 if (canvas->redraw_when_scrolled)
2051 {
2052 /* Unmap the temporary window, causing the entire canvas to be
2053 redrawn. */
2054 #if GTK_CHECK_VERSION (2, 19, 6)
2055 if (gtk_widget_get_mapped (GTK_WIDGET (canvas)))
2056 #else
2057 if (GTK_WIDGET_MAPPED (canvas))
2058 #endif
2059 gdk_window_hide (canvas->tmp_window);
2060 }
2061 else
2062 {
2063 /* Process updates here for smoother scrolling. */
2064 gdk_window_process_updates (canvas->canvas_window, TRUE);
2065
2066 /* Now ensure the static items are redrawn in their new position. */
2067 redraw_static_items_at_position (canvas, priv->window_x,
2068 priv->window_y);
2069 }
2070
2071 /* Notify any accessibility modules that the view has changed. */
2072 accessible = gtk_widget_get_accessible (GTK_WIDGET (canvas));
2073 g_signal_emit_by_name (accessible, "visible_data_changed");
2074 }
2075 }
2076
2077
2078 /* Sets either or both adjustments, If hadj or vadj is NULL a new adjustment
2079 is created. */
2080 static void
goo_canvas_set_adjustments(GooCanvas * canvas,GtkAdjustment * hadj,GtkAdjustment * vadj)2081 goo_canvas_set_adjustments (GooCanvas *canvas,
2082 GtkAdjustment *hadj,
2083 GtkAdjustment *vadj)
2084 {
2085 gboolean need_reconfigure = FALSE;
2086
2087 g_return_if_fail (GOO_IS_CANVAS (canvas));
2088
2089 if (hadj)
2090 g_return_if_fail (GTK_IS_ADJUSTMENT (hadj));
2091 else if (canvas->hadjustment)
2092 hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
2093
2094 if (vadj)
2095 g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
2096 else if (canvas->vadjustment)
2097 vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
2098
2099 if (canvas->hadjustment && (canvas->hadjustment != hadj))
2100 {
2101 g_signal_handlers_disconnect_by_func (canvas->hadjustment,
2102 goo_canvas_adjustment_value_changed,
2103 canvas);
2104 g_object_unref (canvas->hadjustment);
2105 }
2106
2107 if (canvas->vadjustment && (canvas->vadjustment != vadj))
2108 {
2109 g_signal_handlers_disconnect_by_func (canvas->vadjustment,
2110 goo_canvas_adjustment_value_changed,
2111 canvas);
2112 g_object_unref (canvas->vadjustment);
2113 }
2114
2115 if (canvas->hadjustment != hadj)
2116 {
2117 canvas->hadjustment = hadj;
2118 g_object_ref_sink (canvas->hadjustment);
2119
2120 g_signal_connect (canvas->hadjustment, "value_changed",
2121 G_CALLBACK (goo_canvas_adjustment_value_changed),
2122 canvas);
2123 need_reconfigure = TRUE;
2124 }
2125
2126 if (canvas->vadjustment != vadj)
2127 {
2128 canvas->vadjustment = vadj;
2129 g_object_ref_sink (canvas->vadjustment);
2130
2131 g_signal_connect (canvas->vadjustment, "value_changed",
2132 G_CALLBACK (goo_canvas_adjustment_value_changed),
2133 canvas);
2134 need_reconfigure = TRUE;
2135 }
2136
2137 if (need_reconfigure)
2138 reconfigure_canvas (canvas, TRUE);
2139 }
2140
2141
2142 /* Sets one of our pointers to an item, adding a reference to it and
2143 releasing any reference to the current item. */
2144 static void
set_item_pointer(GooCanvasItem ** item,GooCanvasItem * new)2145 set_item_pointer (GooCanvasItem **item,
2146 GooCanvasItem *new)
2147 {
2148 /* If the item hasn't changed, just return. */
2149 if (*item == new)
2150 return;
2151
2152 /* Unref the current item, if it isn't NULL. */
2153 if (*item)
2154 g_object_unref (*item);
2155
2156 /* Set the new item. */
2157 *item = new;
2158
2159 /* Add a reference to it, if it isn't NULL. */
2160 if (*item)
2161 g_object_ref (*item);
2162 }
2163
2164
2165 /**
2166 * goo_canvas_get_bounds:
2167 * @canvas: a #GooCanvas.
2168 * @left: a pointer to a #gdouble to return the left edge, or %NULL.
2169 * @top: a pointer to a #gdouble to return the top edge, or %NULL.
2170 * @right: a pointer to a #gdouble to return the right edge, or %NULL.
2171 * @bottom: a pointer to a #gdouble to return the bottom edge, or %NULL.
2172 *
2173 * Gets the bounds of the canvas, in canvas units.
2174 *
2175 * By default, canvas units are pixels, though the #GooCanvas:units property
2176 * can be used to change the units to points, inches or millimeters.
2177 **/
2178 void
goo_canvas_get_bounds(GooCanvas * canvas,gdouble * left,gdouble * top,gdouble * right,gdouble * bottom)2179 goo_canvas_get_bounds (GooCanvas *canvas,
2180 gdouble *left,
2181 gdouble *top,
2182 gdouble *right,
2183 gdouble *bottom)
2184 {
2185 g_return_if_fail (GOO_IS_CANVAS (canvas));
2186
2187 if (left)
2188 *left = canvas->bounds.x1;
2189 if (top)
2190 *top = canvas->bounds.y1;
2191 if (right)
2192 *right = canvas->bounds.x2;
2193 if (bottom)
2194 *bottom = canvas->bounds.y2;
2195 }
2196
2197
2198 /**
2199 * goo_canvas_set_bounds:
2200 * @canvas: a #GooCanvas.
2201 * @left: the left edge.
2202 * @top: the top edge.
2203 * @right: the right edge.
2204 * @bottom: the bottom edge.
2205 *
2206 * Sets the bounds of the #GooCanvas, in canvas units.
2207 *
2208 * By default, canvas units are pixels, though the #GooCanvas:units property
2209 * can be used to change the units to points, inches or millimeters.
2210 **/
2211 void
goo_canvas_set_bounds(GooCanvas * canvas,gdouble left,gdouble top,gdouble right,gdouble bottom)2212 goo_canvas_set_bounds (GooCanvas *canvas,
2213 gdouble left,
2214 gdouble top,
2215 gdouble right,
2216 gdouble bottom)
2217 {
2218 g_return_if_fail (GOO_IS_CANVAS (canvas));
2219
2220 canvas->bounds.x1 = left;
2221 canvas->bounds.y1 = top;
2222 canvas->bounds.x2 = right;
2223 canvas->bounds.y2 = bottom;
2224
2225 reconfigure_canvas (canvas, FALSE);
2226 gtk_widget_queue_draw (GTK_WIDGET (canvas));
2227 }
2228
2229
2230 /**
2231 * goo_canvas_scroll_to:
2232 * @canvas: a #GooCanvas.
2233 * @left: the x coordinate to scroll to.
2234 * @top: the y coordinate to scroll to.
2235 *
2236 * Scrolls the canvas, placing the given point as close to the top-left of
2237 * the view as possible.
2238 **/
2239 void
goo_canvas_scroll_to(GooCanvas * canvas,gdouble left,gdouble top)2240 goo_canvas_scroll_to (GooCanvas *canvas,
2241 gdouble left,
2242 gdouble top)
2243 {
2244 gdouble x = left, y = top;
2245
2246 g_return_if_fail (GOO_IS_CANVAS (canvas));
2247
2248 /* The scrollbar adjustments use pixel values, so convert to pixels. */
2249 goo_canvas_convert_to_pixels (canvas, &x, &y);
2250
2251 /* Make sure we stay within the bounds. */
2252 x = CLAMP (x, canvas->hadjustment->lower,
2253 canvas->hadjustment->upper - canvas->hadjustment->page_size);
2254 y = CLAMP (y, canvas->vadjustment->lower,
2255 canvas->vadjustment->upper - canvas->vadjustment->page_size);
2256
2257 canvas->freeze_count++;
2258
2259 gtk_adjustment_set_value (canvas->hadjustment, x);
2260 gtk_adjustment_set_value (canvas->vadjustment, y);
2261
2262 canvas->freeze_count--;
2263 goo_canvas_adjustment_value_changed (NULL, canvas);
2264 }
2265
2266
2267 /* This makes sure the given item is displayed, scrolling if necessary. */
2268 static void
goo_canvas_scroll_to_item(GooCanvas * canvas,GooCanvasItem * item)2269 goo_canvas_scroll_to_item (GooCanvas *canvas,
2270 GooCanvasItem *item)
2271 {
2272 GooCanvasBounds bounds;
2273 gdouble hvalue, vvalue;
2274
2275 /* We can't scroll to static items. */
2276 if (goo_canvas_item_get_is_static (item))
2277 return;
2278
2279 goo_canvas_item_get_bounds (item, &bounds);
2280
2281 goo_canvas_convert_to_pixels (canvas, &bounds.x1, &bounds.y1);
2282 goo_canvas_convert_to_pixels (canvas, &bounds.x2, &bounds.y2);
2283
2284 canvas->freeze_count++;
2285
2286 /* Remember the current adjustment values. */
2287 hvalue = canvas->hadjustment->value;
2288 vvalue = canvas->vadjustment->value;
2289
2290 /* Update the adjustments so the item is displayed. */
2291 gtk_adjustment_clamp_page (canvas->hadjustment, bounds.x1, bounds.x2);
2292 gtk_adjustment_clamp_page (canvas->vadjustment, bounds.y1, bounds.y2);
2293
2294 canvas->freeze_count--;
2295
2296 /* If the adjustments have changed we need to scroll. */
2297 if (hvalue != canvas->hadjustment->value
2298 || vvalue != canvas->vadjustment->value)
2299 goo_canvas_adjustment_value_changed (NULL, canvas);
2300 }
2301
2302
2303 /**
2304 * goo_canvas_get_scale:
2305 * @canvas: a #GooCanvas.
2306 *
2307 * Gets the current scale of the canvas.
2308 *
2309 * The scale specifies the magnification factor of the canvas, e.g. if an item
2310 * has a width of 2 pixels and the scale is set to 3, it will be displayed with
2311 * a width of 2 x 3 = 6 pixels.
2312 *
2313 * Returns: the current scale setting.
2314 **/
2315 gdouble
goo_canvas_get_scale(GooCanvas * canvas)2316 goo_canvas_get_scale (GooCanvas *canvas)
2317 {
2318 g_return_val_if_fail (GOO_IS_CANVAS (canvas), 1.0);
2319
2320 return canvas->scale;
2321 }
2322
2323
2324 static void
goo_canvas_set_scale_internal(GooCanvas * canvas,gdouble scale_x,gdouble scale_y)2325 goo_canvas_set_scale_internal (GooCanvas *canvas,
2326 gdouble scale_x,
2327 gdouble scale_y)
2328 {
2329 gdouble x, y;
2330
2331 g_return_if_fail (GOO_IS_CANVAS (canvas));
2332
2333 /* Calculate the coords of the current center point in pixels. */
2334 x = canvas->hadjustment->value + canvas->hadjustment->page_size / 2;
2335 y = canvas->vadjustment->value + canvas->vadjustment->page_size / 2;
2336
2337 /* Convert from pixel units to device units. */
2338 goo_canvas_convert_from_pixels (canvas, &x, &y);
2339
2340 /* Show our temporary window above the canvas window, so that the windowing
2341 system doesn't try to scroll the contents when we change the adjustments.
2342 Since we are changing the scale we need to redraw everything so the
2343 scrolling is unnecessary and really ugly.
2344 FIXME: There is a possible issue with keyboard focus/input methods here,
2345 since hidden windows can't have the keyboard focus. */
2346 #if GTK_CHECK_VERSION (2, 19, 6)
2347 if (gtk_widget_get_mapped (GTK_WIDGET (canvas)))
2348 #else
2349 if (GTK_WIDGET_MAPPED (canvas))
2350 #endif
2351 gdk_window_show (canvas->tmp_window);
2352
2353 canvas->freeze_count++;
2354
2355 canvas->scale_x = scale_x;
2356 canvas->scale_y = scale_y;
2357 canvas->scale = MIN (scale_x, scale_y);
2358 reconfigure_canvas (canvas, FALSE);
2359
2360 /* Convert from the center point to the new desired top-left posision. */
2361 x -= canvas->hadjustment->page_size / canvas->device_to_pixels_x / 2;
2362 y -= canvas->vadjustment->page_size / canvas->device_to_pixels_y / 2;
2363
2364 /* Now try to scroll to it. */
2365 goo_canvas_scroll_to (canvas, x, y);
2366
2367 canvas->freeze_count--;
2368 goo_canvas_adjustment_value_changed (NULL, canvas);
2369
2370 /* Now hide the temporary window, so the canvas window will get an expose
2371 event. */
2372 #if GTK_CHECK_VERSION (2, 19, 6)
2373 if (gtk_widget_get_mapped (GTK_WIDGET (canvas)))
2374 #else
2375 if (GTK_WIDGET_MAPPED (canvas))
2376 #endif
2377 gdk_window_hide (canvas->tmp_window);
2378 }
2379
2380
2381 /**
2382 * goo_canvas_set_scale:
2383 * @canvas: a #GooCanvas.
2384 * @scale: the new scale setting.
2385 *
2386 * Sets the scale of the canvas.
2387 *
2388 * The scale specifies the magnification factor of the canvas, e.g. if an item
2389 * has a width of 2 pixels and the scale is set to 3, it will be displayed with
2390 * a width of 2 x 3 = 6 pixels.
2391 **/
2392 void
goo_canvas_set_scale(GooCanvas * canvas,gdouble scale)2393 goo_canvas_set_scale (GooCanvas *canvas,
2394 gdouble scale)
2395 {
2396 g_return_if_fail (GOO_IS_CANVAS (canvas));
2397
2398 goo_canvas_set_scale_internal (canvas, scale, scale);
2399 }
2400
2401
2402 /**
2403 * goo_canvas_unregister_item:
2404 * @canvas: a #GooCanvas.
2405 * @model: the item model whose canvas item is being finalized.
2406 *
2407 * This function is only intended to be used when implementing new canvas
2408 * items.
2409 *
2410 * It should be called in the finalize method of #GooCanvasItem
2411 * objects, to remove the canvas item from the #GooCanvas's hash table.
2412 **/
2413 void
goo_canvas_unregister_item(GooCanvas * canvas,GooCanvasItemModel * model)2414 goo_canvas_unregister_item (GooCanvas *canvas,
2415 GooCanvasItemModel *model)
2416 {
2417 if (canvas->model_to_item)
2418 g_hash_table_remove (canvas->model_to_item, model);
2419 }
2420
2421
2422 /**
2423 * goo_canvas_create_item:
2424 * @canvas: a #GooCanvas.
2425 * @model: the item model to create a canvas item for.
2426 *
2427 * This function is only intended to be used when implementing new canvas
2428 * items, typically container items such as #GooCanvasGroup.
2429 *
2430 * It creates a new canvas item for the given item model, and recursively
2431 * creates items for any children.
2432 *
2433 * It uses the create_item() virtual method if it has been set.
2434 * Subclasses of #GooCanvas can define this method if they want to use
2435 * custom views for items.
2436 *
2437 * It emits the #GooCanvas::item-created signal after creating the view, so
2438 * application code can connect signal handlers to the new view if desired.
2439 *
2440 * Returns: a new canvas item.
2441 **/
2442 GooCanvasItem*
goo_canvas_create_item(GooCanvas * canvas,GooCanvasItemModel * model)2443 goo_canvas_create_item (GooCanvas *canvas,
2444 GooCanvasItemModel *model)
2445 {
2446 GooCanvasItem *item = NULL;
2447
2448 /* Use the virtual method if it has been set. */
2449 if (GOO_CANVAS_GET_CLASS (canvas)->create_item)
2450 item = GOO_CANVAS_GET_CLASS (canvas)->create_item (canvas, model);
2451
2452 /* The virtual method can return NULL to use the default view for an item. */
2453 if (!item)
2454 item = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model)->create_item (model,
2455 canvas);
2456
2457 if (canvas->model_to_item)
2458 g_hash_table_insert (canvas->model_to_item, model, item);
2459
2460 /* Emit a signal so apps can hook up signal handlers if they want. */
2461 g_signal_emit (canvas, canvas_signals[ITEM_CREATED], 0, item, model);
2462
2463 return item;
2464 }
2465
2466
2467 static void
goo_canvas_update_automatic_bounds(GooCanvas * canvas)2468 goo_canvas_update_automatic_bounds (GooCanvas *canvas)
2469 {
2470 GooCanvasBounds bounds = { 0.0, 0.0, GOO_CANVAS_DEFAULT_WIDTH,
2471 GOO_CANVAS_DEFAULT_HEIGHT };
2472
2473 if (canvas->root_item)
2474 goo_canvas_item_get_bounds (canvas->root_item, &bounds);
2475
2476 /* Calculate the new automatic bounds, which is the bounds of all the items
2477 in the canvas plus any specified padding. If bounds_from_origin is set
2478 x1 and y1 are set to 0.0. */
2479 if (canvas->bounds_from_origin)
2480 {
2481 bounds.x1 = 0.0;
2482 bounds.y1 = 0.0;
2483 bounds.x2 += canvas->bounds_padding;
2484 bounds.y2 += canvas->bounds_padding;
2485 }
2486 else
2487 {
2488 bounds.x1 -= canvas->bounds_padding;
2489 bounds.y1 -= canvas->bounds_padding;
2490 bounds.x2 += canvas->bounds_padding;
2491 bounds.y2 += canvas->bounds_padding;
2492 }
2493
2494 /* Make sure the bounds are sane. */
2495 if (bounds.x2 < bounds.x1)
2496 bounds.x2 = bounds.x1;
2497 if (bounds.y2 < bounds.y1)
2498 bounds.y2 = bounds.y1;
2499
2500 /* If the bounds have changed, reconfigure the canvas and redraw. */
2501 if (bounds.x1 != canvas->bounds.x1
2502 || bounds.y1 != canvas->bounds.y1
2503 || bounds.x2 != canvas->bounds.x2
2504 || bounds.y2 != canvas->bounds.y2)
2505 {
2506 canvas->bounds = bounds;
2507 reconfigure_canvas (canvas, FALSE);
2508 gtk_widget_queue_draw (GTK_WIDGET (canvas));
2509 }
2510 }
2511
2512
2513 static void
goo_canvas_update_internal(GooCanvas * canvas,cairo_t * cr)2514 goo_canvas_update_internal (GooCanvas *canvas,
2515 cairo_t *cr)
2516 {
2517 GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
2518 GooCanvasBounds bounds, static_bounds;
2519
2520 /* It is possible that processing the first set of updates causes other
2521 updates to be scheduled, so we loop round until all are done. Items
2522 should ensure that they don't cause this to loop forever. */
2523 while (canvas->need_update)
2524 {
2525 gboolean entire_tree = canvas->need_entire_subtree_update;
2526
2527 canvas->need_update = FALSE;
2528 canvas->need_entire_subtree_update = FALSE;
2529 if (canvas->root_item)
2530 goo_canvas_item_update (canvas->root_item, entire_tree, cr, &bounds);
2531
2532 if (priv->static_root_item)
2533 goo_canvas_item_update (priv->static_root_item, entire_tree, cr,
2534 &static_bounds);
2535 }
2536
2537 /* If the bounds are automatically-calculated, update them now. */
2538 if (canvas->root_item && canvas->automatic_bounds)
2539 goo_canvas_update_automatic_bounds (canvas);
2540
2541 /* Check which item is under the pointer. */
2542 update_pointer_item (canvas, NULL);
2543 }
2544
2545
2546 /**
2547 * goo_canvas_update:
2548 * @canvas: a #GooCanvas.
2549 *
2550 * This function is only intended to be used by subclasses of #GooCanvas or
2551 * #GooCanvasItem implementations.
2552 *
2553 * It updates any items that need updating.
2554 *
2555 * If the bounds of items change, they will request a redraw of the old and
2556 * new bounds so the display is updated correctly.
2557 **/
2558 void
goo_canvas_update(GooCanvas * canvas)2559 goo_canvas_update (GooCanvas *canvas)
2560 {
2561 cairo_t *cr = goo_canvas_create_cairo_context (canvas);
2562 goo_canvas_update_internal (canvas, cr);
2563 cairo_destroy (cr);
2564 }
2565
2566
2567 static gint
goo_canvas_idle_handler(GooCanvas * canvas)2568 goo_canvas_idle_handler (GooCanvas *canvas)
2569 {
2570 GDK_THREADS_ENTER ();
2571
2572 goo_canvas_update (canvas);
2573
2574 /* Reset idle id. Note that we do this after goo_canvas_update(), to
2575 make sure we don't schedule another idle handler while that is running. */
2576 canvas->idle_id = 0;
2577
2578 GDK_THREADS_LEAVE ();
2579
2580 /* Return FALSE to remove the idle handler. */
2581 return FALSE;
2582 }
2583
2584
2585 /**
2586 * goo_canvas_request_update:
2587 * @canvas: a #GooCanvas.
2588 *
2589 * This function is only intended to be used by subclasses of #GooCanvas or
2590 * #GooCanvasItem implementations.
2591 *
2592 * It schedules an update of the #GooCanvas. This will be performed in
2593 * the idle loop, after all pending events have been handled, but before
2594 * the canvas has been repainted.
2595 **/
2596 void
goo_canvas_request_update(GooCanvas * canvas)2597 goo_canvas_request_update (GooCanvas *canvas)
2598 {
2599 canvas->need_update = TRUE;
2600
2601 /* We have to wait until we are realized. We'll do a full update then. */
2602 #if GTK_CHECK_VERSION (2, 19, 6)
2603 if (!gtk_widget_get_realized (GTK_WIDGET (canvas)))
2604 #else
2605 if (!GTK_WIDGET_REALIZED (canvas))
2606 #endif
2607 return;
2608
2609 /* We use a higher priority than the normal GTK+ resize/redraw idle handlers
2610 * so the canvas state will be updated before allocating sizes & redrawing.
2611 */
2612 if (!canvas->idle_id)
2613 canvas->idle_id = g_idle_add_full (GTK_PRIORITY_RESIZE - 5, (GSourceFunc) goo_canvas_idle_handler, canvas, NULL);
2614 }
2615
2616
2617 /**
2618 * goo_canvas_request_redraw:
2619 * @canvas: a #GooCanvas.
2620 * @bounds: the bounds to redraw, in device space.
2621 *
2622 * This function is only intended to be used by subclasses of #GooCanvas or
2623 * #GooCanvasItem implementations.
2624 *
2625 * Requests that the given bounds be redrawn. The bounds must be in the canvas
2626 * coordinate space.
2627 **/
2628 void
goo_canvas_request_redraw(GooCanvas * canvas,const GooCanvasBounds * bounds)2629 goo_canvas_request_redraw (GooCanvas *canvas,
2630 const GooCanvasBounds *bounds)
2631 {
2632 GdkRectangle rect;
2633
2634 #if GTK_CHECK_VERSION (2, 19, 6)
2635 if (!gtk_widget_is_drawable (GTK_WIDGET (canvas)) || (bounds->x1 == bounds->x2))
2636 #else
2637 if (!GTK_WIDGET_DRAWABLE (canvas) || (bounds->x1 == bounds->x2))
2638 #endif
2639 return;
2640
2641 /* We subtract one from the left & top edges, in case anti-aliasing makes
2642 the drawing use an extra pixel. */
2643 rect.x = (double) (bounds->x1 - canvas->bounds.x1) * canvas->device_to_pixels_x - 1;
2644 rect.y = (double) (bounds->y1 - canvas->bounds.y1) * canvas->device_to_pixels_y - 1;
2645
2646 /* We add an extra one here for the same reason. (The other extra one is to
2647 round up to the next pixel.) And one for luck! */
2648 rect.width = (double) (bounds->x2 - canvas->bounds.x1) * canvas->device_to_pixels_x
2649 - rect.x + 2 + 1;
2650 rect.height = (double) (bounds->y2 - canvas->bounds.y1) * canvas->device_to_pixels_y
2651 - rect.y + 2 + 1;
2652
2653 rect.x += canvas->canvas_x_offset;
2654 rect.y += canvas->canvas_y_offset;
2655
2656 gdk_window_invalidate_rect (canvas->canvas_window, &rect, FALSE);
2657 }
2658
2659
2660 /**
2661 * goo_canvas_request_item_redraw:
2662 * @canvas: a #GooCanvas.
2663 * @bounds: the bounds of the item to redraw.
2664 * @is_static: if the item is static.
2665 *
2666 * This function is only intended to be used by subclasses of #GooCanvas or
2667 * #GooCanvasItem implementations.
2668 *
2669 * Requests that the given bounds be redrawn. If @is_static is %TRUE the bounds
2670 * are assumed to be in the static item coordinate space, otherwise they are
2671 * assumed to be in the canvas coordinate space.
2672 *
2673 * If @is_static is %FALSE this function behaves the same as
2674 * goo_canvas_request_redraw().
2675 **/
2676 void
goo_canvas_request_item_redraw(GooCanvas * canvas,const GooCanvasBounds * bounds,gboolean is_static)2677 goo_canvas_request_item_redraw (GooCanvas *canvas,
2678 const GooCanvasBounds *bounds,
2679 gboolean is_static)
2680 {
2681 /* If the canvas hasn't been painted yet, we can just return as it all needs
2682 a redraw. This can save a lot of time if there are lots of items. */
2683 if (canvas->before_initial_expose)
2684 return;
2685
2686 if (is_static)
2687 request_static_redraw (canvas, bounds);
2688 else
2689 goo_canvas_request_redraw (canvas, bounds);
2690 }
2691
2692
2693 static void
paint_static_items(GooCanvas * canvas,GdkEventExpose * event,cairo_t * cr)2694 paint_static_items (GooCanvas *canvas,
2695 GdkEventExpose *event,
2696 cairo_t *cr)
2697 {
2698 GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
2699 GooCanvasBounds static_bounds;
2700 double static_x_offset, static_y_offset;
2701
2702 cairo_save (cr);
2703 cairo_identity_matrix (cr);
2704 static_x_offset = floor (canvas->hadjustment->value);
2705 static_y_offset = floor (canvas->vadjustment->value);
2706 cairo_translate (cr, static_x_offset, static_y_offset);
2707 /* FIXME: Uses pixels at present - use canvas units instead? */
2708 static_bounds.x1 = event->area.x - static_x_offset;
2709 static_bounds.y1 = event->area.y - static_y_offset;
2710 static_bounds.x2 = event->area.width + static_bounds.x1;
2711 static_bounds.y2 = event->area.height + static_bounds.y1;
2712 goo_canvas_item_paint (priv->static_root_item, cr, &static_bounds, 1.0);
2713 cairo_restore (cr);
2714 }
2715
2716
2717 static gboolean
goo_canvas_expose_event(GtkWidget * widget,GdkEventExpose * event)2718 goo_canvas_expose_event (GtkWidget *widget,
2719 GdkEventExpose *event)
2720 {
2721 GooCanvas *canvas = GOO_CANVAS (widget);
2722 GooCanvasBounds bounds, root_item_bounds;
2723 cairo_t *cr;
2724 double x1, y1, x2, y2;
2725
2726 if (!canvas->root_item)
2727 {
2728 canvas->before_initial_expose = FALSE;
2729 return FALSE;
2730 }
2731
2732 if (event->window != canvas->canvas_window)
2733 return FALSE;
2734
2735 /* Clear the background. */
2736 if (canvas->clear_background)
2737 {
2738 gdk_draw_rectangle (canvas->canvas_window,
2739 widget->style->base_gc[widget->state], TRUE,
2740 event->area.x, event->area.y,
2741 event->area.width, event->area.height);
2742 }
2743
2744 cr = goo_canvas_create_cairo_context (canvas);
2745
2746 cairo_save (cr);
2747
2748 if (canvas->need_update)
2749 goo_canvas_update_internal (canvas, cr);
2750
2751 bounds.x1 = ((event->area.x - canvas->canvas_x_offset) / canvas->device_to_pixels_x)
2752 + canvas->bounds.x1;
2753 bounds.y1 = ((event->area.y - canvas->canvas_y_offset) / canvas->device_to_pixels_y)
2754 + canvas->bounds.y1;
2755 bounds.x2 = (event->area.width / canvas->device_to_pixels_x) + bounds.x1;
2756 bounds.y2 = (event->area.height / canvas->device_to_pixels_y) + bounds.y1;
2757
2758 /* Translate it to use the canvas pixel offsets (used when the canvas is
2759 smaller than the window and the anchor isn't set to NORTH_WEST). */
2760 cairo_translate (cr, canvas->canvas_x_offset, canvas->canvas_y_offset);
2761
2762 /* Scale it so we can use canvas coordinates. */
2763 cairo_scale (cr, canvas->device_to_pixels_x, canvas->device_to_pixels_y);
2764
2765 /* Translate it so the top-left of the canvas becomes (0,0). */
2766 cairo_translate (cr, -canvas->bounds.x1, -canvas->bounds.y1);
2767
2768 /* Clip to the canvas bounds, if necessary. We only need to clip if the
2769 items in the canvas extend outside the canvas bounds and the canvas
2770 bounds is less than the area being painted. */
2771 goo_canvas_item_get_bounds (canvas->root_item, &root_item_bounds);
2772 if ((root_item_bounds.x1 < canvas->bounds.x1
2773 && canvas->bounds.x1 > bounds.x1)
2774 || (root_item_bounds.x2 > canvas->bounds.x2
2775 && canvas->bounds.x2 < bounds.x2)
2776 || (root_item_bounds.y1 < canvas->bounds.y1
2777 && canvas->bounds.y1 > bounds.y1)
2778 || (root_item_bounds.y2 > canvas->bounds.y2
2779 && canvas->bounds.y2 < bounds.y2))
2780 {
2781 /* Clip to the intersection of the canvas bounds and the expose
2782 bounds, to avoid cairo's 16-bit limits. */
2783 x1 = MAX (canvas->bounds.x1, bounds.x1);
2784 y1 = MAX (canvas->bounds.y1, bounds.y1);
2785 x2 = MIN (canvas->bounds.x2, bounds.x2);
2786 y2 = MIN (canvas->bounds.y2, bounds.y2);
2787
2788 cairo_new_path (cr);
2789 cairo_move_to (cr, x1, y1);
2790 cairo_line_to (cr, x2, y1);
2791 cairo_line_to (cr, x2, y2);
2792 cairo_line_to (cr, x1, y2);
2793 cairo_close_path (cr);
2794 cairo_clip (cr);
2795 }
2796
2797 goo_canvas_item_paint (canvas->root_item, cr, &bounds, canvas->scale);
2798
2799 cairo_restore (cr);
2800
2801 paint_static_items (canvas, event, cr);
2802
2803 cairo_destroy (cr);
2804
2805 GTK_WIDGET_CLASS (goo_canvas_parent_class)->expose_event (widget, event);
2806
2807 canvas->before_initial_expose = FALSE;
2808
2809 return FALSE;
2810 }
2811
2812
2813 /**
2814 * goo_canvas_render:
2815 * @canvas: a #GooCanvas.
2816 * @cr: a cairo context.
2817 * @bounds: the area to render, or %NULL to render the entire canvas.
2818 * @scale: the scale to compare with each item's visibility
2819 * threshold to see if they should be rendered. This only affects items that
2820 * have their visibility set to %GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD.
2821 *
2822 * Renders all or part of a canvas to the given cairo context.
2823 **/
2824 void
goo_canvas_render(GooCanvas * canvas,cairo_t * cr,const GooCanvasBounds * bounds,gdouble scale)2825 goo_canvas_render (GooCanvas *canvas,
2826 cairo_t *cr,
2827 const GooCanvasBounds *bounds,
2828 gdouble scale)
2829 {
2830 if (canvas->need_update)
2831 goo_canvas_update (canvas);
2832
2833 /* Set the default line width based on the current units setting. */
2834 cairo_set_line_width (cr, goo_canvas_get_default_line_width (canvas));
2835
2836 if (bounds)
2837 {
2838 /* Clip to the given bounds. */
2839 cairo_new_path (cr);
2840 cairo_move_to (cr, bounds->x1, bounds->y1);
2841 cairo_line_to (cr, bounds->x2, bounds->y1);
2842 cairo_line_to (cr, bounds->x2, bounds->y2);
2843 cairo_line_to (cr, bounds->x1, bounds->y2);
2844 cairo_close_path (cr);
2845 cairo_clip (cr);
2846
2847 goo_canvas_item_paint (canvas->root_item, cr, bounds, scale);
2848 }
2849 else
2850 {
2851 goo_canvas_item_paint (canvas->root_item, cr, &canvas->bounds, scale);
2852 }
2853 }
2854
2855
2856 /*
2857 * Keyboard/Mouse Event & Grab Handling.
2858 */
2859
2860 /* Initializes a synthesized crossing event from a given button press/release,
2861 motion, or crossing event. */
2862 static void
initialize_crossing_event(GooCanvas * canvas,GdkEvent * event)2863 initialize_crossing_event (GooCanvas *canvas,
2864 GdkEvent *event)
2865 {
2866 GdkEventCrossing *crossing_event = &canvas->crossing_event;
2867
2868 /* Initialize the crossing event. */
2869 crossing_event->type = event->any.type;
2870 crossing_event->window = event->any.window;
2871 crossing_event->send_event = event->any.send_event;
2872 crossing_event->subwindow = NULL;
2873 crossing_event->detail = GDK_NOTIFY_ANCESTOR;
2874 crossing_event->focus = FALSE;
2875 crossing_event->mode = GDK_CROSSING_NORMAL;
2876
2877 switch (event->type)
2878 {
2879 case GDK_MOTION_NOTIFY:
2880 crossing_event->time = event->motion.time;
2881 crossing_event->x = event->motion.x;
2882 crossing_event->y = event->motion.y;
2883 crossing_event->x_root = event->motion.x_root;
2884 crossing_event->y_root = event->motion.y_root;
2885 crossing_event->state = event->motion.state;
2886 break;
2887
2888 case GDK_ENTER_NOTIFY:
2889 case GDK_LEAVE_NOTIFY:
2890 crossing_event->time = event->crossing.time;
2891 crossing_event->x = event->crossing.x;
2892 crossing_event->y = event->crossing.y;
2893 crossing_event->x_root = event->crossing.x_root;
2894 crossing_event->y_root = event->crossing.y_root;
2895 crossing_event->state = event->crossing.state;
2896 break;
2897
2898 case GDK_SCROLL:
2899 crossing_event->time = event->scroll.time;
2900 crossing_event->x = event->scroll.x;
2901 crossing_event->y = event->scroll.y;
2902 crossing_event->x_root = event->scroll.x_root;
2903 crossing_event->y_root = event->scroll.y_root;
2904 crossing_event->state = event->scroll.state;
2905 break;
2906
2907 default:
2908 /* It must be a button press/release event. */
2909 crossing_event->time = event->button.time;
2910 crossing_event->x = event->button.x;
2911 crossing_event->y = event->button.y;
2912 crossing_event->x_root = event->button.x_root;
2913 crossing_event->y_root = event->button.y_root;
2914 crossing_event->state = event->button.state;
2915 break;
2916 }
2917 }
2918
2919
2920 /* Emits a signal for an item and all its parents, until one of them
2921 returns TRUE. */
2922 static gboolean
propagate_event(GooCanvas * canvas,GooCanvasItem * item,gchar * signal_name,GdkEvent * event)2923 propagate_event (GooCanvas *canvas,
2924 GooCanvasItem *item,
2925 gchar *signal_name,
2926 GdkEvent *event)
2927 {
2928 GooCanvasItem *ancestor;
2929 gboolean stop_emission = FALSE, valid;
2930
2931 /* Don't emit any events if the canvas is not realized. */
2932 #if GTK_CHECK_VERSION (2, 19, 6)
2933 if (!gtk_widget_get_realized (GTK_WIDGET (canvas)))
2934 #else
2935 if (!GTK_WIDGET_REALIZED (canvas))
2936 #endif
2937 return FALSE;
2938
2939 if (item)
2940 {
2941 /* Check if the item is still in the canvas. */
2942 if (!ITEM_IS_VALID (item))
2943 return FALSE;
2944 ancestor = item;
2945 }
2946 else
2947 {
2948 /* If there is no target item, we send the event to the root item,
2949 with target set to NULL. */
2950 ancestor = canvas->root_item;
2951 }
2952
2953 /* Make sure the item pointer remains valid throughout the emission. */
2954 if (item)
2955 g_object_ref (item);
2956
2957 while (ancestor)
2958 {
2959 g_object_ref (ancestor);
2960
2961 g_signal_emit_by_name (ancestor, signal_name, item, event,
2962 &stop_emission);
2963
2964 /* Check if the ancestor is still in the canvas. */
2965 valid = ITEM_IS_VALID (ancestor) ? TRUE : FALSE;
2966
2967 g_object_unref (ancestor);
2968
2969 if (stop_emission || !valid)
2970 break;
2971
2972 ancestor = goo_canvas_item_get_parent (ancestor);
2973 }
2974
2975 if (item)
2976 g_object_unref (item);
2977
2978 return stop_emission;
2979 }
2980
2981
2982 /* This is called to emit pointer events - enter/leave notify, motion notify,
2983 and button press/release. */
2984 static gboolean
emit_pointer_event(GooCanvas * canvas,gchar * signal_name,GdkEvent * original_event)2985 emit_pointer_event (GooCanvas *canvas,
2986 gchar *signal_name,
2987 GdkEvent *original_event)
2988 {
2989 GdkEvent event = *original_event;
2990 GooCanvasItem *target_item = canvas->pointer_item;
2991 double *x, *y, *x_root, *y_root;
2992
2993 /* Check if an item has grabbed the pointer. */
2994 if (canvas->pointer_grab_item)
2995 {
2996 /* When the pointer is grabbed, it receives all the pointer motion,
2997 button press/release events and its own enter/leave notify events.
2998 Enter/leave notify events for other items are discarded. */
2999 if ((event.type == GDK_ENTER_NOTIFY || event.type == GDK_LEAVE_NOTIFY)
3000 && canvas->pointer_item != canvas->pointer_grab_item)
3001 return FALSE;
3002
3003 target_item = canvas->pointer_grab_item;
3004 }
3005
3006 /* Check if the target item is still in the canvas. */
3007 if (target_item && !ITEM_IS_VALID (target_item))
3008 return FALSE;
3009
3010 /* Translate the x & y coordinates to the item's space. */
3011 switch (event.type)
3012 {
3013 case GDK_MOTION_NOTIFY:
3014 x = &event.motion.x;
3015 y = &event.motion.y;
3016 x_root = &event.motion.x_root;
3017 y_root = &event.motion.y_root;
3018 break;
3019 case GDK_ENTER_NOTIFY:
3020 case GDK_LEAVE_NOTIFY:
3021 x = &event.crossing.x;
3022 y = &event.crossing.y;
3023 x_root = &event.crossing.x_root;
3024 y_root = &event.crossing.y_root;
3025 break;
3026 case GDK_SCROLL:
3027 x = &event.scroll.x;
3028 y = &event.scroll.y;
3029 x_root = &event.scroll.x_root;
3030 y_root = &event.scroll.y_root;
3031 break;
3032 default:
3033 /* It must be a button press/release event. */
3034 x = &event.button.x;
3035 y = &event.button.y;
3036 x_root = &event.button.x_root;
3037 y_root = &event.button.y_root;
3038 break;
3039 }
3040
3041 /* Add 0.5 to the pixel coordinates so we use the center of the pixel. */
3042 *x += 0.5;
3043 *y += 0.5;
3044
3045 /* Convert to the canvas coordinate space. */
3046 goo_canvas_convert_from_pixels (canvas, x, y);
3047
3048 /* Convert to static item space, if necessary. */
3049 if (target_item && goo_canvas_item_get_is_static (target_item))
3050 goo_canvas_convert_to_static_item_space (canvas, x, y);
3051
3052 /* Copy to the x_root & y_root fields. */
3053 *x_root = *x;
3054 *y_root = *y;
3055
3056 /* Convert to the item's coordinate space. */
3057 goo_canvas_convert_to_item_space (canvas, target_item, x, y);
3058
3059 return propagate_event (canvas, target_item, signal_name, &event);
3060 }
3061
3062
3063 /* Finds the item that the mouse is over, using the given event's
3064 * coordinates. It emits enter/leave events for items as appropriate.
3065 */
3066 static void
update_pointer_item(GooCanvas * canvas,GdkEvent * event)3067 update_pointer_item (GooCanvas *canvas,
3068 GdkEvent *event)
3069 {
3070 GooCanvasItem *new_item = NULL;
3071
3072 if (event)
3073 initialize_crossing_event (canvas, event);
3074
3075 /* If the event type is GDK_LEAVE_NOTIFY, the mouse has left the canvas,
3076 so we leave new_item as NULL, otherwise we find which item is
3077 underneath the mouse. Note that we initialize the type to GDK_LEAVE_NOTIFY
3078 in goo_canvas_init() to indicate the mouse isn't in the canvas. */
3079 if (canvas->crossing_event.type != GDK_LEAVE_NOTIFY && canvas->root_item)
3080 {
3081 double x = canvas->crossing_event.x;
3082 double y = canvas->crossing_event.y;
3083
3084 goo_canvas_convert_from_pixels (canvas, &x, &y);
3085 new_item = goo_canvas_get_item_at (canvas, x, y, TRUE);
3086 }
3087
3088 /* If the current item hasn't changed, just return. */
3089 if (new_item == canvas->pointer_item)
3090 return;
3091
3092 /* Ref the new item, in case it is removed below. */
3093 if (new_item)
3094 g_object_ref (new_item);
3095
3096 /* Emit a leave-notify event for the current item. */
3097 if (canvas->pointer_item)
3098 {
3099 canvas->crossing_event.type = GDK_LEAVE_NOTIFY;
3100 emit_pointer_event (canvas, "leave_notify_event",
3101 (GdkEvent*) &canvas->crossing_event);
3102 }
3103
3104 /* If there is no new item we are done. */
3105 if (!new_item)
3106 {
3107 set_item_pointer (&canvas->pointer_item, NULL);
3108 return;
3109 }
3110
3111 /* If the new item isn't in the canvas any more, don't use it. */
3112 if (!ITEM_IS_VALID (new_item))
3113 {
3114 set_item_pointer (&canvas->pointer_item, NULL);
3115 g_object_unref (new_item);
3116 return;
3117 }
3118
3119 /* Emit an enter-notify for the new current item. */
3120 set_item_pointer (&canvas->pointer_item, new_item);
3121 canvas->crossing_event.type = GDK_ENTER_NOTIFY;
3122 emit_pointer_event (canvas, "enter_notify_event",
3123 (GdkEvent*) &canvas->crossing_event);
3124
3125 g_object_unref (new_item);
3126 }
3127
3128
3129 static gboolean
goo_canvas_crossing(GtkWidget * widget,GdkEventCrossing * event)3130 goo_canvas_crossing (GtkWidget *widget,
3131 GdkEventCrossing *event)
3132 {
3133 GooCanvas *canvas = GOO_CANVAS (widget);
3134
3135 if (event->window != canvas->canvas_window)
3136 return FALSE;
3137
3138 /* This will result in synthesizing focus_in/out events as appropriate. */
3139 update_pointer_item (canvas, (GdkEvent*) event);
3140
3141 return FALSE;
3142 }
3143
3144
3145 static gboolean
goo_canvas_motion(GtkWidget * widget,GdkEventMotion * event)3146 goo_canvas_motion (GtkWidget *widget,
3147 GdkEventMotion *event)
3148 {
3149 GooCanvas *canvas = GOO_CANVAS (widget);
3150
3151 if (event->window != canvas->canvas_window)
3152 return FALSE;
3153
3154 /* For motion notify hint events we need to call gdk_window_get_pointer()
3155 to let X know we're ready for another pointer event. */
3156 if (event->is_hint)
3157 gdk_window_get_pointer (event->window, NULL, NULL, NULL);
3158
3159 update_pointer_item (canvas, (GdkEvent*) event);
3160
3161 return emit_pointer_event (canvas, "motion_notify_event", (GdkEvent*) event);
3162 }
3163
3164
3165 static gboolean
goo_canvas_button_press(GtkWidget * widget,GdkEventButton * event)3166 goo_canvas_button_press (GtkWidget *widget,
3167 GdkEventButton *event)
3168 {
3169 GooCanvas *canvas = GOO_CANVAS (widget);
3170 GdkDisplay *display;
3171
3172 if (event->window != canvas->canvas_window)
3173 return FALSE;
3174
3175 update_pointer_item (canvas, (GdkEvent*) event);
3176
3177 /* Check if this is the start of an implicit pointer grab, i.e. if we
3178 don't already have a grab and the app has no active grab. */
3179 display = gtk_widget_get_display (widget);
3180 if (!canvas->pointer_grab_item
3181 && !gdk_display_pointer_is_grabbed (display))
3182 {
3183 set_item_pointer (&canvas->pointer_grab_initial_item,
3184 canvas->pointer_item);
3185 set_item_pointer (&canvas->pointer_grab_item,
3186 canvas->pointer_item);
3187 canvas->pointer_grab_button = event->button;
3188 }
3189
3190 return emit_pointer_event (canvas, "button_press_event", (GdkEvent*) event);
3191 }
3192
3193
3194 static gboolean
goo_canvas_button_release(GtkWidget * widget,GdkEventButton * event)3195 goo_canvas_button_release (GtkWidget *widget,
3196 GdkEventButton *event)
3197 {
3198 GooCanvas *canvas = GOO_CANVAS (widget);
3199 GdkDisplay *display;
3200 gboolean retval;
3201
3202 if (event->window != canvas->canvas_window)
3203 return FALSE;
3204
3205 update_pointer_item (canvas, (GdkEvent*) event);
3206
3207 retval = emit_pointer_event (canvas, "button_release_event",
3208 (GdkEvent*) event);
3209
3210 /* Check if an implicit (passive) grab has ended. */
3211 display = gtk_widget_get_display (widget);
3212 if (canvas->pointer_grab_item
3213 && event->button == canvas->pointer_grab_button
3214 && !gdk_display_pointer_is_grabbed (display))
3215 {
3216 /* We set the pointer item back to the item it was in before the
3217 grab, so we'll synthesize enter/leave notify events as appropriate. */
3218 if (canvas->pointer_grab_initial_item
3219 && ITEM_IS_VALID (canvas->pointer_grab_initial_item))
3220 set_item_pointer (&canvas->pointer_item,
3221 canvas->pointer_grab_initial_item);
3222 else
3223 set_item_pointer (&canvas->pointer_item, NULL);
3224
3225 set_item_pointer (&canvas->pointer_grab_item, NULL);
3226 set_item_pointer (&canvas->pointer_grab_initial_item, NULL);
3227
3228 update_pointer_item (canvas, (GdkEvent*) event);
3229 }
3230
3231 return retval;
3232 }
3233
3234
3235 static gint
goo_canvas_scroll(GtkWidget * widget,GdkEventScroll * event)3236 goo_canvas_scroll (GtkWidget *widget,
3237 GdkEventScroll *event)
3238 {
3239 GooCanvas *canvas = GOO_CANVAS (widget);
3240 GtkAdjustment *adj;
3241 gdouble delta, new_value;
3242
3243 if (event->window == canvas->canvas_window)
3244 {
3245 /* See if the current item wants the scroll event. */
3246 update_pointer_item (canvas, (GdkEvent*) event);
3247 if (emit_pointer_event (canvas, "scroll_event", (GdkEvent*) event))
3248 return TRUE;
3249 }
3250
3251 if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_DOWN)
3252 adj = canvas->vadjustment;
3253 else
3254 adj = canvas->hadjustment;
3255
3256 delta = pow (adj->page_size, 2.0 / 3.0);
3257
3258 if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_LEFT)
3259 delta = - delta;
3260
3261 new_value = CLAMP (adj->value + delta, adj->lower,
3262 adj->upper - adj->page_size);
3263
3264 gtk_adjustment_set_value (adj, new_value);
3265
3266 return TRUE;
3267 }
3268
3269
3270 static gboolean
goo_canvas_focus_in(GtkWidget * widget,GdkEventFocus * event)3271 goo_canvas_focus_in (GtkWidget *widget,
3272 GdkEventFocus *event)
3273 {
3274 GooCanvas *canvas = GOO_CANVAS (widget);
3275
3276 GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
3277
3278 if (canvas->focused_item)
3279 return propagate_event (canvas, canvas->focused_item,
3280 "focus_in_event", (GdkEvent*) event);
3281 else
3282 return FALSE;
3283 }
3284
3285
3286 static gboolean
goo_canvas_focus_out(GtkWidget * widget,GdkEventFocus * event)3287 goo_canvas_focus_out (GtkWidget *widget,
3288 GdkEventFocus *event)
3289 {
3290 GooCanvas *canvas = GOO_CANVAS (widget);
3291
3292 GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
3293
3294 if (canvas->focused_item)
3295 return propagate_event (canvas, canvas->focused_item,
3296 "focus_out_event", (GdkEvent*) event);
3297 else
3298 return FALSE;
3299 }
3300
3301
3302 static gboolean
goo_canvas_key_press(GtkWidget * widget,GdkEventKey * event)3303 goo_canvas_key_press (GtkWidget *widget,
3304 GdkEventKey *event)
3305 {
3306 GooCanvas *canvas = GOO_CANVAS (widget);
3307 #if GTK_CHECK_VERSION (2, 19, 6)
3308 if (gtk_widget_has_focus (GTK_WIDGET (canvas)) && canvas->focused_item)
3309 #else
3310 if (GTK_WIDGET_HAS_FOCUS (canvas) && canvas->focused_item)
3311 #endif
3312 if (propagate_event (canvas, canvas->focused_item, "key_press_event",
3313 (GdkEvent*) event))
3314 return TRUE;
3315
3316 return GTK_WIDGET_CLASS (goo_canvas_parent_class)->key_press_event (widget, event);
3317 }
3318
3319
3320 static gboolean
goo_canvas_key_release(GtkWidget * widget,GdkEventKey * event)3321 goo_canvas_key_release (GtkWidget *widget,
3322 GdkEventKey *event)
3323 {
3324 GooCanvas *canvas = GOO_CANVAS (widget);
3325
3326 #if GTK_CHECK_VERSION (2, 19, 6)
3327 if (gtk_widget_has_focus (GTK_WIDGET (canvas)) && canvas->focused_item)
3328 #else
3329 if (GTK_WIDGET_HAS_FOCUS (canvas) && canvas->focused_item)
3330 #endif
3331 if (propagate_event (canvas, canvas->focused_item, "key_release_event",
3332 (GdkEvent*) event))
3333 return TRUE;
3334
3335 return GTK_WIDGET_CLASS (goo_canvas_parent_class)->key_release_event (widget, event);
3336 }
3337
3338
3339 static void
generate_grab_broken(GooCanvas * canvas,GooCanvasItem * item,gboolean keyboard,gboolean implicit)3340 generate_grab_broken (GooCanvas *canvas,
3341 GooCanvasItem *item,
3342 gboolean keyboard,
3343 gboolean implicit)
3344
3345 {
3346 GdkEventGrabBroken event;
3347
3348 if (!ITEM_IS_VALID (item))
3349 return;
3350
3351 event.type = GDK_GRAB_BROKEN;
3352 event.window = canvas->canvas_window;
3353 event.send_event = 0;
3354 event.keyboard = keyboard;
3355 event.implicit = implicit;
3356 event.grab_window = event.window;
3357
3358 propagate_event (canvas, item, "grab_broken_event",
3359 (GdkEvent*) &event);
3360 }
3361
3362
3363 static gboolean
goo_canvas_grab_broken(GtkWidget * widget,GdkEventGrabBroken * event)3364 goo_canvas_grab_broken (GtkWidget *widget,
3365 GdkEventGrabBroken *event)
3366 {
3367 GooCanvas *canvas;
3368
3369 g_return_val_if_fail (GOO_IS_CANVAS (widget), FALSE);
3370
3371 canvas = GOO_CANVAS (widget);
3372
3373 if (event->keyboard)
3374 {
3375 if (canvas->keyboard_grab_item)
3376 {
3377 generate_grab_broken (canvas, canvas->keyboard_grab_item,
3378 event->keyboard, event->implicit);
3379 set_item_pointer (&canvas->keyboard_grab_item, NULL);
3380 }
3381 }
3382 else
3383 {
3384 if (canvas->pointer_grab_item)
3385 {
3386 generate_grab_broken (canvas, canvas->pointer_grab_item,
3387 event->keyboard, event->implicit);
3388 set_item_pointer (&canvas->pointer_grab_item, NULL);
3389 }
3390 }
3391
3392 return TRUE;
3393 }
3394
3395
3396 /**
3397 * goo_canvas_grab_focus:
3398 * @canvas: a #GooCanvas.
3399 * @item: the item to grab the focus.
3400 *
3401 * Grabs the keyboard focus for the given item.
3402 **/
3403 void
goo_canvas_grab_focus(GooCanvas * canvas,GooCanvasItem * item)3404 goo_canvas_grab_focus (GooCanvas *canvas,
3405 GooCanvasItem *item)
3406 {
3407 GdkEventFocus event;
3408
3409 g_return_if_fail (GOO_IS_CANVAS (canvas));
3410 g_return_if_fail (GOO_IS_CANVAS_ITEM (item));
3411 g_return_if_fail (GTK_WIDGET_CAN_FOCUS (canvas));
3412
3413 if (canvas->focused_item) {
3414 event.type = GDK_FOCUS_CHANGE;
3415 event.window = canvas->canvas_window;
3416 event.send_event = FALSE;
3417 event.in = FALSE;
3418
3419 propagate_event (canvas, canvas->focused_item,
3420 "focus_out_event", (GdkEvent*) &event);
3421 }
3422
3423 set_item_pointer (&canvas->focused_item, item);
3424
3425 gtk_widget_grab_focus (GTK_WIDGET (canvas));
3426
3427 if (canvas->focused_item) {
3428 event.type = GDK_FOCUS_CHANGE;
3429 event.window = canvas->canvas_window;
3430 event.send_event = FALSE;
3431 event.in = TRUE;
3432
3433 propagate_event (canvas, canvas->focused_item,
3434 "focus_in_event", (GdkEvent*) &event);
3435 }
3436 }
3437
3438
3439 /*
3440 * Pointer/keyboard grabbing & ungrabbing.
3441 */
3442
3443 /**
3444 * goo_canvas_pointer_grab:
3445 * @canvas: a #GooCanvas.
3446 * @item: the item to grab the pointer for.
3447 * @event_mask: the events to receive during the grab.
3448 * @cursor: the cursor to display during the grab, or NULL.
3449 * @time: the time of the event that lead to the pointer grab. This should
3450 * come from the relevant #GdkEvent.
3451 *
3452 * Attempts to grab the pointer for the given item.
3453 *
3454 * Returns: %GDK_GRAB_SUCCESS if the grab succeeded.
3455 **/
3456 GdkGrabStatus
goo_canvas_pointer_grab(GooCanvas * canvas,GooCanvasItem * item,GdkEventMask event_mask,GdkCursor * cursor,guint32 time)3457 goo_canvas_pointer_grab (GooCanvas *canvas,
3458 GooCanvasItem *item,
3459 GdkEventMask event_mask,
3460 GdkCursor *cursor,
3461 guint32 time)
3462 {
3463 GdkGrabStatus status = GDK_GRAB_SUCCESS;
3464
3465 g_return_val_if_fail (GOO_IS_CANVAS (canvas), GDK_GRAB_NOT_VIEWABLE);
3466 g_return_val_if_fail (GOO_IS_CANVAS_ITEM (item), GDK_GRAB_NOT_VIEWABLE);
3467
3468 /* If another item already has the pointer grab, we need to synthesize a
3469 grab-broken event for the current grab item. */
3470 if (canvas->pointer_grab_item
3471 && canvas->pointer_grab_item != item)
3472 {
3473 generate_grab_broken (canvas, canvas->pointer_grab_item,
3474 FALSE, FALSE);
3475 set_item_pointer (&canvas->pointer_grab_item, NULL);
3476 }
3477
3478 /* This overrides any existing grab. */
3479 status = gdk_pointer_grab (canvas->canvas_window, FALSE,
3480 event_mask, NULL, cursor, time);
3481
3482 if (status == GDK_GRAB_SUCCESS)
3483 {
3484 set_item_pointer (&canvas->pointer_grab_initial_item,
3485 canvas->pointer_item);
3486 set_item_pointer (&canvas->pointer_grab_item,
3487 item);
3488 }
3489
3490 return status;
3491 }
3492
3493
3494 /**
3495 * goo_canvas_pointer_ungrab:
3496 * @canvas: a #GooCanvas.
3497 * @item: the item that has the grab.
3498 * @time: the time of the event that lead to the pointer ungrab. This should
3499 * come from the relevant #GdkEvent.
3500 *
3501 * Ungrabs the pointer, if the given item has the pointer grab.
3502 **/
3503 void
goo_canvas_pointer_ungrab(GooCanvas * canvas,GooCanvasItem * item,guint32 time)3504 goo_canvas_pointer_ungrab (GooCanvas *canvas,
3505 GooCanvasItem *item,
3506 guint32 time)
3507 {
3508 GdkDisplay *display;
3509
3510 g_return_if_fail (GOO_IS_CANVAS (canvas));
3511 g_return_if_fail (GOO_IS_CANVAS_ITEM (item));
3512
3513 /* If the item doesn't actually have the pointer grab, just return. */
3514 if (canvas->pointer_grab_item != item)
3515 return;
3516
3517 /* If it is an active pointer grab, ungrab it explicitly. */
3518 display = gtk_widget_get_display (GTK_WIDGET (canvas));
3519 if (gdk_display_pointer_is_grabbed (display))
3520 gdk_display_pointer_ungrab (display, time);
3521
3522 /* We set the pointer item back to the item it was in before the
3523 grab, so we'll synthesize enter/leave notify events as appropriate. */
3524 if (canvas->pointer_grab_initial_item
3525 && ITEM_IS_VALID (canvas->pointer_grab_initial_item))
3526 set_item_pointer (&canvas->pointer_item,
3527 canvas->pointer_grab_initial_item);
3528 else
3529 set_item_pointer (&canvas->pointer_item, NULL);
3530
3531 set_item_pointer (&canvas->pointer_grab_item, NULL);
3532 set_item_pointer (&canvas->pointer_grab_initial_item, NULL);
3533
3534 update_pointer_item (canvas, NULL);
3535 }
3536
3537
3538 /**
3539 * goo_canvas_keyboard_grab:
3540 * @canvas: a #GooCanvas.
3541 * @item: the item to grab the keyboard for.
3542 * @owner_events: %TRUE if keyboard events for this application will be
3543 * reported normally, or %FALSE if all keyboard events will be reported with
3544 * respect to the grab item.
3545 * @time: the time of the event that lead to the keyboard grab. This should
3546 * come from the relevant #GdkEvent.
3547 *
3548 * Attempts to grab the keyboard for the given item.
3549 *
3550 * Returns: %GDK_GRAB_SUCCESS if the grab succeeded.
3551 **/
3552 GdkGrabStatus
goo_canvas_keyboard_grab(GooCanvas * canvas,GooCanvasItem * item,gboolean owner_events,guint32 time)3553 goo_canvas_keyboard_grab (GooCanvas *canvas,
3554 GooCanvasItem *item,
3555 gboolean owner_events,
3556 guint32 time)
3557 {
3558 GdkGrabStatus status = GDK_GRAB_SUCCESS;
3559
3560 g_return_val_if_fail (GOO_IS_CANVAS (canvas), GDK_GRAB_NOT_VIEWABLE);
3561 g_return_val_if_fail (GOO_IS_CANVAS_ITEM (item), GDK_GRAB_NOT_VIEWABLE);
3562
3563 /* Check if the item already has the keyboard grab. */
3564 if (canvas->keyboard_grab_item == item)
3565 return GDK_GRAB_ALREADY_GRABBED;
3566
3567 /* If another item already has the keyboard grab, we need to synthesize a
3568 grab-broken event for the current grab item. */
3569 if (canvas->keyboard_grab_item)
3570 {
3571 generate_grab_broken (canvas, canvas->keyboard_grab_item, TRUE, FALSE);
3572 set_item_pointer (&canvas->keyboard_grab_item, NULL);
3573 }
3574
3575 /* This overrides any existing grab. */
3576 status = gdk_keyboard_grab (canvas->canvas_window,
3577 owner_events, time);
3578
3579 if (status == GDK_GRAB_SUCCESS)
3580 set_item_pointer (&canvas->keyboard_grab_item, item);
3581
3582 return status;
3583 }
3584
3585
3586 /**
3587 * goo_canvas_keyboard_ungrab:
3588 * @canvas: a #GooCanvas.
3589 * @item: the item that has the keyboard grab.
3590 * @time: the time of the event that lead to the keyboard ungrab. This should
3591 * come from the relevant #GdkEvent.
3592 *
3593 * Ungrabs the keyboard, if the given item has the keyboard grab.
3594 **/
3595 void
goo_canvas_keyboard_ungrab(GooCanvas * canvas,GooCanvasItem * item,guint32 time)3596 goo_canvas_keyboard_ungrab (GooCanvas *canvas,
3597 GooCanvasItem *item,
3598 guint32 time)
3599 {
3600 GdkDisplay *display;
3601
3602 g_return_if_fail (GOO_IS_CANVAS (canvas));
3603 g_return_if_fail (GOO_IS_CANVAS_ITEM (item));
3604
3605 /* If the item doesn't actually have the keyboard grab, just return. */
3606 if (canvas->keyboard_grab_item != item)
3607 return;
3608
3609 set_item_pointer (&canvas->keyboard_grab_item, NULL);
3610
3611 display = gtk_widget_get_display (GTK_WIDGET (canvas));
3612 gdk_display_keyboard_ungrab (display, time);
3613 }
3614
3615
3616 /*
3617 * Coordinate conversion.
3618 */
3619
3620 /**
3621 * goo_canvas_convert_to_pixels:
3622 * @canvas: a #GooCanvas.
3623 * @x: a pointer to the x coordinate to convert.
3624 * @y: a pointer to the y coordinate to convert.
3625 *
3626 * Converts a coordinate from the canvas coordinate space to pixels.
3627 *
3628 * The canvas coordinate space is specified in the call to
3629 * goo_canvas_set_bounds().
3630 *
3631 * The pixel coordinate space specifies pixels from the top-left of the entire
3632 * canvas window, according to the current scale setting.
3633 * See goo_canvas_set_scale().
3634 **/
3635 void
goo_canvas_convert_to_pixels(GooCanvas * canvas,gdouble * x,gdouble * y)3636 goo_canvas_convert_to_pixels (GooCanvas *canvas,
3637 gdouble *x,
3638 gdouble *y)
3639 {
3640 *x = ((*x - canvas->bounds.x1) * canvas->device_to_pixels_x) + canvas->canvas_x_offset;
3641 *y = ((*y - canvas->bounds.y1) * canvas->device_to_pixels_y) + canvas->canvas_y_offset;
3642 }
3643
3644
3645 /**
3646 * goo_canvas_convert_from_pixels:
3647 * @canvas: a #GooCanvas.
3648 * @x: a pointer to the x coordinate to convert.
3649 * @y: a pointer to the y coordinate to convert.
3650 *
3651 * Converts a coordinate from pixels to the canvas coordinate space.
3652 *
3653 * The pixel coordinate space specifies pixels from the top-left of the entire
3654 * canvas window, according to the current scale setting.
3655 * See goo_canvas_set_scale().
3656 *
3657 * The canvas coordinate space is specified in the call to
3658 * goo_canvas_set_bounds().
3659 *
3660 **/
3661 void
goo_canvas_convert_from_pixels(GooCanvas * canvas,gdouble * x,gdouble * y)3662 goo_canvas_convert_from_pixels (GooCanvas *canvas,
3663 gdouble *x,
3664 gdouble *y)
3665 {
3666 *x = ((*x - canvas->canvas_x_offset) / canvas->device_to_pixels_x) + canvas->bounds.x1;
3667 *y = ((*y - canvas->canvas_y_offset) / canvas->device_to_pixels_y) + canvas->bounds.y1;
3668 }
3669
3670
3671 static void
goo_canvas_convert_from_window_pixels(GooCanvas * canvas,gdouble * x,gdouble * y)3672 goo_canvas_convert_from_window_pixels (GooCanvas *canvas,
3673 gdouble *x,
3674 gdouble *y)
3675 {
3676 *x += canvas->hadjustment->value;
3677 *y += canvas->vadjustment->value;
3678 goo_canvas_convert_from_pixels (canvas, x, y);
3679 }
3680
3681
3682 /* Converts from the canvas coordinate space to the static item coordinate
3683 space, i.e. in pixels from the top-left of the viewport window. */
3684 static void
goo_canvas_convert_to_static_item_space(GooCanvas * canvas,gdouble * x,gdouble * y)3685 goo_canvas_convert_to_static_item_space (GooCanvas *canvas,
3686 gdouble *x,
3687 gdouble *y)
3688 {
3689 *x = ((*x - canvas->bounds.x1) * canvas->device_to_pixels_x)
3690 + canvas->canvas_x_offset - canvas->hadjustment->value;
3691 *y = ((*y - canvas->bounds.y1) * canvas->device_to_pixels_y)
3692 + canvas->canvas_y_offset - canvas->vadjustment->value;
3693 }
3694
3695
3696 static void
get_transform_to_item_space(GooCanvasItem * item,cairo_matrix_t * transform)3697 get_transform_to_item_space (GooCanvasItem *item,
3698 cairo_matrix_t *transform)
3699 {
3700 GooCanvasItem *tmp = item, *parent, *child;
3701 GList *list = NULL, *l;
3702 cairo_matrix_t item_transform, inverse = { 1, 0, 0, 1, 0, 0 };
3703 gboolean has_transform;
3704
3705 /* Step up from the item to the top, pushing items onto the list. */
3706 while (tmp)
3707 {
3708 list = g_list_prepend (list, tmp);
3709 tmp = goo_canvas_item_get_parent (tmp);
3710 }
3711
3712 /* Now step down applying the inverse of each item's transformation. */
3713 for (l = list; l; l = l->next)
3714 {
3715 parent = (GooCanvasItem*) l->data;
3716 child = l->next ? (GooCanvasItem*) l->next->data : NULL;
3717 has_transform = goo_canvas_item_get_transform_for_child (parent, child,
3718 &item_transform);
3719 if (has_transform)
3720 {
3721 cairo_matrix_invert (&item_transform);
3722 cairo_matrix_multiply (&inverse, &inverse, &item_transform);
3723 }
3724 }
3725 g_list_free (list);
3726
3727 *transform = inverse;
3728 }
3729
3730
3731 /**
3732 * goo_canvas_convert_to_item_space:
3733 * @canvas: a #GooCanvas.
3734 * @item: a #GooCanvasItem.
3735 * @x: a pointer to the x coordinate to convert.
3736 * @y: a pointer to the y coordinate to convert.
3737 *
3738 * Converts a coordinate from the canvas coordinate space to the given
3739 * item's coordinate space, applying all transformation matrices including the
3740 * item's own transformation matrix, if it has one.
3741 **/
3742 void
goo_canvas_convert_to_item_space(GooCanvas * canvas,GooCanvasItem * item,gdouble * x,gdouble * y)3743 goo_canvas_convert_to_item_space (GooCanvas *canvas,
3744 GooCanvasItem *item,
3745 gdouble *x,
3746 gdouble *y)
3747 {
3748 cairo_matrix_t transform;
3749
3750 get_transform_to_item_space (item, &transform);
3751 cairo_matrix_transform_point (&transform, x, y);
3752 }
3753
3754
3755 /**
3756 * goo_canvas_convert_from_item_space:
3757 * @canvas: a #GooCanvas.
3758 * @item: a #GooCanvasItem.
3759 * @x: a pointer to the x coordinate to convert.
3760 * @y: a pointer to the y coordinate to convert.
3761 *
3762 * Converts a coordinate from the given item's coordinate space to the canvas
3763 * coordinate space, applying all transformation matrices including the
3764 * item's own transformation matrix, if it has one.
3765 **/
3766 void
goo_canvas_convert_from_item_space(GooCanvas * canvas,GooCanvasItem * item,gdouble * x,gdouble * y)3767 goo_canvas_convert_from_item_space (GooCanvas *canvas,
3768 GooCanvasItem *item,
3769 gdouble *x,
3770 gdouble *y)
3771 {
3772 GooCanvasItem *tmp = item, *parent, *child;
3773 GList *list = NULL, *l;
3774 cairo_matrix_t item_transform, transform = { 1, 0, 0, 1, 0, 0 };
3775 gboolean has_transform;
3776
3777 /* Step up from the item to the top, pushing items onto the list. */
3778 while (tmp)
3779 {
3780 list = g_list_prepend (list, tmp);
3781 tmp = goo_canvas_item_get_parent (tmp);
3782 }
3783
3784 /* Now step down applying each item's transformation. */
3785 for (l = list; l; l = l->next)
3786 {
3787 parent = (GooCanvasItem*) l->data;
3788 child = l->next ? (GooCanvasItem*) l->next->data : NULL;
3789 has_transform = goo_canvas_item_get_transform_for_child (parent, child,
3790 &item_transform);
3791 if (has_transform)
3792 {
3793 cairo_matrix_multiply (&transform, &item_transform, &transform);
3794 }
3795 }
3796 g_list_free (list);
3797
3798 /* Now convert the coordinates. */
3799 cairo_matrix_transform_point (&transform, x, y);
3800 }
3801
3802
3803 /**
3804 * goo_canvas_convert_bounds_to_item_space:
3805 * @canvas: a #GooCanvas.
3806 * @item: a #GooCanvasItem.
3807 * @bounds: the bounds in canvas coordinate space, to be converted.
3808 *
3809 * Converts the given bounds in the canvas coordinate space to a bounding box
3810 * in item space. This is useful in the item paint() methods to convert the
3811 * bounds to be painted to the item's coordinate space.
3812 **/
3813 void
goo_canvas_convert_bounds_to_item_space(GooCanvas * canvas,GooCanvasItem * item,GooCanvasBounds * bounds)3814 goo_canvas_convert_bounds_to_item_space (GooCanvas *canvas,
3815 GooCanvasItem *item,
3816 GooCanvasBounds *bounds)
3817 {
3818 GooCanvasBounds tmp_bounds = *bounds, tmp_bounds2 = *bounds;
3819 cairo_matrix_t transform;
3820
3821 get_transform_to_item_space (item, &transform);
3822
3823 /* Convert the top-left and bottom-right corners to device coords. */
3824 cairo_matrix_transform_point (&transform, &tmp_bounds.x1, &tmp_bounds.y1);
3825 cairo_matrix_transform_point (&transform, &tmp_bounds.x2, &tmp_bounds.y2);
3826
3827 /* Now convert the top-right and bottom-left corners. */
3828 cairo_matrix_transform_point (&transform, &tmp_bounds2.x1, &tmp_bounds2.y2);
3829 cairo_matrix_transform_point (&transform, &tmp_bounds2.x2, &tmp_bounds2.y1);
3830
3831 /* Calculate the minimum x coordinate seen and put in x1. */
3832 bounds->x1 = MIN (tmp_bounds.x1, tmp_bounds.x2);
3833 bounds->x1 = MIN (bounds->x1, tmp_bounds2.x1);
3834 bounds->x1 = MIN (bounds->x1, tmp_bounds2.x2);
3835
3836 /* Calculate the maximum x coordinate seen and put in x2. */
3837 bounds->x2 = MAX (tmp_bounds.x1, tmp_bounds.x2);
3838 bounds->x2 = MAX (bounds->x2, tmp_bounds2.x1);
3839 bounds->x2 = MAX (bounds->x2, tmp_bounds2.x2);
3840
3841 /* Calculate the minimum y coordinate seen and put in y1. */
3842 bounds->y1 = MIN (tmp_bounds.y1, tmp_bounds.y2);
3843 bounds->y1 = MIN (bounds->y1, tmp_bounds2.y1);
3844 bounds->y1 = MIN (bounds->y1, tmp_bounds2.y2);
3845
3846 /* Calculate the maximum y coordinate seen and put in y2. */
3847 bounds->y2 = MAX (tmp_bounds.y1, tmp_bounds.y2);
3848 bounds->y2 = MAX (bounds->y2, tmp_bounds2.y1);
3849 bounds->y2 = MAX (bounds->y2, tmp_bounds2.y2);
3850 }
3851
3852
3853 /*
3854 * Keyboard focus navigation.
3855 */
3856
3857 typedef struct _GooCanvasFocusData GooCanvasFocusData;
3858 struct _GooCanvasFocusData
3859 {
3860 /* The item to start from, usually the currently focused item, or NULL. */
3861 GooCanvasItem *start_item;
3862
3863 /* The bounds of the start item. We try to find the next closest one in the
3864 desired direction. */
3865 GooCanvasBounds start_bounds;
3866 gdouble start_center_x, start_center_y;
3867
3868 /* The direction to move the focus in. */
3869 GtkDirectionType direction;
3870
3871 /* The text direction of the widget. */
3872 GtkTextDirection text_direction;
3873
3874 /* The best item found so far, and the offsets for it. */
3875 GooCanvasItem *best_item;
3876 gdouble best_x_offset, best_y_offset, best_score;
3877
3878 /* The offsets for the item being tested. */
3879 GooCanvasBounds current_bounds;
3880 gdouble current_x_offset, current_y_offset, current_score;
3881 };
3882
3883
3884 /* This tries to figure out the bounds of the start item or widget. */
3885 static void
goo_canvas_get_start_bounds(GooCanvas * canvas,GooCanvasFocusData * data)3886 goo_canvas_get_start_bounds (GooCanvas *canvas,
3887 GooCanvasFocusData *data)
3888 {
3889 GooCanvasBounds *bounds;
3890 GtkWidget *toplevel, *focus_widget;
3891 GtkAllocation *allocation;
3892 gint focus_widget_x, focus_widget_y;
3893
3894 /* If an item is currently focused, we just need its bounds. */
3895 if (data->start_item)
3896 {
3897 goo_canvas_item_get_bounds (data->start_item, &data->start_bounds);
3898 return;
3899 }
3900
3901 /* Otherwise try to get the currently focused widget in the window. */
3902 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (canvas));
3903 bounds = &data->start_bounds;
3904 if (toplevel && GTK_IS_WINDOW (toplevel)
3905 && GTK_WINDOW (toplevel)->focus_widget)
3906 {
3907 focus_widget = GTK_WINDOW (toplevel)->focus_widget;
3908
3909 /* Translate the allocation to be relative to the GooCanvas.
3910 Skip ancestor widgets as the coords won't help. */
3911 if (!gtk_widget_is_ancestor (GTK_WIDGET (canvas), focus_widget)
3912 && gtk_widget_translate_coordinates (focus_widget,
3913 GTK_WIDGET (canvas),
3914 0, 0,
3915 &focus_widget_x,
3916 &focus_widget_y))
3917 {
3918 /* Translate into device units. */
3919 bounds->x1 = focus_widget_x;
3920 bounds->y1 = focus_widget_y;
3921 bounds->x2 = focus_widget_x + focus_widget->allocation.width;
3922 bounds->y2 = focus_widget_y + focus_widget->allocation.height;
3923
3924 goo_canvas_convert_from_window_pixels (canvas, &bounds->x1,
3925 &bounds->y1);
3926 goo_canvas_convert_from_window_pixels (canvas, &bounds->x2,
3927 &bounds->y2);
3928 return;
3929 }
3930 }
3931
3932 /* As a last resort, we guess a starting position based on the direction. */
3933 allocation = >K_WIDGET (canvas)->allocation;
3934 switch (data->direction)
3935 {
3936 case GTK_DIR_DOWN:
3937 case GTK_DIR_RIGHT:
3938 /* Start from top-left. */
3939 bounds->x1 = 0.0;
3940 bounds->y1 = 0.0;
3941 break;
3942
3943 case GTK_DIR_UP:
3944 /* Start from bottom-left. */
3945 bounds->x1 = 0.0;
3946 bounds->y1 = allocation->height;
3947 break;
3948
3949 case GTK_DIR_LEFT:
3950 /* Start from top-right. */
3951 bounds->x1 = allocation->width;
3952 bounds->y1 = 0.0;
3953 break;
3954
3955 case GTK_DIR_TAB_FORWARD:
3956 bounds->y1 = 0.0;
3957 if (data->text_direction == GTK_TEXT_DIR_RTL)
3958 /* Start from top-right. */
3959 bounds->x1 = allocation->width;
3960 else
3961 /* Start from top-left. */
3962 bounds->x1 = 0.0;
3963 break;
3964
3965 case GTK_DIR_TAB_BACKWARD:
3966 bounds->y1 = allocation->height;
3967 if (data->text_direction == GTK_TEXT_DIR_RTL)
3968 /* Start from bottom-left. */
3969 bounds->x1 = 0.0;
3970 else
3971 /* Start from bottom-right. */
3972 bounds->x1 = allocation->width;
3973 break;
3974 }
3975
3976 goo_canvas_convert_from_window_pixels (canvas, &bounds->x1, &bounds->y1);
3977 bounds->x2 = bounds->x1;
3978 bounds->y2 = bounds->y1;
3979 }
3980
3981
3982 /* Check if the given item is a better candidate for the focus than
3983 the current best one in the data struct. */
3984 static gboolean
goo_canvas_focus_check_is_best(GooCanvas * canvas,GooCanvasItem * item,GooCanvasFocusData * data)3985 goo_canvas_focus_check_is_best (GooCanvas *canvas,
3986 GooCanvasItem *item,
3987 GooCanvasFocusData *data)
3988 {
3989 gdouble center_x, center_y;
3990 gdouble abs_x_offset = 0.0, abs_y_offset = 0.0;
3991
3992 data->current_score = 0.0;
3993
3994 goo_canvas_item_get_bounds (item, &data->current_bounds);
3995 center_x = (data->current_bounds.x1 + data->current_bounds.x2) / 2.0;
3996 center_y = (data->current_bounds.y1 + data->current_bounds.y2) / 2.0;
3997
3998 /* Calculate the offsets of the center of this item from the center
3999 of the current focus item or widget. */
4000 data->current_x_offset = center_x - data->start_center_x;
4001 data->current_y_offset = center_y - data->start_center_y;
4002
4003 /* If the item overlaps the current one at all we use 0 as the offset. */
4004 if (data->current_bounds.x1 > data->start_bounds.x2
4005 || data->current_bounds.x2 < data->start_bounds.x2)
4006 abs_x_offset = fabs (data->current_x_offset);
4007
4008 if (data->current_bounds.y1 > data->start_bounds.y2
4009 || data->current_bounds.y2 < data->start_bounds.y2)
4010 abs_y_offset = fabs (data->current_y_offset);
4011
4012 /* FIXME: I'm still not sure about the score calculations here. There
4013 are still a few odd jumps when using keyboard focus navigation. */
4014 switch (data->direction)
4015 {
4016 case GTK_DIR_UP:
4017 /* If the y offset is > 0 we can discard this item. */
4018 if (data->current_y_offset >= 0 || abs_x_offset > abs_y_offset)
4019 return FALSE;
4020
4021 /* Compute a score (lower is best) and check if it is the best. */
4022 data->current_score = abs_x_offset * 2 + abs_y_offset;
4023 if (!data->best_item || data->current_score < data->best_score)
4024 return TRUE;
4025 break;
4026
4027 case GTK_DIR_DOWN:
4028 /* If the y offset is < 0 we can discard this item. */
4029 if (data->current_y_offset <= 0 || abs_x_offset > abs_y_offset)
4030 return FALSE;
4031
4032 /* Compute a score (lower is best) and check if it is the best. */
4033 data->current_score = abs_x_offset /** 2*/ + abs_y_offset;
4034 if (!data->best_item || data->current_score < data->best_score)
4035 return TRUE;
4036 break;
4037
4038 case GTK_DIR_LEFT:
4039 /* If the x offset is > 0 we can discard this item. */
4040 if (data->current_x_offset >= 0 || abs_y_offset > abs_x_offset)
4041 return FALSE;
4042
4043 /* Compute a score (lower is best) and check if it is the best. */
4044 data->current_score = abs_y_offset * 2 + abs_x_offset;
4045 if (!data->best_item || data->current_score < data->best_score)
4046 return TRUE;
4047 break;
4048
4049 case GTK_DIR_RIGHT:
4050 /* If the x offset is < 0 we can discard this item. */
4051 if (data->current_x_offset <= 0 || abs_y_offset > abs_x_offset)
4052 return FALSE;
4053
4054 /* Compute a score (lower is best) and check if it is the best. */
4055 data->current_score = abs_y_offset * 2 + abs_x_offset;
4056 if (!data->best_item || data->current_score < data->best_score)
4057 return TRUE;
4058 break;
4059
4060 case GTK_DIR_TAB_BACKWARD:
4061 /* We need to handle this differently depending on text direction. */
4062 if (data->text_direction == GTK_TEXT_DIR_RTL)
4063 {
4064 /* If the y offset is > 0, or it is 0 and the x offset > 0 we can
4065 discard this item. */
4066 if (data->current_y_offset > 0
4067 || (data->current_y_offset == 0 && data->current_x_offset < 0))
4068 return FALSE;
4069
4070 /* If the y offset is > the current best y offset, this is best. */
4071 if (!data->best_item || data->current_y_offset > data->best_y_offset)
4072 return TRUE;
4073
4074 /* If the y offsets are the same, choose the largest x offset. */
4075 if (data->current_y_offset == data->best_y_offset
4076 && data->current_x_offset < data->best_x_offset)
4077 return TRUE;
4078 }
4079 else
4080 {
4081 /* If the y offset is > 0, or it is 0 and the x offset > 0 we can
4082 discard this item. */
4083 if (data->current_y_offset > 0
4084 || (data->current_y_offset == 0 && data->current_x_offset > 0))
4085 return FALSE;
4086
4087 /* If the y offset is > the current best y offset, this is best. */
4088 if (!data->best_item || data->current_y_offset > data->best_y_offset)
4089 return TRUE;
4090
4091 /* If the y offsets are the same, choose the largest x offset. */
4092 if (data->current_y_offset == data->best_y_offset
4093 && data->current_x_offset > data->best_x_offset)
4094 return TRUE;
4095 }
4096 break;
4097
4098 case GTK_DIR_TAB_FORWARD:
4099 /* We need to handle this differently depending on text direction. */
4100 if (data->text_direction == GTK_TEXT_DIR_RTL)
4101 {
4102 /* If the y offset is < 0, or it is 0 and the x offset > 0 we can
4103 discard this item. */
4104 if (data->current_y_offset < 0
4105 || (data->current_y_offset == 0 && data->current_x_offset > 0))
4106 return FALSE;
4107
4108 /* If the y offset is < the current best y offset, this is best. */
4109 if (!data->best_item || data->current_y_offset < data->best_y_offset)
4110 return TRUE;
4111
4112 /* If the y offsets are the same, choose the largest x offset. */
4113 if (data->current_y_offset == data->best_y_offset
4114 && data->current_x_offset > data->best_x_offset)
4115 return TRUE;
4116 }
4117 else
4118 {
4119 /* If the y offset is < 0, or it is 0 and the x offset < 0 we can
4120 discard this item. */
4121 if (data->current_y_offset < 0
4122 || (data->current_y_offset == 0 && data->current_x_offset < 0))
4123 return FALSE;
4124
4125 /* If the y offset is < the current best y offset, this is best. */
4126 if (!data->best_item || data->current_y_offset < data->best_y_offset)
4127 return TRUE;
4128
4129 /* If the y offsets are the same, choose the smallest x offset. */
4130 if (data->current_y_offset == data->best_y_offset
4131 && data->current_x_offset < data->best_x_offset)
4132 return TRUE;
4133 }
4134 break;
4135 }
4136
4137 return FALSE;
4138 }
4139
4140
4141 /* Recursively look for the next best item in the desired direction. */
4142 static void
goo_canvas_focus_recurse(GooCanvas * canvas,GooCanvasItem * item,GooCanvasFocusData * data)4143 goo_canvas_focus_recurse (GooCanvas *canvas,
4144 GooCanvasItem *item,
4145 GooCanvasFocusData *data)
4146 {
4147 GooCanvasItem *child;
4148 gboolean can_focus = FALSE, is_best;
4149 gint n_children, i;
4150
4151 /* If the item is not a possible candidate, just return. */
4152 is_best = goo_canvas_focus_check_is_best (canvas, item, data);
4153
4154 if (is_best && goo_canvas_item_is_visible (item))
4155 {
4156 /* Check if the item can take the focus. */
4157 if (GOO_IS_CANVAS_WIDGET (item))
4158 {
4159 /* We have to assume that widget items can take focus, since any
4160 of their descendants may take the focus. */
4161 if (((GooCanvasWidget*) item)->widget)
4162 can_focus = TRUE;
4163 }
4164 else
4165 {
4166 g_object_get (item, "can-focus", &can_focus, NULL);
4167 }
4168
4169 /* If the item can take the focus itself, and it isn't the current
4170 focus item, save its score and return. (If it is a container it takes
4171 precedence over its children). */
4172 if (can_focus && item != data->start_item)
4173 {
4174 data->best_item = item;
4175 data->best_x_offset = data->current_x_offset;
4176 data->best_y_offset = data->current_y_offset;
4177 data->best_score = data->current_score;
4178 return;
4179 }
4180 }
4181
4182 /* If the item is a container, check the children recursively. */
4183 n_children = goo_canvas_item_get_n_children (item);
4184 if (n_children)
4185 {
4186 /* Check if we can skip the entire group. */
4187 switch (data->direction)
4188 {
4189 case GTK_DIR_UP:
4190 /* If the group is below the bottom of the current focused item
4191 we can skip it. */
4192 if (data->current_bounds.y1 > data->start_bounds.y2)
4193 return;
4194 break;
4195 case GTK_DIR_DOWN:
4196 /* If the group is above the top of the current focused item
4197 we can skip it. */
4198 if (data->current_bounds.y2 < data->start_bounds.y1)
4199 return;
4200 break;
4201 case GTK_DIR_LEFT:
4202 /* If the group is to the right of the current focused item
4203 we can skip it. */
4204 if (data->current_bounds.x1 > data->start_bounds.x2)
4205 return;
4206 break;
4207 case GTK_DIR_RIGHT:
4208 /* If the group is to the left of the current focused item
4209 we can skip it. */
4210 if (data->current_bounds.x2 < data->start_bounds.x1)
4211 return;
4212 break;
4213 default:
4214 break;
4215 }
4216
4217 for (i = 0; i < n_children; i++)
4218 {
4219 child = goo_canvas_item_get_child (item, i);
4220 goo_canvas_focus_recurse (canvas, child, data);
4221 }
4222 }
4223 }
4224
4225
4226 /* FIXME: We could add support for a focus chain, like GtkContainer. */
4227 static gboolean
goo_canvas_focus(GtkWidget * widget,GtkDirectionType direction)4228 goo_canvas_focus (GtkWidget *widget,
4229 GtkDirectionType direction)
4230 {
4231 GooCanvas *canvas;
4232 GooCanvasFocusData data;
4233 GtkWidget *old_focus_child;
4234 gboolean found_item;
4235 gint try;
4236
4237 g_return_val_if_fail (GOO_IS_CANVAS (widget), FALSE);
4238
4239 canvas = GOO_CANVAS (widget);
4240
4241 /* If keyboard navigation has been turned off for the canvas, return FALSE.*/
4242 if (!GTK_WIDGET_CAN_FOCUS (canvas))
4243 return FALSE;
4244
4245 /* If a child widget has the focus, try moving the focus within that. */
4246 old_focus_child = GTK_CONTAINER (canvas)->focus_child;
4247 if (old_focus_child && gtk_widget_child_focus (old_focus_child, direction))
4248 return TRUE;
4249
4250 data.direction = direction;
4251 data.text_direction = gtk_widget_get_direction (widget);
4252 data.start_item = NULL;
4253
4254 #if GTK_CHECK_VERSION (2, 19, 6)
4255 if (gtk_widget_has_focus (GTK_WIDGET (canvas)))
4256 #else
4257 if (GTK_WIDGET_HAS_FOCUS (canvas))
4258 #endif
4259 data.start_item = canvas->focused_item;
4260 else if (old_focus_child && GOO_IS_CANVAS_WIDGET (old_focus_child))
4261 data.start_item = g_object_get_data (G_OBJECT (old_focus_child),
4262 "goo-canvas-item");
4263
4264 /* Keep looping until we find an item to focus or we fail. I've added a
4265 limit on the number of tries just in case we get into an infinite loop. */
4266 for (try = 1; try < 1000; try++)
4267 {
4268 /* Get the bounds of the currently focused item or widget. */
4269 goo_canvas_get_start_bounds (canvas, &data);
4270 data.start_center_x = (data.start_bounds.x1 + data.start_bounds.x2) / 2.0;
4271 data.start_center_y = (data.start_bounds.y1 + data.start_bounds.y2) / 2.0;
4272 data.best_item = NULL;
4273
4274 /* Recursively look for the next best item in the desired direction. */
4275 goo_canvas_focus_recurse (canvas, canvas->root_item, &data);
4276
4277 /* If we found an item to focus, grab the focus and return TRUE. */
4278 if (!data.best_item)
4279 break;
4280
4281 if (GOO_IS_CANVAS_WIDGET (data.best_item))
4282 {
4283 found_item = gtk_widget_child_focus (((GooCanvasWidget*) data.best_item)->widget, direction);
4284 }
4285 else
4286 {
4287 found_item = TRUE;
4288 goo_canvas_grab_focus (canvas, data.best_item);
4289 }
4290
4291 if (found_item)
4292 {
4293 goo_canvas_scroll_to_item (canvas, data.best_item);
4294 return TRUE;
4295 }
4296
4297 /* Try again from the last item tried. */
4298 data.start_item = data.best_item;
4299 }
4300
4301 return FALSE;
4302 }
4303
4304
4305 /**
4306 * goo_canvas_register_widget_item:
4307 * @canvas: a #GooCanvas.
4308 * @witem: a #GooCanvasWidget item.
4309 *
4310 * This function should only be used by #GooCanvasWidget and subclass
4311 * implementations.
4312 *
4313 * It registers a widget item with the canvas, so that the canvas can do the
4314 * necessary actions to move and resize the widget as needed.
4315 **/
4316 void
goo_canvas_register_widget_item(GooCanvas * canvas,GooCanvasWidget * witem)4317 goo_canvas_register_widget_item (GooCanvas *canvas,
4318 GooCanvasWidget *witem)
4319 {
4320 g_return_if_fail (GOO_IS_CANVAS (canvas));
4321 g_return_if_fail (GOO_IS_CANVAS_WIDGET (witem));
4322
4323 canvas->widget_items = g_list_append (canvas->widget_items, witem);
4324 }
4325
4326
4327 /**
4328 * goo_canvas_unregister_widget_item:
4329 * @canvas: a #GooCanvas.
4330 * @witem: a #GooCanvasWidget item.
4331 *
4332 * This function should only be used by #GooCanvasWidget and subclass
4333 * implementations.
4334 *
4335 * It unregisters a widget item from the canvas, when the item is no longer in
4336 * the canvas.
4337 **/
4338 void
goo_canvas_unregister_widget_item(GooCanvas * canvas,GooCanvasWidget * witem)4339 goo_canvas_unregister_widget_item (GooCanvas *canvas,
4340 GooCanvasWidget *witem)
4341 {
4342 GList *tmp_list;
4343 GooCanvasWidget *tmp_witem;
4344
4345 g_return_if_fail (GOO_IS_CANVAS (canvas));
4346 g_return_if_fail (GOO_IS_CANVAS_WIDGET (witem));
4347
4348 tmp_list = canvas->widget_items;
4349 while (tmp_list)
4350 {
4351 tmp_witem = tmp_list->data;
4352 if (tmp_witem == witem)
4353 break;
4354 tmp_list = tmp_list->next;
4355 }
4356
4357 if (tmp_list)
4358 {
4359 canvas->widget_items = g_list_remove_link (canvas->widget_items,
4360 tmp_list);
4361 g_list_free_1 (tmp_list);
4362 }
4363 }
4364
4365
4366 static void
goo_canvas_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)4367 goo_canvas_forall (GtkContainer *container,
4368 gboolean include_internals,
4369 GtkCallback callback,
4370 gpointer callback_data)
4371 {
4372 GooCanvas *canvas;
4373 GList *tmp_list;
4374 GooCanvasWidget *witem;
4375
4376 g_return_if_fail (GOO_IS_CANVAS (container));
4377 g_return_if_fail (callback != NULL);
4378
4379 canvas = GOO_CANVAS (container);
4380
4381 tmp_list = canvas->widget_items;
4382 while (tmp_list)
4383 {
4384 witem = tmp_list->data;
4385 tmp_list = tmp_list->next;
4386
4387 if (witem->widget)
4388 (* callback) (witem->widget, callback_data);
4389 }
4390 }
4391
4392
4393 static void
goo_canvas_remove(GtkContainer * container,GtkWidget * widget)4394 goo_canvas_remove (GtkContainer *container,
4395 GtkWidget *widget)
4396 {
4397 GooCanvas *canvas;
4398 GList *tmp_list;
4399 GooCanvasWidget *witem;
4400 GooCanvasItem *parent;
4401 gint child_num;
4402
4403 g_return_if_fail (GOO_IS_CANVAS (container));
4404
4405 canvas = GOO_CANVAS (container);
4406
4407 tmp_list = canvas->widget_items;
4408 while (tmp_list)
4409 {
4410 witem = tmp_list->data;
4411 tmp_list = tmp_list->next;
4412
4413 if (witem->widget == widget)
4414 {
4415 parent = goo_canvas_item_get_parent ((GooCanvasItem*) witem);
4416 child_num = goo_canvas_item_find_child (parent,
4417 (GooCanvasItem*) witem);
4418 goo_canvas_item_remove_child (parent, child_num);
4419
4420 break;
4421 }
4422 }
4423 }
4424
4425
4426 static gboolean
goo_canvas_query_tooltip(GtkWidget * widget,gint x,gint y,gboolean keyboard_tip,GtkTooltip * tooltip)4427 goo_canvas_query_tooltip (GtkWidget *widget,
4428 gint x,
4429 gint y,
4430 gboolean keyboard_tip,
4431 GtkTooltip *tooltip)
4432 {
4433 GooCanvas *canvas = (GooCanvas*) widget;
4434 GooCanvasItem *item = canvas->pointer_item, *parent;
4435 gdouble item_x = x, item_y = y;
4436 gboolean tip_set = FALSE, has_transform;
4437 cairo_matrix_t transform;
4438
4439 if (!item)
4440 return FALSE;
4441
4442 /* Convert from pixels to the item's coordinate space. */
4443 goo_canvas_convert_from_pixels (canvas, &item_x, &item_y);
4444 goo_canvas_convert_to_item_space (canvas, item, &item_x, &item_y);
4445
4446 for (;;)
4447 {
4448 g_signal_emit_by_name (item, "query-tooltip", item_x, item_y,
4449 keyboard_tip, tooltip, &tip_set);
4450 if (tip_set)
4451 return TRUE;
4452
4453 parent = goo_canvas_item_get_parent (item);
4454 if (!parent)
4455 break;
4456
4457 /* Convert x & y to the parent's coordinate space. */
4458 has_transform = goo_canvas_item_get_transform_for_child (parent, item,
4459 &transform);
4460 if (has_transform)
4461 cairo_matrix_transform_point (&transform, &item_x, &item_y);
4462
4463 item = parent;
4464 }
4465
4466 /* We call the parent method in case the canvas itself has a tooltip set. */
4467 return GTK_WIDGET_CLASS (goo_canvas_parent_class)->query_tooltip (widget, x, y, keyboard_tip, tooltip);
4468 }
4469
4470