]> Pileus Git - ~andy/gtk/blob - gtk/gtkcellareabox.c
a341dd7f08a5427a1aff2b1125c348ac67f1a203
[~andy/gtk] / gtk / gtkcellareabox.c
1 /* gtkcellareabox.c
2  *
3  * Copyright (C) 2010 Openismus GmbH
4  *
5  * Authors:
6  *      Tristan Van Berkom <tristanvb@openismus.com>
7  *
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.
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  * Library General Public License for more details.
17  *
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.
22  */
23
24 #include "config.h"
25 #include "gtkintl.h"
26 #include "gtkorientable.h"
27 #include "gtkcelllayout.h"
28 #include "gtkcellareabox.h"
29 #include "gtkcellareaboxiter.h"
30 #include "gtkprivate.h"
31
32
33 /* GObjectClass */
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,
37                                                                    guint               prop_id,
38                                                                    const GValue       *value,
39                                                                    GParamSpec         *pspec);
40 static void      gtk_cell_area_box_get_property                   (GObject            *object,
41                                                                    guint               prop_id,
42                                                                    GValue             *value,
43                                                                    GParamSpec         *pspec);
44
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,
54                                                                    GtkCellAreaIter    *iter,
55                                                                    GtkWidget          *widget,
56                                                                    GdkEvent           *event,
57                                                                    const GdkRectangle *cell_area);
58 static void      gtk_cell_area_box_render                         (GtkCellArea        *area,
59                                                                    GtkCellAreaIter    *iter,
60                                                                    GtkWidget          *widget,
61                                                                    cairo_t            *cr,
62                                                                    const GdkRectangle *cell_area);
63
64 static GtkCellAreaIter    *gtk_cell_area_box_create_iter          (GtkCellArea        *area);
65 static GtkSizeRequestMode  gtk_cell_area_box_get_request_mode     (GtkCellArea        *area);
66 static void      gtk_cell_area_box_get_preferred_width            (GtkCellArea        *area,
67                                                                    GtkCellAreaIter    *iter,
68                                                                    GtkWidget          *widget,
69                                                                    gint               *minimum_width,
70                                                                    gint               *natural_width);
71 static void      gtk_cell_area_box_get_preferred_height           (GtkCellArea        *area,
72                                                                    GtkCellAreaIter    *iter,
73                                                                    GtkWidget          *widget,
74                                                                    gint               *minimum_height,
75                                                                    gint               *natural_height);
76 static void      gtk_cell_area_box_get_preferred_height_for_width (GtkCellArea        *area,
77                                                                    GtkCellAreaIter    *iter,
78                                                                    GtkWidget          *widget,
79                                                                    gint                width,
80                                                                    gint               *minimum_height,
81                                                                    gint               *natural_height);
82 static void      gtk_cell_area_box_get_preferred_width_for_height (GtkCellArea        *area,
83                                                                    GtkCellAreaIter    *iter,
84                                                                    GtkWidget          *widget,
85                                                                    gint                height,
86                                                                    gint               *minimum_width,
87                                                                    gint               *natural_width);
88
89 /* GtkCellLayoutIface */
90 static void      gtk_cell_area_box_cell_layout_init               (GtkCellLayoutIface *iface);
91 static void      gtk_cell_area_box_layout_pack_start              (GtkCellLayout      *cell_layout,
92                                                                    GtkCellRenderer    *renderer,
93                                                                    gboolean            expand);
94 static void      gtk_cell_area_box_layout_pack_end                (GtkCellLayout      *cell_layout,
95                                                                    GtkCellRenderer    *renderer,
96                                                                    gboolean            expand);
97 static void      gtk_cell_area_box_layout_reorder                 (GtkCellLayout      *cell_layout,
98                                                                    GtkCellRenderer    *renderer,
99                                                                    gint                position);
100
101
102 /* CellInfo/CellGroup metadata handling and convenience functions */
103 typedef struct {
104   GtkCellRenderer *renderer;
105
106   guint            expand : 1; /* Whether the cell expands */
107   guint            pack   : 1; /* Whether the cell is packed from the start or end */
108   guint            align  : 1; /* Whether to align this cell's position with adjacent rows */
109 } CellInfo;
110
111 typedef struct {
112   GList *cells;
113
114   guint  id     : 16;
115   guint  expand : 1;
116 } CellGroup;
117
118 static CellInfo  *cell_info_new          (GtkCellRenderer    *renderer, 
119                                           GtkPackType         pack,
120                                           gboolean            expand,
121                                           gboolean            align);
122 static void       cell_info_free         (CellInfo           *info);
123 static gint       cell_info_find         (CellInfo           *info,
124                                           GtkCellRenderer    *renderer);
125
126 static CellGroup *cell_group_new         (guint               id);
127 static void       cell_group_free        (CellGroup          *group);
128
129 static GList     *list_consecutive_cells (GtkCellAreaBox     *box);
130 static GList     *construct_cell_groups  (GtkCellAreaBox     *box);
131 static gint       count_expand_groups    (GtkCellAreaBox     *box);
132 static gint       count_expand_cells     (CellGroup          *group);
133 static void       iter_weak_notify       (GtkCellAreaBox     *box,
134                                           GtkCellAreaBoxIter *dead_iter);
135 static void       flush_iters            (GtkCellAreaBox     *box);
136 static void       init_iter_groups       (GtkCellAreaBox     *box);
137 static void       init_iter_group        (GtkCellAreaBox     *box,
138                                           GtkCellAreaBoxIter *iter);
139
140
141 struct _GtkCellAreaBoxPrivate
142 {
143   GtkOrientation  orientation;
144
145   GList          *cells;
146   GList          *groups;
147
148   GSList         *iters;
149
150   gint            spacing;
151 };
152
153 enum {
154   PROP_0,
155   PROP_ORIENTATION,
156   PROP_SPACING
157 };
158
159 G_DEFINE_TYPE_WITH_CODE (GtkCellAreaBox, gtk_cell_area_box, GTK_TYPE_CELL_AREA,
160                          G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
161                                                 gtk_cell_area_box_cell_layout_init)
162                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL));
163
164 #define OPPOSITE_ORIENTATION(orientation)                       \
165   ((orientation) == GTK_ORIENTATION_HORIZONTAL ?                \
166    GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL)
167
168 static void
169 gtk_cell_area_box_init (GtkCellAreaBox *box)
170 {
171   GtkCellAreaBoxPrivate *priv;
172
173   box->priv = G_TYPE_INSTANCE_GET_PRIVATE (box,
174                                            GTK_TYPE_CELL_AREA_BOX,
175                                            GtkCellAreaBoxPrivate);
176   priv = box->priv;
177
178   priv->orientation = GTK_ORIENTATION_HORIZONTAL;
179   priv->cells       = NULL;
180   priv->groups      = NULL;
181   priv->iters       = NULL;
182   priv->spacing     = 0;
183 }
184
185 static void 
186 gtk_cell_area_box_class_init (GtkCellAreaBoxClass *class)
187 {
188   GObjectClass     *object_class = G_OBJECT_CLASS (class);
189   GtkCellAreaClass *area_class   = GTK_CELL_AREA_CLASS (class);
190
191   /* GObjectClass */
192   object_class->finalize     = gtk_cell_area_box_finalize;
193   object_class->dispose      = gtk_cell_area_box_dispose;
194   object_class->set_property = gtk_cell_area_box_set_property;
195   object_class->get_property = gtk_cell_area_box_get_property;
196
197   /* GtkCellAreaClass */
198   area_class->add                            = gtk_cell_area_box_add;
199   area_class->remove                         = gtk_cell_area_box_remove;
200   area_class->forall                         = gtk_cell_area_box_forall;
201   area_class->event                          = gtk_cell_area_box_event;
202   area_class->render                         = gtk_cell_area_box_render;
203   
204   area_class->create_iter                    = gtk_cell_area_box_create_iter;
205   area_class->get_request_mode               = gtk_cell_area_box_get_request_mode;
206   area_class->get_preferred_width            = gtk_cell_area_box_get_preferred_width;
207   area_class->get_preferred_height           = gtk_cell_area_box_get_preferred_height;
208   area_class->get_preferred_height_for_width = gtk_cell_area_box_get_preferred_height_for_width;
209   area_class->get_preferred_width_for_height = gtk_cell_area_box_get_preferred_width_for_height;
210
211   g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
212
213   g_object_class_install_property (object_class,
214                                    PROP_SPACING,
215                                    g_param_spec_int ("spacing",
216                                                      P_("Spacing"),
217                                                      P_("Space which is inserted between cells"),
218                                                      0,
219                                                      G_MAXINT,
220                                                      0,
221                                                      GTK_PARAM_READWRITE));
222
223   g_type_class_add_private (object_class, sizeof (GtkCellAreaBoxPrivate));
224 }
225
226
227 /*************************************************************
228  *    CellInfo/CellGroup basics and convenience functions    *
229  *************************************************************/
230 static CellInfo *
231 cell_info_new  (GtkCellRenderer *renderer, 
232                 GtkPackType      pack,
233                 gboolean         expand,
234                 gboolean         align)
235 {
236   CellInfo *info = g_slice_new (CellInfo);
237   
238   info->renderer = g_object_ref_sink (renderer);
239   info->pack     = pack;
240   info->expand   = expand;
241   info->align    = align;
242
243   return info;
244 }
245
246 static void
247 cell_info_free (CellInfo *info)
248 {
249   g_object_unref (info->renderer);
250
251   g_slice_free (CellInfo, info);
252 }
253
254 static gint
255 cell_info_find (CellInfo        *info,
256                 GtkCellRenderer *renderer)
257 {
258   return (info->renderer == renderer) ? 0 : -1;
259 }
260
261 static CellGroup *
262 cell_group_new (guint id)
263 {
264   CellGroup *group = g_slice_new0 (CellGroup);
265
266   group->id = id;
267
268   return group;
269 }
270
271 static void
272 cell_group_free (CellGroup *group)
273 {
274   g_list_free (group->cells);
275   g_slice_free (CellGroup, group);  
276 }
277
278 static GList *
279 list_consecutive_cells (GtkCellAreaBox *box)
280 {
281   GtkCellAreaBoxPrivate *priv = box->priv;
282   GList                 *l, *consecutive_cells = NULL, *pack_end_cells = NULL;
283   CellInfo              *info;
284
285   /* List cells in consecutive order taking their 
286    * PACK_START/PACK_END options into account 
287    */
288   for (l = priv->cells; l; l = l->next)
289     {
290       info = l->data;
291       
292       if (info->pack == GTK_PACK_START)
293         consecutive_cells = g_list_prepend (consecutive_cells, info);
294     }
295
296   for (l = priv->cells; l; l = l->next)
297     {
298       info = l->data;
299       
300       if (info->pack == GTK_PACK_END)
301         pack_end_cells = g_list_prepend (pack_end_cells, info);
302     }
303
304   consecutive_cells = g_list_reverse (consecutive_cells);
305   consecutive_cells = g_list_concat (consecutive_cells, pack_end_cells);
306
307   return consecutive_cells;
308 }
309
310 static GList *
311 construct_cell_groups (GtkCellAreaBox  *box)
312 {
313   GtkCellAreaBoxPrivate *priv = box->priv;
314   CellGroup             *group;
315   GList                 *cells, *l;
316   GList                 *groups = NULL;
317   guint                  id = 0;
318
319   if (!priv->cells)
320     return NULL;
321
322   cells  = list_consecutive_cells (box);
323   group  = cell_group_new (id++);
324   groups = g_list_prepend (groups, group);
325
326   for (l = cells; l; l = l->next)
327     {
328       CellInfo *info = l->data;
329
330       /* A new group starts with any aligned cell, the first group is implied */
331       if (info->align && l != cells)
332         {
333           group  = cell_group_new (id++);
334           groups = g_list_prepend (groups, group);
335         }
336
337       group->cells = g_list_prepend (group->cells, info);
338
339       /* A group expands if it contains any expand cells */
340       if (info->expand)
341         group->expand = TRUE;
342     }
343
344   g_list_free (cells);
345
346   for (l = cells; l; l = l->next)
347     {
348       group = l->data;
349       group->cells = g_list_reverse (group->cells);
350     }
351
352   return g_list_reverse (groups);
353 }
354
355 static gint
356 count_expand_groups (GtkCellAreaBox  *box)
357 {
358   GtkCellAreaBoxPrivate *priv = box->priv;
359   GList                 *l;
360   gint                   expand_groups = 0;
361
362   for (l = priv->groups; l; l = l->next)
363     {
364       CellGroup *group = l->data;
365
366       if (group->expand)
367         expand_groups++;
368     }
369
370   return expand_groups;
371 }
372
373 static gint
374 count_expand_cells (CellGroup *group)
375 {
376   GList *l;
377   gint   expand_cells = 0;
378
379   if (!group->expand)
380     return 0;
381
382   for (l = group->cells; l; l = l->next)
383     {
384       CellInfo *info = l->data;
385
386       if (info->expand)
387         expand_cells++;
388     }
389
390   return expand_cells;
391 }
392
393 static void 
394 iter_weak_notify (GtkCellAreaBox     *box,
395                   GtkCellAreaBoxIter *dead_iter)
396 {
397   GtkCellAreaBoxPrivate *priv = box->priv;
398
399   priv->iters = g_slist_remove (priv->iters, dead_iter);
400 }
401
402 static void
403 init_iter_group (GtkCellAreaBox     *box,
404                  GtkCellAreaBoxIter *iter)
405 {
406   GtkCellAreaBoxPrivate *priv = box->priv;
407   gint                   n_groups, *expand_groups, i;
408   GList                 *l;
409
410   n_groups      = g_list_length (priv->groups);
411   expand_groups = g_new (gboolean, n_groups);
412
413   for (i = 0, l = priv->groups; l; l = l->next, i++)
414     {
415       CellGroup *group = l->data;
416
417       expand_groups[i] = group->expand;
418     }
419
420   gtk_cell_area_box_init_groups (iter, n_groups, expand_groups);
421   g_free (expand_groups);
422 }
423
424 static void
425 init_iter_groups (GtkCellAreaBox *box)
426 {
427   GtkCellAreaBoxPrivate *priv = box->priv;
428   GSList                *l;
429
430   /* When the box's groups are reconstructed, iters need to
431    * be reinitialized.
432    */
433   for (l = priv->iters; l; l = l->next)
434     {
435       GtkCellAreaBoxIter *iter = l->data;
436
437       init_iter_group (box, iter);
438     }
439 }
440
441 static void
442 flush_iters (GtkCellAreaBox *box)
443 {
444   GtkCellAreaBoxPrivate *priv = box->priv;
445   GSList                *l;
446
447   /* When the box layout changes, iters need to
448    * be flushed and sizes for the box get requested again
449    */
450   for (l = priv->iters; l; l = l->next)
451     {
452       GtkCellAreaIter *iter = l->data;
453
454       gtk_cell_area_iter_flush (iter);
455     }
456 }
457
458 /*************************************************************
459  *                      GObjectClass                         *
460  *************************************************************/
461 static void
462 gtk_cell_area_box_finalize (GObject *object)
463 {
464   GtkCellAreaBox        *box = GTK_CELL_AREA_BOX (object);
465   GtkCellAreaBoxPrivate *priv = box->priv;
466   GSList                *l;
467
468   for (l = priv->iters; l; l = l->next)
469     g_object_weak_unref (G_OBJECT (l->data), (GWeakNotify)iter_weak_notify, box);
470
471   g_slist_free (priv->iters);
472
473   G_OBJECT_CLASS (gtk_cell_area_box_parent_class)->finalize (object);
474 }
475
476 static void
477 gtk_cell_area_box_dispose (GObject *object)
478 {
479   G_OBJECT_CLASS (gtk_cell_area_box_parent_class)->dispose (object);
480 }
481
482 static void
483 gtk_cell_area_box_set_property (GObject       *object,
484                                 guint          prop_id,
485                                 const GValue  *value,
486                                 GParamSpec    *pspec)
487 {
488   GtkCellAreaBox *box = GTK_CELL_AREA_BOX (object);
489
490   switch (prop_id)
491     {
492     case PROP_ORIENTATION:
493       box->priv->orientation = g_value_get_enum (value);
494
495       /* Notify that size needs to be requested again */
496       flush_iters (box);
497       break;
498     case PROP_SPACING:
499       gtk_cell_area_box_set_spacing (box, g_value_get_int (value));
500       break;
501     default:
502       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
503       break;
504     }
505 }
506
507 static void
508 gtk_cell_area_box_get_property (GObject     *object,
509                                 guint        prop_id,
510                                 GValue      *value,
511                                 GParamSpec  *pspec)
512 {
513   GtkCellAreaBox *box = GTK_CELL_AREA_BOX (object);
514
515   switch (prop_id)
516     {
517     case PROP_ORIENTATION:
518       g_value_set_enum (value, box->priv->orientation);
519       break;
520     case PROP_SPACING:
521       g_value_set_int (value, gtk_cell_area_box_get_spacing (box));
522       break;
523     default:
524       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
525       break;
526     }
527 }
528
529 /*************************************************************
530  *                    GtkCellAreaClass                       *
531  *************************************************************/
532 static void      
533 gtk_cell_area_box_add (GtkCellArea        *area,
534                        GtkCellRenderer    *renderer)
535 {
536   gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area),
537                                 renderer, FALSE, TRUE);
538 }
539
540 static void
541 gtk_cell_area_box_remove (GtkCellArea        *area,
542                           GtkCellRenderer    *renderer)
543 {
544   GtkCellAreaBox        *box  = GTK_CELL_AREA_BOX (area);
545   GtkCellAreaBoxPrivate *priv = box->priv;
546   GList                 *node;
547
548   node = g_list_find_custom (priv->cells, renderer, 
549                              (GCompareFunc)cell_info_find);
550
551   if (node)
552     {
553       CellInfo *info = node->data;
554
555       cell_info_free (info);
556
557       priv->cells = g_list_delete_link (priv->cells, node);
558
559       /* Reconstruct cell groups */
560       g_list_foreach (priv->groups, (GFunc)cell_group_free, NULL);
561       g_list_free (priv->groups);
562       priv->groups = construct_cell_groups (box);
563
564       /* Reinitialize groups on iters */
565       init_iter_groups (box);
566     }
567   else
568     g_warning ("Trying to remove a cell renderer that is not present GtkCellAreaBox");
569 }
570
571 static void
572 gtk_cell_area_box_forall (GtkCellArea        *area,
573                           GtkCellCallback     callback,
574                           gpointer            callback_data)
575 {
576   GtkCellAreaBox        *box  = GTK_CELL_AREA_BOX (area);
577   GtkCellAreaBoxPrivate *priv = box->priv;
578   GList                 *list;
579
580   for (list = priv->cells; list; list = list->next)
581     {
582       CellInfo *info = list->data;
583
584       callback (info->renderer, callback_data);
585     }
586 }
587
588 static gint
589 gtk_cell_area_box_event (GtkCellArea        *area,
590                          GtkCellAreaIter    *iter,
591                          GtkWidget          *widget,
592                          GdkEvent           *event,
593                          const GdkRectangle *cell_area)
594 {
595
596
597   return 0;
598 }
599
600 static void
601 gtk_cell_area_box_render (GtkCellArea        *area,
602                           GtkCellAreaIter    *iter,
603                           GtkWidget          *widget,
604                           cairo_t            *cr,
605                           const GdkRectangle *cell_area)
606 {
607
608 }
609
610 static GtkCellAreaIter *
611 gtk_cell_area_box_create_iter (GtkCellArea *area)
612 {
613   GtkCellAreaBox        *box  = GTK_CELL_AREA_BOX (area);
614   GtkCellAreaBoxPrivate *priv = box->priv;
615   GtkCellAreaIter       *iter =
616     (GtkCellAreaIter *)g_object_new (GTK_TYPE_CELL_AREA_BOX_ITER, NULL);
617
618   priv->iters = g_slist_prepend (priv->iters, iter);
619
620   g_object_weak_ref (G_OBJECT (iter), (GWeakNotify)iter_weak_notify, box);
621
622   /* Tell the new group about our cell layout */
623   init_iter_group (box, GTK_CELL_AREA_BOX_ITER (iter));
624
625   return iter;
626 }
627
628 static GtkSizeRequestMode 
629 gtk_cell_area_box_get_request_mode (GtkCellArea *area)
630 {
631   GtkCellAreaBox        *box  = GTK_CELL_AREA_BOX (area);
632   GtkCellAreaBoxPrivate *priv = box->priv;
633
634   return (priv->orientation) == GTK_ORIENTATION_HORIZONTAL ?
635     GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH :
636     GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
637 }
638
639 static void
640 get_renderer_size (GtkCellRenderer    *renderer,
641                    GtkOrientation      orientation,
642                    GtkWidget          *widget,
643                    gint                for_size,
644                    gint               *minimum_size,
645                    gint               *natural_size)
646 {
647   if (orientation == GTK_ORIENTATION_HORIZONTAL)
648     {
649       if (for_size < 0)
650         gtk_cell_renderer_get_preferred_width (renderer, widget, minimum_size, natural_size);
651       else
652         gtk_cell_renderer_get_preferred_width_for_height (renderer, widget, for_size, 
653                                                           minimum_size, natural_size);
654     }
655   else /* GTK_ORIENTATION_VERTICAL */
656     {
657       if (for_size < 0)
658         gtk_cell_renderer_get_preferred_height (renderer, widget, minimum_size, natural_size);
659       else
660         gtk_cell_renderer_get_preferred_height_for_width (renderer, widget, for_size, 
661                                                           minimum_size, natural_size);
662     }
663 }
664
665 static void
666 compute_size (GtkCellAreaBox     *box,
667               GtkOrientation      orientation,
668               GtkCellAreaBoxIter *iter,
669               GtkWidget          *widget,
670               gint                for_size,
671               gint               *minimum_size,
672               gint               *natural_size)
673 {
674   GtkCellAreaBoxPrivate *priv = box->priv;
675   CellGroup             *group;
676   CellInfo              *info;
677   GList                 *cell_list, *group_list;
678   gint                   min_size = 0;
679   gint                   nat_size = 0;
680   
681   for (group_list = priv->groups; group_list; group_list = group_list->next)
682     {
683       gint group_min_size = 0;
684       gint group_nat_size = 0;
685
686       group = group_list->data;
687
688       for (cell_list = group->cells; cell_list; cell_list = cell_list->next)
689         {
690           gint renderer_min_size, renderer_nat_size;
691           
692           info = cell_list->data;
693           
694           get_renderer_size (info->renderer, orientation, widget, for_size, 
695                              &renderer_min_size, &renderer_nat_size);
696
697           if (orientation == priv->orientation)
698             {
699               if (min_size > 0)
700                 {
701                   min_size += priv->spacing;
702                   nat_size += priv->spacing;
703                 }
704               
705               if (group_min_size > 0)
706                 {
707                   group_min_size += priv->spacing;
708                   group_nat_size += priv->spacing;
709                 }
710               
711               min_size       += renderer_min_size;
712               nat_size       += renderer_nat_size;
713               group_min_size += renderer_min_size;
714               group_nat_size += renderer_nat_size;
715             }
716           else
717             {
718               min_size       = MAX (min_size, renderer_min_size);
719               nat_size       = MAX (nat_size, renderer_nat_size);
720               group_min_size = MAX (group_min_size, renderer_min_size);
721               group_nat_size = MAX (group_nat_size, renderer_nat_size);
722             }
723         }
724
725       if (orientation == GTK_ORIENTATION_HORIZONTAL)
726         {
727           if (for_size < 0)
728             gtk_cell_area_box_iter_push_group_width (iter, group->id, group_min_size, group_nat_size);
729           else
730             gtk_cell_area_box_iter_push_group_width_for_height (iter, group->id, for_size,
731                                                                 group_min_size, group_nat_size);
732         }
733       else
734         {
735           if (for_size < 0)
736             gtk_cell_area_box_iter_push_group_height (iter, group->id, group_min_size, group_nat_size);
737           else
738             gtk_cell_area_box_iter_push_group_height_for_width (iter, group->id, for_size,
739                                                                 group_min_size, group_nat_size);
740         }
741     }
742
743   *minimum_size = min_size;
744   *natural_size = nat_size;
745 }
746
747 GtkRequestedSize *
748 get_group_sizes (CellGroup      *group,
749                  GtkOrientation  orientation,
750                  GtkWidget      *widget,
751                  gint           *n_sizes)
752 {
753   GtkRequestedSize *sizes;
754   GList            *l;
755   gint              i;
756
757   *n_sizes = g_list_length (group->cells);
758   sizes    = g_new (GtkRequestedSize, *n_sizes);
759
760   for (l = group->cells, i = 0; l; l = l->next, i++)
761     {
762       CellInfo *info = l->data;
763
764       sizes[i].data = info;
765       
766       get_renderer_size (info->renderer,
767                          orientation, widget, -1,
768                          &sizes[i].minimum_size,
769                          &sizes[i].natural_size);
770     }
771
772   return sizes;
773 }
774
775 static void
776 compute_group_size_for_opposing_orientation (GtkCellAreaBox     *box,
777                                              CellGroup          *group,
778                                              GtkWidget          *widget, 
779                                              gint                for_size,
780                                              gint               *minimum_size, 
781                                              gint               *natural_size)
782 {
783   GtkCellAreaBoxPrivate *priv = box->priv;
784
785   /* Exception for single cell groups */
786   if (!group->cells->next)
787     {
788       CellInfo *info = group->cells->data;
789
790       get_renderer_size (info->renderer,
791                          OPPOSITE_ORIENTATION (priv->orientation),
792                          widget, for_size, minimum_size, natural_size);
793     }
794   else
795     {
796       GtkRequestedSize *orientation_sizes;
797       CellInfo         *info;
798       gint              n_sizes, i;
799       gint              n_expand_cells = count_expand_cells (group);
800       gint              avail_size     = for_size;
801       gint              extra_size, extra_extra;
802       gint              min_size = 0, nat_size = 0;
803
804       orientation_sizes = get_group_sizes (group, priv->orientation, widget, &n_sizes);
805
806       /* First naturally allocate the cells in the group into the for_size */
807       avail_size -= (n_sizes - 1) * priv->spacing;
808       for (i = 0; i < n_sizes; i++)
809         avail_size -= orientation_sizes[i].minimum_size;
810
811       avail_size = gtk_distribute_natural_allocation (avail_size, n_sizes, orientation_sizes);
812
813       /* Calculate/distribute expand for cells */
814       if (n_expand_cells > 0)
815         {
816           extra_size  = avail_size / n_expand_cells;
817           extra_extra = avail_size % n_expand_cells;
818         }
819       else
820         extra_size = extra_extra = 0;
821
822       for (i = 0; i < n_sizes; i++)
823         {
824           gint cell_min, cell_nat;
825
826           info = orientation_sizes[i].data;
827
828           if (info->expand)
829             {
830               orientation_sizes[i].minimum_size += extra_size;
831               if (extra_extra)
832                 {
833                   orientation_sizes[i].minimum_size++;
834                   extra_extra--;
835                 }
836             }
837
838           get_renderer_size (info->renderer,
839                              OPPOSITE_ORIENTATION (priv->orientation),
840                              widget, 
841                              orientation_sizes[i].minimum_size,
842                              &cell_min, &cell_nat);
843
844           min_size = MAX (min_size, cell_min);
845           nat_size = MAX (nat_size, cell_nat);
846         }
847
848       *minimum_size = min_size;
849       *natural_size = nat_size;
850
851       g_free (orientation_sizes);
852     }
853 }
854
855 static void
856 compute_size_for_opposing_orientation (GtkCellAreaBox     *box, 
857                                        GtkCellAreaBoxIter *iter, 
858                                        GtkWidget          *widget, 
859                                        gint                for_size,
860                                        gint               *minimum_size, 
861                                        gint               *natural_size)
862 {
863   GtkCellAreaBoxPrivate *priv = box->priv;
864   CellGroup             *group;
865   GList                 *group_list;
866   GtkRequestedSize      *orientation_sizes;
867   gint                   n_groups, n_expand_groups, i;
868   gint                   avail_size = for_size;
869   gint                   extra_size, extra_extra;
870   gint                   min_size = 0, nat_size = 0;
871
872   n_expand_groups = count_expand_groups (box);
873
874   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
875     orientation_sizes = gtk_cell_area_box_iter_get_widths (iter, &n_groups);
876   else
877     orientation_sizes = gtk_cell_area_box_iter_get_heights (iter, &n_groups);
878
879   /* First start by naturally allocating space among groups of cells */
880   avail_size -= (n_groups - 1) * priv->spacing;
881   for (i = 0; i < n_groups; i++)
882     avail_size -= orientation_sizes[i].minimum_size;
883
884   avail_size = gtk_distribute_natural_allocation (avail_size, n_groups, orientation_sizes);
885
886   /* Calculate/distribute expand for groups */
887   if (n_expand_groups > 0)
888     {
889       extra_size  = avail_size / n_expand_groups;
890       extra_extra = avail_size % n_expand_groups;
891     }
892   else
893     extra_size = extra_extra = 0;
894
895   /* Now we need to naturally allocate sizes for cells in each group
896    * and push the height-for-width for each group accordingly while accumulating
897    * the overall height-for-width for this row.
898    */
899   for (group_list = priv->groups; group_list; group_list = group_list->next)
900     {
901       gint group_min, group_nat;
902
903       group = group_list->data;
904
905       if (group->expand)
906         {
907           orientation_sizes[group->id].minimum_size += extra_size;
908           if (extra_extra)
909             {
910               orientation_sizes[group->id].minimum_size++;
911               extra_extra--;
912             }
913         }
914
915       /* Now we have the allocation for the group, request it's height-for-width */
916       compute_group_size_for_opposing_orientation (box, group, widget,
917                                                    orientation_sizes[group->id].minimum_size,
918                                                    &group_min, &group_nat);
919
920       min_size = MAX (min_size, group_min);
921       nat_size = MAX (nat_size, group_nat);
922
923       if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
924         {
925           gtk_cell_area_box_iter_push_group_height_for_width (iter, group->id, for_size,
926                                                               group_min, group_nat);
927         }
928       else
929         {
930           gtk_cell_area_box_iter_push_group_width_for_height (iter, group->id, for_size,
931                                                               group_min, group_nat);
932         }
933     }
934
935   *minimum_size = min_size;
936   *natural_size = nat_size;
937
938   g_free (orientation_sizes);
939 }
940
941
942
943 static void
944 gtk_cell_area_box_get_preferred_width (GtkCellArea        *area,
945                                        GtkCellAreaIter    *iter,
946                                        GtkWidget          *widget,
947                                        gint               *minimum_width,
948                                        gint               *natural_width)
949 {
950   GtkCellAreaBox        *box = GTK_CELL_AREA_BOX (area);
951   GtkCellAreaBoxIter    *box_iter;
952   gint                   min_width, nat_width;
953
954   g_return_if_fail (GTK_IS_CELL_AREA_BOX_ITER (iter));
955
956   box_iter = GTK_CELL_AREA_BOX_ITER (iter);
957
958   /* Compute the size of all renderers for current row data, 
959    * bumping cell alignments in the iter along the way */
960   compute_size (box, GTK_ORIENTATION_HORIZONTAL,
961                 box_iter, widget, -1, &min_width, &nat_width);
962
963   if (minimum_width)
964     *minimum_width = min_width;
965
966   if (natural_width)
967     *natural_width = nat_width;
968 }
969
970 static void
971 gtk_cell_area_box_get_preferred_height (GtkCellArea        *area,
972                                         GtkCellAreaIter    *iter,
973                                         GtkWidget          *widget,
974                                         gint               *minimum_height,
975                                         gint               *natural_height)
976 {
977   GtkCellAreaBox        *box = GTK_CELL_AREA_BOX (area);
978   GtkCellAreaBoxIter    *box_iter;
979   gint                   min_height, nat_height;
980
981   g_return_if_fail (GTK_IS_CELL_AREA_BOX_ITER (iter));
982
983   box_iter = GTK_CELL_AREA_BOX_ITER (iter);
984
985   /* Compute the size of all renderers for current row data, 
986    * bumping cell alignments in the iter along the way */
987   compute_size (box, GTK_ORIENTATION_VERTICAL,
988                 box_iter, widget, -1, &min_height, &nat_height);
989
990   if (minimum_height)
991     *minimum_height = min_height;
992
993   if (natural_height)
994     *natural_height = nat_height;
995 }
996
997 static void
998 gtk_cell_area_box_get_preferred_height_for_width (GtkCellArea        *area,
999                                                   GtkCellAreaIter    *iter,
1000                                                   GtkWidget          *widget,
1001                                                   gint                width,
1002                                                   gint               *minimum_height,
1003                                                   gint               *natural_height)
1004 {
1005   GtkCellAreaBox        *box = GTK_CELL_AREA_BOX (area);
1006   GtkCellAreaBoxIter    *box_iter;
1007   GtkCellAreaBoxPrivate *priv;
1008   gint                   min_height, nat_height;
1009
1010   g_return_if_fail (GTK_IS_CELL_AREA_BOX_ITER (iter));
1011
1012   box_iter = GTK_CELL_AREA_BOX_ITER (iter);
1013   priv     = box->priv;
1014
1015   if (priv->orientation == GTK_ORIENTATION_VERTICAL)
1016     {
1017       /* Add up vertical requests of height for width and push the overall 
1018        * cached sizes for alignments */
1019       compute_size (box, priv->orientation, box_iter, widget, width, &min_height, &nat_height);
1020     }
1021   else
1022     {
1023       /* Juice: virtually allocate cells into the for_width using the 
1024        * alignments and then return the overall height for that width, and cache it */
1025       compute_size_for_opposing_orientation (box, box_iter, widget, width, &min_height, &nat_height);
1026     }
1027
1028   if (minimum_height)
1029     *minimum_height = min_height;
1030
1031   if (natural_height)
1032     *natural_height = nat_height;
1033 }
1034
1035 static void
1036 gtk_cell_area_box_get_preferred_width_for_height (GtkCellArea        *area,
1037                                                   GtkCellAreaIter    *iter,
1038                                                   GtkWidget          *widget,
1039                                                   gint                height,
1040                                                   gint               *minimum_width,
1041                                                   gint               *natural_width)
1042 {
1043   GtkCellAreaBox        *box = GTK_CELL_AREA_BOX (area);
1044   GtkCellAreaBoxIter    *box_iter;
1045   GtkCellAreaBoxPrivate *priv;
1046   gint                   min_width, nat_width;
1047
1048   g_return_if_fail (GTK_IS_CELL_AREA_BOX_ITER (iter));
1049
1050   box_iter = GTK_CELL_AREA_BOX_ITER (iter);
1051   priv     = box->priv;
1052
1053   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1054     {
1055       /* Add up horizontal requests of width for height and push the overall 
1056        * cached sizes for alignments */
1057       compute_size (box, priv->orientation, box_iter, widget, height, &min_width, &nat_width);
1058     }
1059   else
1060     {
1061       /* Juice: horizontally allocate cells into the for_height using the 
1062        * alignments and then return the overall width for that height, and cache it */
1063       compute_size_for_opposing_orientation (box, box_iter, widget, height, &min_width, &nat_width);
1064     }
1065
1066   if (minimum_width)
1067     *minimum_width = min_width;
1068
1069   if (natural_width)
1070     *natural_width = nat_width;
1071 }
1072
1073
1074 /*************************************************************
1075  *                    GtkCellLayoutIface                     *
1076  *************************************************************/
1077 static void
1078 gtk_cell_area_box_cell_layout_init (GtkCellLayoutIface *iface)
1079 {
1080   iface->pack_start = gtk_cell_area_box_layout_pack_start;
1081   iface->pack_end   = gtk_cell_area_box_layout_pack_end;
1082   iface->reorder    = gtk_cell_area_box_layout_reorder;
1083 }
1084
1085 static void
1086 gtk_cell_area_box_layout_pack_start (GtkCellLayout      *cell_layout,
1087                                      GtkCellRenderer    *renderer,
1088                                      gboolean            expand)
1089 {
1090   gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (cell_layout), renderer, expand, TRUE);
1091 }
1092
1093 static void
1094 gtk_cell_area_box_layout_pack_end (GtkCellLayout      *cell_layout,
1095                                    GtkCellRenderer    *renderer,
1096                                    gboolean            expand)
1097 {
1098   gtk_cell_area_box_pack_end (GTK_CELL_AREA_BOX (cell_layout), renderer, expand, TRUE);
1099 }
1100
1101 static void
1102 gtk_cell_area_box_layout_reorder (GtkCellLayout      *cell_layout,
1103                                   GtkCellRenderer    *renderer,
1104                                   gint                position)
1105 {
1106   GtkCellAreaBox        *box  = GTK_CELL_AREA_BOX (cell_layout);
1107   GtkCellAreaBoxPrivate *priv = box->priv;
1108   GList                 *node;
1109   CellInfo              *info;
1110   
1111   node = g_list_find_custom (priv->cells, renderer, 
1112                              (GCompareFunc)cell_info_find);
1113
1114   if (node)
1115     {
1116       info = node->data;
1117
1118       priv->cells = g_list_delete_link (priv->cells, node);
1119       priv->cells = g_list_insert (priv->cells, info, position);
1120
1121       /* Reconstruct cell groups */
1122       g_list_foreach (priv->groups, (GFunc)cell_group_free, NULL);
1123       g_list_free (priv->groups);
1124       priv->groups = construct_cell_groups (box);
1125       
1126       /* Reinitialize groups on iters */
1127       init_iter_groups (box);
1128     }
1129 }
1130
1131 /*************************************************************
1132  *                            API                            *
1133  *************************************************************/
1134 GtkCellArea *
1135 gtk_cell_area_box_new (void)
1136 {
1137   return (GtkCellArea *)g_object_new (GTK_TYPE_CELL_AREA_BOX, NULL);
1138 }
1139
1140 void
1141 gtk_cell_area_box_pack_start  (GtkCellAreaBox  *box,
1142                                GtkCellRenderer *renderer,
1143                                gboolean         expand,
1144                                gboolean         align)
1145 {
1146   GtkCellAreaBoxPrivate *priv;
1147   CellInfo              *info;
1148
1149   g_return_if_fail (GTK_IS_CELL_AREA_BOX (box));
1150   g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
1151
1152   priv = box->priv;
1153
1154   if (g_list_find_custom (priv->cells, renderer, 
1155                           (GCompareFunc)cell_info_find))
1156     {
1157       g_warning ("Refusing to add the same cell renderer to a GtkCellAreaBox twice");
1158       return;
1159     }
1160
1161   info = cell_info_new (renderer, GTK_PACK_START, expand, align);
1162
1163   priv->cells = g_list_append (priv->cells, info);
1164
1165   /* Reconstruct cell groups */
1166   g_list_foreach (priv->groups, (GFunc)cell_group_free, NULL);
1167   g_list_free (priv->groups);
1168   priv->groups = construct_cell_groups (box);
1169
1170   /* Reinitialize groups on iters */
1171   init_iter_groups (box);
1172 }
1173
1174 void
1175 gtk_cell_area_box_pack_end (GtkCellAreaBox  *box,
1176                             GtkCellRenderer *renderer,
1177                             gboolean         expand, 
1178                             gboolean         align)
1179 {
1180   GtkCellAreaBoxPrivate *priv;
1181   CellInfo              *info;
1182
1183   g_return_if_fail (GTK_IS_CELL_AREA_BOX (box));
1184   g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
1185
1186   priv = box->priv;
1187
1188   if (g_list_find_custom (priv->cells, renderer, 
1189                           (GCompareFunc)cell_info_find))
1190     {
1191       g_warning ("Refusing to add the same cell renderer to a GtkCellArea twice");
1192       return;
1193     }
1194
1195   info = cell_info_new (renderer, GTK_PACK_END, expand, align);
1196
1197   priv->cells = g_list_append (priv->cells, info);
1198
1199   /* Reconstruct cell groups */
1200   g_list_foreach (priv->groups, (GFunc)cell_group_free, NULL);
1201   g_list_free (priv->groups);
1202   priv->groups = construct_cell_groups (box);
1203
1204   /* Reinitialize groups on iters */
1205   init_iter_groups (box);
1206 }
1207
1208 gint
1209 gtk_cell_area_box_get_spacing (GtkCellAreaBox  *box)
1210 {
1211   g_return_val_if_fail (GTK_IS_CELL_AREA_BOX (box), 0);
1212
1213   return box->priv->spacing;
1214 }
1215
1216 void
1217 gtk_cell_area_box_set_spacing (GtkCellAreaBox  *box,
1218                                gint             spacing)
1219 {
1220   GtkCellAreaBoxPrivate *priv;
1221
1222   g_return_if_fail (GTK_IS_CELL_AREA_BOX (box));
1223
1224   priv = box->priv;
1225
1226   if (priv->spacing != spacing)
1227     {
1228       priv->spacing = spacing;
1229
1230       g_object_notify (G_OBJECT (box), "spacing");
1231
1232       /* Notify that size needs to be requested again */
1233       flush_iters (box);
1234     }
1235 }