No issues found
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3 * st-table.c: Table layout widget
4 *
5 * Copyright 2008, 2009 Intel Corporation.
6 * Copyright 2009, 2010 Red Hat, Inc.
7 * Copyright 2009 Abderrahim Kitouni
8 *
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms and conditions of the GNU Lesser General Public License,
11 * version 2.1, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope it will be useful, but WITHOUT ANY
14 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
16 * more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 /**
23 * SECTION:st-table
24 * @short_description: A multi-child layout container based on rows
25 * and columns
26 *
27 * #StTable is a mult-child layout container based on a table arrangement
28 * with rows and columns. #StTable adds several child properties to it's
29 * children that control their position and size in the table.
30 */
31
32 #ifdef HAVE_CONFIG_H
33 #include "config.h"
34 #endif
35
36 #include "st-table.h"
37
38 #include <stdlib.h>
39 #include <string.h>
40 #include <glib.h>
41 #include <clutter/clutter.h>
42
43 #include "st-enum-types.h"
44 #include "st-private.h"
45 #include "st-table-child.h"
46 #include "st-table-private.h"
47
48 enum
49 {
50 PROP_0,
51
52 PROP_HOMOGENEOUS,
53
54 PROP_ROW_COUNT,
55 PROP_COL_COUNT,
56 };
57
58 #define ST_TABLE_GET_PRIVATE(obj) \
59 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_TABLE, StTablePrivate))
60
61 struct _StTablePrivate
62 {
63 gint col_spacing;
64 gint row_spacing;
65
66 gint n_rows;
67 gint n_cols;
68
69 gint active_row;
70 gint active_col;
71
72 GArray *min_widths;
73 GArray *pref_widths;
74 GArray *min_heights;
75 GArray *pref_heights;
76
77 GArray *is_expand_col;
78 GArray *is_expand_row;
79
80 GArray *col_widths;
81 GArray *row_heights;
82
83 guint homogeneous : 1;
84 };
85
86 static void st_table_container_iface_init (ClutterContainerIface *iface);
87
88 G_DEFINE_TYPE_WITH_CODE (StTable, st_table, ST_TYPE_WIDGET,
89 G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
90 st_table_container_iface_init));
91
92
93
94 /*
95 * ClutterContainer Implementation
96 */
97 static void
98 st_table_actor_removed (ClutterContainer *container,
99 ClutterActor *actor)
100 {
101 StTablePrivate *priv = ST_TABLE (container)->priv;
102 gint n_rows = 0;
103 gint n_cols = 0;
104 ClutterActor *child;
105
106 /* Calculate and update the number of rows / columns */
107 for (child = clutter_actor_get_first_child (CLUTTER_ACTOR (container));
108 child != NULL;
109 child = clutter_actor_get_next_sibling (child))
110 {
111 StTableChild *meta;
112
113 if (child == actor)
114 continue;
115
116 meta = (StTableChild *) clutter_container_get_child_meta (container, child);
117 n_rows = MAX (n_rows, meta->row + 1);
118 n_cols = MAX (n_cols, meta->col + 1);
119 }
120
121 g_object_freeze_notify (G_OBJECT (container));
122
123 if (priv->n_rows != n_rows)
124 {
125 priv->n_rows = n_rows;
126 g_object_notify (G_OBJECT (container), "row-count");
127 }
128
129 if (priv->n_cols != n_cols)
130 {
131 priv->n_cols = n_cols;
132 g_object_notify (G_OBJECT (container), "column-count");
133 }
134
135 g_object_thaw_notify (G_OBJECT (container));
136 }
137
138 static void
139 st_table_container_iface_init (ClutterContainerIface *iface)
140 {
141 iface->actor_removed = st_table_actor_removed;
142 iface->child_meta_type = ST_TYPE_TABLE_CHILD;
143 }
144
145 /* StTable Class Implementation */
146
147 static void
148 st_table_set_property (GObject *gobject,
149 guint prop_id,
150 const GValue *value,
151 GParamSpec *pspec)
152 {
153 StTable *table = ST_TABLE (gobject);
154
155 switch (prop_id)
156 {
157 case PROP_HOMOGENEOUS:
158 if (table->priv->homogeneous != g_value_get_boolean (value))
159 {
160 table->priv->homogeneous = g_value_get_boolean (value);
161 clutter_actor_queue_relayout ((ClutterActor *) gobject);
162 }
163 break;
164
165 default:
166 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
167 break;
168 }
169 }
170
171 static void
172 st_table_get_property (GObject *gobject,
173 guint prop_id,
174 GValue *value,
175 GParamSpec *pspec)
176 {
177 StTablePrivate *priv = ST_TABLE (gobject)->priv;
178
179 switch (prop_id)
180 {
181 case PROP_HOMOGENEOUS:
182 g_value_set_boolean (value, priv->homogeneous);
183 break;
184
185 case PROP_COL_COUNT:
186 g_value_set_int (value, priv->n_cols);
187 break;
188
189 case PROP_ROW_COUNT:
190 g_value_set_int (value, priv->n_rows);
191 break;
192
193 default:
194 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
195 break;
196 }
197 }
198
199 static void
200 st_table_finalize (GObject *gobject)
201 {
202 StTablePrivate *priv = ST_TABLE (gobject)->priv;
203
204 g_array_free (priv->min_widths, TRUE);
205 g_array_free (priv->pref_widths, TRUE);
206
207 g_array_free (priv->min_heights, TRUE);
208 g_array_free (priv->pref_heights, TRUE);
209
210 g_array_free (priv->is_expand_col, TRUE);
211 g_array_free (priv->is_expand_row, TRUE);
212
213 g_array_free (priv->col_widths, TRUE);
214 g_array_free (priv->row_heights, TRUE);
215
216 G_OBJECT_CLASS (st_table_parent_class)->finalize (gobject);
217 }
218
219 static void
220 st_table_homogeneous_allocate (ClutterActor *self,
221 const ClutterActorBox *content_box,
222 gboolean flags)
223 {
224 gfloat col_width, row_height;
225 gint row_spacing, col_spacing;
226 StTablePrivate *priv = ST_TABLE (self)->priv;
227 gboolean ltr = clutter_actor_get_text_direction (self) == CLUTTER_TEXT_DIRECTION_LTR;
228 ClutterActor *child;
229
230 col_spacing = priv->col_spacing;
231 row_spacing = priv->row_spacing;
232
233 col_width = (int) ((content_box->x2 - content_box->x1
234 - (col_spacing * (priv->n_cols - 1)))
235 / priv->n_cols + 0.5);
236 row_height = (int) ((content_box->y2 - content_box->y1
237 - (row_spacing * (priv->n_rows - 1)))
238 / priv->n_rows + 0.5);
239
240 for (child = clutter_actor_get_first_child (self);
241 child != NULL;
242 child = clutter_actor_get_next_sibling (child))
243 {
244 gint row, col, row_span, col_span;
245 StTableChild *meta;
246 ClutterActorBox childbox;
247 gdouble x_align_f, y_align_f;
248
249 meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (self), child);
250
251 if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child))
252 continue;
253
254 /* get child properties */
255 col = meta->col;
256 row = meta->row;
257 row_span = meta->row_span;
258 col_span = meta->col_span;
259
260 _st_get_align_factors (meta->x_align, meta->y_align,
261 &x_align_f, &y_align_f);
262
263 if (ltr)
264 {
265 childbox.x1 = content_box->x1 + (col_width + col_spacing) * col;
266 childbox.x2 = childbox.x1 + (col_width * col_span) + (col_spacing * (col_span - 1));
267 }
268 else
269 {
270 childbox.x2 = content_box->x2 - (col_width + col_spacing) * col;
271 childbox.x1 = childbox.x2 - (col_width * col_span) - (col_spacing * (col_span - 1));
272 }
273
274 childbox.y1 = content_box->y1 + (row_height + row_spacing) * row;
275 childbox.y2 = childbox.y1 + (row_height * row_span) + (row_spacing * (row_span - 1));
276
277 clutter_actor_allocate_align_fill (child, &childbox,
278 x_align_f, y_align_f,
279 meta->x_fill, meta->y_fill,
280 flags);
281 }
282
283 }
284
285
286 static gint *
287 st_table_calculate_col_widths (StTable *table,
288 gint for_width)
289 {
290 gint total_min_width, i;
291 StTablePrivate *priv = table->priv;
292 gboolean *is_expand_col;
293 gint extra_col_width, n_expanded_cols = 0, expanded_cols = 0;
294 gint *pref_widths, *min_widths;
295 ClutterActor *child;
296
297 g_array_set_size (priv->is_expand_col, 0);
298 g_array_set_size (priv->is_expand_col, priv->n_cols);
299 is_expand_col = (gboolean *) priv->is_expand_col->data;
300
301 g_array_set_size (priv->pref_widths, 0);
302 g_array_set_size (priv->pref_widths, priv->n_cols);
303 pref_widths = (gint *) priv->pref_widths->data;
304
305 g_array_set_size (priv->min_widths, 0);
306 g_array_set_size (priv->min_widths, priv->n_cols);
307 min_widths = (gint *) priv->min_widths->data;
308
309 for (child = clutter_actor_get_first_child (CLUTTER_ACTOR (table));
310 child != NULL;
311 child = clutter_actor_get_next_sibling (child))
312 {
313 gint col;
314 gfloat w_min, w_pref;
315 gboolean x_expand;
316 StTableChild *meta;
317 gint col_span;
318
319 meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (table), child);
320
321 if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child))
322 continue;
323
324 /* get child properties */
325 col = meta->col;
326 x_expand = meta->x_expand;
327 col_span = meta->col_span;
328
329 if (x_expand)
330 is_expand_col[col] = TRUE;
331
332 _st_actor_get_preferred_width (child, -1, meta->y_fill, &w_min, &w_pref);
333 if (col_span == 1 && w_pref > pref_widths[col])
334 {
335 pref_widths[col] = w_pref;
336 }
337 if (col_span == 1 && w_min > min_widths[col])
338 {
339 min_widths[col] = w_min;
340 }
341
342 }
343
344 total_min_width = priv->col_spacing * (priv->n_cols - 1);
345 for (i = 0; i < priv->n_cols; i++)
346 total_min_width += pref_widths[i];
347
348 /* calculate the remaining space and distribute it evenly onto all rows/cols
349 * with the x/y expand property set. */
350 for (i = 0; i < priv->n_cols; i++)
351 if (is_expand_col[i])
352 {
353 expanded_cols += pref_widths[i];
354 n_expanded_cols++;
355 }
356
357 /* for_width - total_min_width */
358 extra_col_width = for_width - total_min_width;
359 if (extra_col_width)
360 for (i = 0; i < priv->n_cols; i++)
361 if (is_expand_col[i])
362 {
363 if (extra_col_width < 0)
364 {
365 pref_widths[i] =
366 MAX (min_widths[i],
367 pref_widths[i]
368 + (extra_col_width * (pref_widths[i] / (float) expanded_cols)));
369
370 /* if we reached the minimum width for this column, we need to
371 * stop counting it as expanded */
372 if (pref_widths[i] == min_widths[i])
373 {
374 /* restart calculations :-( */
375 expanded_cols -= pref_widths[i];
376 is_expand_col[i] = 0;
377 n_expanded_cols--;
378 i = -1;
379 }
380 }
381 else
382 pref_widths[i] += extra_col_width / n_expanded_cols;
383 }
384
385 return pref_widths;
386 }
387
388 static gint *
389 st_table_calculate_row_heights (StTable *table,
390 gint for_height,
391 gint * col_widths)
392 {
393 StTablePrivate *priv = ST_TABLE (table)->priv;
394 gint *is_expand_row, *min_heights, *pref_heights, *row_heights, extra_row_height;
395 gint i, total_min_height;
396 gint expanded_rows = 0;
397 gint n_expanded_rows = 0;
398 ClutterActor *child;
399
400 g_array_set_size (priv->row_heights, 0);
401 g_array_set_size (priv->row_heights, priv->n_rows);
402 row_heights = (gboolean *) priv->row_heights->data;
403
404 g_array_set_size (priv->is_expand_row, 0);
405 g_array_set_size (priv->is_expand_row, priv->n_rows);
406 is_expand_row = (gboolean *) priv->is_expand_row->data;
407
408 g_array_set_size (priv->min_heights, 0);
409 g_array_set_size (priv->min_heights, priv->n_rows);
410 min_heights = (gboolean *) priv->min_heights->data;
411
412 g_array_set_size (priv->pref_heights, 0);
413 g_array_set_size (priv->pref_heights, priv->n_rows);
414 pref_heights = (gboolean *) priv->pref_heights->data;
415
416 for (child = clutter_actor_get_first_child (CLUTTER_ACTOR (table));
417 child != NULL;
418 child = clutter_actor_get_next_sibling (child))
419 {
420 gint row, col, cell_width;
421 gfloat h_min, h_pref;
422 gboolean y_expand;
423 StTableChild *meta;
424 gint col_span, row_span;
425
426 meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (table), child);
427
428 if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child))
429 continue;
430
431 /* get child properties */
432 col = meta->col;
433 row = meta->row;
434 y_expand = meta->y_expand;
435 col_span = meta->col_span;
436 row_span = meta->row_span;
437
438 if (y_expand)
439 is_expand_row[row] = TRUE;
440
441 /* calculate the cell width by including any spanned columns */
442 cell_width = 0;
443 for (i = 0; i < col_span && col + i < priv->n_cols; i++)
444 cell_width += (float)(col_widths[col + i]);
445
446 if (!meta->x_fill)
447 {
448 gfloat width;
449 _st_actor_get_preferred_width (child, -1, meta->y_fill, NULL, &width);
450 cell_width = MIN (cell_width, width);
451 }
452
453 _st_actor_get_preferred_height (child, cell_width, meta->x_fill,
454 &h_min, &h_pref);
455
456 if (row_span == 1 && h_pref > pref_heights[row])
457 {
458 pref_heights[row] = (int)(h_pref);
459 }
460 if (row_span == 1 && h_min > min_heights[row])
461 {
462 min_heights[row] = (int)(h_min);
463 }
464 }
465
466 total_min_height = 0; // priv->row_spacing * (priv->n_rows - 1);
467 for (i = 0; i < priv->n_rows; i++)
468 total_min_height += pref_heights[i];
469
470 /* calculate the remaining space and distribute it evenly onto all rows/cols
471 * with the x/y expand property set. */
472 for (i = 0; i < priv->n_rows; i++)
473 if (is_expand_row[i])
474 {
475 expanded_rows += pref_heights[i];
476 n_expanded_rows++;
477 }
478
479 /* extra row height = for height - row spacings - total_min_height */
480 for_height -= (priv->row_spacing * (priv->n_rows - 1));
481 extra_row_height = for_height - total_min_height;
482
483
484 if (extra_row_height < 0)
485 {
486 gint *skip = g_slice_alloc0 (sizeof (gint) * priv->n_rows);
487 gint total_shrink_height;
488
489 /* If we need to shrink rows, we need to do multiple passes.
490 *
491 * We start by assuming all rows can shrink. All rows are sized
492 * proportional to their height in the total table size. If a row would be
493 * sized smaller than its minimum size, we mark it as non-shrinkable, and
494 * reduce extra_row_height by the amount it has been shrunk. The amount
495 * it has been shrunk by is the difference between the preferred and
496 * minimum height, since all rows start at their preferred height. We
497 * also then reduce the total table size (stored in total_shrink_height) by the height
498 * of the row we are going to be skipping.
499 *
500 */
501
502 /* We start by assuming all rows can shrink */
503 total_shrink_height = total_min_height;
504 for (i = 0; i < priv->n_rows; i++)
505 {
506 if (!skip[i])
507 {
508 gint tmp;
509
510 /* Calculate the height of the row by starting with the preferred
511 * height and taking away the extra row height proportional to
512 * the preferred row height over the rows that are being shrunk
513 */
514 tmp = pref_heights[i]
515 + (extra_row_height * (pref_heights[i] / (float) total_shrink_height));
516
517 if (tmp < min_heights[i])
518 {
519 /* This was a row we *were* set to shrink, but we now find it would have
520 * been shrunk too much. We remove it from the list of rows to shrink and
521 * adjust extra_row_height and total_shrink_height appropriately */
522 skip[i] = TRUE;
523 row_heights[i] = min_heights[i];
524
525 /* Reduce extra_row_height by the amount we have reduced this
526 * actor by */
527 extra_row_height += (pref_heights[i] - min_heights[i]);
528 /* now take off the row from the total shrink height */
529 total_shrink_height -= pref_heights[i];
530
531 /* restart the loop */
532 i = -1;
533 }
534 else
535 {
536 skip[i] = FALSE;
537 row_heights[i] = tmp;
538 }
539 }
540
541 }
542
543 g_slice_free1 (sizeof (gint) * priv->n_rows, skip);
544 }
545 else
546 {
547 for (i = 0; i < priv->n_rows; i++)
548 {
549 if (is_expand_row[i])
550 row_heights[i] = pref_heights[i] + (extra_row_height / n_expanded_rows);
551 else
552 row_heights[i] = pref_heights[i];
553 }
554 }
555
556
557 return row_heights;
558 }
559
560 static void
561 st_table_preferred_allocate (ClutterActor *self,
562 const ClutterActorBox *content_box,
563 gboolean flags)
564 {
565 gint row_spacing, col_spacing;
566 gint i;
567 gint *col_widths, *row_heights;
568 StTable *table;
569 StTablePrivate *priv;
570 gboolean ltr;
571 ClutterActor *child;
572
573 table = ST_TABLE (self);
574 priv = ST_TABLE (self)->priv;
575
576 col_spacing = (priv->col_spacing);
577 row_spacing = (priv->row_spacing);
578
579 col_widths =
580 st_table_calculate_col_widths (table,
581 (int) (content_box->x2 - content_box->x1));
582
583 row_heights =
584 st_table_calculate_row_heights (table,
585 (int) (content_box->y2 - content_box->y1),
586 col_widths);
587
588 ltr = (clutter_actor_get_text_direction (self) == CLUTTER_TEXT_DIRECTION_LTR);
589
590 for (child = clutter_actor_get_first_child (self);
591 child != NULL;
592 child = clutter_actor_get_next_sibling (child))
593 {
594 gint row, col, row_span, col_span;
595 gint col_width, row_height;
596 StTableChild *meta;
597 ClutterActorBox childbox;
598 gint child_x, child_y;
599 gdouble x_align_f, y_align_f;
600
601 meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (self), child);
602
603 if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child))
604 continue;
605
606 /* get child properties */
607 col = meta->col;
608 row = meta->row;
609 row_span = meta->row_span;
610 col_span = meta->col_span;
611
612 _st_get_align_factors (meta->x_align, meta->y_align,
613 &x_align_f, &y_align_f);
614
615 /* initialise the width and height */
616 col_width = col_widths[col];
617 row_height = row_heights[row];
618
619 /* Add the widths of the spanned columns:
620 *
621 * First check that we have a non-zero span. Then we loop over each of
622 * the columns that we're spanning but we stop short if we go past the
623 * number of columns in the table. This is necessary to avoid accessing
624 * uninitialised memory. We add the spacing in here too since we only
625 * want to add as much spacing as times we successfully span.
626 */
627 if (col + col_span > priv->n_cols)
628 g_warning ("StTable: col-span exceeds number of columns");
629 #if 0
630 if (row + row_span > priv->n_rows)
631 g_warning ("StTable: row-span exceeds number of rows");
632 #endif
633 if (col_span > 1)
634 {
635 for (i = col + 1; i < col + col_span && i < priv->n_cols; i++)
636 {
637 col_width += col_widths[i];
638 col_width += col_spacing;
639 }
640 }
641
642 /* add the height of the spanned rows */
643 if (row_span > 1)
644 {
645 for (i = row + 1; i < row + row_span && i < priv->n_rows; i++)
646 {
647 row_height += row_heights[i];
648 row_height += row_spacing;
649 }
650 }
651
652 /* calculate child x */
653 if (ltr)
654 {
655 child_x = (int) content_box->x1
656 + col_spacing * col;
657 for (i = 0; i < col; i++)
658 child_x += col_widths[i];
659 }
660 else
661 {
662 child_x = (int) content_box->x2
663 - col_spacing * col;
664 for (i = 0; i < col; i++)
665 child_x -= col_widths[i];
666 }
667
668 /* calculate child y */
669 child_y = (int) content_box->y1
670 + row_spacing * row;
671 for (i = 0; i < row; i++)
672 child_y += row_heights[i];
673
674 /* set up childbox */
675 if (ltr)
676 {
677 childbox.x1 = (float) child_x;
678 childbox.x2 = (float) MAX (0, child_x + col_width);
679 }
680 else
681 {
682 childbox.x2 = (float) child_x;
683 childbox.x1 = (float) MAX (0, child_x - col_width);
684 }
685
686 childbox.y1 = (float) child_y;
687 childbox.y2 = (float) MAX (0, child_y + row_height);
688
689
690 clutter_actor_allocate_align_fill (child, &childbox,
691 x_align_f, y_align_f,
692 meta->x_fill, meta->y_fill,
693 flags);
694 }
695 }
696
697 static void
698 st_table_allocate (ClutterActor *self,
699 const ClutterActorBox *box,
700 ClutterAllocationFlags flags)
701 {
702 StTablePrivate *priv = ST_TABLE (self)->priv;
703 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
704 ClutterActorBox content_box;
705
706 clutter_actor_set_allocation (self, box, flags);
707
708 if (priv->n_cols < 1 || priv->n_rows < 1)
709 return;
710
711 st_theme_node_get_content_box (theme_node, box, &content_box);
712
713 if (priv->homogeneous)
714 st_table_homogeneous_allocate (self, &content_box, flags);
715 else
716 st_table_preferred_allocate (self, &content_box, flags);
717 }
718
719 static void
720 st_table_get_preferred_width (ClutterActor *self,
721 gfloat for_height,
722 gfloat *min_width_p,
723 gfloat *natural_width_p)
724 {
725 gint *min_widths, *pref_widths;
726 gfloat total_min_width, total_pref_width;
727 StTablePrivate *priv = ST_TABLE (self)->priv;
728 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
729 gint i;
730 ClutterActor *child;
731
732 if (priv->n_cols < 1)
733 {
734 *min_width_p = 0;
735 *natural_width_p = 0;
736 return;
737 }
738
739 /* Setting size to zero and then what we want it to be causes a clear if
740 * clear flag is set (which it should be.)
741 */
742 g_array_set_size (priv->min_widths, 0);
743 g_array_set_size (priv->pref_widths, 0);
744 g_array_set_size (priv->min_widths, priv->n_cols);
745 g_array_set_size (priv->pref_widths, priv->n_cols);
746
747 min_widths = (gint *) priv->min_widths->data;
748 pref_widths = (gint *) priv->pref_widths->data;
749
750 /* calculate minimum row widths */
751 for (child = clutter_actor_get_first_child (self);
752 child != NULL;
753 child = clutter_actor_get_next_sibling (child))
754 {
755 gint col, col_span;
756 gfloat w_min, w_pref;
757 StTableChild *meta;
758
759 meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (self), child);
760
761 if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child))
762 continue;
763
764 /* get child properties */
765 col = meta->col;
766 col_span = meta->col_span;
767
768 _st_actor_get_preferred_width (child, -1, meta->y_fill, &w_min, &w_pref);
769
770 if (col_span == 1 && w_min > min_widths[col])
771 min_widths[col] = w_min;
772 if (col_span == 1 && w_pref > pref_widths[col])
773 pref_widths[col] = w_pref;
774 }
775
776 total_min_width = (priv->n_cols - 1) * (float) priv->col_spacing;
777 total_pref_width = total_min_width;
778
779 for (i = 0; i < priv->n_cols; i++)
780 {
781 total_min_width += min_widths[i];
782 total_pref_width += pref_widths[i];
783 }
784
785 /* If we were requested width-for-height, then we reported minimum/natural
786 * heights based on our natural width. If we were allocated less than our
787 * natural width, then we need more height. So in the width-for-height
788 * case we need to disable shrinking.
789 */
790 if (for_height >= 0)
791 total_min_width = total_pref_width;
792
793 if (min_width_p)
794 *min_width_p = total_min_width;
795 if (natural_width_p)
796 *natural_width_p = total_pref_width;
797
798 st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
799 }
800
801 static void
802 st_table_get_preferred_height (ClutterActor *self,
803 gfloat for_width,
804 gfloat *min_height_p,
805 gfloat *natural_height_p)
806 {
807 gint *min_heights, *pref_heights;
808 gfloat total_min_height, total_pref_height;
809 StTablePrivate *priv = ST_TABLE (self)->priv;
810 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
811 gint i;
812 gint *min_widths;
813 ClutterActor *child;
814
815 /* We only support height-for-width allocation. So if we are called
816 * width-for-height, calculate heights based on our natural width
817 */
818 if (for_width < 0)
819 {
820 float natural_width;
821
822 clutter_actor_get_preferred_width (self, -1, NULL, &natural_width);
823 for_width = natural_width;
824 }
825
826 if (priv->n_rows < 1)
827 {
828 *min_height_p = 0;
829 *natural_height_p = 0;
830 return;
831 }
832
833 st_theme_node_adjust_for_width (theme_node, &for_width);
834
835 /* Setting size to zero and then what we want it to be causes a clear if
836 * clear flag is set (which it should be.)
837 */
838 g_array_set_size (priv->min_heights, 0);
839 g_array_set_size (priv->pref_heights, 0);
840 g_array_set_size (priv->min_heights, priv->n_rows);
841 g_array_set_size (priv->pref_heights, priv->n_rows);
842
843 /* use min_widths to help allocation of height-for-width widgets */
844 min_widths = st_table_calculate_col_widths (ST_TABLE (self), for_width);
845
846 min_heights = (gint *) priv->min_heights->data;
847 pref_heights = (gint *) priv->pref_heights->data;
848
849 /* calculate minimum row heights */
850 for (child = clutter_actor_get_first_child (self);
851 child != NULL;
852 child = clutter_actor_get_next_sibling (child))
853 {
854 gint row, col, col_span, cell_width, row_span;
855 gfloat min, pref;
856 StTableChild *meta;
857
858 meta = (StTableChild *) clutter_container_get_child_meta (CLUTTER_CONTAINER (self), child);
859
860 if (!meta->allocate_hidden && !CLUTTER_ACTOR_IS_VISIBLE (child))
861 continue;
862
863 /* get child properties */
864 row = meta->row;
865 col = meta->col;
866 col_span = meta->col_span;
867 row_span = meta->row_span;
868
869 cell_width = 0;
870 for (i = 0; i < col_span && col + i < priv->n_cols; i++)
871 cell_width += min_widths[col + i];
872
873 _st_actor_get_preferred_height (child, (float) cell_width, meta->x_fill,
874 &min, &pref);
875
876 if (row_span == 1 && min > min_heights[row])
877 min_heights[row] = min;
878 if (row_span == 1 && pref > pref_heights[row])
879 pref_heights[row] = pref;
880 }
881
882 /* start off with row spacing */
883 total_min_height = (priv->n_rows - 1) * (float) (priv->row_spacing);
884 total_pref_height = total_min_height;
885
886 for (i = 0; i < priv->n_rows; i++)
887 {
888 total_min_height += min_heights[i];
889 total_pref_height += pref_heights[i];
890 }
891
892 if (min_height_p)
893 *min_height_p = total_min_height;
894 if (natural_height_p)
895 *natural_height_p = total_pref_height;
896
897 st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
898 }
899
900 static void
901 st_table_style_changed (StWidget *self)
902 {
903 StTablePrivate *priv = ST_TABLE (self)->priv;
904 StThemeNode *theme_node = st_widget_get_theme_node (self);
905 int old_row_spacing = priv->row_spacing;
906 int old_col_spacing = priv->col_spacing;
907 double row_spacing, col_spacing;
908
909 row_spacing = st_theme_node_get_length (theme_node, "spacing-rows");
910 priv->row_spacing = (int)(row_spacing + 0.5);
911 col_spacing = st_theme_node_get_length (theme_node, "spacing-columns");
912 priv->col_spacing = (int)(col_spacing + 0.5);
913
914 if (priv->row_spacing != old_row_spacing ||
915 priv->col_spacing != old_col_spacing)
916 clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
917
918 ST_WIDGET_CLASS (st_table_parent_class)->style_changed (self);
919 }
920
921 static void
922 st_table_class_init (StTableClass *klass)
923 {
924 GParamSpec *pspec;
925 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
926 ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
927
928 StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
929
930 g_type_class_add_private (klass, sizeof (StTablePrivate));
931
932 gobject_class->set_property = st_table_set_property;
933 gobject_class->get_property = st_table_get_property;
934 gobject_class->finalize = st_table_finalize;
935
936 actor_class->allocate = st_table_allocate;
937 actor_class->get_preferred_width = st_table_get_preferred_width;
938 actor_class->get_preferred_height = st_table_get_preferred_height;
939
940 widget_class->style_changed = st_table_style_changed;
941
942 pspec = g_param_spec_boolean ("homogeneous",
943 "Homogeneous",
944 "Homogeneous rows and columns",
945 TRUE,
946 ST_PARAM_READWRITE);
947 g_object_class_install_property (gobject_class,
948 PROP_HOMOGENEOUS,
949 pspec);
950
951 pspec = g_param_spec_int ("row-count",
952 "Row Count",
953 "The number of rows in the table",
954 0, G_MAXINT, 0,
955 ST_PARAM_READABLE);
956 g_object_class_install_property (gobject_class,
957 PROP_ROW_COUNT,
958 pspec);
959
960 pspec = g_param_spec_int ("column-count",
961 "Column Count",
962 "The number of columns in the table",
963 0, G_MAXINT, 0,
964 ST_PARAM_READABLE);
965 g_object_class_install_property (gobject_class,
966 PROP_COL_COUNT,
967 pspec);
968 }
969
970 static void
971 st_table_init (StTable *table)
972 {
973 table->priv = ST_TABLE_GET_PRIVATE (table);
974
975 table->priv->n_cols = 0;
976 table->priv->n_rows = 0;
977
978 table->priv->min_widths = g_array_new (FALSE,
979 TRUE,
980 sizeof (gint));
981 table->priv->pref_widths = g_array_new (FALSE,
982 TRUE,
983 sizeof (gint));
984 table->priv->min_heights = g_array_new (FALSE,
985 TRUE,
986 sizeof (gint));
987 table->priv->pref_heights = g_array_new (FALSE,
988 TRUE,
989 sizeof (gint));
990
991 table->priv->is_expand_col = g_array_new (FALSE,
992 TRUE,
993 sizeof (gboolean));
994 table->priv->is_expand_row = g_array_new (FALSE,
995 TRUE,
996 sizeof (gboolean));
997
998 table->priv->col_widths = g_array_new (FALSE,
999 TRUE,
1000 sizeof (gint));
1001 table->priv->row_heights = g_array_new (FALSE,
1002 TRUE,
1003 sizeof (gint));
1004 }
1005
1006 /* used by StTableChild to update row/column count */
1007 void _st_table_update_row_col (StTable *table,
1008 gint row,
1009 gint col)
1010 {
1011 if (col > -1)
1012 table->priv->n_cols = MAX (table->priv->n_cols, col + 1);
1013
1014 if (row > -1)
1015 table->priv->n_rows = MAX (table->priv->n_rows, row + 1);
1016
1017 }
1018
1019 /*** Public Functions ***/
1020
1021 /**
1022 * st_table_new:
1023 *
1024 * Create a new #StTable
1025 *
1026 * Returns: a new #StTable
1027 */
1028 StWidget*
1029 st_table_new (void)
1030 {
1031 return g_object_new (ST_TYPE_TABLE, NULL);
1032 }
1033
1034 /**
1035 * st_table_get_row_count:
1036 * @table: A #StTable
1037 *
1038 * Retrieve the current number rows in the @table
1039 *
1040 * Returns: the number of rows
1041 */
1042 gint
1043 st_table_get_row_count (StTable *table)
1044 {
1045 g_return_val_if_fail (ST_IS_TABLE (table), -1);
1046
1047 return ST_TABLE (table)->priv->n_rows;
1048 }
1049
1050 /**
1051 * st_table_get_column_count:
1052 * @table: A #StTable
1053 *
1054 * Retrieve the current number of columns in @table
1055 *
1056 * Returns: the number of columns
1057 */
1058 gint
1059 st_table_get_column_count (StTable *table)
1060 {
1061 g_return_val_if_fail (ST_IS_TABLE (table), -1);
1062
1063 return ST_TABLE (table)->priv->n_cols;
1064 }