]> Pileus Git - ~andy/gtk/blob - gtk/gtkcellareabox.c
Fixing GtkCellAreaBox to rebuild groups when align/expand child properties change.
[~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 void      gtk_cell_area_box_get_cell_allocation            (GtkCellArea          *area,
54                                                                    GtkCellAreaIter      *iter,  
55                                                                    GtkWidget            *widget,
56                                                                    GtkCellRenderer      *renderer,
57                                                                    const GdkRectangle   *cell_area,
58                                                                    GdkRectangle         *allocation);
59 static gint      gtk_cell_area_box_event                          (GtkCellArea          *area,
60                                                                    GtkCellAreaIter      *iter,
61                                                                    GtkWidget            *widget,
62                                                                    GdkEvent             *event,
63                                                                    const GdkRectangle   *cell_area,
64                                                                    GtkCellRendererState  flags);
65 static void      gtk_cell_area_box_render                         (GtkCellArea          *area,
66                                                                    GtkCellAreaIter      *iter,
67                                                                    GtkWidget            *widget,
68                                                                    cairo_t              *cr,
69                                                                    const GdkRectangle   *cell_area,
70                                                                    GtkCellRendererState  flags);
71 static void      gtk_cell_area_box_set_cell_property              (GtkCellArea          *area,
72                                                                    GtkCellRenderer      *renderer,
73                                                                    guint                 prop_id,
74                                                                    const GValue         *value,
75                                                                    GParamSpec           *pspec);
76 static void      gtk_cell_area_box_get_cell_property              (GtkCellArea          *area,
77                                                                    GtkCellRenderer      *renderer,
78                                                                    guint                 prop_id,
79                                                                    GValue               *value,
80                                                                    GParamSpec           *pspec);
81 static GtkCellAreaIter    *gtk_cell_area_box_create_iter          (GtkCellArea          *area);
82 static GtkSizeRequestMode  gtk_cell_area_box_get_request_mode     (GtkCellArea          *area);
83 static void      gtk_cell_area_box_get_preferred_width            (GtkCellArea          *area,
84                                                                    GtkCellAreaIter      *iter,
85                                                                    GtkWidget            *widget,
86                                                                    gint                 *minimum_width,
87                                                                    gint                 *natural_width);
88 static void      gtk_cell_area_box_get_preferred_height           (GtkCellArea          *area,
89                                                                    GtkCellAreaIter      *iter,
90                                                                    GtkWidget            *widget,
91                                                                    gint                 *minimum_height,
92                                                                    gint                 *natural_height);
93 static void      gtk_cell_area_box_get_preferred_height_for_width (GtkCellArea          *area,
94                                                                    GtkCellAreaIter      *iter,
95                                                                    GtkWidget            *widget,
96                                                                    gint                  width,
97                                                                    gint                 *minimum_height,
98                                                                    gint                 *natural_height);
99 static void      gtk_cell_area_box_get_preferred_width_for_height (GtkCellArea          *area,
100                                                                    GtkCellAreaIter      *iter,
101                                                                    GtkWidget            *widget,
102                                                                    gint                  height,
103                                                                    gint                 *minimum_width,
104                                                                    gint                 *natural_width);
105 static void      gtk_cell_area_box_grab_focus                     (GtkCellArea          *area,
106                                                                    GtkDirectionType      direction);
107
108 /* GtkCellLayoutIface */
109 static void      gtk_cell_area_box_cell_layout_init               (GtkCellLayoutIface *iface);
110 static void      gtk_cell_area_box_layout_pack_start              (GtkCellLayout      *cell_layout,
111                                                                    GtkCellRenderer    *renderer,
112                                                                    gboolean            expand);
113 static void      gtk_cell_area_box_layout_pack_end                (GtkCellLayout      *cell_layout,
114                                                                    GtkCellRenderer    *renderer,
115                                                                    gboolean            expand);
116 static void      gtk_cell_area_box_layout_reorder                 (GtkCellLayout      *cell_layout,
117                                                                    GtkCellRenderer    *renderer,
118                                                                    gint                position);
119
120
121 /* CellInfo/CellGroup metadata handling and convenience functions */
122 typedef struct {
123   GtkCellRenderer *renderer;
124
125   guint            expand : 1; /* Whether the cell expands */
126   guint            pack   : 1; /* Whether the cell is packed from the start or end */
127   guint            align  : 1; /* Whether to align this cell's position with adjacent rows */
128 } CellInfo;
129
130 typedef struct {
131   GList *cells;
132
133   guint  id           : 8;
134   guint  n_cells      : 8;
135   guint  expand_cells : 8;
136 } CellGroup;
137
138 typedef struct {
139   GtkCellRenderer *renderer;
140
141   gint             position;
142   gint             size;
143 } AllocatedCell;
144
145 static CellInfo      *cell_info_new          (GtkCellRenderer    *renderer, 
146                                               GtkPackType         pack,
147                                               gboolean            expand,
148                                               gboolean            align);
149 static void           cell_info_free         (CellInfo           *info);
150 static gint           cell_info_find         (CellInfo           *info,
151                                               GtkCellRenderer    *renderer);
152
153 static AllocatedCell *allocated_cell_new     (GtkCellRenderer    *renderer,
154                                               gint                position,
155                                               gint                size);
156 static void           allocated_cell_free    (AllocatedCell      *cell);
157 static GList         *list_consecutive_cells (GtkCellAreaBox     *box);
158 static gint           count_expand_groups    (GtkCellAreaBox     *box);
159 static void           iter_weak_notify       (GtkCellAreaBox     *box,
160                                               GtkCellAreaBoxIter *dead_iter);
161 static void           flush_iters            (GtkCellAreaBox     *box);
162 static void           init_iter_groups       (GtkCellAreaBox     *box);
163 static void           init_iter_group        (GtkCellAreaBox     *box,
164                                               GtkCellAreaBoxIter *iter);
165 static GSList        *get_allocated_cells    (GtkCellAreaBox     *box,
166                                               GtkCellAreaBoxIter *iter,
167                                               GtkWidget          *widget);
168
169
170 struct _GtkCellAreaBoxPrivate
171 {
172   GtkOrientation  orientation;
173
174   GList          *cells;
175   GArray         *groups;
176
177   GSList         *iters;
178
179   gint            spacing;
180 };
181
182 enum {
183   PROP_0,
184   PROP_ORIENTATION,
185   PROP_SPACING
186 };
187
188 enum {
189   CELL_PROP_0,
190   CELL_PROP_EXPAND,
191   CELL_PROP_ALIGN,
192   CELL_PROP_PACK_TYPE
193 };
194
195 G_DEFINE_TYPE_WITH_CODE (GtkCellAreaBox, gtk_cell_area_box, GTK_TYPE_CELL_AREA,
196                          G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
197                                                 gtk_cell_area_box_cell_layout_init)
198                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL));
199
200 #define OPPOSITE_ORIENTATION(orientation)                       \
201   ((orientation) == GTK_ORIENTATION_HORIZONTAL ?                \
202    GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL)
203
204 static void
205 gtk_cell_area_box_init (GtkCellAreaBox *box)
206 {
207   GtkCellAreaBoxPrivate *priv;
208
209   box->priv = G_TYPE_INSTANCE_GET_PRIVATE (box,
210                                            GTK_TYPE_CELL_AREA_BOX,
211                                            GtkCellAreaBoxPrivate);
212   priv = box->priv;
213
214   priv->orientation = GTK_ORIENTATION_HORIZONTAL;
215   priv->groups      = g_array_new (FALSE, TRUE, sizeof (CellGroup));
216   priv->cells       = NULL;
217   priv->iters       = NULL;
218   priv->spacing     = 0;
219 }
220
221 static void 
222 gtk_cell_area_box_class_init (GtkCellAreaBoxClass *class)
223 {
224   GObjectClass     *object_class = G_OBJECT_CLASS (class);
225   GtkCellAreaClass *area_class   = GTK_CELL_AREA_CLASS (class);
226
227   /* GObjectClass */
228   object_class->finalize     = gtk_cell_area_box_finalize;
229   object_class->dispose      = gtk_cell_area_box_dispose;
230   object_class->set_property = gtk_cell_area_box_set_property;
231   object_class->get_property = gtk_cell_area_box_get_property;
232
233   /* GtkCellAreaClass */
234   area_class->add                 = gtk_cell_area_box_add;
235   area_class->remove              = gtk_cell_area_box_remove;
236   area_class->forall              = gtk_cell_area_box_forall;
237   area_class->get_cell_allocation = gtk_cell_area_box_get_cell_allocation;
238   area_class->event               = gtk_cell_area_box_event;
239   area_class->render              = gtk_cell_area_box_render;
240   area_class->set_cell_property   = gtk_cell_area_box_set_cell_property;
241   area_class->get_cell_property   = gtk_cell_area_box_get_cell_property;
242   
243   area_class->create_iter                    = gtk_cell_area_box_create_iter;
244   area_class->get_request_mode               = gtk_cell_area_box_get_request_mode;
245   area_class->get_preferred_width            = gtk_cell_area_box_get_preferred_width;
246   area_class->get_preferred_height           = gtk_cell_area_box_get_preferred_height;
247   area_class->get_preferred_height_for_width = gtk_cell_area_box_get_preferred_height_for_width;
248   area_class->get_preferred_width_for_height = gtk_cell_area_box_get_preferred_width_for_height;
249
250   area_class->grab_focus = gtk_cell_area_box_grab_focus;
251
252   /* Properties */
253   g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
254
255   g_object_class_install_property (object_class,
256                                    PROP_SPACING,
257                                    g_param_spec_int ("spacing",
258                                                      P_("Spacing"),
259                                                      P_("Space which is inserted between cells"),
260                                                      0,
261                                                      G_MAXINT,
262                                                      0,
263                                                      GTK_PARAM_READWRITE));
264
265   /* Cell Properties */
266   gtk_cell_area_class_install_cell_property (area_class,
267                                              CELL_PROP_EXPAND,
268                                              g_param_spec_boolean 
269                                              ("expand",
270                                               P_("Expand"),
271                                               P_("Whether the cell expands"),
272                                               FALSE,
273                                               GTK_PARAM_READWRITE));
274   
275   gtk_cell_area_class_install_cell_property (area_class,
276                                              CELL_PROP_ALIGN,
277                                              g_param_spec_boolean
278                                              ("align",
279                                               P_("Align"),
280                                               P_("Whether cell should align with adjacent rows"),
281                                               TRUE,
282                                               GTK_PARAM_READWRITE));
283
284   gtk_cell_area_class_install_cell_property (area_class,
285                                              CELL_PROP_PACK_TYPE,
286                                              g_param_spec_enum
287                                              ("pack-type",
288                                               P_("Pack Type"),
289                                               P_("A GtkPackType indicating whether the cell is packed with "
290                                                  "reference to the start or end of the cell area"),
291                                               GTK_TYPE_PACK_TYPE, GTK_PACK_START,
292                                               GTK_PARAM_READWRITE));
293
294   g_type_class_add_private (object_class, sizeof (GtkCellAreaBoxPrivate));
295 }
296
297
298 /*************************************************************
299  *    CellInfo/CellGroup basics and convenience functions    *
300  *************************************************************/
301 static CellInfo *
302 cell_info_new  (GtkCellRenderer *renderer, 
303                 GtkPackType      pack,
304                 gboolean         expand,
305                 gboolean         align)
306 {
307   CellInfo *info = g_slice_new (CellInfo);
308   
309   info->renderer = g_object_ref_sink (renderer);
310   info->pack     = pack;
311   info->expand   = expand;
312   info->align    = align;
313
314   return info;
315 }
316
317 static void
318 cell_info_free (CellInfo *info)
319 {
320   g_object_unref (info->renderer);
321
322   g_slice_free (CellInfo, info);
323 }
324
325 static gint
326 cell_info_find (CellInfo        *info,
327                 GtkCellRenderer *renderer)
328 {
329   return (info->renderer == renderer) ? 0 : -1;
330 }
331
332 static AllocatedCell *
333 allocated_cell_new (GtkCellRenderer *renderer,
334                     gint             position,
335                     gint             size)
336 {
337   AllocatedCell *cell = g_slice_new (AllocatedCell);
338
339   cell->renderer = renderer;
340   cell->position = position;
341   cell->size     = size;
342
343   return cell;
344 }
345
346 static void
347 allocated_cell_free (AllocatedCell *cell)
348 {
349   g_slice_free (AllocatedCell, cell);
350 }
351
352 static GList *
353 list_consecutive_cells (GtkCellAreaBox *box)
354 {
355   GtkCellAreaBoxPrivate *priv = box->priv;
356   GList                 *l, *consecutive_cells = NULL, *pack_end_cells = NULL;
357   CellInfo              *info;
358
359   /* List cells in consecutive order taking their 
360    * PACK_START/PACK_END options into account 
361    */
362   for (l = priv->cells; l; l = l->next)
363     {
364       info = l->data;
365       
366       if (info->pack == GTK_PACK_START)
367         consecutive_cells = g_list_prepend (consecutive_cells, info);
368     }
369
370   for (l = priv->cells; l; l = l->next)
371     {
372       info = l->data;
373       
374       if (info->pack == GTK_PACK_END)
375         pack_end_cells = g_list_prepend (pack_end_cells, info);
376     }
377
378   consecutive_cells = g_list_reverse (consecutive_cells);
379   consecutive_cells = g_list_concat (consecutive_cells, pack_end_cells);
380
381   return consecutive_cells;
382 }
383
384 static void
385 cell_groups_clear (GtkCellAreaBox     *box)
386 {
387   GtkCellAreaBoxPrivate *priv  = box->priv;
388   gint                   i;
389
390   for (i = 0; i < priv->groups->len; i++)
391     {
392       CellGroup *group = &g_array_index (priv->groups, CellGroup, i);
393
394       g_list_free (group->cells);
395     }
396
397   g_array_set_size (priv->groups, 0);
398 }
399
400 static void
401 cell_groups_rebuild (GtkCellAreaBox *box)
402 {
403   GtkCellAreaBoxPrivate *priv  = box->priv;
404   CellGroup              group = { 0, };
405   CellGroup             *group_ptr;
406   GList                 *cells, *l;
407   guint                  id = 0;
408
409   cell_groups_clear (box);
410
411   if (!priv->cells)
412     return;
413
414   cells = list_consecutive_cells (box);
415
416   /* First group is implied */
417   g_array_append_val (priv->groups, group);
418   group_ptr = &g_array_index (priv->groups, CellGroup, id);
419
420   for (l = cells; l; l = l->next)
421     {
422       CellInfo *info = l->data;
423
424       /* A new group starts with any aligned cell, the first group is implied */
425       if (info->align && l != cells)
426         {
427           memset (&group, 0x0, sizeof (CellGroup));
428           group.id = ++id;
429
430           g_array_append_val (priv->groups, group);
431           group_ptr = &g_array_index (priv->groups, CellGroup, id);
432         }
433
434       group_ptr->cells = g_list_prepend (group_ptr->cells, info);
435       group_ptr->n_cells++;
436
437       /* A group expands if it contains any expand cells */
438       if (info->expand)
439         group_ptr->expand_cells++;
440     }
441
442   g_list_free (cells);
443
444   for (id = 0; id < priv->groups->len; id++)
445     {
446       group_ptr = &g_array_index (priv->groups, CellGroup, id);
447
448       group_ptr->cells = g_list_reverse (group_ptr->cells);
449     }
450
451   /* Iters need to be updated with the new grouping information */
452   init_iter_groups (box);
453 }
454
455 static gint
456 count_visible_cells (CellGroup *group, 
457                      gint      *expand_cells)
458 {
459   GList *l;
460   gint   visible_cells = 0;
461   gint   n_expand_cells = 0;
462
463   for (l = group->cells; l; l = l->next)
464     {
465       CellInfo *info = l->data;
466
467       if (gtk_cell_renderer_get_visible (info->renderer))
468         {
469           visible_cells++;
470
471           if (info->expand)
472             n_expand_cells++;
473         }
474     }
475
476   if (expand_cells)
477     *expand_cells = n_expand_cells;
478
479   return visible_cells;
480 }
481
482 static gint
483 count_expand_groups (GtkCellAreaBox  *box)
484 {
485   GtkCellAreaBoxPrivate *priv = box->priv;
486   gint                   i;
487   gint                   expand_groups = 0;
488
489   for (i = 0; i < priv->groups->len; i++)
490     {
491       CellGroup *group = &g_array_index (priv->groups, CellGroup, i);
492
493       if (group->expand_cells > 0)
494         expand_groups++;
495     }
496
497   return expand_groups;
498 }
499
500 static void 
501 iter_weak_notify (GtkCellAreaBox     *box,
502                   GtkCellAreaBoxIter *dead_iter)
503 {
504   GtkCellAreaBoxPrivate *priv = box->priv;
505
506   priv->iters = g_slist_remove (priv->iters, dead_iter);
507 }
508
509 static void
510 init_iter_group (GtkCellAreaBox     *box,
511                  GtkCellAreaBoxIter *iter)
512 {
513   GtkCellAreaBoxPrivate *priv = box->priv;
514   gint                  *expand_groups, i;
515
516   expand_groups = g_new (gboolean, priv->groups->len);
517
518   for (i = 0; i < priv->groups->len; i++)
519     {
520       CellGroup *group = &g_array_index (priv->groups, CellGroup, i);
521
522       expand_groups[i] = (group->expand_cells > 0);
523     }
524
525   /* This call implies flushing the request info */
526   gtk_cell_area_box_init_groups (iter, priv->groups->len, expand_groups);
527   g_free (expand_groups);
528 }
529
530 static void
531 init_iter_groups (GtkCellAreaBox *box)
532 {
533   GtkCellAreaBoxPrivate *priv = box->priv;
534   GSList                *l;
535
536   /* When the box's groups are reconstructed, iters need to
537    * be reinitialized.
538    */
539   for (l = priv->iters; l; l = l->next)
540     {
541       GtkCellAreaBoxIter *iter = l->data;
542
543       init_iter_group (box, iter);
544     }
545 }
546
547 static void
548 flush_iters (GtkCellAreaBox *box)
549 {
550   GtkCellAreaBoxPrivate *priv = box->priv;
551   GSList                *l;
552
553   /* When the box layout changes, iters need to
554    * be flushed and sizes for the box get requested again
555    */
556   for (l = priv->iters; l; l = l->next)
557     {
558       GtkCellAreaIter *iter = l->data;
559
560       gtk_cell_area_iter_flush (iter);
561     }
562 }
563
564 /* Returns an allocation for each cell in the orientation of the box,
565  * used in ->render()/->event() implementations to get a straight-forward
566  * list of allocated cells to operate on.
567  */
568 static GSList *
569 get_allocated_cells (GtkCellAreaBox     *box,
570                      GtkCellAreaBoxIter *iter,
571                      GtkWidget          *widget)
572 {
573   const GtkCellAreaBoxAllocation *group_allocs;
574   GtkCellArea                    *area = GTK_CELL_AREA (box);
575   GtkCellAreaBoxPrivate          *priv = box->priv;
576   GList                          *cell_list;
577   GSList                         *allocated_cells = NULL;
578   gint                            i, j, n_allocs;
579
580   group_allocs = gtk_cell_area_box_iter_get_orientation_allocs (iter, &n_allocs);
581   if (!group_allocs)
582     {
583       g_warning ("Trying to operate on an unallocated GtkCellAreaIter, "
584                  "GtkCellAreaBox requires that the iter be allocated at least "
585                  "in the orientation of the box");
586       return NULL;
587     }
588
589   for (i = 0; i < n_allocs; i++)
590     {
591       /* We dont always allocate all groups, sometimes the requested group has only invisible
592        * cells for every row, hence the usage of group_allocs[i].group_idx here
593        */
594       CellGroup *group = &g_array_index (priv->groups, CellGroup, group_allocs[i].group_idx);
595
596       /* Exception for single cell groups */
597       if (group->n_cells == 1)
598         {
599           CellInfo      *info = group->cells->data;
600           AllocatedCell *cell = 
601             allocated_cell_new (info->renderer, group_allocs[i].position, group_allocs[i].size);
602
603           allocated_cells = g_slist_prepend (allocated_cells, cell);
604         }
605       else
606         {
607           GtkRequestedSize *sizes;
608           gint              avail_size, position;
609           gint              visible_cells, expand_cells;
610           gint              extra_size, extra_extra;
611
612           visible_cells = count_visible_cells (group, &expand_cells);
613
614           /* If this row has no visible cells in this group, just
615            * skip the allocation */
616           if (visible_cells == 0)
617             continue;
618
619           /* Offset the allocation to the group position and allocate into 
620            * the group's available size */
621           position   = group_allocs[i].position;
622           avail_size = group_allocs[i].size;
623
624           sizes = g_new (GtkRequestedSize, visible_cells);
625
626           for (j = 0, cell_list = group->cells; cell_list; cell_list = cell_list->next)
627             {
628               CellInfo *info = cell_list->data;
629
630               if (!gtk_cell_renderer_get_visible (info->renderer))
631                 continue;
632
633               gtk_cell_area_request_renderer (area, info->renderer,
634                                               priv->orientation,
635                                               widget, -1,
636                                               &sizes[j].minimum_size,
637                                               &sizes[j].natural_size);
638
639               sizes[j].data = info;
640               avail_size   -= sizes[j].minimum_size;
641
642               j++;
643             }
644
645           /* Distribute cells naturally within the group */
646           avail_size -= (visible_cells - 1) * priv->spacing;
647           avail_size = gtk_distribute_natural_allocation (avail_size, visible_cells, sizes);
648
649           /* Calculate/distribute expand for cells */
650           if (expand_cells > 0)
651             {
652               extra_size  = avail_size / expand_cells;
653               extra_extra = avail_size % expand_cells;
654             }
655           else
656             extra_size = extra_extra = 0;
657
658           /* Create the allocated cells (loop only over visible cells here) */
659           for (j = 0; j < visible_cells; j++)
660             {
661               CellInfo      *info = sizes[j].data;
662               AllocatedCell *cell;
663
664               if (info->expand)
665                 {
666                   sizes[j].minimum_size += extra_size;
667                   if (extra_extra)
668                     {
669                       sizes[j].minimum_size++;
670                       extra_extra--;
671                     }
672                 }
673               
674               cell = allocated_cell_new (info->renderer, position, sizes[j].minimum_size);
675
676               allocated_cells = g_slist_prepend (allocated_cells, cell);
677               
678               position += sizes[j].minimum_size;
679               position += priv->spacing;
680             }
681
682           g_free (sizes);
683         }
684     }
685
686   /* Note it might not be important to reverse the list here at all,
687    * we have the correct positions, no need to allocate from left to right */
688   return g_slist_reverse (allocated_cells);
689 }
690
691 /*************************************************************
692  *                      GObjectClass                         *
693  *************************************************************/
694 static void
695 gtk_cell_area_box_finalize (GObject *object)
696 {
697   GtkCellAreaBox        *box = GTK_CELL_AREA_BOX (object);
698   GtkCellAreaBoxPrivate *priv = box->priv;
699   GSList                *l;
700
701   /* Unref/free the iter list */
702   for (l = priv->iters; l; l = l->next)
703     g_object_weak_unref (G_OBJECT (l->data), (GWeakNotify)iter_weak_notify, box);
704
705   g_slist_free (priv->iters);
706   priv->iters = NULL;
707
708   /* Free the cell grouping info */
709   cell_groups_clear (box);
710   g_array_free (priv->groups, TRUE);
711   
712   G_OBJECT_CLASS (gtk_cell_area_box_parent_class)->finalize (object);
713 }
714
715 static void
716 gtk_cell_area_box_dispose (GObject *object)
717 {
718   G_OBJECT_CLASS (gtk_cell_area_box_parent_class)->dispose (object);
719 }
720
721 static void
722 gtk_cell_area_box_set_property (GObject       *object,
723                                 guint          prop_id,
724                                 const GValue  *value,
725                                 GParamSpec    *pspec)
726 {
727   GtkCellAreaBox *box = GTK_CELL_AREA_BOX (object);
728
729   switch (prop_id)
730     {
731     case PROP_ORIENTATION:
732       box->priv->orientation = g_value_get_enum (value);
733
734       /* Notify that size needs to be requested again */
735       flush_iters (box);
736       break;
737     case PROP_SPACING:
738       gtk_cell_area_box_set_spacing (box, g_value_get_int (value));
739       break;
740     default:
741       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
742       break;
743     }
744 }
745
746 static void
747 gtk_cell_area_box_get_property (GObject     *object,
748                                 guint        prop_id,
749                                 GValue      *value,
750                                 GParamSpec  *pspec)
751 {
752   GtkCellAreaBox *box = GTK_CELL_AREA_BOX (object);
753
754   switch (prop_id)
755     {
756     case PROP_ORIENTATION:
757       g_value_set_enum (value, box->priv->orientation);
758       break;
759     case PROP_SPACING:
760       g_value_set_int (value, gtk_cell_area_box_get_spacing (box));
761       break;
762     default:
763       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
764       break;
765     }
766 }
767
768 /*************************************************************
769  *                    GtkCellAreaClass                       *
770  *************************************************************/
771 static void      
772 gtk_cell_area_box_add (GtkCellArea        *area,
773                        GtkCellRenderer    *renderer)
774 {
775   gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area),
776                                 renderer, FALSE, TRUE);
777 }
778
779 static void
780 gtk_cell_area_box_remove (GtkCellArea        *area,
781                           GtkCellRenderer    *renderer)
782 {
783   GtkCellAreaBox        *box  = GTK_CELL_AREA_BOX (area);
784   GtkCellAreaBoxPrivate *priv = box->priv;
785   GList                 *node;
786
787   node = g_list_find_custom (priv->cells, renderer, 
788                              (GCompareFunc)cell_info_find);
789
790   if (node)
791     {
792       CellInfo *info = node->data;
793
794       cell_info_free (info);
795
796       priv->cells = g_list_delete_link (priv->cells, node);
797
798       /* Reconstruct cell groups */
799       cell_groups_rebuild (box);
800     }
801   else
802     g_warning ("Trying to remove a cell renderer that is not present GtkCellAreaBox");
803 }
804
805 static void
806 gtk_cell_area_box_forall (GtkCellArea        *area,
807                           GtkCellCallback     callback,
808                           gpointer            callback_data)
809 {
810   GtkCellAreaBox        *box  = GTK_CELL_AREA_BOX (area);
811   GtkCellAreaBoxPrivate *priv = box->priv;
812   GList                 *list;
813
814   for (list = priv->cells; list; list = list->next)
815     {
816       CellInfo *info = list->data;
817
818       callback (info->renderer, callback_data);
819     }
820 }
821
822 static void
823 gtk_cell_area_box_get_cell_allocation (GtkCellArea          *area,
824                                        GtkCellAreaIter      *iter,      
825                                        GtkWidget            *widget,
826                                        GtkCellRenderer      *renderer,
827                                        const GdkRectangle   *cell_area,
828                                        GdkRectangle         *allocation)
829 {
830   GtkCellAreaBox        *box      = GTK_CELL_AREA_BOX (area);
831   GtkCellAreaBoxPrivate *priv     = box->priv;
832   GtkCellAreaBoxIter    *box_iter = GTK_CELL_AREA_BOX_ITER (iter);
833   GSList                *allocated_cells, *l;
834
835   *allocation = *cell_area;
836
837   /* Get a list of cells with allocation sizes decided regardless
838    * of alignments and pack order etc. */
839   allocated_cells = get_allocated_cells (box, box_iter, widget);
840
841   for (l = allocated_cells; l; l = l->next)
842     {
843       AllocatedCell *cell = l->data;
844
845       if (cell->renderer == renderer)
846         {
847           if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
848             {
849               allocation->x     = cell_area->x + cell->position;
850               allocation->width = cell->size;
851             }
852           else
853             {
854               allocation->y      = cell_area->y + cell->position;
855               allocation->height = cell->size;
856             }
857
858           break;
859         }
860     }
861
862   g_slist_foreach (allocated_cells, (GFunc)allocated_cell_free, NULL);
863   g_slist_free (allocated_cells);
864 }
865
866 enum {
867   FOCUS_NONE,
868   FOCUS_PREV,
869   FOCUS_NEXT
870 };
871
872 static void
873 gtk_cell_area_cycle_focus (GtkCellAreaBox   *box,
874                            GtkCellRenderer  *focus_cell,
875                            GtkDirectionType  direction)
876 {
877   GtkCellAreaBoxPrivate *priv = box->priv;
878   GtkCellArea           *area = GTK_CELL_AREA (box);
879   gint                   cycle = FOCUS_NONE;
880
881   switch (direction)
882     {
883     case GTK_DIR_TAB_FORWARD:
884       cycle = FOCUS_NEXT;
885       break;
886     case GTK_DIR_TAB_BACKWARD:
887       cycle = FOCUS_PREV;
888       break;
889     case GTK_DIR_UP: 
890       if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
891         gtk_cell_area_focus_leave (area, direction);
892       else
893         cycle = FOCUS_PREV;
894       break;
895     case GTK_DIR_DOWN:
896       if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
897         gtk_cell_area_focus_leave (area, direction);
898       else
899         cycle = FOCUS_NEXT;
900       break;
901     case GTK_DIR_LEFT:
902       if (priv->orientation == GTK_ORIENTATION_VERTICAL)
903         gtk_cell_area_focus_leave (area, direction);
904       else
905         cycle = FOCUS_PREV;
906       break;
907     case GTK_DIR_RIGHT:
908       if (priv->orientation == GTK_ORIENTATION_VERTICAL)
909         gtk_cell_area_focus_leave (area, direction);
910       else
911         cycle = FOCUS_NEXT;
912       break;
913     default:
914       break;
915     }
916
917   if (cycle != FOCUS_NONE)
918     {
919       gboolean  found_cell = FALSE;
920       gboolean  cycled_focus = FALSE;
921       GList    *list;
922       gint      i;
923
924       for (i = (cycle == FOCUS_NEXT) ? 0 : priv->groups->len -1; 
925            i >= 0 && i < priv->groups->len;
926            i = (cycle == FOCUS_NEXT) ? i + 1 : i - 1)
927         {
928           CellGroup *group = &g_array_index (priv->groups, CellGroup, i);
929           
930           for (list = (cycle == FOCUS_NEXT) ? g_list_first (group->cells) : g_list_last (group->cells); 
931                list; list = (cycle == FOCUS_NEXT) ? list->next : list->prev)
932             {
933               CellInfo *info = list->data;
934
935               if (!found_cell && info->renderer == focus_cell)
936                 found_cell = TRUE;
937               else if (found_cell)
938                 {
939                   if (gtk_cell_renderer_can_focus (info->renderer))
940                     {
941                       gtk_cell_area_set_focus_cell (area, info->renderer);
942
943                       cycled_focus = TRUE;
944                       break;
945                     }
946                 }
947             }
948         }
949       
950       /* We cycled right out of the area, signal the parent we
951        * need to focus out of the area */
952       if (!cycled_focus)
953         gtk_cell_area_focus_leave (area, direction);
954     }
955 }
956
957 static gint
958 gtk_cell_area_box_event (GtkCellArea          *area,
959                          GtkCellAreaIter      *iter,
960                          GtkWidget            *widget,
961                          GdkEvent             *event,
962                          const GdkRectangle   *cell_area,
963                          GtkCellRendererState  flags)
964 {
965   gint retval;
966
967   /* First let the parent class handle activation of cells via keystrokes */
968   retval = 
969     GTK_CELL_AREA_CLASS (gtk_cell_area_box_parent_class)->event (area, iter, widget,
970                                                                  event, cell_area, flags);
971   
972   if (retval)
973     return retval;
974
975   /* Now detect keystrokes that move focus directionally inside the area
976    * or signal that focus should leave the area in a given direction.
977    *
978    * To navigate focus we only need to loop through the groups and 
979    * observe the orientation and push focus along to the next cell
980    * or signal that focus should leave the area.
981    */
982   if (event->type == GDK_KEY_PRESS && (flags & GTK_CELL_RENDERER_FOCUSED) != 0)
983     {
984       GdkEventKey        *key_event = (GdkEventKey *)event;
985       GtkCellRenderer    *focus_cell;
986
987       focus_cell = gtk_cell_area_get_focus_cell (area);
988
989       if (focus_cell)
990         {
991           GtkCellAreaBox        *box            = GTK_CELL_AREA_BOX (area);
992           GtkDirectionType       direction      = GTK_DIR_TAB_FORWARD;
993           gboolean               have_direction = FALSE;
994
995           /* Check modifiers and TAB keys ! */
996           switch (key_event->keyval)
997             {
998             case GDK_KEY_KP_Up:
999             case GDK_KEY_Up:
1000               direction = GTK_DIR_UP;
1001               have_direction = TRUE;
1002               break;
1003             case GDK_KEY_KP_Down:
1004             case GDK_KEY_Down:
1005               direction = GTK_DIR_DOWN;
1006               have_direction = TRUE;
1007               break;
1008             case GDK_KEY_KP_Left:
1009             case GDK_KEY_Left:
1010               direction = GTK_DIR_LEFT;
1011               have_direction = TRUE;
1012               break;
1013             case GDK_KEY_KP_Right:
1014             case GDK_KEY_Right:
1015               direction = GTK_DIR_RIGHT;
1016               have_direction = TRUE;
1017               break;
1018             default:
1019               break;
1020             }
1021
1022           if (have_direction)
1023             {
1024               gtk_cell_area_cycle_focus (box, focus_cell, direction);
1025               return TRUE;
1026             }
1027         }
1028     }
1029
1030   /* Also detect mouse events, for mouse events we need to allocate the renderers
1031    * and find which renderer needs to be activated.
1032    */
1033
1034
1035   return 0;
1036 }
1037
1038 static void
1039 gtk_cell_area_box_render (GtkCellArea          *area,
1040                           GtkCellAreaIter      *iter,
1041                           GtkWidget            *widget,
1042                           cairo_t              *cr,
1043                           const GdkRectangle   *cell_area,
1044                           GtkCellRendererState  flags)
1045 {
1046   GtkCellAreaBox        *box      = GTK_CELL_AREA_BOX (area);
1047   GtkCellAreaBoxPrivate *priv     = box->priv;
1048   GtkCellAreaBoxIter    *box_iter = GTK_CELL_AREA_BOX_ITER (iter);
1049   GSList                *allocated_cells, *l;
1050   GdkRectangle           background_area, inner_area;
1051
1052   background_area = *cell_area;
1053
1054   /* Get a list of cells with allocation sizes decided regardless
1055    * of alignments and pack order etc. */
1056   allocated_cells = get_allocated_cells (box, box_iter, widget);
1057
1058   for (l = allocated_cells; l; l = l->next)
1059     {
1060       AllocatedCell *cell = l->data;
1061
1062       if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1063         {
1064           background_area.x     = cell_area->x + cell->position;
1065           background_area.width = cell->size;
1066         }
1067       else
1068         {
1069           background_area.y      = cell_area->y + cell->position;
1070           background_area.height = cell->size;
1071         }
1072
1073       /* Remove margins from the background area to produce the cell area
1074        */
1075       gtk_cell_area_inner_cell_area (area, &background_area, &inner_area);
1076
1077       /* XXX We have to do some per-cell considerations for the 'flags'
1078        * for focus handling */
1079       gtk_cell_renderer_render (cell->renderer, cr, widget,
1080                                 &background_area, &inner_area,
1081                                 flags);
1082
1083     }
1084
1085   g_slist_foreach (allocated_cells, (GFunc)allocated_cell_free, NULL);
1086   g_slist_free (allocated_cells);
1087 }
1088
1089 static void
1090 gtk_cell_area_box_set_cell_property (GtkCellArea        *area,
1091                                      GtkCellRenderer    *renderer,
1092                                      guint               prop_id,
1093                                      const GValue       *value,
1094                                      GParamSpec         *pspec)
1095 {
1096   GtkCellAreaBox        *box  = GTK_CELL_AREA_BOX (area); 
1097   GtkCellAreaBoxPrivate *priv = box->priv;
1098   GList                 *node;
1099   CellInfo              *info;
1100   gboolean               rebuild = FALSE;
1101   gboolean               val;
1102   GtkPackType            pack_type;
1103
1104   node = g_list_find_custom (priv->cells, renderer, 
1105                              (GCompareFunc)cell_info_find);
1106   if (!node)
1107     return;
1108
1109   info = node->data;
1110
1111   switch (prop_id)
1112     {
1113     case CELL_PROP_EXPAND:
1114       val = g_value_get_boolean (value);
1115
1116       if (info->expand != val)
1117         {
1118           info->expand = val;
1119           rebuild      = TRUE;
1120         }
1121       break;
1122
1123     case CELL_PROP_ALIGN:
1124       val = g_value_get_boolean (value);
1125
1126       if (info->align != val)
1127         {
1128           info->align = val;
1129           rebuild     = TRUE;
1130         }
1131       break;
1132
1133     case CELL_PROP_PACK_TYPE:
1134       pack_type = g_value_get_enum (value);
1135
1136       if (info->pack != pack_type)
1137         {
1138           info->pack = pack_type;
1139           rebuild    = TRUE;
1140         }
1141       break;
1142     default:
1143       GTK_CELL_AREA_WARN_INVALID_CHILD_PROPERTY_ID (area, prop_id, pspec);
1144       break;
1145     }
1146
1147   /* Groups need to be rebuilt */
1148   if (rebuild)
1149     cell_groups_rebuild (box);
1150 }
1151
1152 static void
1153 gtk_cell_area_box_get_cell_property (GtkCellArea        *area,
1154                                      GtkCellRenderer    *renderer,
1155                                      guint               prop_id,
1156                                      GValue             *value,
1157                                      GParamSpec         *pspec)
1158 {
1159   GtkCellAreaBox        *box  = GTK_CELL_AREA_BOX (area); 
1160   GtkCellAreaBoxPrivate *priv = box->priv;
1161   GList                 *node;
1162   CellInfo              *info;
1163
1164   node = g_list_find_custom (priv->cells, renderer, 
1165                              (GCompareFunc)cell_info_find);
1166   if (!node)
1167     return;
1168
1169   info = node->data;
1170
1171   switch (prop_id)
1172     {
1173     case CELL_PROP_EXPAND:
1174       g_value_set_boolean (value, info->expand);
1175       break;
1176
1177     case CELL_PROP_ALIGN:
1178       g_value_set_boolean (value, info->align);
1179       break;
1180
1181     case CELL_PROP_PACK_TYPE:
1182       g_value_set_enum (value, info->pack);
1183       break;
1184     default:
1185       GTK_CELL_AREA_WARN_INVALID_CHILD_PROPERTY_ID (area, prop_id, pspec);
1186       break;
1187     }
1188 }
1189
1190
1191 static GtkCellAreaIter *
1192 gtk_cell_area_box_create_iter (GtkCellArea *area)
1193 {
1194   GtkCellAreaBox        *box  = GTK_CELL_AREA_BOX (area);
1195   GtkCellAreaBoxPrivate *priv = box->priv;
1196   GtkCellAreaIter       *iter =
1197     (GtkCellAreaIter *)g_object_new (GTK_TYPE_CELL_AREA_BOX_ITER, 
1198                                      "area", area, NULL);
1199
1200   priv->iters = g_slist_prepend (priv->iters, iter);
1201
1202   g_object_weak_ref (G_OBJECT (iter), (GWeakNotify)iter_weak_notify, box);
1203
1204   /* Tell the new group about our cell layout */
1205   init_iter_group (box, GTK_CELL_AREA_BOX_ITER (iter));
1206
1207   return iter;
1208 }
1209
1210 static GtkSizeRequestMode 
1211 gtk_cell_area_box_get_request_mode (GtkCellArea *area)
1212 {
1213   GtkCellAreaBox        *box  = GTK_CELL_AREA_BOX (area);
1214   GtkCellAreaBoxPrivate *priv = box->priv;
1215
1216   return (priv->orientation) == GTK_ORIENTATION_HORIZONTAL ?
1217     GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH :
1218     GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
1219 }
1220
1221 static void
1222 compute_size (GtkCellAreaBox     *box,
1223               GtkOrientation      orientation,
1224               GtkCellAreaBoxIter *iter,
1225               GtkWidget          *widget,
1226               gint                for_size,
1227               gint               *minimum_size,
1228               gint               *natural_size)
1229 {
1230   GtkCellAreaBoxPrivate *priv = box->priv;
1231   GtkCellArea           *area = GTK_CELL_AREA (box);
1232   GList                 *list;
1233   gint                   i;
1234   gint                   min_size = 0;
1235   gint                   nat_size = 0;
1236   
1237   for (i = 0; i < priv->groups->len; i++)
1238     {
1239       CellGroup *group = &g_array_index (priv->groups, CellGroup, i);
1240       gint       group_min_size = 0;
1241       gint       group_nat_size = 0;
1242
1243       for (list = group->cells; list; list = list->next)
1244         {
1245           CellInfo *info = list->data;
1246           gint      renderer_min_size, renderer_nat_size;
1247
1248           if (!gtk_cell_renderer_get_visible (info->renderer))
1249               continue;
1250           
1251           gtk_cell_area_request_renderer (area, info->renderer, orientation, widget, for_size, 
1252                                           &renderer_min_size, &renderer_nat_size);
1253
1254           if (orientation == priv->orientation)
1255             {
1256               if (min_size > 0)
1257                 {
1258                   min_size += priv->spacing;
1259                   nat_size += priv->spacing;
1260                 }
1261               
1262               if (group_min_size > 0)
1263                 {
1264                   group_min_size += priv->spacing;
1265                   group_nat_size += priv->spacing;
1266                 }
1267               
1268               min_size       += renderer_min_size;
1269               nat_size       += renderer_nat_size;
1270               group_min_size += renderer_min_size;
1271               group_nat_size += renderer_nat_size;
1272             }
1273           else
1274             {
1275               min_size       = MAX (min_size, renderer_min_size);
1276               nat_size       = MAX (nat_size, renderer_nat_size);
1277               group_min_size = MAX (group_min_size, renderer_min_size);
1278               group_nat_size = MAX (group_nat_size, renderer_nat_size);
1279             }
1280         }
1281
1282       if (orientation == GTK_ORIENTATION_HORIZONTAL)
1283         {
1284           if (for_size < 0)
1285             gtk_cell_area_box_iter_push_group_width (iter, group->id, group_min_size, group_nat_size);
1286           else
1287             gtk_cell_area_box_iter_push_group_width_for_height (iter, group->id, for_size,
1288                                                                 group_min_size, group_nat_size);
1289         }
1290       else
1291         {
1292           if (for_size < 0)
1293             gtk_cell_area_box_iter_push_group_height (iter, group->id, group_min_size, group_nat_size);
1294           else
1295             gtk_cell_area_box_iter_push_group_height_for_width (iter, group->id, for_size,
1296                                                                 group_min_size, group_nat_size);
1297         }
1298     }
1299
1300   *minimum_size = min_size;
1301   *natural_size = nat_size;
1302 }
1303
1304 GtkRequestedSize *
1305 get_group_sizes (GtkCellArea    *area,
1306                  CellGroup      *group,
1307                  GtkOrientation  orientation,
1308                  GtkWidget      *widget,
1309                  gint           *n_sizes)
1310 {
1311   GtkRequestedSize *sizes;
1312   GList            *l;
1313   gint              i;
1314
1315   *n_sizes = count_visible_cells (group, NULL);
1316   sizes    = g_new (GtkRequestedSize, *n_sizes);
1317
1318   for (l = group->cells, i = 0; l; l = l->next)
1319     {
1320       CellInfo *info = l->data;
1321
1322       if (!gtk_cell_renderer_get_visible (info->renderer))
1323         continue;
1324
1325       sizes[i].data = info;
1326       
1327       gtk_cell_area_request_renderer (area, info->renderer,
1328                                       orientation, widget, -1,
1329                                       &sizes[i].minimum_size,
1330                                       &sizes[i].natural_size);
1331
1332       i++;
1333     }
1334
1335   return sizes;
1336 }
1337
1338 static void
1339 compute_group_size_for_opposing_orientation (GtkCellAreaBox     *box,
1340                                              CellGroup          *group,
1341                                              GtkWidget          *widget, 
1342                                              gint                for_size,
1343                                              gint               *minimum_size, 
1344                                              gint               *natural_size)
1345 {
1346   GtkCellAreaBoxPrivate *priv = box->priv;
1347   GtkCellArea           *area = GTK_CELL_AREA (box);
1348
1349   /* Exception for single cell groups */
1350   if (group->n_cells == 1)
1351     {
1352       CellInfo *info = group->cells->data;
1353
1354       gtk_cell_area_request_renderer (area, info->renderer,
1355                                       OPPOSITE_ORIENTATION (priv->orientation),
1356                                       widget, for_size, minimum_size, natural_size);
1357     }
1358   else
1359     {
1360       GtkRequestedSize *orientation_sizes;
1361       CellInfo         *info;
1362       gint              n_sizes, i;
1363       gint              avail_size     = for_size;
1364       gint              extra_size, extra_extra;
1365       gint              min_size = 0, nat_size = 0;
1366
1367       orientation_sizes = get_group_sizes (area, group, priv->orientation, widget, &n_sizes);
1368
1369       /* First naturally allocate the cells in the group into the for_size */
1370       avail_size -= (n_sizes - 1) * priv->spacing;
1371       for (i = 0; i < n_sizes; i++)
1372         avail_size -= orientation_sizes[i].minimum_size;
1373
1374       avail_size = gtk_distribute_natural_allocation (avail_size, n_sizes, orientation_sizes);
1375
1376       /* Calculate/distribute expand for cells */
1377       if (group->expand_cells > 0)
1378         {
1379           extra_size  = avail_size / group->expand_cells;
1380           extra_extra = avail_size % group->expand_cells;
1381         }
1382       else
1383         extra_size = extra_extra = 0;
1384
1385       for (i = 0; i < n_sizes; i++)
1386         {
1387           gint cell_min, cell_nat;
1388
1389           info = orientation_sizes[i].data;
1390
1391           if (info->expand)
1392             {
1393               orientation_sizes[i].minimum_size += extra_size;
1394               if (extra_extra)
1395                 {
1396                   orientation_sizes[i].minimum_size++;
1397                   extra_extra--;
1398                 }
1399             }
1400
1401           gtk_cell_area_request_renderer (area, info->renderer,
1402                                           OPPOSITE_ORIENTATION (priv->orientation),
1403                                           widget, 
1404                                           orientation_sizes[i].minimum_size,
1405                                           &cell_min, &cell_nat);
1406
1407           min_size = MAX (min_size, cell_min);
1408           nat_size = MAX (nat_size, cell_nat);
1409         }
1410
1411       *minimum_size = min_size;
1412       *natural_size = nat_size;
1413
1414       g_free (orientation_sizes);
1415     }
1416 }
1417
1418 static void
1419 compute_size_for_opposing_orientation (GtkCellAreaBox     *box, 
1420                                        GtkCellAreaBoxIter *iter, 
1421                                        GtkWidget          *widget, 
1422                                        gint                for_size,
1423                                        gint               *minimum_size, 
1424                                        gint               *natural_size)
1425 {
1426   GtkCellAreaBoxPrivate *priv = box->priv;
1427   CellGroup             *group;
1428   GtkRequestedSize      *orientation_sizes;
1429   gint                   n_groups, n_expand_groups, i;
1430   gint                   avail_size = for_size;
1431   gint                   extra_size, extra_extra;
1432   gint                   min_size = 0, nat_size = 0;
1433
1434   n_expand_groups = count_expand_groups (box);
1435
1436   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1437     orientation_sizes = gtk_cell_area_box_iter_get_widths (iter, &n_groups);
1438   else
1439     orientation_sizes = gtk_cell_area_box_iter_get_heights (iter, &n_groups);
1440
1441   /* First start by naturally allocating space among groups of cells */
1442   avail_size -= (n_groups - 1) * priv->spacing;
1443   for (i = 0; i < n_groups; i++)
1444     avail_size -= orientation_sizes[i].minimum_size;
1445
1446   avail_size = gtk_distribute_natural_allocation (avail_size, n_groups, orientation_sizes);
1447
1448   /* Calculate/distribute expand for groups */
1449   if (n_expand_groups > 0)
1450     {
1451       extra_size  = avail_size / n_expand_groups;
1452       extra_extra = avail_size % n_expand_groups;
1453     }
1454   else
1455     extra_size = extra_extra = 0;
1456
1457   /* Now we need to naturally allocate sizes for cells in each group
1458    * and push the height-for-width for each group accordingly while accumulating
1459    * the overall height-for-width for this row.
1460    */
1461   for (i = 0; i < n_groups; i++)
1462     {
1463       gint group_min, group_nat;
1464       gint group_idx = GPOINTER_TO_INT (orientation_sizes[i].data);
1465       
1466       group = &g_array_index (priv->groups, CellGroup, group_idx);
1467
1468       if (group->expand_cells > 0)
1469         {
1470           orientation_sizes[i].minimum_size += extra_size;
1471           if (extra_extra)
1472             {
1473               orientation_sizes[i].minimum_size++;
1474               extra_extra--;
1475             }
1476         }
1477
1478       /* Now we have the allocation for the group, request it's height-for-width */
1479       compute_group_size_for_opposing_orientation (box, group, widget,
1480                                                    orientation_sizes[i].minimum_size,
1481                                                    &group_min, &group_nat);
1482
1483       min_size = MAX (min_size, group_min);
1484       nat_size = MAX (nat_size, group_nat);
1485
1486       if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1487         {
1488           gtk_cell_area_box_iter_push_group_height_for_width (iter, group_idx, for_size,
1489                                                               group_min, group_nat);
1490         }
1491       else
1492         {
1493           gtk_cell_area_box_iter_push_group_width_for_height (iter, group_idx, for_size,
1494                                                               group_min, group_nat);
1495         }
1496     }
1497
1498   *minimum_size = min_size;
1499   *natural_size = nat_size;
1500
1501   g_free (orientation_sizes);
1502 }
1503
1504
1505
1506 static void
1507 gtk_cell_area_box_get_preferred_width (GtkCellArea        *area,
1508                                        GtkCellAreaIter    *iter,
1509                                        GtkWidget          *widget,
1510                                        gint               *minimum_width,
1511                                        gint               *natural_width)
1512 {
1513   GtkCellAreaBox        *box = GTK_CELL_AREA_BOX (area);
1514   GtkCellAreaBoxIter    *box_iter;
1515   gint                   min_width, nat_width;
1516
1517   g_return_if_fail (GTK_IS_CELL_AREA_BOX_ITER (iter));
1518
1519   box_iter = GTK_CELL_AREA_BOX_ITER (iter);
1520
1521   /* Compute the size of all renderers for current row data, 
1522    * bumping cell alignments in the iter along the way */
1523   compute_size (box, GTK_ORIENTATION_HORIZONTAL,
1524                 box_iter, widget, -1, &min_width, &nat_width);
1525
1526   if (minimum_width)
1527     *minimum_width = min_width;
1528
1529   if (natural_width)
1530     *natural_width = nat_width;
1531 }
1532
1533 static void
1534 gtk_cell_area_box_get_preferred_height (GtkCellArea        *area,
1535                                         GtkCellAreaIter    *iter,
1536                                         GtkWidget          *widget,
1537                                         gint               *minimum_height,
1538                                         gint               *natural_height)
1539 {
1540   GtkCellAreaBox        *box = GTK_CELL_AREA_BOX (area);
1541   GtkCellAreaBoxIter    *box_iter;
1542   gint                   min_height, nat_height;
1543
1544   g_return_if_fail (GTK_IS_CELL_AREA_BOX_ITER (iter));
1545
1546   box_iter = GTK_CELL_AREA_BOX_ITER (iter);
1547
1548   /* Compute the size of all renderers for current row data, 
1549    * bumping cell alignments in the iter along the way */
1550   compute_size (box, GTK_ORIENTATION_VERTICAL,
1551                 box_iter, widget, -1, &min_height, &nat_height);
1552
1553   if (minimum_height)
1554     *minimum_height = min_height;
1555
1556   if (natural_height)
1557     *natural_height = nat_height;
1558 }
1559
1560 static void
1561 gtk_cell_area_box_get_preferred_height_for_width (GtkCellArea        *area,
1562                                                   GtkCellAreaIter    *iter,
1563                                                   GtkWidget          *widget,
1564                                                   gint                width,
1565                                                   gint               *minimum_height,
1566                                                   gint               *natural_height)
1567 {
1568   GtkCellAreaBox        *box = GTK_CELL_AREA_BOX (area);
1569   GtkCellAreaBoxIter    *box_iter;
1570   GtkCellAreaBoxPrivate *priv;
1571   gint                   min_height, nat_height;
1572
1573   g_return_if_fail (GTK_IS_CELL_AREA_BOX_ITER (iter));
1574
1575   box_iter = GTK_CELL_AREA_BOX_ITER (iter);
1576   priv     = box->priv;
1577
1578   if (priv->orientation == GTK_ORIENTATION_VERTICAL)
1579     {
1580       /* Add up vertical requests of height for width and push the overall 
1581        * cached sizes for alignments */
1582       compute_size (box, priv->orientation, box_iter, widget, width, &min_height, &nat_height);
1583     }
1584   else
1585     {
1586       /* Juice: virtually allocate cells into the for_width using the 
1587        * alignments and then return the overall height for that width, and cache it */
1588       compute_size_for_opposing_orientation (box, box_iter, widget, width, &min_height, &nat_height);
1589     }
1590
1591   if (minimum_height)
1592     *minimum_height = min_height;
1593
1594   if (natural_height)
1595     *natural_height = nat_height;
1596 }
1597
1598 static void
1599 gtk_cell_area_box_get_preferred_width_for_height (GtkCellArea        *area,
1600                                                   GtkCellAreaIter    *iter,
1601                                                   GtkWidget          *widget,
1602                                                   gint                height,
1603                                                   gint               *minimum_width,
1604                                                   gint               *natural_width)
1605 {
1606   GtkCellAreaBox        *box = GTK_CELL_AREA_BOX (area);
1607   GtkCellAreaBoxIter    *box_iter;
1608   GtkCellAreaBoxPrivate *priv;
1609   gint                   min_width, nat_width;
1610
1611   g_return_if_fail (GTK_IS_CELL_AREA_BOX_ITER (iter));
1612
1613   box_iter = GTK_CELL_AREA_BOX_ITER (iter);
1614   priv     = box->priv;
1615
1616   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1617     {
1618       /* Add up horizontal requests of width for height and push the overall 
1619        * cached sizes for alignments */
1620       compute_size (box, priv->orientation, box_iter, widget, height, &min_width, &nat_width);
1621     }
1622   else
1623     {
1624       /* Juice: horizontally allocate cells into the for_height using the 
1625        * alignments and then return the overall width for that height, and cache it */
1626       compute_size_for_opposing_orientation (box, box_iter, widget, height, &min_width, &nat_width);
1627     }
1628
1629   if (minimum_width)
1630     *minimum_width = min_width;
1631
1632   if (natural_width)
1633     *natural_width = nat_width;
1634 }
1635
1636 static void
1637 gtk_cell_area_box_grab_focus (GtkCellArea      *area,
1638                               GtkDirectionType  direction)
1639 {
1640   GtkCellAreaBox        *box = GTK_CELL_AREA_BOX (area);
1641   GtkCellAreaBoxPrivate *priv;
1642   gboolean               first_cell = FALSE;
1643   gint                   i;
1644   GList                 *list;
1645
1646   priv = box->priv;
1647
1648   switch (direction)
1649     {
1650     case GTK_DIR_TAB_FORWARD:
1651     case GTK_DIR_DOWN:
1652     case GTK_DIR_RIGHT:
1653       first_cell = TRUE;
1654       break;
1655
1656     case GTK_DIR_TAB_BACKWARD:
1657     case GTK_DIR_UP:
1658     case GTK_DIR_LEFT:
1659     default:
1660       first_cell = FALSE;
1661       break;
1662     }
1663
1664   for (i = first_cell ? 0 : priv->groups->len -1; 
1665        i >= 0 && i < priv->groups->len;
1666        i = first_cell ? i + 1 : i - 1)
1667     {
1668       CellGroup *group = &g_array_index (priv->groups, CellGroup, i);
1669
1670       for (list = first_cell ? g_list_first (group->cells) : g_list_last (group->cells); 
1671            list; list = first_cell ? list->next : list->prev)
1672         {
1673           CellInfo *info = list->data;
1674           
1675           if (gtk_cell_renderer_can_focus (info->renderer))
1676             {
1677               gtk_cell_area_set_focus_cell (area, info->renderer);
1678               break;
1679             }
1680         }
1681     }
1682 }
1683
1684
1685 /*************************************************************
1686  *                    GtkCellLayoutIface                     *
1687  *************************************************************/
1688 static void
1689 gtk_cell_area_box_cell_layout_init (GtkCellLayoutIface *iface)
1690 {
1691   iface->pack_start = gtk_cell_area_box_layout_pack_start;
1692   iface->pack_end   = gtk_cell_area_box_layout_pack_end;
1693   iface->reorder    = gtk_cell_area_box_layout_reorder;
1694 }
1695
1696 static void
1697 gtk_cell_area_box_layout_pack_start (GtkCellLayout      *cell_layout,
1698                                      GtkCellRenderer    *renderer,
1699                                      gboolean            expand)
1700 {
1701   gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (cell_layout), renderer, expand, TRUE);
1702 }
1703
1704 static void
1705 gtk_cell_area_box_layout_pack_end (GtkCellLayout      *cell_layout,
1706                                    GtkCellRenderer    *renderer,
1707                                    gboolean            expand)
1708 {
1709   gtk_cell_area_box_pack_end (GTK_CELL_AREA_BOX (cell_layout), renderer, expand, TRUE);
1710 }
1711
1712 static void
1713 gtk_cell_area_box_layout_reorder (GtkCellLayout      *cell_layout,
1714                                   GtkCellRenderer    *renderer,
1715                                   gint                position)
1716 {
1717   GtkCellAreaBox        *box  = GTK_CELL_AREA_BOX (cell_layout);
1718   GtkCellAreaBoxPrivate *priv = box->priv;
1719   GList                 *node;
1720   CellInfo              *info;
1721   
1722   node = g_list_find_custom (priv->cells, renderer, 
1723                              (GCompareFunc)cell_info_find);
1724
1725   if (node)
1726     {
1727       info = node->data;
1728
1729       priv->cells = g_list_delete_link (priv->cells, node);
1730       priv->cells = g_list_insert (priv->cells, info, position);
1731
1732       cell_groups_rebuild (box);
1733     }
1734 }
1735
1736 /*************************************************************
1737  *                            API                            *
1738  *************************************************************/
1739 GtkCellArea *
1740 gtk_cell_area_box_new (void)
1741 {
1742   return (GtkCellArea *)g_object_new (GTK_TYPE_CELL_AREA_BOX, NULL);
1743 }
1744
1745 void
1746 gtk_cell_area_box_pack_start  (GtkCellAreaBox  *box,
1747                                GtkCellRenderer *renderer,
1748                                gboolean         expand,
1749                                gboolean         align)
1750 {
1751   GtkCellAreaBoxPrivate *priv;
1752   CellInfo              *info;
1753
1754   g_return_if_fail (GTK_IS_CELL_AREA_BOX (box));
1755   g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
1756
1757   priv = box->priv;
1758
1759   if (g_list_find_custom (priv->cells, renderer, 
1760                           (GCompareFunc)cell_info_find))
1761     {
1762       g_warning ("Refusing to add the same cell renderer to a GtkCellAreaBox twice");
1763       return;
1764     }
1765
1766   info = cell_info_new (renderer, GTK_PACK_START, expand, align);
1767
1768   priv->cells = g_list_append (priv->cells, info);
1769
1770   cell_groups_rebuild (box);
1771 }
1772
1773 void
1774 gtk_cell_area_box_pack_end (GtkCellAreaBox  *box,
1775                             GtkCellRenderer *renderer,
1776                             gboolean         expand, 
1777                             gboolean         align)
1778 {
1779   GtkCellAreaBoxPrivate *priv;
1780   CellInfo              *info;
1781
1782   g_return_if_fail (GTK_IS_CELL_AREA_BOX (box));
1783   g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
1784
1785   priv = box->priv;
1786
1787   if (g_list_find_custom (priv->cells, renderer, 
1788                           (GCompareFunc)cell_info_find))
1789     {
1790       g_warning ("Refusing to add the same cell renderer to a GtkCellArea twice");
1791       return;
1792     }
1793
1794   info = cell_info_new (renderer, GTK_PACK_END, expand, align);
1795
1796   priv->cells = g_list_append (priv->cells, info);
1797
1798   cell_groups_rebuild (box);
1799 }
1800
1801 gint
1802 gtk_cell_area_box_get_spacing (GtkCellAreaBox  *box)
1803 {
1804   g_return_val_if_fail (GTK_IS_CELL_AREA_BOX (box), 0);
1805
1806   return box->priv->spacing;
1807 }
1808
1809 void
1810 gtk_cell_area_box_set_spacing (GtkCellAreaBox  *box,
1811                                gint             spacing)
1812 {
1813   GtkCellAreaBoxPrivate *priv;
1814
1815   g_return_if_fail (GTK_IS_CELL_AREA_BOX (box));
1816
1817   priv = box->priv;
1818
1819   if (priv->spacing != spacing)
1820     {
1821       priv->spacing = spacing;
1822
1823       g_object_notify (G_OBJECT (box), "spacing");
1824
1825       /* Notify that size needs to be requested again */
1826       flush_iters (box);
1827     }
1828 }