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  *  &num;include &lt;goocanvas.h&gt;
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  *    /&ast; Initialize GTK+. &ast;/
32  *    gtk_set_locale&nbsp;();
33  *    gtk_init (&amp;argc, &amp;argv);
34  *
35  *    /&ast; Create the window and widgets. &ast;/
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&nbsp;();
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  *    /&ast; Add a few simple items. &ast;/
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  *    /&ast; Connect a signal handler for the rectangle item. &ast;/
72  *    g_signal_connect (rect_item, "button_press_event",
73  *                      (GtkSignalFunc) on_rect_button_press, NULL);
74  *
75  *    /&ast; Pass control to the GTK+ main event loop. &ast;/
76  *    gtk_main&nbsp;();
77  *
78  *    return 0;
79  *  }
80  *
81  *
82  *  /&ast; This handles button presses in item views. We simply output a message to
83  *     the console. &ast;/
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 = &GTK_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