[docs] Update the Since tags
[clutter-gtk] / clutter-gtk / gtk-clutter-viewport.c
1 /**
2  * SECTION:gtk-clutter-viewport
3  * @short_description: A scrollable actor
4  *
5  * #GtkClutterViewport is a scrollable actor that can contain a single
6  * #ClutterActor. Using two #GtkAdjustment<!-- -->s it is possible to
7  * control the visible area of the child actor if the size of the viewport
8  * is smaller than the size of the child.
9  *
10  * The #GtkAdjustment<!-- -->s used to control the horizontal and
11  * vertical scrolling can be attached to a #GtkScrollbar subclass,
12  * like #GtkHScrollbar or #GtkVScrollbar.
13  *
14  * The #GtkClutterViewport can be used inside any #ClutterContainer
15  * implementation.
16  *
17  * #GtkClutterViewport is available since Clutter-GTK 0.10
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include <cogl/cogl.h>
25
26 #include "gtk-clutter-viewport.h"
27
28 #include "gtk-clutter-scrollable.h"
29 #include "gtk-clutter-util.h"
30 #include "gtk-clutter-zoomable.h"
31
32 /* XXX - GtkAdjustment accessors have been added with GTK+ 2.14,
33  * but I want Clutter-GTK to be future-proof, so let's do this
34  * little #define dance.
35  */
36 #if !GTK_CHECK_VERSION (2, 14, 0)
37 #define gtk_adjustment_set_page_size(a,v)       ((a)->page_size = (v))
38 #define gtk_adjustment_set_upper(a,v)           ((a)->upper = (v))
39 #define gtk_adjustment_set_page_increment(a,v)  ((a)->page_increment = (v))
40 #define gtk_adjustment_set_step_increment(a,v)  ((a)->step_increment = (v))
41 #define gtk_adjustment_set_lower(a,v)           ((a)->lower = (v))
42
43 #define gtk_adjustment_get_lower(a)             ((a)->lower)
44 #define gtk_adjustment_get_upper(a)             ((a)->upper)
45 #define gtk_adjustment_get_page_size(a)         ((a)->page_size)
46 #endif
47
48 #define GET_PRIVATE(obj)        (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_CLUTTER_TYPE_VIEWPORT, GtkClutterViewportPrivate))
49
50 #define I_(str) (g_intern_static_string ((str)))
51
52 typedef enum {
53   VIEWPORT_X_AXIS,
54   VIEWPORT_Y_AXIS,
55   VIEWPORT_Z_AXIS
56 } ViewportAxis;
57
58 static void clutter_container_iface_init      (gpointer g_iface);
59 static void gtk_clutter_scrollable_iface_init (gpointer g_iface);
60 static void gtk_clutter_zoomable_iface_init   (gpointer g_iface);
61
62 G_DEFINE_TYPE_WITH_CODE (GtkClutterViewport,
63                          gtk_clutter_viewport,
64                          CLUTTER_TYPE_ACTOR,
65                          G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
66                                                 clutter_container_iface_init)
67                          G_IMPLEMENT_INTERFACE (GTK_CLUTTER_TYPE_SCROLLABLE,
68                                                 gtk_clutter_scrollable_iface_init)
69                          G_IMPLEMENT_INTERFACE (GTK_CLUTTER_TYPE_ZOOMABLE,
70                                                 gtk_clutter_zoomable_iface_init));
71
72 struct _GtkClutterViewportPrivate
73 {
74   ClutterVertex origin;
75
76   ClutterActor *child;
77
78   GtkAdjustment *x_adjustment;
79   GtkAdjustment *y_adjustment;
80   GtkAdjustment *z_adjustment;
81 };
82
83 enum
84 {
85   PROP_0,
86
87   PROP_CHILD,
88   PROP_ORIGIN,
89
90   PROP_H_ADJUSTMENT,
91   PROP_V_ADJUSTMENT,
92   PROP_Z_ADJUSTMENT
93 };
94
95 static void
96 gtk_clutter_viewport_add (ClutterContainer *container,
97                           ClutterActor     *actor)
98 {
99   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (container)->priv;
100
101   if (priv->child)
102     clutter_actor_unparent (priv->child);
103
104   clutter_actor_set_parent (actor, CLUTTER_ACTOR (container));
105   priv->child = actor;
106
107   clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
108
109   g_signal_emit_by_name (container, "actor-added", actor);
110   g_object_notify (G_OBJECT (container), "child");
111 }
112
113 static void
114 gtk_clutter_viewport_remove (ClutterContainer *container,
115                              ClutterActor     *actor)
116 {
117   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (container)->priv;
118
119   if (G_LIKELY (priv->child == actor))
120     {
121       g_object_ref (actor);
122
123       clutter_actor_unparent (actor);
124       priv->child = NULL;
125
126       clutter_actor_queue_relayout (CLUTTER_ACTOR (container));
127
128       g_signal_emit_by_name (container, "actor-removed", actor);
129
130       g_object_unref (actor);
131     }
132 }
133
134 static void
135 gtk_clutter_viewport_foreach (ClutterContainer *container,
136                               ClutterCallback   callback,
137                               gpointer          callback_data)
138 {
139   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (container)->priv;
140
141   if (G_LIKELY (priv->child))
142     callback (priv->child, callback_data);
143 }
144
145 static void
146 clutter_container_iface_init (gpointer g_iface)
147 {
148   ClutterContainerIface *iface = g_iface;
149
150   iface->add = gtk_clutter_viewport_add;
151   iface->remove = gtk_clutter_viewport_remove;
152   iface->foreach = gtk_clutter_viewport_foreach;
153 }
154
155 static void
156 viewport_adjustment_value_changed (GtkAdjustment      *adjustment,
157                                    GtkClutterViewport *viewport)
158 {
159   GtkClutterViewportPrivate *priv = viewport->priv;
160
161   if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
162     {
163       GtkAdjustment *x_adjust = priv->x_adjustment;
164       GtkAdjustment *y_adjust = priv->y_adjustment;
165       GtkAdjustment *z_adjust = priv->z_adjustment;
166       gfloat new_x, new_y, new_z;
167
168       new_x = gtk_adjustment_get_value (x_adjust);
169       new_y = gtk_adjustment_get_value (y_adjust);
170       new_z = gtk_adjustment_get_value (z_adjust);
171
172       /* change the origin and queue a relayout */
173       if (new_x != priv->origin.x ||
174           new_y != priv->origin.y ||
175           new_z != priv->origin.z)
176         {
177           priv->origin.x = new_x;
178           priv->origin.y = new_y;
179           priv->origin.z = new_z;
180
181           clutter_actor_queue_relayout (CLUTTER_ACTOR (viewport));
182
183           g_object_notify (G_OBJECT (viewport), "origin");
184         }
185     }
186 }
187
188 static gboolean
189 viewport_reclamp_adjustment (GtkAdjustment *adjustment)
190 {
191   gdouble value = gtk_adjustment_get_value (adjustment);
192   gdouble limit;
193
194   limit = gtk_adjustment_get_upper (adjustment)
195         - gtk_adjustment_get_page_size (adjustment);
196
197   value = CLAMP (value, 0, limit);
198   if (value != gtk_adjustment_get_value (adjustment))
199     {
200       gtk_adjustment_set_value (adjustment, value);
201       return TRUE;
202     }
203   else
204     return FALSE;
205 }
206
207 static gboolean
208 viewport_set_hadjustment_values (GtkClutterViewport *viewport,
209                                  gfloat              width)
210 {
211   GtkClutterViewportPrivate *priv = viewport->priv;
212   GtkAdjustment *h_adjust = priv->x_adjustment;
213
214   if (width < 0)
215     clutter_actor_get_preferred_width (CLUTTER_ACTOR (viewport), -1,
216                                        NULL,
217                                        &width);
218
219   gtk_adjustment_set_page_size (h_adjust, width);
220   gtk_adjustment_set_step_increment (h_adjust, width * 0.1);
221   gtk_adjustment_set_page_increment (h_adjust, width * 0.9);
222   gtk_adjustment_set_lower (h_adjust, 0);
223
224   if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
225     {
226       gfloat natural_width;
227
228       clutter_actor_get_preferred_size (priv->child,
229                                         NULL, NULL,
230                                         &natural_width, NULL);
231
232       gtk_adjustment_set_upper (h_adjust, MAX (natural_width, width));
233     }
234   else
235     gtk_adjustment_set_upper (h_adjust, width);
236
237   return viewport_reclamp_adjustment (h_adjust);
238 }
239
240 static gboolean
241 viewport_set_vadjustment_values (GtkClutterViewport *viewport,
242                                  gfloat              height)
243 {
244   GtkClutterViewportPrivate *priv = viewport->priv;
245   GtkAdjustment *v_adjust = priv->y_adjustment;
246
247   if (height < 0)
248     clutter_actor_get_preferred_height (CLUTTER_ACTOR (viewport), -1,
249                                         NULL,
250                                         &height);
251
252   gtk_adjustment_set_page_size (v_adjust, height);
253   gtk_adjustment_set_step_increment (v_adjust, height * 0.1);
254   gtk_adjustment_set_page_increment (v_adjust, height * 0.9);
255   gtk_adjustment_set_lower (v_adjust, 0);
256
257   if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
258     {
259       gfloat natural_height;
260
261       clutter_actor_get_preferred_size (priv->child,
262                                         NULL, NULL,
263                                         NULL, &natural_height);
264
265       gtk_adjustment_set_upper (v_adjust, MAX (natural_height, height));
266     }
267   else
268     gtk_adjustment_set_upper (v_adjust, height);
269
270   return viewport_reclamp_adjustment (v_adjust);
271 }
272
273 static gboolean
274 viewport_set_zadjustment_values (GtkClutterViewport *viewport,
275                                  gfloat              width,
276                                  gfloat              height)
277 {
278   GtkClutterViewportPrivate *priv = viewport->priv;
279   GtkAdjustment *z_adjust = priv->z_adjustment;
280   gfloat depth;
281
282   if (width < 0)
283     clutter_actor_get_preferred_width (CLUTTER_ACTOR (viewport), -1,
284                                        NULL,
285                                        &width);
286
287   if (height < 0)
288     clutter_actor_get_preferred_height (CLUTTER_ACTOR (viewport), -1,
289                                         NULL,
290                                         &height);
291
292   depth = clutter_actor_get_depth (CLUTTER_ACTOR (viewport));
293
294   gtk_adjustment_set_page_size (z_adjust, 0.0);
295   gtk_adjustment_set_step_increment (z_adjust, MIN (width, height) * 0.1);
296   gtk_adjustment_set_page_increment (z_adjust, MIN (width, height) * 0.9);
297   gtk_adjustment_set_lower (z_adjust, 0.0);
298   gtk_adjustment_set_upper (z_adjust, MAX (width, height));
299
300 #if 0
301   g_debug ("%s: zadjustment: { %.2f lower, %.2f page, %.2f upper }",
302            G_STRLOC,
303            gtk_adjustment_get_lower (z_adjust),
304            gtk_adjustment_get_page_size (z_adjust),
305            gtk_adjustment_get_upper (z_adjust));
306 #endif
307
308   return viewport_reclamp_adjustment (z_adjust);
309 }
310
311 static inline void
312 disconnect_adjustment (GtkClutterViewport *viewport,
313                        ViewportAxis        axis)
314 {
315   GtkClutterViewportPrivate *priv = viewport->priv;
316   GtkAdjustment **adj_p = NULL;
317
318   switch (axis)
319     {
320     case VIEWPORT_X_AXIS:
321       adj_p = &priv->x_adjustment;
322       break;
323
324     case VIEWPORT_Y_AXIS:
325       adj_p = &priv->y_adjustment;
326       break;
327
328     case VIEWPORT_Z_AXIS:
329       adj_p = &priv->z_adjustment;
330       break;
331
332     default:
333       g_assert_not_reached ();
334       break;
335     }
336
337   if (*adj_p != NULL)
338     {
339       g_signal_handlers_disconnect_by_func (*adj_p,
340                                             viewport_adjustment_value_changed,
341                                             viewport);
342       g_object_unref (*adj_p);
343       *adj_p = NULL;
344     }
345 }
346
347 static inline void
348 connect_adjustment (GtkClutterViewport *viewport,
349                     ViewportAxis        axis,
350                     GtkAdjustment      *adjustment)
351 {
352   GtkClutterViewportPrivate *priv = viewport->priv;
353   GtkAdjustment **adj_p = NULL;
354   gboolean value_changed = FALSE;
355   gfloat width, height;
356
357   switch (axis)
358     {
359     case VIEWPORT_X_AXIS:
360       adj_p = &priv->x_adjustment;
361       break;
362
363     case VIEWPORT_Y_AXIS:
364       adj_p = &priv->y_adjustment;
365       break;
366
367     case VIEWPORT_Z_AXIS:
368       adj_p = &priv->z_adjustment;
369       break;
370
371     default:
372       g_assert_not_reached ();
373       break;
374     }
375
376   if (adjustment && adjustment == *adj_p)
377     return;
378
379   if (!adjustment)
380     adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, 0, 0, 0, 0));
381
382   disconnect_adjustment (viewport, axis);
383   *adj_p = g_object_ref_sink (adjustment);
384
385   clutter_actor_get_size (CLUTTER_ACTOR (viewport), &width, &height);
386
387   switch (axis)
388     {
389     case VIEWPORT_X_AXIS:
390       value_changed = viewport_set_hadjustment_values (viewport, width);
391       break;
392
393     case VIEWPORT_Y_AXIS:
394       value_changed = viewport_set_vadjustment_values (viewport, height);
395       break;
396
397     case VIEWPORT_Z_AXIS:
398       value_changed = viewport_set_zadjustment_values (viewport,
399                                                        width,
400                                                        height);
401       break;
402
403     default:
404       g_assert_not_reached ();
405       break;
406     }
407
408   g_signal_connect (adjustment, "value-changed",
409                     G_CALLBACK (viewport_adjustment_value_changed),
410                     viewport);
411
412   gtk_adjustment_changed (adjustment);
413
414   if (value_changed)
415     gtk_adjustment_value_changed (adjustment);
416   else
417     viewport_adjustment_value_changed (adjustment, viewport);
418
419   switch (axis)
420     {
421     case VIEWPORT_X_AXIS:
422       g_object_notify (G_OBJECT (viewport), "hadjustment");
423       break;
424
425     case VIEWPORT_Y_AXIS:
426       g_object_notify (G_OBJECT (viewport), "vadjustment");
427       break;
428
429     case VIEWPORT_Z_AXIS:
430       g_object_notify (G_OBJECT (viewport), "zadjustment");
431       break;
432
433     default:
434       g_assert_not_reached ();
435       break;
436     }
437 }
438
439 static void
440 scrollable_set_adjustments (GtkClutterScrollable *scrollable,
441                             GtkAdjustment        *h_adjust,
442                             GtkAdjustment        *v_adjust)
443 {
444   g_object_freeze_notify (G_OBJECT (scrollable));
445
446   connect_adjustment (GTK_CLUTTER_VIEWPORT (scrollable),
447                       VIEWPORT_X_AXIS,
448                       h_adjust);
449   connect_adjustment (GTK_CLUTTER_VIEWPORT (scrollable),
450                       VIEWPORT_Y_AXIS,
451                       v_adjust);
452
453   g_object_thaw_notify (G_OBJECT (scrollable));
454 }
455
456 static void
457 scrollable_get_adjustments (GtkClutterScrollable  *scrollable,
458                             GtkAdjustment        **h_adjust,
459                             GtkAdjustment        **v_adjust)
460 {
461   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (scrollable)->priv;
462
463   if (h_adjust)
464     *h_adjust = priv->x_adjustment;
465
466   if (v_adjust)
467     *v_adjust = priv->y_adjustment;
468 }
469
470 static void
471 gtk_clutter_scrollable_iface_init (gpointer g_iface)
472 {
473   GtkClutterScrollableIface *iface = g_iface;
474
475   iface->set_adjustments = scrollable_set_adjustments;
476   iface->get_adjustments = scrollable_get_adjustments;
477 }
478
479 static void
480 zoomable_set_adjustment (GtkClutterZoomable *zoomable,
481                          GtkAdjustment      *adjust)
482 {
483   g_object_freeze_notify (G_OBJECT (zoomable));
484
485   connect_adjustment (GTK_CLUTTER_VIEWPORT (zoomable),
486                       VIEWPORT_Z_AXIS,
487                       adjust);
488
489   g_object_thaw_notify (G_OBJECT (zoomable));
490 }
491
492 static GtkAdjustment *
493 zoomable_get_adjustment (GtkClutterZoomable *zoomable)
494 {
495   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (zoomable)->priv;
496
497   return priv->z_adjustment;
498 }
499
500 static void
501 gtk_clutter_zoomable_iface_init (gpointer g_iface)
502 {
503   GtkClutterZoomableIface *iface = g_iface;
504
505   iface->set_adjustment = zoomable_set_adjustment;
506   iface->get_adjustment = zoomable_get_adjustment;
507 }
508
509 static void
510 gtk_clutter_viewport_set_property (GObject      *gobject,
511                                    guint         prop_id,
512                                    const GValue *value,
513                                    GParamSpec   *pspec)
514 {
515   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
516
517   switch (prop_id)
518     {
519     case PROP_CHILD:
520       clutter_container_add_actor (CLUTTER_CONTAINER (gobject),
521                                    g_value_get_object (value));
522       break;
523
524     case PROP_ORIGIN:
525       {
526         ClutterVertex *v = g_value_get_boxed (value);
527
528         priv->origin = *v;
529
530         if (CLUTTER_ACTOR_IS_VISIBLE (gobject))
531           clutter_actor_queue_redraw (CLUTTER_ACTOR (gobject));
532       }
533       break;
534
535     case PROP_H_ADJUSTMENT:
536       connect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
537                           VIEWPORT_X_AXIS,
538                           g_value_get_object (value));
539       break;
540
541     case PROP_V_ADJUSTMENT:
542       connect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
543                           VIEWPORT_Y_AXIS,
544                           g_value_get_object (value));
545       break;
546
547     case PROP_Z_ADJUSTMENT:
548       connect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
549                           VIEWPORT_Z_AXIS,
550                           g_value_get_object (value));
551       break;
552
553     default:
554       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
555       break;
556     }
557 }
558
559 static void
560 gtk_clutter_viewport_get_property (GObject    *gobject,
561                                    guint       prop_id,
562                                    GValue     *value,
563                                    GParamSpec *pspec)
564 {
565   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
566
567   switch (prop_id)
568     {
569     case PROP_CHILD:
570       g_value_set_object (value, priv->child);
571       break;
572
573     case PROP_ORIGIN:
574       g_value_set_boxed (value, &priv->origin);
575       break;
576
577     case PROP_H_ADJUSTMENT:
578       g_value_set_object (value, priv->x_adjustment);
579       break;
580
581     case PROP_V_ADJUSTMENT:
582       g_value_set_object (value, priv->y_adjustment);
583       break;
584
585     case PROP_Z_ADJUSTMENT:
586       g_value_set_object (value, priv->z_adjustment);
587       break;
588
589     default:
590       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
591       break;
592     }
593 }
594
595 static void
596 gtk_clutter_viewport_dispose (GObject *gobject)
597 {
598   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (gobject)->priv;
599
600   if (priv->child)
601     {
602       clutter_actor_destroy (priv->child);
603       priv->child = NULL;
604     }
605
606   disconnect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
607                          VIEWPORT_X_AXIS);
608   disconnect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
609                          VIEWPORT_Y_AXIS);
610   disconnect_adjustment (GTK_CLUTTER_VIEWPORT (gobject),
611                          VIEWPORT_Z_AXIS);
612
613   G_OBJECT_CLASS (gtk_clutter_viewport_parent_class)->dispose (gobject);
614 }
615
616 static void
617 gtk_clutter_viewport_get_preferred_width (ClutterActor *actor,
618                                           gfloat        for_height,
619                                           gfloat       *min_width_p,
620                                           gfloat       *natural_width_p)
621 {
622   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
623
624   /* we don't have a minimum size */
625   if (min_width_p)
626     *min_width_p = 0;
627
628   /* if we have a child, we want to be as big as the child
629    * wishes to be; otherwise, we don't have a preferred width
630    */
631   if (priv->child)
632     clutter_actor_get_preferred_width (priv->child, for_height,
633                                        NULL,
634                                        natural_width_p);
635   else
636     {
637       if (natural_width_p)
638         *natural_width_p = 0;
639     }
640 }
641
642 static void
643 gtk_clutter_viewport_get_preferred_height (ClutterActor *actor,
644                                            gfloat        for_width,
645                                            gfloat       *min_height_p,
646                                            gfloat       *natural_height_p)
647 {
648   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
649
650   /* we don't have a minimum size */
651   if (min_height_p)
652     *min_height_p = 0;
653
654   /* if we have a child, we want to be as big as the child
655    * wishes to be; otherwise, we don't have a preferred height
656    */
657   if (priv->child)
658     clutter_actor_get_preferred_height (priv->child, for_width,
659                                         NULL,
660                                         natural_height_p);
661   else
662     {
663       if (natural_height_p)
664         *natural_height_p = 0;
665     }
666 }
667
668 static void
669 gtk_clutter_viewport_allocate (ClutterActor           *actor,
670                                const ClutterActorBox  *box,
671                                ClutterAllocationFlags  flags)
672 {
673   GtkClutterViewport *viewport = GTK_CLUTTER_VIEWPORT (actor);
674   GtkClutterViewportPrivate *priv = viewport->priv;
675   ClutterActorClass *parent_class;
676   gboolean x_adjustment_value_changed;
677   gboolean y_adjustment_value_changed;
678   gboolean z_adjustment_value_changed;
679   gfloat width, height;
680
681   parent_class = CLUTTER_ACTOR_CLASS (gtk_clutter_viewport_parent_class);
682   parent_class->allocate (actor, box, flags);
683
684   width  = box->x2 - box->x1;
685   height = box->y2 - box->y1;
686
687   x_adjustment_value_changed =
688     viewport_set_hadjustment_values (viewport, width);
689   y_adjustment_value_changed =
690     viewport_set_vadjustment_values (viewport, height);
691   z_adjustment_value_changed =
692     viewport_set_zadjustment_values (viewport, width, height);
693
694   if (priv->child && CLUTTER_ACTOR_IS_VISIBLE (priv->child))
695     {
696       ClutterActorBox child_allocation = { 0, };
697       gfloat alloc_width, alloc_height;
698
699       /* a viewport is a boundless actor which can contain a child
700        * without constraints; hence, we give any child exactly the
701        * wanted natural size, no matter how small the viewport
702        * actually is.
703        */
704       clutter_actor_get_preferred_size (priv->child,
705                                         NULL, NULL,
706                                         &alloc_width,
707                                         &alloc_height);
708
709       child_allocation.x1 = 0;
710       child_allocation.y1 = 0;
711       child_allocation.x2 = child_allocation.x1 + alloc_width;
712       child_allocation.y2 = child_allocation.y1 + alloc_height;
713
714       clutter_actor_allocate (priv->child, &child_allocation, flags);
715     }
716
717   gtk_adjustment_changed (priv->x_adjustment);
718   gtk_adjustment_changed (priv->y_adjustment);
719   gtk_adjustment_changed (priv->z_adjustment);
720
721   if (x_adjustment_value_changed)
722     gtk_adjustment_value_changed (priv->x_adjustment);
723
724   if (y_adjustment_value_changed)
725     gtk_adjustment_value_changed (priv->y_adjustment);
726
727   if (z_adjustment_value_changed)
728     gtk_adjustment_value_changed (priv->z_adjustment);
729 }
730
731 static void
732 gtk_clutter_viewport_apply_transform (ClutterActor *actor,
733                                       CoglMatrix   *matrix)
734 {
735   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
736   ClutterActorClass *parent_class;
737
738   parent_class = CLUTTER_ACTOR_CLASS (gtk_clutter_viewport_parent_class);
739   parent_class->apply_transform (actor, matrix);
740
741   cogl_matrix_translate (matrix,
742                          priv->origin.x * -1,
743                          priv->origin.y * -1,
744                          priv->origin.z * -1);
745 }
746
747 static void
748 gtk_clutter_viewport_paint (ClutterActor *actor)
749 {
750   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
751
752   if (priv->child)
753     clutter_actor_paint (priv->child);
754 }
755
756 static void
757 gtk_clutter_viewport_pick (ClutterActor       *actor,
758                            const ClutterColor *pick_color)
759 {
760   GtkClutterViewportPrivate *priv = GTK_CLUTTER_VIEWPORT (actor)->priv;
761   ClutterActorClass *parent_class;
762
763   parent_class = CLUTTER_ACTOR_CLASS (gtk_clutter_viewport_parent_class);
764   parent_class->pick (actor, pick_color);
765
766   if (priv->child)
767     clutter_actor_paint (priv->child);
768 }
769
770 static void
771 gtk_clutter_viewport_class_init (GtkClutterViewportClass *klass)
772 {
773   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
774   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
775   GParamSpec *pspec;
776
777   g_type_class_add_private (klass, sizeof (GtkClutterViewportPrivate));
778
779   gobject_class->set_property = gtk_clutter_viewport_set_property;
780   gobject_class->get_property = gtk_clutter_viewport_get_property;
781   gobject_class->dispose = gtk_clutter_viewport_dispose;
782
783   actor_class->get_preferred_width = gtk_clutter_viewport_get_preferred_width;
784   actor_class->get_preferred_height = gtk_clutter_viewport_get_preferred_height;
785   actor_class->allocate = gtk_clutter_viewport_allocate;
786   actor_class->apply_transform = gtk_clutter_viewport_apply_transform;
787   actor_class->paint = gtk_clutter_viewport_paint;
788   actor_class->pick = gtk_clutter_viewport_pick;
789
790   /**
791    * GtkClutterViewport:child:
792    *
793    * The #ClutterActor inside the viewport.
794    *
795    * Since: 0.10
796    */
797   pspec = g_param_spec_object ("child",
798                                "Child",
799                                "The ClutterActor inside the viewport",
800                                CLUTTER_TYPE_ACTOR,
801                                G_PARAM_READWRITE);
802   g_object_class_install_property (gobject_class, PROP_CHILD, pspec);
803
804   /**
805    * GtkClutterViewport:origin:
806    *
807    * The current origin of the viewport. You should use the
808    * vertex to convert event coordinates for the child of the
809    * viewport.
810    *
811    * Since: 0.10
812    */
813   pspec = g_param_spec_boxed ("origin",
814                               "Origin",
815                               "The current origin of the viewport",
816                               CLUTTER_TYPE_VERTEX,
817                               G_PARAM_READABLE);
818   g_object_class_install_property (gobject_class, PROP_ORIGIN, pspec);
819
820   /* GtkClutterScrollable properties */
821   g_object_class_override_property (gobject_class,
822                                     PROP_H_ADJUSTMENT,
823                                     "hadjustment");
824   g_object_class_override_property (gobject_class,
825                                     PROP_V_ADJUSTMENT,
826                                     "vadjustment");
827
828   /* GtkClutterZoomable properties */
829   g_object_class_override_property (gobject_class,
830                                     PROP_Z_ADJUSTMENT,
831                                     "zadjustment");
832 }
833
834 static void
835 gtk_clutter_viewport_init (GtkClutterViewport *viewport)
836 {
837   GtkClutterViewportPrivate *priv;
838
839   viewport->priv = priv = GET_PRIVATE (viewport);
840 }
841
842 /**
843  * gtk_clutter_viewport_new:
844  * @h_adjust: horizontal adjustment, or %NULL
845  * @v_adjust: vertical adjustment, or %NULL
846  * @z_adjust: zoom adjustment, or %NULL
847  *
848  * Creates a new #GtkClutterViewport with the given adjustments.
849  *
850  * Return value: the newly created viewport actor
851  *
852  * Since: 0.10
853  */
854 ClutterActor *
855 gtk_clutter_viewport_new (GtkAdjustment *h_adjust,
856                           GtkAdjustment *v_adjust,
857                           GtkAdjustment *z_adjust)
858 {
859   return g_object_new (GTK_CLUTTER_TYPE_VIEWPORT,
860                        "hadjustment", h_adjust,
861                        "vadjustment", v_adjust,
862                        "zadjustment", z_adjust,
863                        NULL);
864 }
865
866 /**
867  * gtk_clutter_viewport_get_origin:
868  * @viewport: a #GtkClutterViewport
869  * @x: return location for the X origin in pixels, or %NULL
870  * @y: return location for the Y origin in pixels, or %NULL
871  * @z: return location for the Z origin in pixels, or %NULL
872  *
873  * Retrieves the current translation factor ("origin") used when
874  * displaying the child of @viewport.
875  *
876  * Since: 0.10
877  */
878 void
879 gtk_clutter_viewport_get_origin (GtkClutterViewport *viewport,
880                                  gfloat             *x,
881                                  gfloat             *y,
882                                  gfloat             *z)
883 {
884   GtkClutterViewportPrivate *priv;
885
886   g_return_if_fail (GTK_CLUTTER_IS_VIEWPORT (viewport));
887
888   priv = viewport->priv;
889
890   if (x)
891     *x = priv->origin.x;
892
893   if (y)
894     *y = priv->origin.y;
895
896   if (z)
897     *z = priv->origin.z;
898 }