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