gtkcellareabox.c 76.6 KB
Newer Older
1
/* gtkcellareabox.c
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 *
 * Copyright (C) 2010 Openismus GmbH
 *
 * Authors:
 *      Tristan Van Berkom <tristanvb@openismus.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
Javier Jardón's avatar
Javier Jardón committed
19
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20
21
 */

22
23

/**
Matthias Clasen's avatar
Matthias Clasen committed
24
25
26
 * GtkCellAreaBox:
 *
 * A cell area that renders GtkCellRenderers into a row or a column
27
 *
Matthias Clasen's avatar
Matthias Clasen committed
28
29
 * The `GtkCellAreaBox` renders cell renderers into a row or a column
 * depending on its `GtkOrientation`.
30
 *
31
 * GtkCellAreaBox uses a notion of packing. Packing
Matthias Clasen's avatar
Matthias Clasen committed
32
 * refers to adding cell renderers with reference to a particular position
Matthias Clasen's avatar
Matthias Clasen committed
33
 * in a `GtkCellAreaBox`. There are two reference positions: the
34
 * start and the end of the box.
Matthias Clasen's avatar
Matthias Clasen committed
35
 * When the `GtkCellAreaBox` is oriented in the %GTK_ORIENTATION_VERTICAL
Matthias Clasen's avatar
Matthias Clasen committed
36
37
38
39
 * orientation, the start is defined as the top of the box and the end is
 * defined as the bottom. In the %GTK_ORIENTATION_HORIZONTAL orientation
 * start is defined as the left side and the end is defined as the right
 * side.
40
 *
Matthias Clasen's avatar
Matthias Clasen committed
41
42
 * Alignments of `GtkCellRenderer`s rendered in adjacent rows can be
 * configured by configuring the `GtkCellAreaBox` align child cell property
Matthias Clasen's avatar
Matthias Clasen committed
43
44
 * with gtk_cell_area_cell_set_property() or by specifying the "align"
 * argument to gtk_cell_area_box_pack_start() and gtk_cell_area_box_pack_end().
45
46
 */

47
48
#include "config.h"
#include "gtkintl.h"
49
#include "gtkorientable.h"
50
#include "gtkcelllayout.h"
51
#include "gtkcellareabox.h"
52
#include "gtkcellareaboxcontextprivate.h"
53
#include "gtktypebuiltins.h"
54
55
#include "gtkprivate.h"

56
57

/* GObjectClass */
58
59
60
static void      gtk_cell_area_box_finalize                       (GObject              *object);
static void      gtk_cell_area_box_dispose                        (GObject              *object);
static void      gtk_cell_area_box_set_property                   (GObject              *object,
Matthias Clasen's avatar
Matthias Clasen committed
61
62
63
                                                                   guint                 prop_id,
                                                                   const GValue         *value,
                                                                   GParamSpec           *pspec);
64
static void      gtk_cell_area_box_get_property                   (GObject              *object,
Matthias Clasen's avatar
Matthias Clasen committed
65
66
67
                                                                   guint                 prop_id,
                                                                   GValue               *value,
                                                                   GParamSpec           *pspec);
68
69

/* GtkCellAreaClass */
70
static void      gtk_cell_area_box_add                            (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
71
                                                                   GtkCellRenderer      *renderer);
72
static void      gtk_cell_area_box_remove                         (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
73
                                                                   GtkCellRenderer      *renderer);
74
static void      gtk_cell_area_box_foreach                        (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
75
76
                                                                   GtkCellCallback       callback,
                                                                   gpointer              callback_data);
77
static void      gtk_cell_area_box_foreach_alloc                  (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
78
79
80
81
82
83
                                                                   GtkCellAreaContext   *context,
                                                                   GtkWidget            *widget,
                                                                   const GdkRectangle   *cell_area,
                                                                   const GdkRectangle   *background_area,
                                                                   GtkCellAllocCallback  callback,
                                                                   gpointer              callback_data);
84
85
86
87
88
static void      gtk_cell_area_box_apply_attributes               (GtkCellArea          *area,
								   GtkTreeModel         *tree_model,
								   GtkTreeIter          *iter,
								   gboolean              is_expander,
								   gboolean              is_expanded);
89
static void      gtk_cell_area_box_set_cell_property              (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
90
91
92
93
                                                                   GtkCellRenderer      *renderer,
                                                                   guint                 prop_id,
                                                                   const GValue         *value,
                                                                   GParamSpec           *pspec);
94
static void      gtk_cell_area_box_get_cell_property              (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
95
96
97
98
                                                                   GtkCellRenderer      *renderer,
                                                                   guint                 prop_id,
                                                                   GValue               *value,
                                                                   GParamSpec           *pspec);
99
static GtkCellAreaContext *gtk_cell_area_box_create_context       (GtkCellArea          *area);
100
static GtkCellAreaContext *gtk_cell_area_box_copy_context         (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
101
                                                                   GtkCellAreaContext   *context);
102
103
static GtkSizeRequestMode  gtk_cell_area_box_get_request_mode     (GtkCellArea          *area);
static void      gtk_cell_area_box_get_preferred_width            (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
104
105
                                                                   GtkCellAreaContext   *context,
                                                                   GtkWidget            *widget,
Benjamin Otte's avatar
Benjamin Otte committed
106
107
                                                                   int                  *minimum_width,
                                                                   int                  *natural_width);
