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, write to the
20 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
26 #include "gtkorientable.h"
27 #include "gtkcelllayout.h"
28 #include "gtkcellareabox.h"
29 #include "gtkcellareaboxiter.h"
30 #include "gtkprivate.h"
34 static void gtk_cell_area_box_finalize (GObject *object);
35 static void gtk_cell_area_box_dispose (GObject *object);
36 static void gtk_cell_area_box_set_property (GObject *object,
40 static void gtk_cell_area_box_get_property (GObject *object,
45 /* GtkCellAreaClass */
46 static void gtk_cell_area_box_add (GtkCellArea *area,
47 GtkCellRenderer *renderer);
48 static void gtk_cell_area_box_remove (GtkCellArea *area,
49 GtkCellRenderer *renderer);
50 static void gtk_cell_area_box_forall (GtkCellArea *area,
51 GtkCellCallback callback,
52 gpointer callback_data);
53 static gint gtk_cell_area_box_event (GtkCellArea *area,
56 const GdkRectangle *cell_area);
57 static void gtk_cell_area_box_render (GtkCellArea *area,
60 const GdkRectangle *cell_area);
62 static GtkCellAreaIter *gtk_cell_area_box_create_iter (GtkCellArea *area);
63 static GtkSizeRequestMode gtk_cell_area_box_get_request_mode (GtkCellArea *area);
64 static void gtk_cell_area_box_get_preferred_width (GtkCellArea *area,
65 GtkCellAreaIter *iter,
69 static void gtk_cell_area_box_get_preferred_height (GtkCellArea *area,
70 GtkCellAreaIter *iter,
73 gint *natural_height);
74 static void gtk_cell_area_box_get_preferred_height_for_width (GtkCellArea *area,
75 GtkCellAreaIter *iter,
79 gint *natural_height);
80 static void gtk_cell_area_box_get_preferred_width_for_height (GtkCellArea *area,
81 GtkCellAreaIter *iter,
87 /* GtkCellLayoutIface */
88 static void gtk_cell_area_box_cell_layout_init (GtkCellLayoutIface *iface);
89 static void gtk_cell_area_box_layout_pack_start (GtkCellLayout *cell_layout,
90 GtkCellRenderer *renderer,
92 static void gtk_cell_area_box_layout_pack_end (GtkCellLayout *cell_layout,
93 GtkCellRenderer *renderer,
95 static void gtk_cell_area_box_layout_reorder (GtkCellLayout *cell_layout,
96 GtkCellRenderer *renderer,
100 /* CellInfo metadata handling */
102 GtkCellRenderer *renderer;
108 static CellInfo *cell_info_new (GtkCellRenderer *renderer,
111 static void cell_info_free (CellInfo *info);
112 static gint cell_info_find (CellInfo *info,
113 GtkCellRenderer *renderer);
116 struct _GtkCellAreaBoxPrivate
118 GtkOrientation orientation;
124 guint align_cells : 1;
134 G_DEFINE_TYPE_WITH_CODE (GtkCellAreaBox, gtk_cell_area_box, GTK_TYPE_CELL_AREA,
135 G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
136 gtk_cell_area_box_cell_layout_init)
137 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL));
140 gtk_cell_area_box_init (GtkCellAreaBox *box)
142 GtkCellAreaBoxPrivate *priv;
144 box->priv = G_TYPE_INSTANCE_GET_PRIVATE (box,
145 GTK_TYPE_CELL_AREA_BOX,
146 GtkCellAreaBoxPrivate);
149 priv->orientation = GTK_ORIENTATION_HORIZONTAL;
152 priv->align_cells = TRUE;
156 gtk_cell_area_box_class_init (GtkCellAreaBoxClass *class)
158 GObjectClass *object_class = G_OBJECT_CLASS (class);
159 GtkCellAreaClass *area_class = GTK_CELL_AREA_CLASS (class);
162 object_class->finalize = gtk_cell_area_box_finalize;
163 object_class->dispose = gtk_cell_area_box_dispose;
164 object_class->set_property = gtk_cell_area_box_set_property;
165 object_class->get_property = gtk_cell_area_box_get_property;
167 /* GtkCellAreaClass */
168 area_class->add = gtk_cell_area_box_add;
169 area_class->remove = gtk_cell_area_box_remove;
170 area_class->forall = gtk_cell_area_box_forall;
171 area_class->event = gtk_cell_area_box_event;
172 area_class->render = gtk_cell_area_box_render;
174 area_class->create_iter = gtk_cell_area_box_create_iter;
175 area_class->get_request_mode = gtk_cell_area_box_get_request_mode;
176 area_class->get_preferred_width = gtk_cell_area_box_get_preferred_width;
177 area_class->get_preferred_height = gtk_cell_area_box_get_preferred_height;
178 area_class->get_preferred_height_for_width = gtk_cell_area_box_get_preferred_height_for_width;
179 area_class->get_preferred_width_for_height = gtk_cell_area_box_get_preferred_width_for_height;
181 g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
183 g_object_class_install_property (object_class,
185 g_param_spec_int ("spacing",
187 P_("Space which is inserted between cells"),
191 GTK_PARAM_READWRITE));
193 g_object_class_install_property (object_class,
195 g_param_spec_boolean ("align-cells",
197 P_("Whether cells should be aligned with those "
198 "rendered in adjacent rows"),
200 GTK_PARAM_READWRITE));
202 g_type_class_add_private (object_class, sizeof (GtkCellAreaBoxPrivate));
206 /*************************************************************
208 *************************************************************/
210 cell_info_new (GtkCellRenderer *renderer,
214 CellInfo *info = g_slice_new (CellInfo);
216 info->renderer = g_object_ref_sink (renderer);
217 info->expand = expand;
224 cell_info_free (CellInfo *info)
226 g_object_unref (info->renderer);
228 g_slice_free (CellInfo, info);
232 cell_info_find (CellInfo *info,
233 GtkCellRenderer *renderer)
235 return (info->renderer == renderer) ? 0 : -1;
238 /*************************************************************
240 *************************************************************/
242 gtk_cell_area_box_finalize (GObject *object)
244 G_OBJECT_CLASS (gtk_cell_area_box_parent_class)->finalize (object);
248 gtk_cell_area_box_dispose (GObject *object)
250 G_OBJECT_CLASS (gtk_cell_area_box_parent_class)->dispose (object);
254 gtk_cell_area_box_set_property (GObject *object,
263 gtk_cell_area_box_get_property (GObject *object,
271 /*************************************************************
273 *************************************************************/
275 gtk_cell_area_box_add (GtkCellArea *area,
276 GtkCellRenderer *renderer)
278 gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area),
283 gtk_cell_area_box_remove (GtkCellArea *area,
284 GtkCellRenderer *renderer)
286 GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
287 GtkCellAreaBoxPrivate *priv = box->priv;
290 node = g_list_find_custom (priv->cells, renderer,
291 (GCompareFunc)cell_info_find);
295 CellInfo *info = node->data;
297 cell_info_free (info);
299 priv->cells = g_list_delete_link (priv->cells, node);
302 g_warning ("Trying to remove a cell renderer that is not present GtkCellAreaBox");
306 gtk_cell_area_box_forall (GtkCellArea *area,
307 GtkCellCallback callback,
308 gpointer callback_data)
310 GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
311 GtkCellAreaBoxPrivate *priv = box->priv;
314 for (list = priv->cells; list; list = list->next)
316 CellInfo *info = list->data;
318 callback (info->renderer, callback_data);
323 gtk_cell_area_box_event (GtkCellArea *area,
326 const GdkRectangle *cell_area)
334 gtk_cell_area_box_render (GtkCellArea *area,
337 const GdkRectangle *cell_area)
342 static GtkCellAreaIter *
343 gtk_cell_area_box_create_iter (GtkCellArea *area)
345 return (GtkCellAreaIter *)g_object_new (GTK_TYPE_CELL_AREA_BOX_ITER, NULL);
348 static GtkSizeRequestMode
349 gtk_cell_area_box_get_request_mode (GtkCellArea *area)
351 GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
352 GtkCellAreaBoxPrivate *priv = box->priv;
354 return (priv->orientation) == GTK_ORIENTATION_HORIZONTAL ?
355 GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH :
356 GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
360 get_renderer_size (GtkCellRenderer *renderer,
361 GtkOrientation orientation,
367 if (orientation == GTK_ORIENTATION_HORIZONTAL)
370 gtk_cell_renderer_get_preferred_width (renderer, widget, minimum_size, natural_size);
372 gtk_cell_renderer_get_preferred_width_for_height (renderer, widget, for_size,
373 minimum_size, natural_size);
375 else /* GTK_ORIENTATION_VERTICAL */
378 gtk_cell_renderer_get_preferred_height (renderer, widget, minimum_size, natural_size);
380 gtk_cell_renderer_get_preferred_height_for_width (renderer, widget, for_size,
381 minimum_size, natural_size);
386 compute_size (GtkCellAreaBox *box,
387 GtkOrientation orientation,
388 GtkCellAreaBoxIter *iter,
393 GtkCellAreaBoxPrivate *priv = box->priv;
398 gboolean first_cell = TRUE;
400 for (l = priv->cells; l; l = l->next)
402 gint renderer_min_size, renderer_nat_size;
406 get_renderer_size (info->renderer, orientation, widget, -1,
407 &renderer_min_size, &renderer_nat_size);
409 /* If we're aligning the cells we need to cache the max results
410 * for all requests performed with the same iter.
412 if (priv->align_cells)
414 if (orientation == GTK_ORIENTATION_HORIZONTAL)
415 gtk_cell_area_box_iter_push_cell_width (iter, info->renderer,
416 renderer_min_size, renderer_nat_size);
418 gtk_cell_area_box_iter_push_cell_height (iter, info->renderer,
419 renderer_min_size, renderer_nat_size);
422 if (orientation == priv->orientation)
424 min_size += renderer_min_size;
425 nat_size += renderer_nat_size;
429 min_size += priv->spacing;
430 nat_size += priv->spacing;
435 min_size = MAX (min_size, renderer_min_size);
436 nat_size = MAX (nat_size, renderer_nat_size);
443 *minimum_size = min_size;
444 *natural_size = nat_size;
448 update_iter_aligned (GtkCellAreaBox *box,
449 GtkCellAreaBoxIter *iter,
452 GtkCellAreaBoxPrivate *priv = box->priv;
457 gboolean first_cell = TRUE;
459 for (l = priv->cells; l; l = l->next)
461 gint aligned_min_size, aligned_nat_size;
465 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
468 gtk_cell_area_box_iter_get_cell_width (iter, info->renderer,
472 gtk_cell_area_box_iter_get_cell_width_for_height (iter, info->renderer,
480 gtk_cell_area_box_iter_get_cell_height (iter, info->renderer,
484 gtk_cell_area_box_iter_get_cell_height_for_width (iter, info->renderer,
490 min_size += aligned_min_size;
491 nat_size += aligned_nat_size;
495 min_size += priv->spacing;
496 nat_size += priv->spacing;
503 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
506 gtk_cell_area_iter_push_preferred_width (GTK_CELL_AREA_ITER (iter), min_size, nat_size);
508 gtk_cell_area_iter_push_preferred_width_for_height (GTK_CELL_AREA_ITER (iter),
509 for_size, min_size, nat_size);
514 gtk_cell_area_iter_push_preferred_height (GTK_CELL_AREA_ITER (iter), min_size, nat_size);
516 gtk_cell_area_iter_push_preferred_height_for_width (GTK_CELL_AREA_ITER (iter),
517 for_size, min_size, nat_size);
522 gtk_cell_area_box_get_preferred_width (GtkCellArea *area,
523 GtkCellAreaIter *iter,
528 GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
529 GtkCellAreaBoxIter *box_iter;
530 GtkCellAreaBoxPrivate *priv;
531 gint min_width, nat_width;
533 g_return_if_fail (GTK_IS_CELL_AREA_BOX_ITER (iter));
535 box_iter = GTK_CELL_AREA_BOX_ITER (iter);
538 /* Compute the size of all renderers for current row data, possibly
539 * bumping cell alignments in the iter along the way */
540 compute_size (box, GTK_ORIENTATION_HORIZONTAL,
541 box_iter, widget, &min_width, &nat_width);
543 /* Update width of the iter based on aligned cell sizes if
545 if (priv->align_cells && priv->orientation == GTK_ORIENTATION_HORIZONTAL)
546 update_iter_aligned (box, box_iter, -1);
548 gtk_cell_area_iter_push_preferred_width (iter, min_width, nat_width);
551 *minimum_width = min_width;
554 *natural_width = nat_width;
558 gtk_cell_area_box_get_preferred_height (GtkCellArea *area,
559 GtkCellAreaIter *iter,
561 gint *minimum_height,
562 gint *natural_height)
564 GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
565 GtkCellAreaBoxIter *box_iter;
566 GtkCellAreaBoxPrivate *priv;
567 gint min_height, nat_height;
569 g_return_if_fail (GTK_IS_CELL_AREA_BOX_ITER (iter));
571 box_iter = GTK_CELL_AREA_BOX_ITER (iter);
574 /* Compute the size of all renderers for current row data, possibly
575 * bumping cell alignments in the iter along the way */
576 compute_size (box, GTK_ORIENTATION_VERTICAL,
577 box_iter, widget, &min_height, &nat_height);
579 /* Update width of the iter based on aligned cell sizes if
581 if (priv->align_cells && priv->orientation == GTK_ORIENTATION_VERTICAL)
582 update_iter_aligned (box, box_iter, -1);
584 gtk_cell_area_iter_push_preferred_height (iter, min_height, nat_height);
587 *minimum_height = min_height;
590 *natural_height = nat_height;
594 compute_size_for_orientation (GtkCellAreaBox *box,
595 GtkCellAreaBoxIter *iter,
601 GtkCellAreaBoxPrivate *priv = box->priv;
606 gboolean first_cell = TRUE;
608 for (l = priv->cells; l; l = l->next)
610 gint renderer_min_size, renderer_nat_size;
614 get_renderer_size (info->renderer, priv->orientation, widget, for_size,
615 &renderer_min_size, &renderer_nat_size);
617 /* If we're aligning the cells we need to cache the max results
618 * for all requests performed with the same iter.
620 if (priv->align_cells)
622 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
623 gtk_cell_area_box_iter_push_cell_width_for_height (iter, info->renderer, for_size,
624 renderer_min_size, renderer_nat_size);
626 gtk_cell_area_box_iter_push_cell_height_for_width (iter, info->renderer, for_size,
627 renderer_min_size, renderer_nat_size);
630 min_size += renderer_min_size;
631 nat_size += renderer_nat_size;
635 min_size += priv->spacing;
636 nat_size += priv->spacing;
643 *minimum_size = min_size;
644 *natural_size = nat_size;
649 gtk_cell_area_box_get_preferred_height_for_width (GtkCellArea *area,
650 GtkCellAreaIter *iter,
653 gint *minimum_height,
654 gint *natural_height)
656 GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
657 GtkCellAreaBoxIter *box_iter;
658 GtkCellAreaBoxPrivate *priv;
659 gint min_height, nat_height;
661 g_return_if_fail (GTK_IS_CELL_AREA_BOX_ITER (iter));
663 box_iter = GTK_CELL_AREA_BOX_ITER (iter);
666 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
668 /* Add up vertical requests of height for width and possibly push the overall
669 * cached sizes for alignments */
670 compute_size_for_orientation (box, box_iter, widget, width, &min_height, &nat_height);
672 /* Update the overall cached height for width based on aligned cells if appropriate */
673 if (priv->align_cells)
674 update_iter_aligned (box, box_iter, width);
676 gtk_cell_area_iter_push_preferred_height_for_width (GTK_CELL_AREA_ITER (iter),
677 width, min_height, nat_height);
681 /* XXX Juice: virtually allocate cells into the for_width possibly using the
682 * alignments and then return the overall height for that width, and cache it */
686 *minimum_height = min_height;
689 *natural_height = nat_height;
693 gtk_cell_area_box_get_preferred_width_for_height (GtkCellArea *area,
694 GtkCellAreaIter *iter,
700 GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
701 GtkCellAreaBoxIter *box_iter;
702 GtkCellAreaBoxPrivate *priv;
703 gint min_width, nat_width;
705 g_return_if_fail (GTK_IS_CELL_AREA_BOX_ITER (iter));
707 box_iter = GTK_CELL_AREA_BOX_ITER (iter);
710 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
712 /* Add up vertical requests of height for width and possibly push the overall
713 * cached sizes for alignments */
714 compute_size_for_orientation (box, box_iter, widget, height, &min_width, &nat_width);
716 /* Update the overall cached height for width based on aligned cells if appropriate */
717 if (priv->align_cells)
718 update_iter_aligned (box, box_iter, height);
720 gtk_cell_area_iter_push_preferred_height_for_width (GTK_CELL_AREA_ITER (iter),
721 height, min_width, nat_width);
725 /* XXX Juice: virtually allocate cells into the for_width possibly using the
726 * alignments and then return the overall height for that width, and cache it */
730 *minimum_width = min_width;
733 *natural_width = nat_width;
737 /*************************************************************
738 * GtkCellLayoutIface *
739 *************************************************************/
741 gtk_cell_area_box_cell_layout_init (GtkCellLayoutIface *iface)
743 iface->pack_start = gtk_cell_area_box_layout_pack_start;
744 iface->pack_end = gtk_cell_area_box_layout_pack_end;
745 iface->reorder = gtk_cell_area_box_layout_reorder;
749 gtk_cell_area_box_layout_pack_start (GtkCellLayout *cell_layout,
750 GtkCellRenderer *renderer,
753 gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (cell_layout), renderer, expand);
757 gtk_cell_area_box_layout_pack_end (GtkCellLayout *cell_layout,
758 GtkCellRenderer *renderer,
761 gtk_cell_area_box_pack_end (GTK_CELL_AREA_BOX (cell_layout), renderer, expand);
765 gtk_cell_area_box_layout_reorder (GtkCellLayout *cell_layout,
766 GtkCellRenderer *renderer,
769 GtkCellAreaBox *box = GTK_CELL_AREA_BOX (cell_layout);
770 GtkCellAreaBoxPrivate *priv = box->priv;
774 node = g_list_find_custom (priv->cells, renderer,
775 (GCompareFunc)cell_info_find);
781 priv->cells = g_list_delete_link (priv->cells, node);
782 priv->cells = g_list_insert (priv->cells, info, position);
786 /*************************************************************
788 *************************************************************/
790 gtk_cell_area_box_new (void)
792 return (GtkCellArea *)g_object_new (GTK_TYPE_CELL_AREA_BOX, NULL);
796 gtk_cell_area_box_pack_start (GtkCellAreaBox *box,
797 GtkCellRenderer *renderer,
800 GtkCellAreaBoxPrivate *priv;
803 g_return_if_fail (GTK_IS_CELL_AREA_BOX (box));
804 g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
808 if (g_list_find_custom (priv->cells, renderer,
809 (GCompareFunc)cell_info_find))
811 g_warning ("Refusing to add the same cell renderer to a GtkCellAreaBox twice");
815 info = cell_info_new (renderer, expand, GTK_PACK_START);
817 priv->cells = g_list_append (priv->cells, info);
821 gtk_cell_area_box_pack_end (GtkCellAreaBox *box,
822 GtkCellRenderer *renderer,
825 GtkCellAreaBoxPrivate *priv;
828 g_return_if_fail (GTK_IS_CELL_AREA_BOX (box));
829 g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
833 if (g_list_find_custom (priv->cells, renderer,
834 (GCompareFunc)cell_info_find))
836 g_warning ("Refusing to add the same cell renderer to a GtkCellArea twice");
840 info = cell_info_new (renderer, expand, GTK_PACK_END);
842 priv->cells = g_list_append (priv->cells, info);
846 gtk_cell_area_box_get_spacing (GtkCellAreaBox *box)
848 g_return_val_if_fail (GTK_IS_CELL_AREA_BOX (box), 0);
850 return box->priv->spacing;
854 gtk_cell_area_box_set_spacing (GtkCellAreaBox *box,
857 GtkCellAreaBoxPrivate *priv;
859 g_return_if_fail (GTK_IS_CELL_AREA_BOX (box));
863 if (priv->spacing != spacing)
865 priv->spacing = spacing;
867 g_object_notify (G_OBJECT (box), "spacing");
872 gtk_cell_area_box_get_align_cells (GtkCellAreaBox *box)
874 g_return_val_if_fail (GTK_IS_CELL_AREA_BOX (box), FALSE);
876 return box->priv->align_cells;
880 gtk_cell_area_box_set_align_cells (GtkCellAreaBox *box,
883 GtkCellAreaBoxPrivate *priv;
885 g_return_if_fail (GTK_IS_CELL_AREA_BOX (box));
889 if (priv->align_cells != align)
891 priv->align_cells = align;
893 g_object_notify (G_OBJECT (box), "align-cells");