1 /*
2  * GooCanvas. Copyright (C) 2005-6 Damon Chaplin.
3  * Released under the GNU LGPL license. See COPYING for details.
4  *
5  * goocanvaswidget.c - wrapper item for an embedded GtkWidget.
6  */
7 
8 /**
9  * SECTION:goocanvaswidget
10  * @Title: GooCanvasWidget
11  * @Short_Description: an embedded widget item.
12  *
13  * GooCanvasWidget provides support for placing any GtkWidget in the canvas.
14  *
15  * The #GooCanvasWidget:width and #GooCanvasWidget:height properties specify
16  * the widget's size. If either of them is -1, then the requested size of the
17  * widget is used instead, which is the default for both width and height.
18  *
19  * Note that there are a number of limitations in the use of #GooCanvasWidget:
20  *
21  * <itemizedlist><listitem><para>
22  * It doesn't support any transformation besides simple translation.
23  * This means you can't scale a canvas with a #GooCanvasWidget in it.
24  * </para></listitem><listitem><para>
25  * It doesn't support layering, so you can't place other items beneath
26  * or above the #GooCanvasWidget.
27  * </para></listitem><listitem><para>
28  * It doesn't support rendering of widgets to a given cairo_t, which
29  * means you can't output the widget to a pdf or postscript file.
30  * </para></listitem><listitem><para>
31  * It doesn't have a model/view variant like the other standard items,
32  * so it can only be used in a simple canvas without a model.
33  * </para></listitem><listitem><para>
34  * It can't be made a static item.
35  * </para></listitem></itemizedlist>
36  */
37 #include <config.h>
38 #include <glib/gi18n-lib.h>
39 #include <gtk/gtk.h>
40 #include "goocanvas.h"
41 #include "goocanvasatk.h"
42 
43 enum {
44   PROP_0,
45 
46   PROP_WIDGET,
47   PROP_X,
48   PROP_Y,
49   PROP_WIDTH,
50   PROP_HEIGHT,
51   PROP_ANCHOR,
52   PROP_VISIBILITY
53 };
54 
55 
56 static void canvas_item_interface_init      (GooCanvasItemIface  *iface);
57 static void goo_canvas_widget_dispose       (GObject             *object);
58 static void goo_canvas_widget_get_property  (GObject             *object,
59 					     guint                param_id,
60 					     GValue              *value,
61 					     GParamSpec          *pspec);
62 static void goo_canvas_widget_set_property  (GObject             *object,
63 					     guint                param_id,
64 					     const GValue        *value,
65 					     GParamSpec          *pspec);
66 
G_DEFINE_TYPE_WITH_CODE(GooCanvasWidget,goo_canvas_widget,GOO_TYPE_CANVAS_ITEM_SIMPLE,G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM,canvas_item_interface_init))67 G_DEFINE_TYPE_WITH_CODE (GooCanvasWidget, goo_canvas_widget,
68 			 GOO_TYPE_CANVAS_ITEM_SIMPLE,
69 			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM,
70 						canvas_item_interface_init))
71 
72 
73 static void
74 goo_canvas_widget_init (GooCanvasWidget *witem)
75 {
76   /* By default we place the widget at the top-left of the canvas at its
77      requested size. */
78   witem->x = 0.0;
79   witem->y = 0.0;
80   witem->width = -1.0;
81   witem->height = -1.0;
82   witem->anchor = GOO_CANVAS_ANCHOR_NW;
83 }
84 
85 
86 /**
87  * goo_canvas_widget_new:
88  * @parent: (skip): the parent item, or %NULL. If a parent is specified, it will assume
89  *  ownership of the item, and the item will automatically be freed when it is
90  *  removed from the parent. Otherwise call g_object_unref() to free it.
91  * @widget: the widget.
92  * @x: the x coordinate of the item.
93  * @y: the y coordinate of the item.
94  * @width: the width of the item, or -1 to use the widget's requested width.
95  * @height: the height of the item, or -1 to use the widget's requested height.
96  * @...: optional pairs of property names and values, and a terminating %NULL.
97  *
98  * Creates a new widget item.
99  *
100  * Here's an example showing how to create an entry widget centered at (100.0,
101  * 100.0):
102  *
103  * <informalexample><programlisting>
104  *  GtkWidget *entry = gtk_entry_new ();
105  *  GooCanvasItem *witem = goo_canvas_widget_new (mygroup, entry,
106  *                                                100, 100, -1, -1,
107  *                                                "anchor", GOO_CANVAS_ANCHOR_CENTER,
108  *                                                NULL);
109  * </programlisting></informalexample>
110  *
111  * Returns: (transfer full): a new widget item.
112  **/
113 GooCanvasItem*
goo_canvas_widget_new(GooCanvasItem * parent,GtkWidget * widget,gdouble x,gdouble y,gdouble width,gdouble height,...)114 goo_canvas_widget_new               (GooCanvasItem    *parent,
115 				     GtkWidget        *widget,
116 				     gdouble           x,
117 				     gdouble           y,
118 				     gdouble           width,
119 				     gdouble           height,
120 				     ...)
121 {
122   GooCanvasItem *item;
123   GooCanvasWidget *witem;
124   const char *first_property;
125   va_list var_args;
126 
127   item = g_object_new (GOO_TYPE_CANVAS_WIDGET, NULL);
128   witem = (GooCanvasWidget*) item;
129 
130   witem->widget = widget;
131   g_object_ref (witem->widget);
132   g_object_set_data (G_OBJECT (witem->widget), "goo-canvas-item", witem);
133 
134   witem->x = x;
135   witem->y = y;
136   witem->width = width;
137   witem->height = height;
138 
139   /* The widget defaults to being visible, like the canvas item, but this
140      can be overridden by the object property below. */
141   if (widget)
142     gtk_widget_show (widget);
143 
144   va_start (var_args, height);
145   first_property = va_arg (var_args, char*);
146   if (first_property)
147     g_object_set_valist ((GObject*) item, first_property, var_args);
148   va_end (var_args);
149 
150   if (parent)
151     {
152       goo_canvas_item_add_child (parent, item, -1);
153       g_object_unref (item);
154     }
155 
156   return item;
157 }
158 
159 
160 /* Returns the anchor position, within the given width. */
161 static gdouble
goo_canvas_widget_anchor_horizontal_pos(GooCanvasAnchorType anchor,gdouble width)162 goo_canvas_widget_anchor_horizontal_pos (GooCanvasAnchorType anchor,
163 					 gdouble       width)
164 {
165   switch(anchor)
166     {
167     case GOO_CANVAS_ANCHOR_N:
168     case GOO_CANVAS_ANCHOR_CENTER:
169     case GOO_CANVAS_ANCHOR_S:
170       return width / 2.0;
171     case GOO_CANVAS_ANCHOR_NE:
172     case GOO_CANVAS_ANCHOR_E:
173     case GOO_CANVAS_ANCHOR_SE:
174       return width;
175     default:
176       return 0.0;
177     }
178 }
179 
180 
181 /* Returns the anchor position, within the given height. */
182 static gdouble
goo_canvas_widget_anchor_vertical_pos(GooCanvasAnchorType anchor,gdouble height)183 goo_canvas_widget_anchor_vertical_pos (GooCanvasAnchorType anchor,
184 				       gdouble       height)
185 {
186   switch (anchor)
187     {
188     case GOO_CANVAS_ANCHOR_W:
189     case GOO_CANVAS_ANCHOR_CENTER:
190     case GOO_CANVAS_ANCHOR_E:
191       return height / 2.0;
192     case GOO_CANVAS_ANCHOR_SW:
193     case GOO_CANVAS_ANCHOR_S:
194     case GOO_CANVAS_ANCHOR_SE:
195       return height;
196     default:
197       return 0.0;
198     }
199 }
200 
201 
202 /* Returns the size to use for the widget, either the item's width & height
203    properties or the widget's own requested width & height. */
204 static void
goo_canvas_widget_get_widget_size(GooCanvasWidget * witem,gdouble * width,gdouble * height)205 goo_canvas_widget_get_widget_size (GooCanvasWidget *witem,
206 				   gdouble         *width,
207 				   gdouble         *height)
208 {
209   GtkRequisition requisition;
210 
211   if (witem->widget)
212     {
213       /* Get the widget's requested size, if we need it. */
214       if (witem->width < 0 || witem->height < 0)
215 	gtk_widget_get_preferred_size (witem->widget, NULL, &requisition);
216 
217       *width = witem->width < 0 ? requisition.width : witem->width;
218       *height = witem->height < 0 ? requisition.height : witem->height;
219     }
220   else
221     {
222       *width = *height = 0.0;
223     }
224 }
225 
226 
227 static void
goo_canvas_widget_set_widget(GooCanvasWidget * witem,GtkWidget * widget)228 goo_canvas_widget_set_widget (GooCanvasWidget *witem,
229 			      GtkWidget       *widget)
230 {
231   GooCanvasItemSimple *simple = (GooCanvasItemSimple*) witem;
232 
233   if (witem->widget)
234     {
235       g_object_set_data (G_OBJECT (witem->widget), "goo-canvas-item", NULL);
236       gtk_widget_unparent (witem->widget);
237       g_object_unref (witem->widget);
238       witem->widget = NULL;
239     }
240 
241   if (widget)
242     {
243       witem->widget = widget;
244       g_object_ref (witem->widget);
245       g_object_set_data (G_OBJECT (witem->widget), "goo-canvas-item", witem);
246 
247       if (simple->simple_data->visibility <= GOO_CANVAS_ITEM_INVISIBLE)
248 	gtk_widget_hide (widget);
249       else
250 	gtk_widget_show (widget);
251 
252       if (simple->canvas)
253 	{
254 	  if (gtk_widget_get_realized (GTK_WIDGET (simple->canvas)))
255 	    gtk_widget_set_parent_window (widget,
256 					  simple->canvas->canvas_window);
257 
258 	  gtk_widget_set_parent (widget, GTK_WIDGET (simple->canvas));
259 	}
260     }
261 }
262 
263 
264 static void
goo_canvas_widget_dispose(GObject * object)265 goo_canvas_widget_dispose (GObject *object)
266 {
267   GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
268   GooCanvasWidget *witem = (GooCanvasWidget*) object;
269 
270   if (simple->canvas)
271     goo_canvas_unregister_widget_item (simple->canvas, witem);
272 
273   goo_canvas_widget_set_widget (witem, NULL);
274 
275   G_OBJECT_CLASS (goo_canvas_widget_parent_class)->dispose (object);
276 }
277 
278 
279 static void
goo_canvas_widget_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)280 goo_canvas_widget_get_property  (GObject             *object,
281 				 guint                prop_id,
282 				 GValue              *value,
283 				 GParamSpec          *pspec)
284 {
285   GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
286   GooCanvasWidget *witem = (GooCanvasWidget*) object;
287 
288   switch (prop_id)
289     {
290     case PROP_WIDGET:
291       g_value_set_object (value, witem->widget);
292       break;
293     case PROP_X:
294       g_value_set_double (value, witem->x);
295       break;
296     case PROP_Y:
297       g_value_set_double (value, witem->y);
298       break;
299     case PROP_WIDTH:
300       g_value_set_double (value, witem->width);
301       break;
302     case PROP_HEIGHT:
303       g_value_set_double (value, witem->height);
304       break;
305     case PROP_ANCHOR:
306       g_value_set_enum (value, witem->anchor);
307       break;
308     case PROP_VISIBILITY:
309       g_value_set_enum (value, simple->simple_data->visibility);
310       break;
311     default:
312       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
313       break;
314     }
315 }
316 
317 
318 static void
goo_canvas_widget_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)319 goo_canvas_widget_set_property  (GObject             *object,
320 				 guint                prop_id,
321 				 const GValue        *value,
322 				 GParamSpec          *pspec)
323 {
324   GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
325   GooCanvasWidget *witem = (GooCanvasWidget*) object;
326 
327   switch (prop_id)
328     {
329     case PROP_WIDGET:
330       goo_canvas_widget_set_widget (witem, g_value_get_object (value));
331       break;
332     case PROP_X:
333       witem->x = g_value_get_double (value);
334       break;
335     case PROP_Y:
336       witem->y = g_value_get_double (value);
337       break;
338     case PROP_WIDTH:
339       witem->width = g_value_get_double (value);
340       break;
341     case PROP_HEIGHT:
342       witem->height = g_value_get_double (value);
343       break;
344     case PROP_ANCHOR:
345       witem->anchor = g_value_get_enum (value);
346       break;
347     case PROP_VISIBILITY:
348       simple->simple_data->visibility = g_value_get_enum (value);
349       if (simple->simple_data->visibility <= GOO_CANVAS_ITEM_INVISIBLE)
350 	gtk_widget_hide (witem->widget);
351       else
352 	gtk_widget_show (witem->widget);
353       break;
354     default:
355       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
356       break;
357     }
358 
359   goo_canvas_item_simple_changed (simple, TRUE);
360 }
361 
362 
363 static void
goo_canvas_widget_set_canvas(GooCanvasItem * item,GooCanvas * canvas)364 goo_canvas_widget_set_canvas  (GooCanvasItem *item,
365 			       GooCanvas     *canvas)
366 {
367   GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
368   GooCanvasWidget *witem = (GooCanvasWidget*) item;
369 
370   if (simple->canvas != canvas)
371     {
372       if (simple->canvas)
373 	goo_canvas_unregister_widget_item (simple->canvas, witem);
374 
375       simple->canvas = canvas;
376 
377       if (simple->canvas)
378 	{
379 	  goo_canvas_register_widget_item (simple->canvas, witem);
380 
381 	  if (witem->widget)
382 	    {
383 	      if (gtk_widget_get_realized (GTK_WIDGET (simple->canvas)))
384 		gtk_widget_set_parent_window (witem->widget,
385 					      simple->canvas->canvas_window);
386 
387 	      gtk_widget_set_parent (witem->widget,
388 				     GTK_WIDGET (simple->canvas));
389 	    }
390 	}
391       else
392 	{
393 	  if (witem->widget)
394 	    gtk_widget_unparent (witem->widget);
395 	}
396     }
397 }
398 
399 
400 static void
goo_canvas_widget_set_parent(GooCanvasItem * item,GooCanvasItem * parent)401 goo_canvas_widget_set_parent (GooCanvasItem  *item,
402 			      GooCanvasItem  *parent)
403 {
404   GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
405   GooCanvas *canvas;
406 
407   simple->parent = parent;
408   simple->need_update = TRUE;
409   simple->need_entire_subtree_update = TRUE;
410 
411   canvas = parent ? goo_canvas_item_get_canvas (parent) : NULL;
412   goo_canvas_widget_set_canvas (item, canvas);
413 }
414 
415 
416 static void
goo_canvas_widget_update(GooCanvasItemSimple * simple,cairo_t * cr)417 goo_canvas_widget_update  (GooCanvasItemSimple *simple,
418 			   cairo_t             *cr)
419 {
420   GooCanvasWidget *witem = (GooCanvasWidget*) simple;
421   gdouble width, height;
422 
423   if (witem->widget)
424     {
425       goo_canvas_widget_get_widget_size (witem, &width, &height);
426 
427       simple->bounds.x1 = witem->x;
428       simple->bounds.y1 = witem->y;
429 
430       simple->bounds.x1 -=
431         goo_canvas_widget_anchor_horizontal_pos (witem->anchor, width);
432       simple->bounds.y1 -=
433         goo_canvas_widget_anchor_vertical_pos (witem->anchor, height);
434 
435       simple->bounds.x2 = simple->bounds.x1 + width;
436       simple->bounds.y2 = simple->bounds.y1 + height;
437 
438       /* Queue a resize of the widget so it gets moved. Note that the widget
439 	 is moved by goo_canvas_size_allocate(). */
440       gtk_widget_queue_resize (witem->widget);
441     }
442   else
443     {
444       simple->bounds.x1 = simple->bounds.y1 = 0.0;
445       simple->bounds.x2 = simple->bounds.y2 = 0.0;
446     }
447 }
448 
449 
450 static void
goo_canvas_widget_allocate_area(GooCanvasItem * item,cairo_t * cr,const GooCanvasBounds * requested_area,const GooCanvasBounds * allocated_area,gdouble x_offset,gdouble y_offset)451 goo_canvas_widget_allocate_area      (GooCanvasItem         *item,
452 				      cairo_t               *cr,
453 				      const GooCanvasBounds *requested_area,
454 				      const GooCanvasBounds *allocated_area,
455 				      gdouble                x_offset,
456 				      gdouble                y_offset)
457 {
458   GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
459   GooCanvasWidget *witem = (GooCanvasWidget*) item;
460   gdouble requested_width, requested_height, allocated_width, allocated_height;
461   gdouble width_proportion, height_proportion;
462   gdouble width, height;
463 
464   width = simple->bounds.x2 - simple->bounds.x1;
465   height = simple->bounds.y2 - simple->bounds.y1;
466 
467   simple->bounds.x1 += x_offset;
468   simple->bounds.y1 += y_offset;
469 
470   requested_width = requested_area->x2 - requested_area->x1;
471   requested_height = requested_area->y2 - requested_area->y1;
472   allocated_width = allocated_area->x2 - allocated_area->x1;
473   allocated_height = allocated_area->y2 - allocated_area->y1;
474 
475   width_proportion = allocated_width / requested_width;
476   height_proportion = allocated_height / requested_height;
477 
478   width *= width_proportion;
479   height *= height_proportion;
480 
481   simple->bounds.x2 = simple->bounds.x1 + width;
482   simple->bounds.y2 = simple->bounds.y1 + height;
483 
484   /* Queue a resize of the widget so it gets moved. Note that the widget
485      is moved by goo_canvas_size_allocate(). */
486   gtk_widget_queue_resize (witem->widget);
487 }
488 
489 
490 static void
goo_canvas_widget_paint(GooCanvasItemSimple * simple,cairo_t * cr,const GooCanvasBounds * bounds)491 goo_canvas_widget_paint (GooCanvasItemSimple   *simple,
492 			 cairo_t               *cr,
493 			 const GooCanvasBounds *bounds)
494 {
495   /* Do nothing for now. Maybe render for printing in future. */
496 }
497 
498 
499 static gboolean
goo_canvas_widget_is_item_at(GooCanvasItemSimple * simple,gdouble x,gdouble y,cairo_t * cr,gboolean is_pointer_event)500 goo_canvas_widget_is_item_at (GooCanvasItemSimple *simple,
501 			      gdouble              x,
502 			      gdouble              y,
503 			      cairo_t             *cr,
504 			      gboolean             is_pointer_event)
505 {
506   /* For now we just assume that the widget covers its entire bounds so we just
507      return TRUE. In future if widget items support transforms we'll need to
508      modify this. */
509   return TRUE;
510 }
511 
512 
513 static void
canvas_item_interface_init(GooCanvasItemIface * iface)514 canvas_item_interface_init (GooCanvasItemIface *iface)
515 {
516   iface->set_canvas     = goo_canvas_widget_set_canvas;
517   iface->set_parent	= goo_canvas_widget_set_parent;
518   iface->allocate_area  = goo_canvas_widget_allocate_area;
519 }
520 
521 
522 static void
goo_canvas_widget_class_init(GooCanvasWidgetClass * klass)523 goo_canvas_widget_class_init (GooCanvasWidgetClass *klass)
524 {
525   GObjectClass *gobject_class = (GObjectClass*) klass;
526   GooCanvasItemSimpleClass *simple_class = (GooCanvasItemSimpleClass*) klass;
527 
528   gobject_class->dispose = goo_canvas_widget_dispose;
529 
530   gobject_class->get_property = goo_canvas_widget_get_property;
531   gobject_class->set_property = goo_canvas_widget_set_property;
532 
533   simple_class->simple_update        = goo_canvas_widget_update;
534   simple_class->simple_paint         = goo_canvas_widget_paint;
535   simple_class->simple_is_item_at    = goo_canvas_widget_is_item_at;
536 
537   /* Register our accessible factory, but only if accessibility is enabled. */
538   if (!ATK_IS_NO_OP_OBJECT_FACTORY (atk_registry_get_factory (atk_get_default_registry (), GTK_TYPE_WIDGET)))
539     {
540       atk_registry_set_factory_type (atk_get_default_registry (),
541 				     GOO_TYPE_CANVAS_WIDGET,
542 				     goo_canvas_widget_accessible_factory_get_type ());
543     }
544 
545   g_object_class_install_property (gobject_class, PROP_WIDGET,
546 				   g_param_spec_object ("widget",
547 							_("Widget"),
548 							_("The widget to place in the canvas"),
549 							GTK_TYPE_WIDGET,
550 							G_PARAM_READWRITE));
551 
552   g_object_class_install_property (gobject_class, PROP_X,
553 				   g_param_spec_double ("x",
554 							"X",
555 							_("The x coordinate of the widget"),
556 							-G_MAXDOUBLE,
557 							G_MAXDOUBLE, 0.0,
558 							G_PARAM_READWRITE));
559 
560   g_object_class_install_property (gobject_class, PROP_Y,
561 				   g_param_spec_double ("y",
562 							"Y",
563 							_("The y coordinate of the widget"),
564 							-G_MAXDOUBLE,
565 							G_MAXDOUBLE, 0.0,
566 							G_PARAM_READWRITE));
567 
568   g_object_class_install_property (gobject_class, PROP_WIDTH,
569 				   g_param_spec_double ("width",
570 							_("Width"),
571 							_("The width of the widget, or -1 to use its requested width"),
572 							-G_MAXDOUBLE,
573 							G_MAXDOUBLE, -1.0,
574 							G_PARAM_READWRITE));
575 
576   g_object_class_install_property (gobject_class, PROP_HEIGHT,
577 				   g_param_spec_double ("height",
578 							_("Height"),
579 							_("The height of the widget, or -1 to use its requested height"),
580 							-G_MAXDOUBLE,
581 							G_MAXDOUBLE, -1.0,
582 							G_PARAM_READWRITE));
583 
584 
585   g_object_class_install_property (gobject_class, PROP_ANCHOR,
586 				   g_param_spec_enum ("anchor",
587 						      _("Anchor"),
588 						      _("How to position the widget relative to the item's x and y coordinate settings"),
589 						      GOO_TYPE_CANVAS_ANCHOR_TYPE,
590 						      GOO_CANVAS_ANCHOR_NW,
591 						      G_PARAM_READWRITE));
592 
593   g_object_class_override_property (gobject_class, PROP_VISIBILITY,
594 				    "visibility");
595 }
596