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