1 /* gtkcellareaboxcontext.c
3 * Copyright (C) 2010 Openismus GmbH
6 * Tristan Van Berkom <tristanvb@openismus.com>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library 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.
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 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public
19 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
24 #include "gtkcellareabox.h"
25 #include "gtkcellareaboxcontextprivate.h"
26 #include "gtkorientable.h"
29 static void _gtk_cell_area_box_context_finalize (GObject *object);
31 /* GtkCellAreaContextClass */
32 static void _gtk_cell_area_box_context_reset (GtkCellAreaContext *context);
33 static void _gtk_cell_area_box_context_get_preferred_height_for_width (GtkCellAreaContext *context,
36 gint *natural_height);
37 static void _gtk_cell_area_box_context_get_preferred_width_for_height (GtkCellAreaContext *context,
44 /* Internal functions */
45 static void _gtk_cell_area_box_context_sum (GtkCellAreaBoxContext *context,
46 GtkOrientation orientation,
50 static void free_cache_array (GArray *array);
51 static GArray *group_array_new (GtkCellAreaBoxContext *context);
52 static GArray *get_array (GtkCellAreaBoxContext *context,
53 GtkOrientation orientation,
55 static gboolean group_expands (GtkCellAreaBoxContext *context,
57 static gint count_expand_groups (GtkCellAreaBoxContext *context);
60 /* CachedSize management */
66 struct _GtkCellAreaBoxContextPrivate
68 /* Table of per renderer CachedSizes */
72 /* Table of per height/width hash tables of per renderer CachedSizes */
76 /* Whether each group expands */
79 /* Whether each group is aligned */
83 G_DEFINE_TYPE (GtkCellAreaBoxContext, _gtk_cell_area_box_context, GTK_TYPE_CELL_AREA_CONTEXT);
86 free_cache_array (GArray *array)
88 g_array_free (array, TRUE);
92 group_array_new (GtkCellAreaBoxContext *context)
94 GtkCellAreaBoxContextPrivate *priv = context->priv;
97 group_array = g_array_new (FALSE, TRUE, sizeof (CachedSize));
98 g_array_set_size (group_array, priv->base_widths->len);
104 get_array (GtkCellAreaBoxContext *context,
105 GtkOrientation orientation,
108 GtkCellAreaBoxContextPrivate *priv = context->priv;
113 if (orientation == GTK_ORIENTATION_HORIZONTAL)
114 array = priv->base_widths;
116 array = priv->base_heights;
120 if (orientation == GTK_ORIENTATION_HORIZONTAL)
122 array = g_hash_table_lookup (priv->widths, GINT_TO_POINTER (for_size));
125 array = priv->base_widths;
129 array = g_hash_table_lookup (priv->heights, GINT_TO_POINTER (for_size));
132 array = priv->base_heights;
140 group_expands (GtkCellAreaBoxContext *context,
143 GtkCellAreaBoxContextPrivate *priv = context->priv;
145 g_assert (group_idx >= 0 && group_idx < priv->base_widths->len);
147 return priv->expand[group_idx];
151 count_expand_groups (GtkCellAreaBoxContext *context)
153 GtkCellAreaBoxContextPrivate *priv = context->priv;
156 for (i = 0; i < priv->base_widths->len; i++)
166 _gtk_cell_area_box_context_init (GtkCellAreaBoxContext *box_context)
168 GtkCellAreaBoxContextPrivate *priv;
170 box_context->priv = G_TYPE_INSTANCE_GET_PRIVATE (box_context,
171 GTK_TYPE_CELL_AREA_BOX_CONTEXT,
172 GtkCellAreaBoxContextPrivate);
173 priv = box_context->priv;
175 priv->base_widths = g_array_new (FALSE, TRUE, sizeof (CachedSize));
176 priv->base_heights = g_array_new (FALSE, TRUE, sizeof (CachedSize));
178 priv->widths = g_hash_table_new_full (g_direct_hash, g_direct_equal,
179 NULL, (GDestroyNotify)free_cache_array);
180 priv->heights = g_hash_table_new_full (g_direct_hash, g_direct_equal,
181 NULL, (GDestroyNotify)free_cache_array);
185 _gtk_cell_area_box_context_class_init (GtkCellAreaBoxContextClass *class)
187 GObjectClass *object_class = G_OBJECT_CLASS (class);
188 GtkCellAreaContextClass *context_class = GTK_CELL_AREA_CONTEXT_CLASS (class);
191 object_class->finalize = _gtk_cell_area_box_context_finalize;
193 context_class->reset = _gtk_cell_area_box_context_reset;
194 context_class->get_preferred_height_for_width = _gtk_cell_area_box_context_get_preferred_height_for_width;
195 context_class->get_preferred_width_for_height = _gtk_cell_area_box_context_get_preferred_width_for_height;
197 g_type_class_add_private (object_class, sizeof (GtkCellAreaBoxContextPrivate));
200 /*************************************************************
202 *************************************************************/
204 _gtk_cell_area_box_context_finalize (GObject *object)
206 GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (object);
207 GtkCellAreaBoxContextPrivate *priv = box_context->priv;
209 g_array_free (priv->base_widths, TRUE);
210 g_array_free (priv->base_heights, TRUE);
211 g_hash_table_destroy (priv->widths);
212 g_hash_table_destroy (priv->heights);
214 g_free (priv->expand);
215 g_free (priv->align);
217 G_OBJECT_CLASS (_gtk_cell_area_box_context_parent_class)->finalize (object);
220 /*************************************************************
221 * GtkCellAreaContextClass *
222 *************************************************************/
224 _gtk_cell_area_box_context_reset (GtkCellAreaContext *context)
226 GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
227 GtkCellAreaBoxContextPrivate *priv = box_context->priv;
231 for (i = 0; i < priv->base_widths->len; i++)
233 size = &g_array_index (priv->base_widths, CachedSize, i);
238 size = &g_array_index (priv->base_heights, CachedSize, i);
244 /* Reset context sizes as well */
245 g_hash_table_remove_all (priv->widths);
246 g_hash_table_remove_all (priv->heights);
248 GTK_CELL_AREA_CONTEXT_CLASS
249 (_gtk_cell_area_box_context_parent_class)->reset (context);
253 _gtk_cell_area_box_context_sum (GtkCellAreaBoxContext *context,
254 GtkOrientation orientation,
259 GtkCellAreaBoxContextPrivate *priv = context->priv;
260 GtkCellAreaBox *area;
261 GtkOrientation box_orientation;
263 gint spacing, i, last_aligned_group_idx;
264 gint min_size = 0, nat_size = 0;
266 area = (GtkCellAreaBox *)gtk_cell_area_context_get_area (GTK_CELL_AREA_CONTEXT (context));
267 spacing = gtk_cell_area_box_get_spacing (area);
268 box_orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (area));
269 array = get_array (context, orientation, for_size);
271 /* Get the last visible aligned group
272 * (we need to get space at least up till this group) */
273 for (i = array->len - 1; i >= 0; i--)
275 if (priv->align[i] &&
276 _gtk_cell_area_box_group_visible (area, i))
279 last_aligned_group_idx = i >= 0 ? i : 0;
281 for (i = 0; i < array->len; i++)
283 CachedSize *size = &g_array_index (array, CachedSize, i);
285 if (box_orientation == orientation)
287 if (i > last_aligned_group_idx &&
288 !_gtk_cell_area_box_group_visible (area, i))
291 /* Dont add spacing for 0 size groups, they can be 0 size because
292 * they contain only invisible cells for this round of requests
294 if (min_size > 0 && size->nat_size > 0)
300 min_size += size->min_size;
301 nat_size += size->nat_size;
305 min_size = MAX (min_size, size->min_size);
306 nat_size = MAX (nat_size, size->nat_size);
312 if (orientation == GTK_ORIENTATION_HORIZONTAL)
313 gtk_cell_area_context_push_preferred_width (GTK_CELL_AREA_CONTEXT (context), min_size, nat_size);
315 gtk_cell_area_context_push_preferred_height (GTK_CELL_AREA_CONTEXT (context), min_size, nat_size);
319 *minimum_size = min_size;
321 *natural_size = nat_size;
325 _gtk_cell_area_box_context_get_preferred_height_for_width (GtkCellAreaContext *context,
327 gint *minimum_height,
328 gint *natural_height)
330 _gtk_cell_area_box_context_sum (GTK_CELL_AREA_BOX_CONTEXT (context), GTK_ORIENTATION_VERTICAL,
331 width, minimum_height, natural_height);
335 _gtk_cell_area_box_context_get_preferred_width_for_height (GtkCellAreaContext *context,
340 _gtk_cell_area_box_context_sum (GTK_CELL_AREA_BOX_CONTEXT (context), GTK_ORIENTATION_HORIZONTAL,
341 height, minimum_width, natural_width);
344 /*************************************************************
346 *************************************************************/
348 copy_size_array (GArray *src_array,
353 for (i = 0; i < src_array->len; i++)
355 CachedSize *src = &g_array_index (src_array, CachedSize, i);
356 CachedSize *dest = &g_array_index (dest_array, CachedSize, i);
358 memcpy (dest, src, sizeof (CachedSize));
363 for_size_copy (gpointer key,
365 GHashTable *dest_hash)
369 new_array = g_array_new (FALSE, TRUE, sizeof (CachedSize));
370 g_array_set_size (new_array, size_array->len);
372 copy_size_array (size_array, new_array);
374 g_hash_table_insert (dest_hash, key, new_array);
377 GtkCellAreaBoxContext *
378 _gtk_cell_area_box_context_copy (GtkCellAreaBox *box,
379 GtkCellAreaBoxContext *context)
381 GtkCellAreaBoxContext *copy;
383 copy = g_object_new (GTK_TYPE_CELL_AREA_BOX_CONTEXT,
386 _gtk_cell_area_box_init_groups (copy,
387 context->priv->base_widths->len,
388 context->priv->expand,
389 context->priv->align);
391 /* Copy the base arrays */
392 copy_size_array (context->priv->base_widths,
393 copy->priv->base_widths);
394 copy_size_array (context->priv->base_heights,
395 copy->priv->base_heights);
397 /* Copy each for size */
398 g_hash_table_foreach (context->priv->heights,
399 (GHFunc)for_size_copy, copy->priv->heights);
400 g_hash_table_foreach (context->priv->widths,
401 (GHFunc)for_size_copy, copy->priv->widths);
408 _gtk_cell_area_box_init_groups (GtkCellAreaBoxContext *box_context,
410 gboolean *expand_groups,
411 gboolean *align_groups)
413 GtkCellAreaBoxContextPrivate *priv;
415 g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
416 g_return_if_fail (n_groups == 0 || expand_groups != NULL);
418 /* When the group dimensions change, all info must be reset
419 * Note this already clears the min/nat values on the CachedSizes
421 gtk_cell_area_context_reset (GTK_CELL_AREA_CONTEXT (box_context));
423 priv = box_context->priv;
424 g_array_set_size (priv->base_widths, n_groups);
425 g_array_set_size (priv->base_heights, n_groups);
427 g_free (priv->expand);
428 priv->expand = g_memdup (expand_groups, n_groups * sizeof (gboolean));
430 g_free (priv->align);
431 priv->align = g_memdup (align_groups, n_groups * sizeof (gboolean));
435 _gtk_cell_area_box_context_push_group_width (GtkCellAreaBoxContext *box_context,
440 GtkCellAreaBoxContextPrivate *priv;
442 gboolean grew = FALSE;
444 g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
446 priv = box_context->priv;
447 g_return_if_fail (group_idx < priv->base_widths->len);
449 size = &g_array_index (priv->base_widths, CachedSize, group_idx);
450 if (minimum_width > size->min_size)
452 size->min_size = minimum_width;
455 if (natural_width > size->nat_size)
457 size->nat_size = natural_width;
462 _gtk_cell_area_box_context_sum (box_context, GTK_ORIENTATION_HORIZONTAL, -1, NULL, NULL);
466 _gtk_cell_area_box_context_push_group_height_for_width (GtkCellAreaBoxContext *box_context,
472 GtkCellAreaBoxContextPrivate *priv;
476 g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
478 priv = box_context->priv;
479 g_return_if_fail (group_idx < priv->base_widths->len);
481 group_array = g_hash_table_lookup (priv->heights, GINT_TO_POINTER (for_width));
484 group_array = group_array_new (box_context);
485 g_hash_table_insert (priv->heights, GINT_TO_POINTER (for_width), group_array);
488 size = &g_array_index (group_array, CachedSize, group_idx);
489 size->min_size = MAX (size->min_size, minimum_height);
490 size->nat_size = MAX (size->nat_size, natural_height);
494 _gtk_cell_area_box_context_push_group_height (GtkCellAreaBoxContext *box_context,
499 GtkCellAreaBoxContextPrivate *priv;
501 gboolean grew = FALSE;
503 g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
505 priv = box_context->priv;
506 g_return_if_fail (group_idx < priv->base_heights->len);
508 size = &g_array_index (priv->base_heights, CachedSize, group_idx);
509 if (minimum_height > size->min_size)
511 size->min_size = minimum_height;
514 if (natural_height > size->nat_size)
516 size->nat_size = natural_height;
521 _gtk_cell_area_box_context_sum (box_context, GTK_ORIENTATION_VERTICAL, -1, NULL, NULL);
525 _gtk_cell_area_box_context_push_group_width_for_height (GtkCellAreaBoxContext *box_context,
531 GtkCellAreaBoxContextPrivate *priv;
535 g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
537 priv = box_context->priv;
538 g_return_if_fail (group_idx < priv->base_widths->len);
540 group_array = g_hash_table_lookup (priv->widths, GINT_TO_POINTER (for_height));
543 group_array = group_array_new (box_context);
544 g_hash_table_insert (priv->widths, GINT_TO_POINTER (for_height), group_array);
547 size = &g_array_index (group_array, CachedSize, group_idx);
548 size->min_size = MAX (size->min_size, minimum_width);
549 size->nat_size = MAX (size->nat_size, natural_width);
553 _gtk_cell_area_box_context_get_group_width (GtkCellAreaBoxContext *box_context,
558 GtkCellAreaBoxContextPrivate *priv;
561 g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
563 priv = box_context->priv;
564 g_return_if_fail (group_idx < priv->base_widths->len);
566 size = &g_array_index (priv->base_widths, CachedSize, group_idx);
569 *minimum_width = size->min_size;
572 *natural_width = size->nat_size;
576 _gtk_cell_area_box_context_get_group_height_for_width (GtkCellAreaBoxContext *box_context,
579 gint *minimum_height,
580 gint *natural_height)
582 GtkCellAreaBoxContextPrivate *priv;
585 g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
587 priv = box_context->priv;
588 g_return_if_fail (group_idx < priv->base_widths->len);
590 group_array = g_hash_table_lookup (priv->heights, GINT_TO_POINTER (for_width));
594 CachedSize *size = &g_array_index (group_array, CachedSize, group_idx);
597 *minimum_height = size->min_size;
600 *natural_height = size->nat_size;
605 *minimum_height = -1;
608 *natural_height = -1;
613 _gtk_cell_area_box_context_get_group_height (GtkCellAreaBoxContext *box_context,
615 gint *minimum_height,
616 gint *natural_height)
618 GtkCellAreaBoxContextPrivate *priv;
621 g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
623 priv = box_context->priv;
624 g_return_if_fail (group_idx < priv->base_heights->len);
626 size = &g_array_index (priv->base_heights, CachedSize, group_idx);
629 *minimum_height = size->min_size;
632 *natural_height = size->nat_size;
636 _gtk_cell_area_box_context_get_group_width_for_height (GtkCellAreaBoxContext *box_context,
642 GtkCellAreaBoxContextPrivate *priv;
645 g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
647 priv = box_context->priv;
648 g_return_if_fail (group_idx < priv->base_widths->len);
650 group_array = g_hash_table_lookup (priv->widths, GINT_TO_POINTER (for_height));
654 CachedSize *size = &g_array_index (group_array, CachedSize, group_idx);
657 *minimum_width = size->min_size;
660 *natural_width = size->nat_size;
672 static GtkRequestedSize *
673 _gtk_cell_area_box_context_get_requests (GtkCellAreaBoxContext *box_context,
674 GtkCellAreaBox *area,
675 GtkOrientation orientation,
679 GtkCellAreaBoxContextPrivate *priv = box_context->priv;
680 GtkRequestedSize *requests;
683 gint visible_groups = 0;
684 gint last_aligned_group_idx = 0;
687 /* Get the last visible aligned group
688 * (we need to get space at least up till this group) */
689 for (i = priv->base_widths->len - 1; i >= 0; i--)
691 if (priv->align[i] &&
692 _gtk_cell_area_box_group_visible (area, i))
695 last_aligned_group_idx = i >= 0 ? i : 0;
697 priv = box_context->priv;
698 array = get_array (box_context, orientation, for_size);
700 for (i = 0; i < array->len; i++)
702 size = &g_array_index (array, CachedSize, i);
704 if (size->nat_size > 0 &&
705 (i <= last_aligned_group_idx ||
706 _gtk_cell_area_box_group_visible (area, i)))
710 requests = g_new (GtkRequestedSize, visible_groups);
712 for (j = 0, i = 0; i < array->len; i++)
714 size = &g_array_index (array, CachedSize, i);
716 if (size->nat_size > 0 &&
717 (i <= last_aligned_group_idx ||
718 _gtk_cell_area_box_group_visible (area, i)))
720 requests[j].data = GINT_TO_POINTER (i);
721 requests[j].minimum_size = size->min_size;
722 requests[j].natural_size = size->nat_size;
728 *n_requests = visible_groups;
733 static GtkCellAreaBoxAllocation *
734 allocate_for_orientation (GtkCellAreaBoxContext *context,
735 GtkCellAreaBox *area,
736 GtkOrientation orientation,
742 GtkCellAreaBoxContextPrivate *priv = context->priv;
743 GtkCellAreaBoxAllocation *allocs;
744 GtkRequestedSize *sizes;
745 gint n_expand_groups = 0;
746 gint i, n_groups, position, vis_position;
747 gint extra_size, extra_extra;
748 gint avail_size = size;
750 sizes = _gtk_cell_area_box_context_get_requests (context, area, orientation, for_size, &n_groups);
751 n_expand_groups = count_expand_groups (context);
753 /* First start by naturally allocating space among groups */
754 avail_size -= (n_groups - 1) * spacing;
755 for (i = 0; i < n_groups; i++)
756 avail_size -= sizes[i].minimum_size;
759 avail_size = gtk_distribute_natural_allocation (avail_size, n_groups, sizes);
763 /* Calculate/distribute expand for groups */
764 if (n_expand_groups > 0)
766 extra_size = avail_size / n_expand_groups;
767 extra_extra = avail_size % n_expand_groups;
770 extra_size = extra_extra = 0;
772 allocs = g_new (GtkCellAreaBoxAllocation, n_groups);
774 for (vis_position = 0, position = 0, i = 0; i < n_groups; i++)
776 allocs[i].group_idx = GPOINTER_TO_INT (sizes[i].data);
778 if (priv->align[allocs[i].group_idx])
779 vis_position = position;
781 allocs[i].position = vis_position;
782 allocs[i].size = sizes[i].minimum_size;
784 if (group_expands (context, allocs[i].group_idx))
786 allocs[i].size += extra_size;
794 position += allocs[i].size;
797 if (_gtk_cell_area_box_group_visible (area, allocs[i].group_idx))
799 vis_position += allocs[i].size;
800 vis_position += spacing;
805 *n_allocs = n_groups;
813 _gtk_cell_area_box_context_get_widths (GtkCellAreaBoxContext *box_context,
816 GtkCellAreaBox *area = (GtkCellAreaBox *)gtk_cell_area_context_get_area (GTK_CELL_AREA_CONTEXT (box_context));
818 return _gtk_cell_area_box_context_get_requests (box_context, area, GTK_ORIENTATION_HORIZONTAL, -1, n_widths);
822 _gtk_cell_area_box_context_get_heights (GtkCellAreaBoxContext *box_context,
825 GtkCellAreaBox *area = (GtkCellAreaBox *)gtk_cell_area_context_get_area (GTK_CELL_AREA_CONTEXT (box_context));
827 return _gtk_cell_area_box_context_get_requests (box_context, area, GTK_ORIENTATION_VERTICAL, -1, n_heights);
830 GtkCellAreaBoxAllocation *
831 _gtk_cell_area_box_context_get_orientation_allocs (GtkCellAreaBoxContext *context,
834 GtkCellAreaContext *ctx = GTK_CELL_AREA_CONTEXT (context);
835 GtkCellAreaBox *area;
836 GtkOrientation orientation;
837 gint spacing, width, height, alloc_count = 0;
838 GtkCellAreaBoxAllocation *allocs = NULL;
840 area = (GtkCellAreaBox *)gtk_cell_area_context_get_area (ctx);
841 orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (area));
842 spacing = gtk_cell_area_box_get_spacing (area);
844 gtk_cell_area_context_get_allocation (ctx, &width, &height);
846 if (orientation == GTK_ORIENTATION_HORIZONTAL && width > 0)
847 allocs = allocate_for_orientation (context, area, orientation,
848 spacing, width, height,
850 else if (orientation == GTK_ORIENTATION_VERTICAL && height > 0)
851 allocs = allocate_for_orientation (context, area, orientation,
852 spacing, height, width,
855 *n_allocs = alloc_count;