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