108
static void      gtk_cell_area_box_get_preferred_height           (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
109
110
                                                                   GtkCellAreaContext   *context,
                                                                   GtkWidget            *widget,
Benjamin Otte's avatar
Benjamin Otte committed
111
112
                                                                   int                  *minimum_height,
                                                                   int                  *natural_height);
113
static void      gtk_cell_area_box_get_preferred_height_for_width (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
114
115
                                                                   GtkCellAreaContext   *context,
                                                                   GtkWidget            *widget,
Benjamin Otte's avatar
Benjamin Otte committed
116
117
118
                                                                   int                   width,
                                                                   int                  *minimum_height,
                                                                   int                  *natural_height);
119
static void      gtk_cell_area_box_get_preferred_width_for_height (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
120
121
                                                                   GtkCellAreaContext   *context,
                                                                   GtkWidget            *widget,
Benjamin Otte's avatar
Benjamin Otte committed
122
123
124
                                                                   int                   height,
                                                                   int                  *minimum_width,
                                                                   int                  *natural_width);
125
static gboolean  gtk_cell_area_box_focus                          (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
126
                                                                   GtkDirectionType      direction);
127

128
129
130
/* GtkCellLayoutIface */
static void      gtk_cell_area_box_cell_layout_init               (GtkCellLayoutIface *iface);
static void      gtk_cell_area_box_layout_pack_start              (GtkCellLayout      *cell_layout,
Matthias Clasen's avatar
Matthias Clasen committed
131
132
                                                                   GtkCellRenderer    *renderer,
                                                                   gboolean            expand);
133
static void      gtk_cell_area_box_layout_pack_end                (GtkCellLayout      *cell_layout,
Matthias Clasen's avatar
Matthias Clasen committed
134
135
                                                                   GtkCellRenderer    *renderer,
                                                                   gboolean            expand);
136
static void      gtk_cell_area_box_layout_reorder                 (GtkCellLayout      *cell_layout,
Matthias Clasen's avatar
Matthias Clasen committed
137
                                                                   GtkCellRenderer    *renderer,
Benjamin Otte's avatar
Benjamin Otte committed
138
                                                                   int                 position);
139
static void      gtk_cell_area_box_focus_changed                  (GtkCellArea        *area,
Matthias Clasen's avatar
Matthias Clasen committed
140
141
                                                                   GParamSpec         *pspec,
                                                                   GtkCellAreaBox     *box);
142
143


144
/* CellInfo/CellGroup metadata handling and convenience functions */
145
146
147
typedef struct {
  GtkCellRenderer *renderer;

148
  guint            expand : 1; /* Whether the cell expands */
Matthias Clasen's avatar
Matthias Clasen committed
149
150
  guint            pack   : 1; /* Whether it is packed from the start or end */
  guint            align  : 1; /* Whether to align its position with adjacent rows */
151
  guint            fixed  : 1; /* Whether to require the same size for all rows */
152
153
} CellInfo;

154
155
156
typedef struct {
  GList *cells;

157
158
159
  guint  id           : 8;
  guint  n_cells      : 8;
  guint  expand_cells : 8;
160
161
  guint  align        : 1;
  guint  visible      : 1;
162
163
} CellGroup;

164
165
166
typedef struct {
  GtkCellRenderer *renderer;

Benjamin Otte's avatar
Benjamin Otte committed
167
168
  int              position;
  int              size;
169
170
} AllocatedCell;

Matthias Clasen's avatar
Matthias Clasen committed
171
172
173
static CellInfo      *cell_info_new          (GtkCellRenderer       *renderer,
                                              GtkPackType            pack,
                                              gboolean               expand,
174
175
                                              gboolean               align,
					      gboolean               fixed);
176
static void           cell_info_free         (CellInfo              *info);
Benjamin Otte's avatar
Benjamin Otte committed
177
static int            cell_info_find         (CellInfo              *info,
Matthias Clasen's avatar
Matthias Clasen committed
178
                                              GtkCellRenderer       *renderer);
179
180

static AllocatedCell *allocated_cell_new     (GtkCellRenderer       *renderer,
Benjamin Otte's avatar
Benjamin Otte committed
181
182
                                              int                    position,
                                              int                    size);
183
184
static void           allocated_cell_free    (AllocatedCell         *cell);
static GList         *list_consecutive_cells (GtkCellAreaBox        *box);
Benjamin Otte's avatar
Benjamin Otte committed
185
static int            count_expand_groups    (GtkCellAreaBox        *box);
186
static void           context_weak_notify    (GtkCellAreaBox        *box,
Matthias Clasen's avatar
Matthias Clasen committed
187
                                              GtkCellAreaBoxContext *dead_context);
188
static void           reset_contexts         (GtkCellAreaBox        *box);
189
190
static void           init_context_groups    (GtkCellAreaBox        *box);
static void           init_context_group     (GtkCellAreaBox        *box,
Matthias Clasen's avatar
Matthias Clasen committed
191
                                              GtkCellAreaBoxContext *context);
