Release Clutter 1.11.4 (snapshot)
[profile/ivi/clutter.git] / clutter / clutter-flow-layout.c
1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Copyright (C) 2009  Intel Corporation.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20  *
21  * Author:
22  *   Emmanuele Bassi <ebassi@linux.intel.com>
23  */
24
25 /**
26  * SECTION:clutter-flow-layout
27  * @short_description: A reflowing layout manager
28  *
29  * #ClutterFlowLayout is a layout manager which implements the following
30  * policy:
31  *
32  * <itemizedlist>
33  *   <listitem><para>the preferred natural size depends on the value
34  *   of the #ClutterFlowLayout:orientation property; the layout will try
35  *   to maintain all its children on a single row or
36  *   column;</para></listitem>
37  *   <listitem><para>if either the width or the height allocated are
38  *   smaller than the preferred ones, the layout will wrap; in this case,
39  *   the preferred height or width, respectively, will take into account
40  *   the amount of columns and rows;</para></listitem>
41  *   <listitem><para>each line (either column or row) in reflowing will
42  *   have the size of the biggest cell on that line; if the
43  *   #ClutterFlowLayout:homogeneous property is set to %FALSE the actor
44  *   will be allocated within that area, and if set to %TRUE instead the
45  *   actor will be given exactly that area;</para></listitem>
46  *   <listitem><para>the size of the columns or rows can be controlled
47  *   for both minimum and maximum; the spacing can also be controlled
48  *   in both columns and rows.</para></listitem>
49  * </itemizedlist>
50  *
51  * <figure id="flow-layout-image">
52  *   <title>Horizontal flow layout</title>
53  *   <para>The image shows a #ClutterFlowLayout with the
54  *   #ClutterFlowLayout:orientation propert set to
55  *   %CLUTTER_FLOW_HORIZONTAL.</para>
56  *   <graphic fileref="flow-layout.png" format="PNG"/>
57  * </figure>
58  *
59  * <informalexample>
60  *  <programlisting>
61  * <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../examples/flow-layout.c">
62  *   <xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback>
63  * </xi:include>
64  *  </programlisting>
65  * </informalexample>
66  *
67  * #ClutterFlowLayout is available since Clutter 1.2
68  */
69
70 #ifdef HAVE_CONFIG_H
71 #include "config.h"
72 #endif
73
74 #include <math.h>
75
76 #define CLUTTER_DISABLE_DEPRECATION_WARNINGS
77 #include "deprecated/clutter-container.h"
78
79 #include "clutter-actor.h"
80 #include "clutter-animatable.h"
81 #include "clutter-child-meta.h"
82 #include "clutter-debug.h"
83 #include "clutter-enum-types.h"
84 #include "clutter-flow-layout.h"
85 #include "clutter-layout-meta.h"
86 #include "clutter-private.h"
87
88 #define CLUTTER_FLOW_LAYOUT_GET_PRIVATE(obj)    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_FLOW_LAYOUT, ClutterFlowLayoutPrivate))
89
90 struct _ClutterFlowLayoutPrivate
91 {
92   ClutterContainer *container;
93
94   ClutterFlowOrientation orientation;
95
96   gfloat col_spacing;
97   gfloat row_spacing;
98
99   gfloat min_col_width;
100   gfloat max_col_width;
101   gfloat col_width;
102
103   gfloat min_row_height;
104   gfloat max_row_height;
105   gfloat row_height;
106
107   /* per-line size */
108   GArray *line_min;
109   GArray *line_natural;
110   gfloat req_width;
111   gfloat req_height;
112
113   guint line_count;
114
115   guint is_homogeneous : 1;
116 };
117
118 enum
119 {
120   PROP_0,
121
122   PROP_ORIENTATION,
123
124   PROP_HOMOGENEOUS,
125
126   PROP_COLUMN_SPACING,
127   PROP_ROW_SPACING,
128
129   PROP_MIN_COLUMN_WIDTH,
130   PROP_MAX_COLUMN_WIDTH,
131   PROP_MIN_ROW_HEGHT,
132   PROP_MAX_ROW_HEIGHT,
133
134   N_PROPERTIES
135 };
136
137 static GParamSpec *flow_properties[N_PROPERTIES] = { NULL, };
138
139 G_DEFINE_TYPE (ClutterFlowLayout,
140                clutter_flow_layout,
141                CLUTTER_TYPE_LAYOUT_MANAGER);
142
143 static gint
144 get_columns (ClutterFlowLayout *self,
145              gfloat             for_width)
146 {
147   ClutterFlowLayoutPrivate *priv = self->priv;
148   gint n_columns;
149
150   if (for_width < 0)
151     return 1;
152
153   if (priv->col_width == 0)
154     return 1;
155
156   n_columns = (gint) (for_width + priv->col_spacing)
157             / (priv->col_width + priv->col_spacing);
158
159   if (n_columns == 0)
160     return 1;
161
162   return n_columns;
163 }
164
165 static gint
166 get_rows (ClutterFlowLayout *self,
167           gfloat             for_height)
168 {
169   ClutterFlowLayoutPrivate *priv = self->priv;
170   gint n_rows;
171
172   if (for_height < 0)
173     return 1;
174
175   if (priv->row_height == 0)
176     return 1;
177
178   n_rows = (gint) (for_height + priv->row_spacing)
179          / (priv->row_height + priv->row_spacing);
180
181   if (n_rows == 0)
182     return 1;
183
184   return n_rows;
185 }
186
187 static gint
188 compute_lines (ClutterFlowLayout *self,
189                gfloat             avail_width,
190                gfloat             avail_height)
191 {
192   ClutterFlowLayoutPrivate *priv = self->priv;
193
194   if (priv->orientation == CLUTTER_FLOW_HORIZONTAL)
195     return get_columns (self, avail_width);
196   else
197     return get_rows (self, avail_height);
198 }
199
200 static void
201 clutter_flow_layout_get_preferred_width (ClutterLayoutManager *manager,
202                                          ClutterContainer     *container,
203                                          gfloat                for_height,
204                                          gfloat               *min_width_p,
205                                          gfloat               *nat_width_p)
206 {
207   ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (manager)->priv;
208   gint n_rows, line_item_count, line_count;
209   gfloat total_min_width, total_natural_width;
210   gfloat line_min_width, line_natural_width;
211   gfloat max_min_width, max_natural_width;
212   ClutterActor *actor, *child;
213   ClutterActorIter iter;
214   gfloat item_y;
215
216   n_rows = get_rows (CLUTTER_FLOW_LAYOUT (manager), for_height);
217
218   total_min_width = 0;
219   total_natural_width = 0;
220
221   line_min_width = 0;
222   line_natural_width = 0;
223
224   line_item_count = 0;
225   line_count = 0;
226
227   item_y = 0;
228
229   actor = CLUTTER_ACTOR (container);
230
231   /* clear the line width arrays */
232   if (priv->line_min != NULL)
233     g_array_free (priv->line_min, TRUE);
234
235   if (priv->line_natural != NULL)
236     g_array_free (priv->line_natural, TRUE);
237
238   priv->line_min = g_array_sized_new (FALSE, FALSE,
239                                       sizeof (gfloat),
240                                       16);
241   priv->line_natural = g_array_sized_new (FALSE, FALSE,
242                                           sizeof (gfloat),
243                                           16);
244
245   if (clutter_actor_get_n_children (actor) != 0)
246     line_count = 1;
247
248   max_min_width = max_natural_width = 0;
249
250   clutter_actor_iter_init (&iter, actor);
251   while (clutter_actor_iter_next (&iter, &child))
252     {
253       gfloat child_min, child_natural;
254       gfloat new_y, item_height;
255
256       if (!CLUTTER_ACTOR_IS_VISIBLE (child))
257         continue;
258
259       if (priv->orientation == CLUTTER_FLOW_VERTICAL && for_height > 0)
260         {
261           if (line_item_count == n_rows)
262             {
263               total_min_width += line_min_width;
264               total_natural_width += line_natural_width;
265
266               g_array_append_val (priv->line_min,
267                                   line_min_width);
268               g_array_append_val (priv->line_natural,
269                                   line_natural_width);
270
271               line_min_width = line_natural_width = 0;
272
273               line_item_count = 0;
274               line_count += 1;
275               item_y = 0;
276             }
277
278           new_y = ((line_item_count + 1) * (for_height + priv->row_spacing))
279                 / n_rows;
280           item_height = new_y - item_y - priv->row_spacing;
281
282           clutter_actor_get_preferred_width (child, item_height,
283                                              &child_min,
284                                              &child_natural);
285
286           line_min_width = MAX (line_min_width, child_min);
287           line_natural_width = MAX (line_natural_width, child_natural);
288
289           item_y = new_y;
290           line_item_count += 1;
291
292           max_min_width = MAX (max_min_width, line_min_width);
293           max_natural_width = MAX (max_natural_width, line_natural_width);
294         }
295       else
296         {
297           clutter_actor_get_preferred_width (child, for_height,
298                                              &child_min,
299                                              &child_natural);
300
301           max_min_width = MAX (max_min_width, child_min);
302           max_natural_width = MAX (max_natural_width, child_natural);
303
304           total_min_width += max_min_width;
305           total_natural_width += max_natural_width;
306           line_count += 1;
307         }
308     }
309
310   priv->col_width = max_natural_width;
311
312   if (priv->max_col_width > 0 && priv->col_width > priv->max_col_width)
313     priv->col_width = MAX (priv->max_col_width, max_min_width);
314
315   if (priv->col_width < priv->min_col_width)
316     priv->col_width = priv->min_col_width;
317
318   if (priv->orientation == CLUTTER_FLOW_VERTICAL && for_height > 0)
319     {
320       /* if we have a non-full row we need to add it */
321       if (line_item_count > 0)
322         {
323           total_min_width += line_min_width;
324           total_natural_width += line_natural_width;
325
326           g_array_append_val (priv->line_min,
327                               line_min_width);
328           g_array_append_val (priv->line_natural,
329                               line_natural_width);
330         }
331
332       priv->line_count = line_count;
333
334       if (priv->line_count > 0)
335         {
336           gfloat total_spacing;
337
338           total_spacing = priv->col_spacing * (priv->line_count - 1);
339
340           total_min_width += total_spacing;
341           total_natural_width += total_spacing;
342         }
343     }
344   else
345     {
346       g_array_append_val (priv->line_min, line_min_width);
347       g_array_append_val (priv->line_natural, line_natural_width);
348
349       priv->line_count = line_count;
350
351       if (priv->line_count > 0)
352         {
353           gfloat total_spacing;
354
355           total_spacing = priv->col_spacing * (priv->line_count - 1);
356
357           total_min_width += total_spacing;
358           total_natural_width += total_spacing;
359         }
360     }
361
362   CLUTTER_NOTE (LAYOUT,
363                 "Flow[w]: %d lines (%d per line): w [ %.2f, %.2f ] for h %.2f",
364                 n_rows, priv->line_count,
365                 total_min_width,
366                 total_natural_width,
367                 for_height);
368
369   priv->req_height = for_height;
370
371   if (min_width_p)
372     *min_width_p = max_min_width;
373
374   if (nat_width_p)
375     *nat_width_p = total_natural_width;
376 }
377
378 static void
379 clutter_flow_layout_get_preferred_height (ClutterLayoutManager *manager,
380                                           ClutterContainer     *container,
381                                           gfloat                for_width,
382                                           gfloat               *min_height_p,
383                                           gfloat               *nat_height_p)
384 {
385   ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (manager)->priv;
386   gint n_columns, line_item_count, line_count;
387   gfloat total_min_height, total_natural_height;
388   gfloat line_min_height, line_natural_height;
389   gfloat max_min_height, max_natural_height;
390   ClutterActor *actor, *child;
391   ClutterActorIter iter;
392   gfloat item_x;
393
394   n_columns = get_columns (CLUTTER_FLOW_LAYOUT (manager), for_width);
395
396   total_min_height = 0;
397   total_natural_height = 0;
398
399   line_min_height = 0;
400   line_natural_height = 0;
401
402   line_item_count = 0;
403   line_count = 0;
404
405   item_x = 0;
406
407   actor = CLUTTER_ACTOR (container);
408
409   /* clear the line height arrays */
410   if (priv->line_min != NULL)
411     g_array_free (priv->line_min, TRUE);
412
413   if (priv->line_natural != NULL)
414     g_array_free (priv->line_natural, TRUE);
415
416   priv->line_min = g_array_sized_new (FALSE, FALSE,
417                                       sizeof (gfloat),
418                                       16);
419   priv->line_natural = g_array_sized_new (FALSE, FALSE,
420                                           sizeof (gfloat),
421                                           16);
422
423   if (clutter_actor_get_n_children (actor) != 0)
424     line_count = 1;
425
426   max_min_height = max_natural_height = 0;
427
428   clutter_actor_iter_init (&iter, actor);
429   while (clutter_actor_iter_next (&iter, &child))
430     {
431       gfloat child_min, child_natural;
432       gfloat new_x, item_width;
433
434       if (!CLUTTER_ACTOR_IS_VISIBLE (child))
435         continue;
436
437       if (priv->orientation == CLUTTER_FLOW_HORIZONTAL && for_width > 0)
438         {
439           if (line_item_count == n_columns)
440             {
441               total_min_height += line_min_height;
442               total_natural_height += line_natural_height;
443
444               g_array_append_val (priv->line_min,
445                                   line_min_height);
446               g_array_append_val (priv->line_natural,
447                                   line_natural_height);
448
449               line_min_height = line_natural_height = 0;
450
451               line_item_count = 0;
452               line_count += 1;
453               item_x = 0;
454             }
455
456           new_x = ((line_item_count + 1) * (for_width + priv->col_spacing))
457                 / n_columns;
458           item_width = new_x - item_x - priv->col_spacing;
459
460           clutter_actor_get_preferred_height (child, item_width,
461                                               &child_min,
462                                               &child_natural);
463
464           line_min_height = MAX (line_min_height, child_min);
465           line_natural_height = MAX (line_natural_height, child_natural);
466
467           item_x = new_x;
468           line_item_count += 1;
469
470           max_min_height = MAX (max_min_height, line_min_height);
471           max_natural_height = MAX (max_natural_height, line_natural_height);
472         }
473       else
474         {
475           clutter_actor_get_preferred_height (child, for_width,
476                                               &child_min,
477                                               &child_natural);
478
479           max_min_height = MAX (max_min_height, child_min);
480           max_natural_height = MAX (max_natural_height, child_natural);
481
482           total_min_height += max_min_height;
483           total_natural_height += max_natural_height;
484
485           line_count += 1;
486         }
487     }
488
489   priv->row_height = max_natural_height;
490
491   if (priv->max_row_height > 0 && priv->row_height > priv->max_row_height)
492     priv->row_height = MAX (priv->max_row_height, max_min_height);
493
494   if (priv->row_height < priv->min_row_height)
495     priv->row_height = priv->min_row_height;
496
497   if (priv->orientation == CLUTTER_FLOW_HORIZONTAL && for_width > 0)
498     {
499       /* if we have a non-full row we need to add it */
500       if (line_item_count > 0)
501         {
502           total_min_height += line_min_height;
503           total_natural_height += line_natural_height;
504
505           g_array_append_val (priv->line_min,
506                               line_min_height);
507           g_array_append_val (priv->line_natural,
508                               line_natural_height);
509         }
510
511       priv->line_count = line_count;
512       if (priv->line_count > 0)
513         {
514           gfloat total_spacing;
515
516           total_spacing = priv->row_spacing * (priv->line_count - 1);
517
518           total_min_height += total_spacing;
519           total_natural_height += total_spacing;
520         }
521     }
522   else
523     {
524       g_array_append_val (priv->line_min, line_min_height);
525       g_array_append_val (priv->line_natural, line_natural_height);
526
527       priv->line_count = line_count;
528
529       if (priv->line_count > 0)
530         {
531           gfloat total_spacing;
532
533           total_spacing = priv->col_spacing * priv->line_count;
534
535           total_min_height += total_spacing;
536           total_natural_height += total_spacing;
537         }
538     }
539
540   CLUTTER_NOTE (LAYOUT,
541                 "Flow[h]: %d lines (%d per line): w [ %.2f, %.2f ] for h %.2f",
542                 n_columns, priv->line_count,
543                 total_min_height,
544                 total_natural_height,
545                 for_width);
546
547   priv->req_width = for_width;
548
549   if (min_height_p)
550     *min_height_p = max_min_height;
551
552   if (nat_height_p)
553     *nat_height_p = total_natural_height;
554 }
555
556 static void
557 clutter_flow_layout_allocate (ClutterLayoutManager   *manager,
558                               ClutterContainer       *container,
559                               const ClutterActorBox  *allocation,
560                               ClutterAllocationFlags  flags)
561 {
562   ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (manager)->priv;
563   ClutterActor *actor, *child;
564   ClutterActorIter iter;
565   gfloat x_off, y_off;
566   gfloat avail_width, avail_height;
567   gfloat item_x, item_y;
568   gint line_item_count;
569   gint items_per_line;
570   gint line_index;
571   gboolean use_animations;
572   ClutterAnimationMode easing_mode;
573   guint easing_duration, easing_delay;
574
575   actor = CLUTTER_ACTOR (container);
576   if (clutter_actor_get_n_children (actor) == 0)
577     return;
578
579   clutter_actor_box_get_origin (allocation, &x_off, &y_off);
580   clutter_actor_box_get_size (allocation, &avail_width, &avail_height);
581
582   /* blow the cached preferred size and re-compute with the given
583    * available size in case the FlowLayout wasn't given the exact
584    * size it requested
585    */
586   if ((priv->req_width >= 0 && avail_width != priv->req_width) ||
587       (priv->req_height >= 0 && avail_height != priv->req_height))
588     {
589       clutter_flow_layout_get_preferred_width (manager, container,
590                                                avail_height,
591                                                NULL, NULL);
592       clutter_flow_layout_get_preferred_height (manager, container,
593                                                 avail_width,
594                                                 NULL, NULL);
595     }
596
597   items_per_line = compute_lines (CLUTTER_FLOW_LAYOUT (manager),
598                                   avail_width, avail_height);
599
600   item_x = x_off;
601   item_y = y_off;
602
603   line_item_count = 0;
604   line_index = 0;
605
606   use_animations = clutter_layout_manager_get_easing_state (manager,
607                                                             &easing_mode,
608                                                             &easing_duration,
609                                                             &easing_delay);
610
611   clutter_actor_iter_init (&iter, actor);
612   while (clutter_actor_iter_next (&iter, &child))
613     {
614       ClutterActorBox child_alloc;
615       gfloat item_width, item_height;
616       gfloat new_x, new_y;
617
618       if (!CLUTTER_ACTOR_IS_VISIBLE (child))
619         continue;
620
621       new_x = new_y = 0;
622
623       if (priv->orientation == CLUTTER_FLOW_HORIZONTAL)
624         {
625           if (line_item_count == items_per_line && line_item_count > 0)
626             {
627               item_y += g_array_index (priv->line_natural,
628                                        gfloat,
629                                        line_index);
630
631               if (line_index >= 0)
632                 item_y += priv->row_spacing;
633
634               line_item_count = 0;
635               line_index += 1;
636
637               item_x = x_off;
638             }
639
640           new_x = x_off + ((line_item_count + 1) * (avail_width + priv->col_spacing))
641                 / items_per_line;
642           item_width = new_x - item_x - priv->col_spacing;
643           item_height = g_array_index (priv->line_natural,
644                                        gfloat,
645                                        line_index);
646
647           if (!priv->is_homogeneous)
648             {
649               gfloat child_min, child_natural;
650
651               clutter_actor_get_preferred_width (child, item_height,
652                                                  &child_min,
653                                                  &child_natural);
654               item_width = MIN (item_width, child_natural);
655
656               clutter_actor_get_preferred_height (child, item_width,
657                                                   &child_min,
658                                                   &child_natural);
659               item_height = MIN (item_height, child_natural);
660             }
661         }
662       else
663         {
664           if (line_item_count == items_per_line && line_item_count > 0)
665             {
666               item_x += g_array_index (priv->line_natural,
667                                        gfloat,
668                                        line_index);
669
670               if (line_index >= 0)
671                 item_x += priv->col_spacing;
672
673               line_item_count = 0;
674               line_index += 1;
675
676               item_y = y_off;
677             }
678
679           new_y = y_off + ((line_item_count + 1) * (avail_height + priv->row_spacing))
680                 / items_per_line;
681           item_height = new_y - item_y - priv->row_spacing;
682           item_width = g_array_index (priv->line_natural,
683                                       gfloat,
684                                       line_index);
685
686           if (!priv->is_homogeneous)
687             {
688               gfloat child_min, child_natural;
689
690               clutter_actor_get_preferred_width (child, item_height,
691                                                  &child_min,
692                                                  &child_natural);
693               item_width = MIN (item_width, child_natural);
694
695               clutter_actor_get_preferred_height (child, item_width,
696                                                   &child_min,
697                                                   &child_natural);
698               item_height = MIN (item_height, child_natural);
699             }
700         }
701
702       CLUTTER_NOTE (LAYOUT,
703                     "flow[line:%d, item:%d/%d] ="
704                     "{ %.2f, %.2f, %.2f, %.2f }",
705                     line_index, line_item_count + 1, items_per_line,
706                     item_x, item_y, item_width, item_height);
707
708       child_alloc.x1 = ceil (item_x);
709       child_alloc.y1 = ceil (item_y);
710       child_alloc.x2 = ceil (child_alloc.x1 + item_width);
711       child_alloc.y2 = ceil (child_alloc.y1 + item_height);
712
713       if (use_animations)
714         {
715           clutter_actor_save_easing_state (child);
716           clutter_actor_set_easing_mode (child, easing_mode);
717           clutter_actor_set_easing_duration (child, easing_duration);
718           clutter_actor_set_easing_delay (child, easing_delay);
719         }
720
721       clutter_actor_allocate (child, &child_alloc, flags);
722
723       if (use_animations)
724         clutter_actor_restore_easing_state (child);
725
726       if (priv->orientation == CLUTTER_FLOW_HORIZONTAL)
727         item_x = new_x;
728       else
729         item_y = new_y;
730
731       line_item_count += 1;
732     }
733 }
734
735 static void
736 clutter_flow_layout_set_container (ClutterLayoutManager *manager,
737                                    ClutterContainer     *container)
738 {
739   ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (manager)->priv;
740   ClutterLayoutManagerClass *parent_class;
741
742   priv->container = container;
743
744   if (priv->container != NULL)
745     {
746       ClutterRequestMode request_mode;
747
748       /* we need to change the :request-mode of the container
749        * to match the orientation
750        */
751       request_mode = (priv->orientation == CLUTTER_FLOW_HORIZONTAL)
752                    ? CLUTTER_REQUEST_HEIGHT_FOR_WIDTH
753                    : CLUTTER_REQUEST_WIDTH_FOR_HEIGHT;
754       clutter_actor_set_request_mode (CLUTTER_ACTOR (priv->container),
755                                       request_mode);
756     }
757
758   parent_class = CLUTTER_LAYOUT_MANAGER_CLASS (clutter_flow_layout_parent_class);
759   parent_class->set_container (manager, container);
760 }
761
762 static void
763 clutter_flow_layout_set_property (GObject      *gobject,
764                                   guint         prop_id,
765                                   const GValue *value,
766                                   GParamSpec   *pspec)
767 {
768   ClutterFlowLayout *self = CLUTTER_FLOW_LAYOUT (gobject);
769
770   switch (prop_id)
771     {
772     case PROP_ORIENTATION:
773       clutter_flow_layout_set_orientation (self, g_value_get_enum (value));
774       break;
775
776     case PROP_HOMOGENEOUS:
777       clutter_flow_layout_set_homogeneous (self, g_value_get_boolean (value));
778       break;
779
780     case PROP_COLUMN_SPACING:
781       clutter_flow_layout_set_column_spacing (self, g_value_get_float (value));
782       break;
783
784     case PROP_ROW_SPACING:
785       clutter_flow_layout_set_row_spacing (self, g_value_get_float (value));
786       break;
787
788     case PROP_MIN_COLUMN_WIDTH:
789       clutter_flow_layout_set_column_width (self,
790                                             g_value_get_float (value),
791                                             self->priv->max_col_width);
792       break;
793
794     case PROP_MAX_COLUMN_WIDTH:
795       clutter_flow_layout_set_column_width (self,
796                                             self->priv->min_col_width,
797                                             g_value_get_float (value));
798       break;
799
800     case PROP_MIN_ROW_HEGHT:
801       clutter_flow_layout_set_row_height (self,
802                                           g_value_get_float (value),
803                                           self->priv->max_row_height);
804       break;
805
806     case PROP_MAX_ROW_HEIGHT:
807       clutter_flow_layout_set_row_height (self,
808                                           self->priv->min_row_height,
809                                           g_value_get_float (value));
810       break;
811
812     default:
813       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
814       break;
815     }
816 }
817
818 static void
819 clutter_flow_layout_get_property (GObject    *gobject,
820                                   guint       prop_id,
821                                   GValue     *value,
822                                   GParamSpec *pspec)
823 {
824   ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (gobject)->priv;
825
826   switch (prop_id)
827     {
828     case PROP_ORIENTATION:
829       g_value_set_enum (value, priv->orientation);
830       break;
831
832     case PROP_HOMOGENEOUS:
833       g_value_set_boolean (value, priv->is_homogeneous);
834       break;
835
836     case PROP_COLUMN_SPACING:
837       g_value_set_float (value, priv->col_spacing);
838       break;
839
840     case PROP_ROW_SPACING:
841       g_value_set_float (value, priv->row_spacing);
842       break;
843
844     case PROP_MIN_COLUMN_WIDTH:
845       g_value_set_float (value, priv->min_col_width);
846       break;
847
848     case PROP_MAX_COLUMN_WIDTH:
849       g_value_set_float (value, priv->max_col_width);
850       break;
851
852     case PROP_MIN_ROW_HEGHT:
853       g_value_set_float (value, priv->min_row_height);
854       break;
855
856     case PROP_MAX_ROW_HEIGHT:
857       g_value_set_float (value, priv->max_row_height);
858       break;
859
860     default:
861       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
862       break;
863     }
864 }
865
866 static void
867 clutter_flow_layout_finalize (GObject *gobject)
868 {
869   ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (gobject)->priv;
870
871   if (priv->line_min != NULL)
872     g_array_free (priv->line_min, TRUE);
873
874   if (priv->line_natural != NULL)
875     g_array_free (priv->line_natural, TRUE);
876
877   G_OBJECT_CLASS (clutter_flow_layout_parent_class)->finalize (gobject);
878 }
879
880 static void
881 clutter_flow_layout_class_init (ClutterFlowLayoutClass *klass)
882 {
883   GObjectClass *gobject_class;
884   ClutterLayoutManagerClass *layout_class;
885
886   g_type_class_add_private (klass, sizeof (ClutterFlowLayoutPrivate));
887
888   gobject_class = G_OBJECT_CLASS (klass);
889   layout_class = CLUTTER_LAYOUT_MANAGER_CLASS (klass);
890
891   layout_class->get_preferred_width =
892     clutter_flow_layout_get_preferred_width;
893   layout_class->get_preferred_height =
894     clutter_flow_layout_get_preferred_height;
895   layout_class->allocate = clutter_flow_layout_allocate;
896   layout_class->set_container = clutter_flow_layout_set_container;
897
898   /**
899    * ClutterFlowLayout:orientation:
900    *
901    * The orientation of the #ClutterFlowLayout. The children
902    * of the layout will be layed out following the orientation.
903    *
904    * This property also controls the overflowing directions
905    *
906    * Since: 1.2
907    */
908   flow_properties[PROP_ORIENTATION] =
909     g_param_spec_enum ("orientation",
910                        P_("Orientation"),
911                        P_("The orientation of the layout"),
912                        CLUTTER_TYPE_FLOW_ORIENTATION,
913                        CLUTTER_FLOW_HORIZONTAL,
914                        CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT);
915
916   /**
917    * ClutterFlowLayout:homogeneous:
918    *
919    * Whether each child inside the #ClutterFlowLayout should receive
920    * the same allocation
921    *
922    * Since: 1.2
923    */
924   flow_properties[PROP_HOMOGENEOUS] =
925     g_param_spec_boolean ("homogeneous",
926                           P_("Homogeneous"),
927                           P_("Whether each item should receive the same allocation"),
928                           FALSE,
929                           CLUTTER_PARAM_READWRITE);
930
931   /**
932    * ClutterFlowLayout:column-spacing:
933    *
934    * The spacing between columns, in pixels; the value of this
935    * property is honoured by horizontal non-overflowing layouts
936    * and by vertical overflowing layouts
937    *
938    * Since: 1.2
939    */
940   flow_properties[PROP_COLUMN_SPACING] =
941     g_param_spec_float ("column-spacing",
942                         P_("Column Spacing"),
943                         P_("The spacing between columns"),
944                         0.0, G_MAXFLOAT,
945                         0.0,
946                         CLUTTER_PARAM_READWRITE);
947
948   /**
949    * ClutterFlowLayout:row-spacing:
950    *
951    * The spacing between rows, in pixels; the value of this
952    * property is honoured by vertical non-overflowing layouts and
953    * by horizontal overflowing layouts
954    *
955    * Since: 1.2
956    */
957   flow_properties[PROP_ROW_SPACING] =
958     g_param_spec_float ("row-spacing",
959                         P_("Row Spacing"),
960                         P_("The spacing between rows"),
961                         0.0, G_MAXFLOAT,
962                         0.0,
963                         CLUTTER_PARAM_READWRITE);
964
965   /**
966    * ClutterFlowLayout:min-column-width:
967    *
968    * Minimum width for each column in the layout, in pixels
969    *
970    * Since: 1.2
971    */
972   flow_properties[PROP_MIN_COLUMN_WIDTH] =
973     g_param_spec_float ("min-column-width",
974                         P_("Minimum Column Width"),
975                         P_("Minimum width for each column"),
976                         0.0, G_MAXFLOAT,
977                         0.0,
978                         CLUTTER_PARAM_READWRITE);
979
980   /**
981    * ClutterFlowLayout:max-column-width:
982    *
983    * Maximum width for each column in the layout, in pixels. If
984    * set to -1 the width will be the maximum child width
985    *
986    * Since: 1.2
987    */
988   flow_properties[PROP_MAX_COLUMN_WIDTH] =
989     g_param_spec_float ("max-column-width",
990                         P_("Maximum Column Width"),
991                         P_("Maximum width for each column"),
992                         -1.0, G_MAXFLOAT,
993                         -1.0,
994                         CLUTTER_PARAM_READWRITE);
995
996   /**
997    * ClutterFlowLayout:min-row-height:
998    *
999    * Minimum height for each row in the layout, in pixels
1000    *
1001    * Since: 1.2
1002    */
1003   flow_properties[PROP_MIN_ROW_HEGHT] =
1004     g_param_spec_float ("min-row-height",
1005                         P_("Minimum Row Height"),
1006                         P_("Minimum height for each row"),
1007                         0.0, G_MAXFLOAT,
1008                         0.0,
1009                         CLUTTER_PARAM_READWRITE);
1010
1011   /**
1012    * ClutterFlowLayout:max-row-height:
1013    *
1014    * Maximum height for each row in the layout, in pixels. If
1015    * set to -1 the width will be the maximum child height
1016    *
1017    * Since: 1.2
1018    */
1019   flow_properties[PROP_MAX_ROW_HEIGHT] =
1020     g_param_spec_float ("max-row-height",
1021                         P_("Maximum Row Height"),
1022                         P_("Maximum height for each row"),
1023                         -1.0, G_MAXFLOAT,
1024                         -1.0,
1025                         CLUTTER_PARAM_READWRITE);
1026
1027   gobject_class->finalize = clutter_flow_layout_finalize;
1028   gobject_class->set_property = clutter_flow_layout_set_property;
1029   gobject_class->get_property = clutter_flow_layout_get_property;
1030   g_object_class_install_properties (gobject_class,
1031                                      N_PROPERTIES,
1032                                      flow_properties);
1033 }
1034
1035 static void
1036 clutter_flow_layout_init (ClutterFlowLayout *self)
1037 {
1038   ClutterFlowLayoutPrivate *priv;
1039
1040   self->priv = priv = CLUTTER_FLOW_LAYOUT_GET_PRIVATE (self);
1041
1042   priv->orientation = CLUTTER_FLOW_HORIZONTAL;
1043
1044   priv->col_spacing = 0;
1045   priv->row_spacing = 0;
1046
1047   priv->min_col_width = priv->min_row_height = 0;
1048   priv->max_col_width = priv->max_row_height = -1;
1049
1050   priv->line_min = NULL;
1051   priv->line_natural = NULL;
1052 }
1053
1054 /**
1055  * clutter_flow_layout_new:
1056  * @orientation: the orientation of the flow layout
1057  *
1058  * Creates a new #ClutterFlowLayout with the given @orientation
1059  *
1060  * Return value: the newly created #ClutterFlowLayout
1061  *
1062  * Since: 1.2
1063  */
1064 ClutterLayoutManager *
1065 clutter_flow_layout_new (ClutterFlowOrientation orientation)
1066 {
1067   return g_object_new (CLUTTER_TYPE_FLOW_LAYOUT,
1068                        "orientation", orientation,
1069                        NULL);
1070 }
1071
1072 /**
1073  * clutter_flow_layout_set_orientation:
1074  * @layout: a #ClutterFlowLayout
1075  * @orientation: the orientation of the layout
1076  *
1077  * Sets the orientation of the flow layout
1078  *
1079  * The orientation controls the direction used to allocate
1080  * the children: either horizontally or vertically. The
1081  * orientation also controls the direction of the overflowing
1082  *
1083  * Since: 1.2
1084  */
1085 void
1086 clutter_flow_layout_set_orientation (ClutterFlowLayout      *layout,
1087                                      ClutterFlowOrientation  orientation)
1088 {
1089   ClutterFlowLayoutPrivate *priv;
1090
1091   g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout));
1092
1093   priv = layout->priv;
1094
1095   if (priv->orientation != orientation)
1096     {
1097       ClutterLayoutManager *manager;
1098
1099       priv->orientation = orientation;
1100
1101       if (priv->container != NULL)
1102         {
1103           ClutterRequestMode request_mode;
1104
1105           /* we need to change the :request-mode of the container
1106            * to match the orientation
1107            */
1108           request_mode = (priv->orientation == CLUTTER_FLOW_HORIZONTAL)
1109                        ? CLUTTER_REQUEST_HEIGHT_FOR_WIDTH
1110                        : CLUTTER_REQUEST_WIDTH_FOR_HEIGHT;
1111           clutter_actor_set_request_mode (CLUTTER_ACTOR (priv->container),
1112                                           request_mode);
1113         }
1114
1115       manager = CLUTTER_LAYOUT_MANAGER (layout);
1116       clutter_layout_manager_layout_changed (manager);
1117
1118       g_object_notify_by_pspec (G_OBJECT (layout),
1119                                 flow_properties[PROP_ORIENTATION]);
1120     }
1121 }
1122
1123 /**
1124  * clutter_flow_layout_get_orientation:
1125  * @layout: a #ClutterFlowLayout
1126  *
1127  * Retrieves the orientation of the @layout
1128  *
1129  * Return value: the orientation of the #ClutterFlowLayout
1130  *
1131  * Since: 1.2
1132  */
1133 ClutterFlowOrientation
1134 clutter_flow_layout_get_orientation (ClutterFlowLayout *layout)
1135 {
1136   g_return_val_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout),
1137                         CLUTTER_FLOW_HORIZONTAL);
1138
1139   return layout->priv->orientation;
1140 }
1141
1142 /**
1143  * clutter_flow_layout_set_homogeneous:
1144  * @layout: a #ClutterFlowLayout
1145  * @homogeneous: whether the layout should be homogeneous or not
1146  *
1147  * Sets whether the @layout should allocate the same space for
1148  * each child
1149  *
1150  * Since: 1.2
1151  */
1152 void
1153 clutter_flow_layout_set_homogeneous (ClutterFlowLayout *layout,
1154                                      gboolean           homogeneous)
1155 {
1156   ClutterFlowLayoutPrivate *priv;
1157
1158   g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout));
1159
1160   priv = layout->priv;
1161
1162   if (priv->is_homogeneous != homogeneous)
1163     {
1164       ClutterLayoutManager *manager;
1165
1166       priv->is_homogeneous = homogeneous;
1167
1168       manager = CLUTTER_LAYOUT_MANAGER (layout);
1169       clutter_layout_manager_layout_changed (manager);
1170
1171       g_object_notify_by_pspec (G_OBJECT (layout),
1172                                 flow_properties[PROP_HOMOGENEOUS]);
1173     }
1174 }
1175
1176 /**
1177  * clutter_flow_layout_get_homogeneous:
1178  * @layout: a #ClutterFlowLayout
1179  *
1180  * Retrieves whether the @layout is homogeneous
1181  *
1182  * Return value: %TRUE if the #ClutterFlowLayout is homogeneous
1183  *
1184  * Since: 1.2
1185  */
1186 gboolean
1187 clutter_flow_layout_get_homogeneous (ClutterFlowLayout *layout)
1188 {
1189   g_return_val_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout), FALSE);
1190
1191   return layout->priv->is_homogeneous;
1192 }
1193
1194 /**
1195  * clutter_flow_layout_set_column_spacing:
1196  * @layout: a #ClutterFlowLayout
1197  * @spacing: the space between columns
1198  *
1199  * Sets the space between columns, in pixels
1200  *
1201  * Since: 1.2
1202  */
1203 void
1204 clutter_flow_layout_set_column_spacing (ClutterFlowLayout *layout,
1205                                         gfloat             spacing)
1206 {
1207   ClutterFlowLayoutPrivate *priv;
1208
1209   g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout));
1210
1211   priv = layout->priv;
1212
1213   if (priv->col_spacing != spacing)
1214     {
1215       ClutterLayoutManager *manager;
1216
1217       priv->col_spacing = spacing;
1218
1219       manager = CLUTTER_LAYOUT_MANAGER (layout);
1220       clutter_layout_manager_layout_changed (manager);
1221
1222       g_object_notify_by_pspec (G_OBJECT (layout),
1223                                 flow_properties[PROP_COLUMN_SPACING]);
1224     }
1225 }
1226
1227 /**
1228  * clutter_flow_layout_get_column_spacing:
1229  * @layout: a #ClutterFlowLayout
1230  *
1231  * Retrieves the spacing between columns
1232  *
1233  * Return value: the spacing between columns of the #ClutterFlowLayout,
1234  *   in pixels
1235  *
1236  * Since: 1.2
1237  */
1238 gfloat
1239 clutter_flow_layout_get_column_spacing (ClutterFlowLayout *layout)
1240 {
1241   g_return_val_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout), 0.0);
1242
1243   return layout->priv->col_spacing;
1244 }
1245
1246 /**
1247  * clutter_flow_layout_set_row_spacing:
1248  * @layout: a #ClutterFlowLayout
1249  * @spacing: the space between rows
1250  *
1251  * Sets the spacing between rows, in pixels
1252  *
1253  * Since: 1.2
1254  */
1255 void
1256 clutter_flow_layout_set_row_spacing (ClutterFlowLayout *layout,
1257                                      gfloat             spacing)
1258 {
1259   ClutterFlowLayoutPrivate *priv;
1260
1261   g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout));
1262
1263   priv = layout->priv;
1264
1265   if (priv->row_spacing != spacing)
1266     {
1267       ClutterLayoutManager *manager;
1268
1269       priv->row_spacing = spacing;
1270
1271       manager = CLUTTER_LAYOUT_MANAGER (layout);
1272       clutter_layout_manager_layout_changed (manager);
1273
1274       g_object_notify_by_pspec (G_OBJECT (layout),
1275                                 flow_properties[PROP_ROW_SPACING]);
1276     }
1277 }
1278
1279 /**
1280  * clutter_flow_layout_get_row_spacing:
1281  * @layout: a #ClutterFlowLayout
1282  *
1283  * Retrieves the spacing between rows
1284  *
1285  * Return value: the spacing between rows of the #ClutterFlowLayout,
1286  *   in pixels
1287  *
1288  * Since: 1.2
1289  */
1290 gfloat
1291 clutter_flow_layout_get_row_spacing (ClutterFlowLayout *layout)
1292 {
1293   g_return_val_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout), 0.0);
1294
1295   return layout->priv->row_spacing;
1296 }
1297
1298 /**
1299  * clutter_flow_layout_set_column_width:
1300  * @layout: a #ClutterFlowLayout
1301  * @min_width: minimum width of a column
1302  * @max_width: maximum width of a column
1303  *
1304  * Sets the minimum and maximum widths that a column can have
1305  *
1306  * Since: 1.2
1307  */
1308 void
1309 clutter_flow_layout_set_column_width (ClutterFlowLayout *layout,
1310                                       gfloat             min_width,
1311                                       gfloat             max_width)
1312 {
1313   ClutterFlowLayoutPrivate *priv;
1314   gboolean notify_min = FALSE, notify_max = FALSE;
1315
1316   g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout));
1317
1318   priv = layout->priv;
1319
1320   if (priv->min_col_width != min_width)
1321     {
1322       priv->min_col_width = min_width;
1323
1324       notify_min = TRUE;
1325     }
1326
1327   if (priv->max_col_width != max_width)
1328     {
1329       priv->max_col_width = max_width;
1330
1331       notify_max = TRUE;
1332     }
1333
1334   g_object_freeze_notify (G_OBJECT (layout));
1335
1336   if (notify_min || notify_max)
1337     {
1338       ClutterLayoutManager *manager = CLUTTER_LAYOUT_MANAGER (layout);
1339
1340       clutter_layout_manager_layout_changed (manager);
1341     }
1342
1343   if (notify_min)
1344     g_object_notify_by_pspec (G_OBJECT (layout),
1345                               flow_properties[PROP_MIN_COLUMN_WIDTH]);
1346
1347   if (notify_max)
1348     g_object_notify_by_pspec (G_OBJECT (layout),
1349                               flow_properties[PROP_MAX_COLUMN_WIDTH]);
1350
1351   g_object_thaw_notify (G_OBJECT (layout));
1352 }
1353
1354 /**
1355  * clutter_flow_layout_get_column_width:
1356  * @layout: a #ClutterFlowLayout
1357  * @min_width: (out): return location for the minimum column width, or %NULL
1358  * @max_width: (out): return location for the maximum column width, or %NULL
1359  *
1360  * Retrieves the minimum and maximum column widths
1361  *
1362  * Since: 1.2
1363  */
1364 void
1365 clutter_flow_layout_get_column_width (ClutterFlowLayout *layout,
1366                                       gfloat            *min_width,
1367                                       gfloat            *max_width)
1368 {
1369   g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout));
1370
1371   if (min_width)
1372     *min_width = layout->priv->min_col_width;
1373
1374   if (max_width)
1375     *max_width = layout->priv->max_col_width;
1376 }
1377
1378 /**
1379  * clutter_flow_layout_set_row_height:
1380  * @layout: a #ClutterFlowLayout
1381  * @min_height: the minimum height of a row
1382  * @max_height: the maximum height of a row
1383  *
1384  * Sets the minimum and maximum heights that a row can have
1385  *
1386  * Since: 1.2
1387  */
1388 void
1389 clutter_flow_layout_set_row_height (ClutterFlowLayout *layout,
1390                                     gfloat              min_height,
1391                                     gfloat              max_height)
1392 {
1393   ClutterFlowLayoutPrivate *priv;
1394   gboolean notify_min = FALSE, notify_max = FALSE;
1395
1396   g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout));
1397
1398   priv = layout->priv;
1399
1400   if (priv->min_row_height != min_height)
1401     {
1402       priv->min_row_height = min_height;
1403
1404       notify_min = TRUE;
1405     }
1406
1407   if (priv->max_row_height != max_height)
1408     {
1409       priv->max_row_height = max_height;
1410
1411       notify_max = TRUE;
1412     }
1413
1414   g_object_freeze_notify (G_OBJECT (layout));
1415
1416   if (notify_min || notify_max)
1417     {
1418       ClutterLayoutManager *manager = CLUTTER_LAYOUT_MANAGER (layout);
1419
1420       clutter_layout_manager_layout_changed (manager);
1421     }
1422
1423   if (notify_min)
1424     g_object_notify_by_pspec (G_OBJECT (layout),
1425                               flow_properties[PROP_MIN_ROW_HEGHT]);
1426
1427   if (notify_max)
1428     g_object_notify_by_pspec (G_OBJECT (layout),
1429                               flow_properties[PROP_MAX_ROW_HEIGHT]);
1430
1431   g_object_thaw_notify (G_OBJECT (layout));
1432 }
1433
1434 /**
1435  * clutter_flow_layout_get_row_height:
1436  * @layout: a #ClutterFlowLayout
1437  * @min_height: (out): return location for the minimum row height, or %NULL
1438  * @max_height: (out): return location for the maximum row height, or %NULL
1439  *
1440  * Retrieves the minimum and maximum row heights
1441  *
1442  * Since: 1.2
1443  */
1444 void
1445 clutter_flow_layout_get_row_height (ClutterFlowLayout *layout,
1446                                     gfloat            *min_height,
1447                                     gfloat            *max_height)
1448 {
1449   g_return_if_fail (CLUTTER_IS_FLOW_LAYOUT (layout));
1450
1451   if (min_height)
1452     *min_height = layout->priv->min_row_height;
1453
1454   if (max_height)
1455     *max_height = layout->priv->max_row_height;
1456 }