192
static GSList        *get_allocated_cells    (GtkCellAreaBox        *box,
Matthias Clasen's avatar
Matthias Clasen committed
193
194
                                              GtkCellAreaBoxContext *context,
                                              GtkWidget             *widget,
Benjamin Otte's avatar
Benjamin Otte committed
195
196
                                              int                    width,
                                              int                    height);
197

198
199
200
201
202
203
204
205
206
207
208
209
typedef struct _GtkCellAreaBoxClass   GtkCellAreaBoxClass;
typedef struct _GtkCellAreaBoxPrivate GtkCellAreaBoxPrivate;

struct _GtkCellAreaBox
{
  GtkCellArea parent_instance;
};

struct _GtkCellAreaBoxClass
{
  GtkCellAreaClass parent_class;
};
210
211
212

struct _GtkCellAreaBoxPrivate
{
213
214
  /* We hold on to the previously focused cell when navigating
   * up and down in a horizontal box (or left and right on a vertical one)
Matthias Clasen's avatar
Matthias Clasen committed
215
216
   * this way we always re-enter the last focused cell.
   */
217
218
  GtkCellRenderer *last_focus_cell;
  gulong           focus_cell_id;
219

220
221
  GList           *cells;
  GArray          *groups;
222

223
224
  GSList          *contexts;

225
  GtkOrientation   orientation;
Benjamin Otte's avatar
Benjamin Otte committed
226
  int              spacing;
227
228

  /* We hold on to the rtl state from a widget we are requested for
Matthias Clasen's avatar
Matthias Clasen committed
229
230
   * so that we can navigate focus correctly
   */
231
  gboolean         rtl;
232
233
234
235
};

enum {
  PROP_0,
236
  PROP_ORIENTATION,
237
  PROP_SPACING
238
239
};

240
241
242
243
enum {
  CELL_PROP_0,
  CELL_PROP_EXPAND,
  CELL_PROP_ALIGN,
244
  CELL_PROP_FIXED_SIZE,
245
246
247
  CELL_PROP_PACK_TYPE
};

248
G_DEFINE_TYPE_WITH_CODE (GtkCellAreaBox, gtk_cell_area_box, GTK_TYPE_CELL_AREA,
249
                         G_ADD_PRIVATE (GtkCellAreaBox)
Matthias Clasen's avatar
Matthias Clasen committed
250
251
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
                                                gtk_cell_area_box_cell_layout_init)
252
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
253
254
255
256

static void
gtk_cell_area_box_init (GtkCellAreaBox *box)
{
257
  GtkCellAreaBoxPrivate *priv = gtk_cell_area_box_get_instance_private (box);
258
259

  priv->orientation = GTK_ORIENTATION_HORIZONTAL;
260
  priv->groups      = g_array_new (FALSE, TRUE, sizeof (CellGroup));
261
  priv->cells       = NULL;
262
  priv->contexts    = NULL;
263
  priv->spacing     = 0;
264
  priv->rtl         = FALSE;
265
266

  /* Watch whenever focus is given to a cell, even if it's not with keynav,
Matthias Clasen's avatar
Matthias Clasen committed
267
268
269
270
271
   * this way we remember upon entry of the area where focus was last time
   * around
   */
  priv->focus_cell_id = g_signal_connect (box, "notify::focus-cell",
                                          G_CALLBACK (gtk_cell_area_box_focus_changed), box);
272
273
}

Matthias Clasen's avatar
Matthias Clasen committed
274
static void
275
276
277
278
279
280
281
282
283
284
285
286
gtk_cell_area_box_class_init (GtkCellAreaBoxClass *class)
{
  GObjectClass     *object_class = G_OBJECT_CLASS (class);
  GtkCellAreaClass *area_class   = GTK_CELL_AREA_CLASS (class);

  /* GObjectClass */
  object_class->finalize     = gtk_cell_area_box_finalize;
  object_class->dispose      = gtk_cell_area_box_dispose;
  object_class->set_property = gtk_cell_area_box_set_property;
  object_class->get_property = gtk_cell_area_box_get_property;

  /* GtkCellAreaClass */
287
288
  area_class->add                 = gtk_cell_area_box_add;
  area_class->remove              = gtk_cell_area_box_remove;
289
  area_class->foreach             = gtk_cell_area_box_foreach;
290
  area_class->foreach_alloc       = gtk_cell_area_box_foreach_alloc;
291
  area_class->apply_attributes    = gtk_cell_area_box_apply_attributes;
292
293
  area_class->set_cell_property   = gtk_cell_area_box_set_cell_property;
  area_class->get_cell_property   = gtk_cell_area_box_get_cell_property;
Matthias Clasen's avatar
Matthias Clasen committed
294

295
  area_class->create_context                 = gtk_cell_area_box_create_context;
296
  area_class->copy_context                   = gtk_cell_area_box_copy_context;
297
298
299
300
301
302
  area_class->get_request_mode               = gtk_cell_area_box_get_request_mode;
  area_class->get_preferred_width            = gtk_cell_area_box_get_preferred_width;
  area_class->get_preferred_height           = gtk_cell_area_box_get_preferred_height;
  area_class->get_preferred_height_for_width = gtk_cell_area_box_get_preferred_height_for_width;
  area_class->get_preferred_width_for_height = gtk_cell_area_box_get_preferred_width_for_height;

303
  area_class->focus = gtk_cell_area_box_focus;
304

305
  /* Properties */
306
307
  g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");

308
309
310
311
312
  /**
   * GtkCellAreaBox:spacing:
   *
   * The amount of space to reserve between cells.
   */
313
314
  g_object_class_install_property (object_class,
                                   PROP_SPACING,
315
                                   g_param_spec_int ("spacing", NULL, NULL,
Matthias Clasen's avatar
Matthias Clasen committed
316
317
318
                                                     0,
                                                     G_MAXINT,
                                                     0,
319
                                                     GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
320

321
  /* Cell Properties */
322
323
324
  /**
   * GtkCellAreaBox:expand:
   *
Matthias Clasen's avatar
Matthias Clasen committed
325
326
   * Whether the cell renderer should receive extra space
   * when the area receives more than its natural size.
327
   */
328
  gtk_cell_area_class_install_cell_property (area_class,
Matthias Clasen's avatar
Matthias Clasen committed
329
330
                                             CELL_PROP_EXPAND,
                                             g_param_spec_boolean
331
                                             ("expand", NULL, NULL,
Matthias Clasen's avatar
Matthias Clasen committed
332
333
334
                                              FALSE,
                                              GTK_PARAM_READWRITE));

335
336
337
338
339
  /**
   * GtkCellAreaBox:align:
   *
   * Whether the cell renderer should be aligned in adjacent rows.
   */
340
  gtk_cell_area_class_install_cell_property (area_class,
Matthias Clasen's avatar
Matthias Clasen committed
341
342
                                             CELL_PROP_ALIGN,
                                             g_param_spec_boolean
343
                                             ("align", NULL, NULL,
344
345
346
347
348
349
350
351
352
353
354
355
                                              FALSE,
                                              GTK_PARAM_READWRITE));

  /**
   * GtkCellAreaBox:fixed-size:
   *
   * Whether the cell renderer should require the same size
   * for all rows for which it was requested.
   */
  gtk_cell_area_class_install_cell_property (area_class,
                                             CELL_PROP_FIXED_SIZE,
                                             g_param_spec_boolean
356
                                             ("fixed-size", NULL, NULL,
Matthias Clasen's avatar
Matthias Clasen committed
357
358
                                              TRUE,
                                              GTK_PARAM_READWRITE));
359

360
361
362
  /**
   * GtkCellAreaBox:pack-type:
   *
Matthias Clasen's avatar
Matthias Clasen committed
363
364
   * A GtkPackType indicating whether the cell renderer is packed
   * with reference to the start or end of the area.
365
   */
366
  gtk_cell_area_class_install_cell_property (area_class,
Matthias Clasen's avatar
Matthias Clasen committed
367
368
                                             CELL_PROP_PACK_TYPE,
                                             g_param_spec_enum
369
                                             ("pack-type", NULL, NULL,
Matthias Clasen's avatar
Matthias Clasen committed
370
371
                                              GTK_TYPE_PACK_TYPE, GTK_PACK_START,
                                              GTK_PARAM_READWRITE));
372
373
374
}


375
/*************************************************************
376
 *    CellInfo/CellGroup basics and convenience functions    *
377
378
 *************************************************************/
static CellInfo *
Matthias Clasen's avatar
Matthias Clasen committed
379
380
381
cell_info_new  (GtkCellRenderer *renderer,
                GtkPackType      pack,
                gboolean         expand,
382
383
                gboolean         align,
		gboolean         fixed)
384
385
{
  CellInfo *info = g_slice_new (CellInfo);
Matthias Clasen's avatar
Matthias Clasen committed
386

387
388
  info->renderer = g_object_ref_sink (renderer);
  info->pack     = pack;
389
390
  info->expand   = expand;
  info->align    = align;
391
  info->fixed    = fixed;
392
393
394
395
396
397
398
399
400
401
402
403

  return info;
}

static void
cell_info_free (CellInfo *info)
{
  g_object_unref (info->renderer);

  g_slice_free (CellInfo, info);
}

Benjamin Otte's avatar
Benjamin Otte committed
404
static int
405
cell_info_find (CellInfo        *info,
Matthias Clasen's avatar
Matthias Clasen committed
406
                GtkCellRenderer *renderer)
407
408
409
410
{
  return (info->renderer == renderer) ? 0 : -1;
}

411
412
static AllocatedCell *
allocated_cell_new (GtkCellRenderer *renderer,
Benjamin Otte's avatar
Benjamin Otte committed
413
414
                    int              position,
                    int              size)
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
{
  AllocatedCell *cell = g_slice_new (AllocatedCell);

  cell->renderer = renderer;
  cell->position = position;
  cell->size     = size;

  return cell;
}

static void
allocated_cell_free (AllocatedCell *cell)
{
  g_slice_free (AllocatedCell, cell);
}

431
432
433
static GList *
list_consecutive_cells (GtkCellAreaBox *box)
{
434
  GtkCellAreaBoxPrivate *priv = gtk_cell_area_box_get_instance_private (box);
435
436
437
  GList                 *l, *consecutive_cells = NULL, *pack_end_cells = NULL;
  CellInfo              *info;

Matthias Clasen's avatar
Matthias Clasen committed
438
439
  /* List cells in consecutive order taking their
   * PACK_START/PACK_END options into account
440
441
442
443
   */
  for (l = priv->cells; l; l = l->next)
    {
      info = l->data;
Matthias Clasen's avatar
Matthias Clasen committed
444

445
      if (info->pack == GTK_PACK_START)
Matthias Clasen's avatar
Matthias Clasen committed
446
        consecutive_cells = g_list_prepend (consecutive_cells, info);
447
448
449
450
451
    }

  for (l = priv->cells; l; l = l->next)
    {
      info = l->data;
Matthias Clasen's avatar
Matthias Clasen committed
452

453
      if (info->pack == GTK_PACK_END)
Matthias Clasen's avatar
Matthias Clasen committed
454
        pack_end_cells = g_list_prepend (pack_end_cells, info);
455
456
457
458
459
460
461
462
    }

  consecutive_cells = g_list_reverse (consecutive_cells);
  consecutive_cells = g_list_concat (consecutive_cells, pack_end_cells);

  return consecutive_cells;
}

463
static void
Matthias Clasen's avatar
Matthias Clasen committed
464
cell_groups_clear (GtkCellAreaBox *box)
465
{
466
  GtkCellAreaBoxPrivate *priv = gtk_cell_area_box_get_instance_private (box);
Benjamin Otte's avatar
Benjamin Otte committed
467
  int                    i;
468
469
470
471
472
473
474
475
476
477
478
479
480
481

  for (i = 0; i < priv->groups->len; i++)
    {
      CellGroup *group = &g_array_index (priv->groups, CellGroup, i);

      g_list_free (group->cells);
    }

  g_array_set_size (priv->groups, 0);
}

static void
cell_groups_rebuild (GtkCellAreaBox *box)
{
482
  GtkCellAreaBoxPrivate *priv = gtk_cell_area_box_get_instance_private (box);
483
  CellGroup              group = { 0, };
484
  CellGroup             *group_ptr;
485
486
  GList                 *cells, *l;
  guint                  id = 0;
487
  gboolean               last_cell_fixed = FALSE;
488

489
490
  cell_groups_clear (box);

491
  if (!priv->cells)
492
    return;
493

494
495
496
497
  cells = list_consecutive_cells (box);

  /* First group is implied */
  g_array_append_val (priv->groups, group);
498
  group_ptr = &g_array_index (priv->groups, CellGroup, id);
499
500
501
502
503

  for (l = cells; l; l = l->next)
    {
      CellInfo *info = l->data;

504
505
506
507
      /* A new group starts with any aligned cell, or
       * at the beginning and end of a fixed size cell. 
       * the first group is implied */
      if ((info->align || info->fixed || last_cell_fixed) && l != cells)
Matthias Clasen's avatar
Matthias Clasen committed
508
509
510
        {
          memset (&group, 0x0, sizeof (CellGroup));
          group.id = ++id;
511

Matthias Clasen's avatar
Matthias Clasen committed
512
513
514
          g_array_append_val (priv->groups, group);
          group_ptr = &g_array_index (priv->groups, CellGroup, id);
        }
515

516
517
      group_ptr->cells = g_list_prepend (group_ptr->cells, info);
      group_ptr->n_cells++;
518

519
520
521
522
523
      /* Not every group is aligned, some are floating
       * fixed size cells */
      if (info->align)
	group_ptr->align = TRUE;

524
525
      /* A group expands if it contains any expand cells */
      if (info->expand)
Matthias Clasen's avatar
Matthias Clasen committed
526
        group_ptr->expand_cells++;
527
528

      last_cell_fixed = info->fixed;
529
530
531
532
    }

  g_list_free (cells);

533
  for (id = 0; id < priv->groups->len; id++)
534
    {
535
      group_ptr = &g_array_index (priv->groups, CellGroup, id);
536
537

      group_ptr->cells = g_list_reverse (group_ptr->cells);
538
539
    }

540
541
  /* Contexts need to be updated with the new grouping information */
  init_context_groups (box);
542
543
}

Benjamin Otte's avatar
Benjamin Otte committed
544
static int
Matthias Clasen's avatar
Matthias Clasen committed
545
count_visible_cells (CellGroup *group,
Benjamin Otte's avatar
Benjamin Otte committed
546
                     int       *expand_cells)
547
548
{
  GList *l;
Benjamin Otte's avatar
Benjamin Otte committed
549
550
  int    visible_cells = 0;
  int    n_expand_cells = 0;
551
552
553
554
555
556

  for (l = group->cells; l; l = l->next)
    {
      CellInfo *info = l->data;

      if (gtk_cell_renderer_get_visible (info->renderer))
Matthias Clasen's avatar
Matthias Clasen committed
557
558
        {
          visible_cells++;
559

Matthias Clasen's avatar
Matthias Clasen committed
560
561
562
          if (info->expand)
            n_expand_cells++;
        }
563
564
565
566
567
568
    }

  if (expand_cells)
    *expand_cells = n_expand_cells;

  return visible_cells;
569
570
}

Benjamin Otte's avatar
Benjamin Otte committed
571
static int
572
573
count_expand_groups (GtkCellAreaBox  *box)
{
574
  GtkCellAreaBoxPrivate *priv = gtk_cell_area_box_get_instance_private (box);
Benjamin Otte's avatar
Benjamin Otte committed
575
576
  int                    i;
  int                    expand_groups = 0;
577

578
  for (i = 0; i < priv->groups->len; i++)
579
    {
580
      CellGroup *group = &g_array_index (priv->groups, CellGroup, i);
581

582
      if (group->expand_cells > 0)
Matthias Clasen's avatar
Matthias Clasen committed
583
        expand_groups++;
584
585
586
587
588
    }

  return expand_groups;
}

Matthias Clasen's avatar
Matthias Clasen committed
589
static void
590
context_weak_notify (GtkCellAreaBox        *box,
Matthias Clasen's avatar
Matthias Clasen committed
591
                     GtkCellAreaBoxContext *dead_context)
592
{
593
  GtkCellAreaBoxPrivate *priv = gtk_cell_area_box_get_instance_private (box);
594

595
  priv->contexts = g_slist_remove (priv->contexts, dead_context);
596
597
598
}

static void
599
init_context_group (GtkCellAreaBox        *box,
Matthias Clasen's avatar
Matthias Clasen committed
600
                    GtkCellAreaBoxContext *context)
601
{
602
  GtkCellAreaBoxPrivate *priv = gtk_cell_area_box_get_instance_private (box);
Benjamin Otte's avatar
Benjamin Otte committed
603
  int                   *expand_groups, *align_groups, i;
604

605
  expand_groups = g_new (gboolean, priv->groups->len);
606
  align_groups  = g_new (gboolean, priv->groups->len);
607

608
  for (i = 0; i < priv->groups->len; i++)
609
    {
610
      CellGroup *group = &g_array_index (priv->groups, CellGroup, i);
611

612
      expand_groups[i] = (group->expand_cells > 0);
613
      align_groups[i]  = group->align;
614
    }
615

616
617
  /* This call implies resetting the request info */
  _gtk_cell_area_box_init_groups (context, priv->groups->len, expand_groups, align_groups);
618
  g_free (expand_groups);
619
  g_free (align_groups);
620
}
621

622
static void
623
init_context_groups (GtkCellAreaBox *box)
624
{
625
  GtkCellAreaBoxPrivate *priv = gtk_cell_area_box_get_instance_private (box);
626
  GSList                *l;
627

Matthias Clasen's avatar
Matthias Clasen committed
628
629
  /* When the box's groups are reconstructed,
   * contexts need to be reinitialized.
630
   */
631
  for (l = priv->contexts; l; l = l->next)
632
    {
633
      GtkCellAreaBoxContext *context = l->data;
634

635
      init_context_group (box, context);
636
    }
637
}
638

639
static void
640
reset_contexts (GtkCellAreaBox *box)
641
{
642
  GtkCellAreaBoxPrivate *priv = gtk_cell_area_box_get_instance_private (box);
643
  GSList                *l;
644

645
  /* When the box layout changes, contexts need to
646
   * be reset and sizes for the box get requested again
647
   */
648
  for (l = priv->contexts; l; l = l->next)
649
    {
650
      GtkCellAreaContext *context = l->data;
651

652
      gtk_cell_area_context_reset (context);
653
    }
654
655
}

656
657
658
659
660
661
662
/* Fall back on a completely unaligned dynamic allocation of cells
 * when not allocated for the said orientation, alignment of cells
 * is not done when each area gets a different size in the orientation
 * of the box.
 */
static GSList *
allocate_cells_manually (GtkCellAreaBox        *box,
Matthias Clasen's avatar
Matthias Clasen committed
663
                         GtkWidget             *widget,
Benjamin Otte's avatar
Benjamin Otte committed
664
665
                         int                    width,
                         int                    height)
666
{
667
  GtkCellAreaBoxPrivate *priv = gtk_cell_area_box_get_instance_private (box);
668
669
670
  GList                    *cells, *l;
  GSList                   *allocated_cells = NULL;
  GtkRequestedSize         *sizes;
Benjamin Otte's avatar
Benjamin Otte committed
671
672
673
674
  int                       i;
  int                       nvisible = 0, nexpand = 0, group_expand;
  int                       avail_size, extra_size, extra_extra, full_size;
  int                       position = 0, for_size;
675
  gboolean                  rtl;
676
677
678
679

  if (!priv->cells)
    return NULL;

Matthias Clasen's avatar
Matthias Clasen committed
680
681
682
  /* For vertical oriented boxes, we just let the cell renderers
   * realign themselves for rtl
   */
683
  rtl = (priv->orientation == GTK_ORIENTATION_HORIZONTAL &&
Matthias Clasen's avatar
Matthias Clasen committed
684
         gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
685

686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
  cells = list_consecutive_cells (box);

  /* Count the visible and expand cells */
  for (i = 0; i < priv->groups->len; i++)
    {
      CellGroup *group = &g_array_index (priv->groups, CellGroup, i);

      nvisible += count_visible_cells (group, &group_expand);
      nexpand  += group_expand;
    }

  if (nvisible <= 0)
    {
      g_list_free (cells);
      return NULL;
    }

  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
704
    {
705
706
      full_size = avail_size = width;
      for_size  = height;
707
    }
708
  else
709
    {
710
711
      full_size = avail_size = height;
      for_size  = width;
712
    }
713
714
715
716
717
718
719
720

  /* Go ahead and collect the requests on the fly */
  sizes = g_new0 (GtkRequestedSize, nvisible);
  for (l = cells, i = 0; l; l = l->next)
    {
      CellInfo *info = l->data;

      if (!gtk_cell_renderer_get_visible (info->renderer))
Matthias Clasen's avatar
Matthias Clasen committed
721
        continue;
722

723
      gtk_cell_area_request_renderer (GTK_CELL_AREA (box), info->renderer,
Matthias Clasen's avatar
Matthias Clasen committed
724
725
726
727
                                      priv->orientation,
                                      widget, for_size,
                                      &sizes[i].minimum_size,
                                      &sizes[i].natural_size);
728
729
730
731
732
733
734
735
736
737

      avail_size -= sizes[i].minimum_size;

      sizes[i].data = info;

      i++;
    }

  /* Naturally distribute the allocation */
  avail_size -= (nvisible - 1) * priv->spacing;
738
739
740
741
  if (avail_size > 0)
    avail_size = gtk_distribute_natural_allocation (avail_size, nvisible, sizes);
  else
    avail_size = 0;
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758

  /* Calculate/distribute expand for cells */
  if (nexpand > 0)
    {
      extra_size  = avail_size / nexpand;
      extra_extra = avail_size % nexpand;
    }
  else
    extra_size = extra_extra = 0;

  /* Create the allocated cells */
  for (i = 0; i < nvisible; i++)
    {
      CellInfo      *info = sizes[i].data;
      AllocatedCell *cell;

      if (info->expand)
Matthias Clasen's avatar
Matthias Clasen committed
759
760
761
762
763
764
765
766
767
        {
          sizes[i].minimum_size += extra_size;
          if (extra_extra)
            {
              sizes[i].minimum_size++;
              extra_extra--;
            }
        }

768
      if (rtl)
Matthias Clasen's avatar
Matthias Clasen committed
769
770
771
        cell = allocated_cell_new (info->renderer,
                                   full_size - (position + sizes[i].minimum_size),
                                   sizes[i].minimum_size);
772
      else
Matthias Clasen's avatar
Matthias Clasen committed
773
        cell = allocated_cell_new (info->renderer, position, sizes[i].minimum_size);
774
775

      allocated_cells = g_slist_prepend (allocated_cells, cell);
Matthias Clasen's avatar
Matthias Clasen committed
776

777
778
779
780
781
782
783
784
      position += sizes[i].minimum_size;
      position += priv->spacing;
    }

  g_free (sizes);
  g_list_free (cells);

  /* Note it might not be important to reverse the list here at all,
Matthias Clasen's avatar
Matthias Clasen committed
785
786
   * we have the correct positions, no need to allocate from left to right
   */
787
788
789
  return g_slist_reverse (allocated_cells);
}

790
791
792
793
794
/* Returns an allocation for each cell in the orientation of the box,
 * used in ->render()/->event() implementations to get a straight-forward
 * list of allocated cells to operate on.
 */
static GSList *
795
get_allocated_cells (GtkCellAreaBox        *box,
Matthias Clasen's avatar
Matthias Clasen committed
796
797
                     GtkCellAreaBoxContext *context,
                     GtkWidget             *widget,
Benjamin Otte's avatar
Benjamin Otte committed
798
799
                     int                    width,
                     int                    height)
800
{
801
  GtkCellAreaBoxPrivate *priv = gtk_cell_area_box_get_instance_private (box);
802
803
804
805
  GtkCellAreaBoxAllocation *group_allocs;
  GtkCellArea              *area = GTK_CELL_AREA (box);
  GList                    *cell_list;
  GSList                   *allocated_cells = NULL;
Benjamin Otte's avatar
Benjamin Otte committed
806
807
  int                       i, j, n_allocs, position;
  int                       for_size, full_size;
808
  gboolean                  rtl;
809

810
  group_allocs = _gtk_cell_area_box_context_get_orientation_allocs (context, &n_allocs);
811
  if (!group_allocs)
812
    return allocate_cells_manually (box, widget, width, height);
813

814
  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
815
816
817
818
    {
      full_size = width;
      for_size  = height;
    }
819
  else
820
821
822
823
824
    {
      full_size = height;
      for_size  = width;
    }

Matthias Clasen's avatar
Matthias Clasen committed
825
826
827
  /* For vertical oriented boxes, we just let the cell renderers
   * realign themselves for rtl
   */
828
  rtl = (priv->orientation == GTK_ORIENTATION_HORIZONTAL &&
Matthias Clasen's avatar
Matthias Clasen committed
829
         gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
830

831
  for (position = 0, i = 0; i < n_allocs; i++)
832
    {
Matthias Clasen's avatar
Matthias Clasen committed
833
834
835
      /* We dont always allocate all groups, sometimes the requested
       * group has only invisible cells for every row, hence the usage
       * of group_allocs[i].group_idx here
836
837
       */
      CellGroup *group = &g_array_index (priv->groups, CellGroup, group_allocs[i].group_idx);
838
839
840

      /* Exception for single cell groups */
      if (group->n_cells == 1)
Matthias Clasen's avatar
Matthias Clasen committed
841
842
843
        {
          CellInfo      *info = group->cells->data;
          AllocatedCell *cell;
Benjamin Otte's avatar
Benjamin Otte committed
844
	  int            cell_position, cell_size;
845

846
847
848
	  if (!gtk_cell_renderer_get_visible (info->renderer))
	    continue;

849
850
851
852
853
854
855
856
857
858
859
	  /* If were not aligned, place the cell after the last cell */
	  if (info->align)
	    position = cell_position = group_allocs[i].position;
	  else
	    cell_position = position;

	  /* If not a fixed size, use only the requested size for this row */
	  if (info->fixed)
	    cell_size = group_allocs[i].size;
	  else
	    {
Benjamin Otte's avatar
Benjamin Otte committed
860
	      int dummy;
861
862
863
864
865
866
867
              gtk_cell_area_request_renderer (area, info->renderer,
                                              priv->orientation,
                                              widget, for_size,
                                              &dummy,
                                              &cell_size);
	      cell_size = MIN (cell_size, group_allocs[i].size);
	    }
Matthias Clasen's avatar
Matthias Clasen committed
868
869
870

          if (rtl)
            cell = allocated_cell_new (info->renderer,
871
                                       full_size - (cell_position + cell_size), cell_size);
Matthias Clasen's avatar
Matthias Clasen committed
872
          else
873
874
875
876
            cell = allocated_cell_new (info->renderer, cell_position, cell_size);

	  position += cell_size;
          position += priv->spacing;
Matthias Clasen's avatar
Matthias Clasen committed
877
878
879

          allocated_cells = g_slist_prepend (allocated_cells, cell);
        }
880
      else
Matthias Clasen's avatar
Matthias Clasen committed
881
882
        {
          GtkRequestedSize *sizes;
Benjamin Otte's avatar
Benjamin Otte committed
883
884
885
          int               avail_size, cell_position;
          int               visible_cells, expand_cells;
          int               extra_size, extra_extra;
886

Matthias Clasen's avatar
Matthias Clasen committed
887
          visible_cells = count_visible_cells (group, &expand_cells);
888

Matthias Clasen's avatar
Matthias Clasen committed
889
890
891
892
893
          /* If this row has no visible cells in this group, just
           * skip the allocation
           */
          if (visible_cells == 0)
            continue;
894

895
896
897
898
899
900
901
902
903
904
905
906
907
	  /* If were not aligned, place the cell after the last cell 
	   * and eat up the extra space
	   */
	  if (group->align)
	    {
	      avail_size = group_allocs[i].size;
	      position   = cell_position = group_allocs[i].position;
	    }
	  else
	    {
	      avail_size    = group_allocs[i].size + (group_allocs[i].position - position);
	      cell_position = position;
	    }
908

Matthias Clasen's avatar
Matthias Clasen committed
909
          sizes = g_new (GtkRequestedSize, visible_cells);
910

Matthias Clasen's avatar
Matthias Clasen committed
911
912
913
          for (j = 0, cell_list = group->cells; cell_list; cell_list = cell_list->next)
            {
              CellInfo *info = cell_list->data;
914

Matthias Clasen's avatar
Matthias Clasen committed
915
916
              if (!gtk_cell_renderer_get_visible (info->renderer))
                continue;
917

Matthias Clasen's avatar
Matthias Clasen committed
918
919
920
921
922
              gtk_cell_area_request_renderer (area, info->renderer,
                                              priv->orientation,
                                              widget, for_size,
                                              &sizes[j].minimum_size,
                                              &sizes[j].natural_size);
923

Matthias Clasen's avatar
Matthias Clasen committed
924
925
              sizes[j].data = info;
              avail_size   -= sizes[j].minimum_size;
926

Matthias Clasen's avatar
Matthias Clasen committed
927
928
              j++;
            }
929

Matthias Clasen's avatar
Matthias Clasen committed
930
931
          /* Distribute cells naturally within the group */
          avail_size -= (visible_cells - 1) * priv->spacing;
932
933
934
935
          if (avail_size > 0)
            avail_size = gtk_distribute_natural_allocation (avail_size, visible_cells, sizes);
          else
            avail_size = 0;
936

Matthias Clasen's avatar
Matthias Clasen committed
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
          /* Calculate/distribute expand for cells */
          if (expand_cells > 0)
            {
              extra_size  = avail_size / expand_cells;
              extra_extra = avail_size % expand_cells;
            }
          else
            extra_size = extra_extra = 0;

          /* Create the allocated cells (loop only over visible cells here) */
          for (j = 0; j < visible_cells; j++)
            {
              CellInfo      *info = sizes[j].data;
              AllocatedCell *cell;

              if (info->expand)
                {
                  sizes[j].minimum_size += extra_size;
                  if (extra_extra)
                    {
                      sizes[j].minimum_size++;
                      extra_extra--;
                    }
                }

              if (rtl)
                cell = allocated_cell_new (info->renderer,
964
                                           full_size - (cell_position + sizes[j].minimum_size),
Matthias Clasen's avatar