gtkcellareabox.c 76.5 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 24

/**
 * SECTION:gtkcellareabox
Matthias Clasen's avatar
Matthias Clasen committed
25 26
 * @Short_Description: A cell area that renders GtkCellRenderers
 *     into a row or a column
27 28
 * @Title: GtkCellAreaBox
 *
Matthias Clasen's avatar
Matthias Clasen committed
29 30
 * The #GtkCellAreaBox renders cell renderers into a row or a column
 * depending on its #GtkOrientation.
31 32
 *
 * GtkCellAreaBox uses a notion of <emphasis>packing</emphasis>. Packing
Matthias Clasen's avatar
Matthias Clasen committed
33
 * refers to adding cell renderers with reference to a particular position
34 35
 * in a #GtkCellAreaBox. There are two reference positions: the
 * <emphasis>start</emphasis> and the <emphasis>end</emphasis> of the box.
Matthias Clasen's avatar
Matthias Clasen committed
36 37 38 39 40
 * When the #GtkCellAreaBox is oriented in the %GTK_ORIENTATION_VERTICAL
 * 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.
41
 *
Matthias Clasen's avatar
Matthias Clasen committed
42 43 44 45
 * Alignments of #GtkCellRenderers rendered in adjacent rows can be
 * configured by configuring the #GtkCellAreaBox:align child cell property
 * 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().
46 47
 */

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

57 58

/* GObjectClass */
59 60 61
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
62 63 64
                                                                   guint                 prop_id,
                                                                   const GValue         *value,
                                                                   GParamSpec           *pspec);
65
static void      gtk_cell_area_box_get_property                   (GObject              *object,
Matthias Clasen's avatar
Matthias Clasen committed
66 67 68
                                                                   guint                 prop_id,
                                                                   GValue               *value,
                                                                   GParamSpec           *pspec);
69 70

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

129 130 131
/* 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
132 133
                                                                   GtkCellRenderer    *renderer,
                                                                   gboolean            expand);
134
static void      gtk_cell_area_box_layout_pack_end                (GtkCellLayout      *cell_layout,
Matthias Clasen's avatar
Matthias Clasen committed
135 136
                                                                   GtkCellRenderer    *renderer,
                                                                   gboolean            expand);
137
static void      gtk_cell_area_box_layout_reorder                 (GtkCellLayout      *cell_layout,
Matthias Clasen's avatar
Matthias Clasen committed
138 139
                                                                   GtkCellRenderer    *renderer,
                                                                   gint                position);
140
static void      gtk_cell_area_box_focus_changed                  (GtkCellArea        *area,
Matthias Clasen's avatar
Matthias Clasen committed
141 142
                                                                   GParamSpec         *pspec,
                                                                   GtkCellAreaBox     *box);
143 144


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

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

155 156 157
typedef struct {
  GList *cells;

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

165 166 167 168 169 170 171
typedef struct {
  GtkCellRenderer *renderer;

  gint             position;
  gint             size;
} AllocatedCell;

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

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

199 200 201

struct _GtkCellAreaBoxPrivate
{
202 203
  /* 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
204 205
   * this way we always re-enter the last focused cell.
   */
206 207
  GtkCellRenderer *last_focus_cell;
  gulong           focus_cell_id;
208

209 210
  GList           *cells;
  GArray          *groups;
211

212 213
  GSList          *contexts;

214
  GtkOrientation   orientation;
215
  gint             spacing;
216 217

  /* We hold on to the rtl state from a widget we are requested for
Matthias Clasen's avatar
Matthias Clasen committed
218 219
   * so that we can navigate focus correctly
   */
220
  gboolean         rtl;
221 222 223 224
};

enum {
  PROP_0,
225
  PROP_ORIENTATION,
226
  PROP_SPACING
227 228
};

229 230 231 232
enum {
  CELL_PROP_0,
  CELL_PROP_EXPAND,
  CELL_PROP_ALIGN,
233
  CELL_PROP_FIXED_SIZE,
234 235 236
  CELL_PROP_PACK_TYPE
};

237
G_DEFINE_TYPE_WITH_CODE (GtkCellAreaBox, gtk_cell_area_box, GTK_TYPE_CELL_AREA,
Matthias Clasen's avatar
Matthias Clasen committed
238 239 240
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
                                                gtk_cell_area_box_cell_layout_init)
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL));
241

Matthias Clasen's avatar
Matthias Clasen committed
242 243
#define OPPOSITE_ORIENTATION(orientation)                       \
  ((orientation) == GTK_ORIENTATION_HORIZONTAL ?                \
244 245
   GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL)

246 247 248 249 250 251 252 253 254 255 256
static void
gtk_cell_area_box_init (GtkCellAreaBox *box)
{
  GtkCellAreaBoxPrivate *priv;

  box->priv = G_TYPE_INSTANCE_GET_PRIVATE (box,
                                           GTK_TYPE_CELL_AREA_BOX,
                                           GtkCellAreaBoxPrivate);
  priv = box->priv;

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

  /* Watch whenever focus is given to a cell, even if it's not with keynav,
Matthias Clasen's avatar
Matthias Clasen committed
264 265 266 267 268
   * 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);
269 270
}

Matthias Clasen's avatar
Matthias Clasen committed
271
static void
272 273 274 275 276 277 278 279 280 281 282 283
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 */
284 285
  area_class->add                 = gtk_cell_area_box_add;
  area_class->remove              = gtk_cell_area_box_remove;
286
  area_class->foreach             = gtk_cell_area_box_foreach;
287
  area_class->foreach_alloc       = gtk_cell_area_box_foreach_alloc;
288
  area_class->apply_attributes    = gtk_cell_area_box_apply_attributes;
289 290
  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
291

292
  area_class->create_context                 = gtk_cell_area_box_create_context;
293
  area_class->copy_context                   = gtk_cell_area_box_copy_context;
294 295 296 297 298 299
  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;

300
  area_class->focus = gtk_cell_area_box_focus;
301

302
  /* Properties */
303 304
  g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");

305 306 307 308 309 310 311
  /**
   * GtkCellAreaBox:spacing:
   *
   * The amount of space to reserve between cells.
   *
   * Since: 3.0
   */
312 313 314
  g_object_class_install_property (object_class,
                                   PROP_SPACING,
                                   g_param_spec_int ("spacing",
Matthias Clasen's avatar
Matthias Clasen committed
315 316 317 318 319 320
                                                     P_("Spacing"),
                                                     P_("Space which is inserted between cells"),
                                                     0,
                                                     G_MAXINT,
                                                     0,
                                                     GTK_PARAM_READWRITE));
321

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

340 341 342 343 344 345 346
  /**
   * GtkCellAreaBox:align:
   *
   * Whether the cell renderer should be aligned in adjacent rows.
   *
   * Since: 3.0
   */
347
  gtk_cell_area_class_install_cell_property (area_class,
Matthias Clasen's avatar
Matthias Clasen committed
348 349 350 351 352
                                             CELL_PROP_ALIGN,
                                             g_param_spec_boolean
                                             ("align",
                                              P_("Align"),
                                              P_("Whether cell should align with adjacent rows"),
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
                                              FALSE,
                                              GTK_PARAM_READWRITE));

  /**
   * GtkCellAreaBox:fixed-size:
   *
   * Whether the cell renderer should require the same size
   * for all rows for which it was requested.
   *
   * Since: 3.0
   */
  gtk_cell_area_class_install_cell_property (area_class,
                                             CELL_PROP_FIXED_SIZE,
                                             g_param_spec_boolean
                                             ("fixed-size",
                                              P_("Fixed Size"),
                                              P_("Whether cells should be the same size in all rows"),
Matthias Clasen's avatar
Matthias Clasen committed
370 371
                                              TRUE,
                                              GTK_PARAM_READWRITE));
372

373 374 375
  /**
   * GtkCellAreaBox:pack-type:
   *
Matthias Clasen's avatar
Matthias Clasen committed
376 377
   * A GtkPackType indicating whether the cell renderer is packed
   * with reference to the start or end of the area.
378 379 380
   *
   * Since: 3.0
   */
381
  gtk_cell_area_class_install_cell_property (area_class,
Matthias Clasen's avatar
Matthias Clasen committed
382 383 384 385 386 387 388 389
                                             CELL_PROP_PACK_TYPE,
                                             g_param_spec_enum
                                             ("pack-type",
                                              P_("Pack Type"),
                                              P_("A GtkPackType indicating whether the cell is packed with "
                                                 "reference to the start or end of the cell area"),
                                              GTK_TYPE_PACK_TYPE, GTK_PACK_START,
                                              GTK_PARAM_READWRITE));
390

391 392 393 394
  g_type_class_add_private (object_class, sizeof (GtkCellAreaBoxPrivate));
}


395
/*************************************************************
396
 *    CellInfo/CellGroup basics and convenience functions    *
397 398
 *************************************************************/
static CellInfo *
Matthias Clasen's avatar
Matthias Clasen committed
399 400 401
cell_info_new  (GtkCellRenderer *renderer,
                GtkPackType      pack,
                gboolean         expand,
402 403
                gboolean         align,
		gboolean         fixed)
404 405
{
  CellInfo *info = g_slice_new (CellInfo);
Matthias Clasen's avatar
Matthias Clasen committed
406

407 408
  info->renderer = g_object_ref_sink (renderer);
  info->pack     = pack;
409 410
  info->expand   = expand;
  info->align    = align;
411
  info->fixed    = fixed;
412 413 414 415 416 417 418 419 420 421 422 423 424 425

  return info;
}

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

  g_slice_free (CellInfo, info);
}

static gint
cell_info_find (CellInfo        *info,
Matthias Clasen's avatar
Matthias Clasen committed
426
                GtkCellRenderer *renderer)
427 428 429 430
{
  return (info->renderer == renderer) ? 0 : -1;
}

431 432
static AllocatedCell *
allocated_cell_new (GtkCellRenderer *renderer,
Matthias Clasen's avatar
Matthias Clasen committed
433 434
                    gint             position,
                    gint             size)
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
{
  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);
}

451 452 453 454 455 456 457
static GList *
list_consecutive_cells (GtkCellAreaBox *box)
{
  GtkCellAreaBoxPrivate *priv = box->priv;
  GList                 *l, *consecutive_cells = NULL, *pack_end_cells = NULL;
  CellInfo              *info;

Matthias Clasen's avatar
Matthias Clasen committed
458 459
  /* List cells in consecutive order taking their
   * PACK_START/PACK_END options into account
460 461 462 463
   */
  for (l = priv->cells; l; l = l->next)
    {
      info = l->data;
Matthias Clasen's avatar
Matthias Clasen committed
464

465
      if (info->pack == GTK_PACK_START)
Matthias Clasen's avatar
Matthias Clasen committed
466
        consecutive_cells = g_list_prepend (consecutive_cells, info);
467 468 469 470 471
    }

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

473
      if (info->pack == GTK_PACK_END)
Matthias Clasen's avatar
Matthias Clasen committed
474
        pack_end_cells = g_list_prepend (pack_end_cells, info);
475 476 477 478 479 480 481 482
    }

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

  return consecutive_cells;
}

483
static void
Matthias Clasen's avatar
Matthias Clasen committed
484
cell_groups_clear (GtkCellAreaBox *box)
485
{
Matthias Clasen's avatar
Matthias Clasen committed
486
  GtkCellAreaBoxPrivate *priv = box->priv;
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
  gint                   i;

  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)
{
Matthias Clasen's avatar
Matthias Clasen committed
502
  GtkCellAreaBoxPrivate *priv = box->priv;
503
  CellGroup              group = { 0, };
504
  CellGroup             *group_ptr;
505 506
  GList                 *cells, *l;
  guint                  id = 0;
507
  gboolean               last_cell_fixed = FALSE;
508

509 510
  cell_groups_clear (box);

511
  if (!priv->cells)
512
    return;
513

514 515 516 517
  cells = list_consecutive_cells (box);

  /* First group is implied */
  g_array_append_val (priv->groups, group);
518
  group_ptr = &g_array_index (priv->groups, CellGroup, id);
519 520 521 522 523

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

524 525 526 527
      /* 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
528 529 530
        {
          memset (&group, 0x0, sizeof (CellGroup));
          group.id = ++id;
531

Matthias Clasen's avatar
Matthias Clasen committed
532 533 534
          g_array_append_val (priv->groups, group);
          group_ptr = &g_array_index (priv->groups, CellGroup, id);
        }
535

536 537
      group_ptr->cells = g_list_prepend (group_ptr->cells, info);
      group_ptr->n_cells++;
538

539 540 541 542 543
      /* Not every group is aligned, some are floating
       * fixed size cells */
      if (info->align)
	group_ptr->align = TRUE;

544 545
      /* A group expands if it contains any expand cells */
      if (info->expand)
Matthias Clasen's avatar
Matthias Clasen committed
546
        group_ptr->expand_cells++;
547 548

      last_cell_fixed = info->fixed;
549 550 551 552
    }

  g_list_free (cells);

553
  for (id = 0; id < priv->groups->len; id++)
554
    {
555
      group_ptr = &g_array_index (priv->groups, CellGroup, id);
556 557

      group_ptr->cells = g_list_reverse (group_ptr->cells);
558 559
    }

560 561
  /* Contexts need to be updated with the new grouping information */
  init_context_groups (box);
562 563 564
}

static gint
Matthias Clasen's avatar
Matthias Clasen committed
565 566
count_visible_cells (CellGroup *group,
                     gint      *expand_cells)
567 568 569 570 571 572 573 574 575 576
{
  GList *l;
  gint   visible_cells = 0;
  gint   n_expand_cells = 0;

  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
577 578
        {
          visible_cells++;
579

Matthias Clasen's avatar
Matthias Clasen committed
580 581 582
          if (info->expand)
            n_expand_cells++;
        }
583 584 585 586 587 588
    }

  if (expand_cells)
    *expand_cells = n_expand_cells;

  return visible_cells;
589 590 591 592 593 594
}

static gint
count_expand_groups (GtkCellAreaBox  *box)
{
  GtkCellAreaBoxPrivate *priv = box->priv;
595
  gint                   i;
596 597
  gint                   expand_groups = 0;

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

602
      if (group->expand_cells > 0)
Matthias Clasen's avatar
Matthias Clasen committed
603
        expand_groups++;
604 605 606 607 608
    }

  return expand_groups;
}

Matthias Clasen's avatar
Matthias Clasen committed
609
static void
610
context_weak_notify (GtkCellAreaBox        *box,
Matthias Clasen's avatar
Matthias Clasen committed
611
                     GtkCellAreaBoxContext *dead_context)
612 613 614
{
  GtkCellAreaBoxPrivate *priv = box->priv;

615
  priv->contexts = g_slist_remove (priv->contexts, dead_context);
616 617 618
}

static void
619
init_context_group (GtkCellAreaBox        *box,
Matthias Clasen's avatar
Matthias Clasen committed
620
                    GtkCellAreaBoxContext *context)
621 622
{
  GtkCellAreaBoxPrivate *priv = box->priv;
623
  gint                  *expand_groups, *align_groups, i;
624

625
  expand_groups = g_new (gboolean, priv->groups->len);
626
  align_groups  = g_new (gboolean, priv->groups->len);
627

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

632
      expand_groups[i] = (group->expand_cells > 0);
633
      align_groups[i]  = group->align;
634
    }
635

636 637
  /* This call implies resetting the request info */
  _gtk_cell_area_box_init_groups (context, priv->groups->len, expand_groups, align_groups);
638
  g_free (expand_groups);
639
  g_free (align_groups);
640
}
641

642
static void
643
init_context_groups (GtkCellAreaBox *box)
644
{
645
  GtkCellAreaBoxPrivate *priv = box->priv;
646
  GSList                *l;
647

Matthias Clasen's avatar
Matthias Clasen committed
648 649
  /* When the box's groups are reconstructed,
   * contexts need to be reinitialized.
650
   */
651
  for (l = priv->contexts; l; l = l->next)
652
    {
653
      GtkCellAreaBoxContext *context = l->data;
654

655
      init_context_group (box, context);
656
    }
657
}
658

659
static void
660
reset_contexts (GtkCellAreaBox *box)
661 662 663
{
  GtkCellAreaBoxPrivate *priv = box->priv;
  GSList                *l;
664

665
  /* When the box layout changes, contexts need to
666
   * be reset and sizes for the box get requested again
667
   */
668
  for (l = priv->contexts; l; l = l->next)
669
    {
670
      GtkCellAreaContext *context = l->data;
671

672
      gtk_cell_area_context_reset (context);
673
    }
674 675
}

676 677 678 679 680 681 682
/* 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
683 684 685
                         GtkWidget             *widget,
                         gint                   width,
                         gint                   height)
686 687 688 689 690 691 692
{
  GtkCellAreaBoxPrivate    *priv = box->priv;
  GList                    *cells, *l;
  GSList                   *allocated_cells = NULL;
  GtkRequestedSize         *sizes;
  gint                      i;
  gint                      nvisible = 0, nexpand = 0, group_expand;
693
  gint                      avail_size, extra_size, extra_extra, full_size;
694
  gint                      position = 0, for_size;
695
  gboolean                  rtl;
696 697 698 699

  if (!priv->cells)
    return NULL;

Matthias Clasen's avatar
Matthias Clasen committed
700 701 702
  /* For vertical oriented boxes, we just let the cell renderers
   * realign themselves for rtl
   */
703
  rtl = (priv->orientation == GTK_ORIENTATION_HORIZONTAL &&
Matthias Clasen's avatar
Matthias Clasen committed
704
         gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
705

706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
  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)
724
    {
725 726
      full_size = avail_size = width;
      for_size  = height;
727
    }
728
  else
729
    {
730 731
      full_size = avail_size = height;
      for_size  = width;
732
    }
733 734 735 736 737 738 739 740

  /* 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
741
        continue;
742

743
      gtk_cell_area_request_renderer (GTK_CELL_AREA (box), info->renderer,
Matthias Clasen's avatar
Matthias Clasen committed
744 745 746 747
                                      priv->orientation,
                                      widget, for_size,
                                      &sizes[i].minimum_size,
                                      &sizes[i].natural_size);
748 749 750 751 752 753 754 755 756 757

      avail_size -= sizes[i].minimum_size;

      sizes[i].data = info;

      i++;
    }

  /* Naturally distribute the allocation */
  avail_size -= (nvisible - 1) * priv->spacing;
758 759 760 761
  if (avail_size > 0)
    avail_size = gtk_distribute_natural_allocation (avail_size, nvisible, sizes);
  else
    avail_size = 0;
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778

  /* 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
779 780 781 782 783 784 785 786 787
        {
          sizes[i].minimum_size += extra_size;
          if (extra_extra)
            {
              sizes[i].minimum_size++;
              extra_extra--;
            }
        }

788
      if (rtl)
Matthias Clasen's avatar
Matthias Clasen committed
789 790 791
        cell = allocated_cell_new (info->renderer,
                                   full_size - (position + sizes[i].minimum_size),
                                   sizes[i].minimum_size);
792
      else
Matthias Clasen's avatar
Matthias Clasen committed
793
        cell = allocated_cell_new (info->renderer, position, sizes[i].minimum_size);
794 795

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

797 798 799 800 801 802 803 804
      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
805 806
   * we have the correct positions, no need to allocate from left to right
   */
807 808 809
  return g_slist_reverse (allocated_cells);
}

810 811 812 813 814
/* 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 *
815
get_allocated_cells (GtkCellAreaBox        *box,
Matthias Clasen's avatar
Matthias Clasen committed
816 817 818 819
                     GtkCellAreaBoxContext *context,
                     GtkWidget             *widget,
                     gint                   width,
                     gint                   height)
820
{
821 822 823 824 825
  GtkCellAreaBoxAllocation *group_allocs;
  GtkCellArea              *area = GTK_CELL_AREA (box);
  GtkCellAreaBoxPrivate    *priv = box->priv;
  GList                    *cell_list;
  GSList                   *allocated_cells = NULL;
826
  gint                      i, j, n_allocs, position;
827 828
  gint                      for_size, full_size;
  gboolean                  rtl;
829

830
  group_allocs = _gtk_cell_area_box_context_get_orientation_allocs (context, &n_allocs);
831
  if (!group_allocs)
832
    return allocate_cells_manually (box, widget, width, height);
833

834
  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
835 836 837 838
    {
      full_size = width;
      for_size  = height;
    }
839
  else
840 841 842 843 844
    {
      full_size = height;
      for_size  = width;
    }

Matthias Clasen's avatar
Matthias Clasen committed
845 846 847
  /* For vertical oriented boxes, we just let the cell renderers
   * realign themselves for rtl
   */
848
  rtl = (priv->orientation == GTK_ORIENTATION_HORIZONTAL &&
Matthias Clasen's avatar
Matthias Clasen committed
849
         gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
850

851
  for (position = 0, i = 0; i < n_allocs; i++)
852
    {
Matthias Clasen's avatar
Matthias Clasen committed
853 854 855
      /* 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
856 857
       */
      CellGroup *group = &g_array_index (priv->groups, CellGroup, group_allocs[i].group_idx);
858 859 860

      /* Exception for single cell groups */
      if (group->n_cells == 1)
Matthias Clasen's avatar
Matthias Clasen committed
861 862 863
        {
          CellInfo      *info = group->cells->data;
          AllocatedCell *cell;
864 865
	  gint           cell_position, cell_size;

866 867 868
	  if (!gtk_cell_renderer_get_visible (info->renderer))
	    continue;

869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887
	  /* 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
	    {
	      gint dummy;
              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
888 889 890

          if (rtl)
            cell = allocated_cell_new (info->renderer,
891
                                       full_size - (cell_position + cell_size), cell_size);
Matthias Clasen's avatar
Matthias Clasen committed
892
          else
893 894 895 896
            cell = allocated_cell_new (info->renderer, cell_position, cell_size);

	  position += cell_size;
          position += priv->spacing;
Matthias Clasen's avatar
Matthias Clasen committed
897 898 899

          allocated_cells = g_slist_prepend (allocated_cells, cell);
        }
900
      else
Matthias Clasen's avatar
Matthias Clasen committed
901 902
        {
          GtkRequestedSize *sizes;
903
          gint              avail_size, cell_position;
Matthias Clasen's avatar
Matthias Clasen committed
904 905
          gint              visible_cells, expand_cells;
          gint              extra_size, extra_extra;
906

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

Matthias Clasen's avatar
Matthias Clasen committed
909 910 911 912 913
          /* If this row has no visible cells in this group, just
           * skip the allocation
           */
          if (visible_cells == 0)
            continue;
914

915 916 917 918 919 920 921 922 923 924 925 926 927
	  /* 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;
	    }
928

Matthias Clasen's avatar
Matthias Clasen committed
929
          sizes = g_new (GtkRequestedSize, visible_cells);
930

Matthias Clasen's avatar
Matthias Clasen committed
931 932 933
          for (j = 0, cell_list = group->cells; cell_list; cell_list = cell_list->next)
            {
              CellInfo *info = cell_list->data;
934

Matthias Clasen's avatar
Matthias Clasen committed
935 936
              if (!gtk_cell_renderer_get_visible (info->renderer))
                continue;
937

Matthias Clasen's avatar
Matthias Clasen committed
938 939 940 941 942
              gtk_cell_area_request_renderer (area, info->renderer,
                                              priv->orientation,
                                              widget, for_size,
                                              &sizes[j].minimum_size,
                                              &sizes[j].natural_size);
943

Matthias Clasen's avatar
Matthias Clasen committed
944 945
              sizes[j].data = info;
              avail_size   -= sizes[j].minimum_size;
946

Matthias Clasen's avatar
Matthias Clasen committed
947 948
              j++;
            }
949

Matthias Clasen's avatar
Matthias Clasen committed
950 951
          /* Distribute cells naturally within the group */
          avail_size -= (visible_cells - 1) * priv->spacing;
952 953 954 955
          if (avail_size > 0)
            avail_size = gtk_distribute_natural_allocation (avail_size, visible_cells, sizes);
          else
            avail_size = 0;
956

Matthias Clasen's avatar
Matthias Clasen committed
957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983
          /* 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,
984
                                           full_size - (cell_position + sizes[j].minimum_size),
Matthias Clasen's avatar
Matthias Clasen committed
985 986
                                           sizes[j].minimum_size);
              else
987
                cell = allocated_cell_new (info->renderer, cell_position, sizes[j].minimum_size);
Matthias Clasen's avatar
Matthias Clasen committed
988 989 990

              allocated_cells = g_slist_prepend (allocated_cells, cell);

991 992
              cell_position += sizes[j].minimum_size;
              cell_position += priv->spacing;
Matthias Clasen's avatar
Matthias Clasen committed
993 994 995
            }

          g_free (sizes);
996 997

	  position = cell_position;
Matthias Clasen's avatar
Matthias Clasen committed
998
        }
999 1000
    }

1001 1002
  g_free (group_allocs);

1003
  /* Note it might not be important to reverse the list here at all,
Matthias Clasen's avatar
Matthias Clasen committed
1004 1005
   * we have the correct positions, no need to allocate from left to right
   */
1006 1007 1008
  return g_slist_reverse (allocated_cells);
}

1009 1010 1011

static void
gtk_cell_area_box_focus_changed (GtkCellArea        *area,
Matthias Clasen's avatar
Matthias Clasen committed
1012 1013
                                 GParamSpec         *pspec,
                                 GtkCellAreaBox     *box)
1014 1015 1016 1017 1018
{
  if (gtk_cell_area_get_focus_cell (area))
    box->priv->last_focus_cell = gtk_cell_area_get_focus_cell (area);
}

1019 1020 1021 1022 1023 1024
/*************************************************************
 *                      GObjectClass                         *
 *************************************************************/
static void
gtk_cell_area_box_finalize (GObject *object)
{
1025 1026 1027 1028
  GtkCellAreaBox        *box = GTK_CELL_AREA_BOX (object);
  GtkCellAreaBoxPrivate *priv = box->priv;
  GSList                *l;

1029 1030 1031
  /* Unref/free the context list */
  for (l = priv->contexts; l; l = l->next)
    g_object_weak_unref (G_OBJECT (l->data), (GWeakNotify)context_weak_notify, box);
1032

1033 1034
  g_slist_free (priv->contexts);
  priv->contexts = NULL;
1035

1036 1037 1038
  /* Free the cell grouping info */
  cell_groups_clear (box);
  g_array_free (priv->groups, TRUE);
Matthias Clasen's avatar
Matthias Clasen committed
1039

1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050
  G_OBJECT_CLASS (gtk_cell_area_box_parent_class)->finalize (object);
}

static void
gtk_cell_area_box_dispose (GObject *object)
{
  G_OBJECT_CLASS (gtk_cell_area_box_parent_class)->dispose (object);
}

static void
gtk_cell_area_box_set_property (GObject       *object,
Matthias Clasen's avatar
Matthias Clasen committed
1051 1052 1053
                                guint          prop_id,
                                const GValue  *value,
                                GParamSpec    *pspec)
1054
{
1055
  GtkCellAreaBox *box = GTK_CELL_AREA_BOX (object);
1056

1057 1058
  switch (prop_id)
    {
1059 1060 1061 1062
    case PROP_ORIENTATION:
      box->priv->orientation = g_value_get_enum (value);

      /* Notify that size needs to be requested again */
1063
      reset_contexts (box);
1064

1065
      break;
1066 1067 1068 1069 1070 1071 1072
    case PROP_SPACING:
      gtk_cell_area_box_set_spacing (box, g_value_get_int (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
1073 1074 1075 1076
}

static void
gtk_cell_area_box_get_property (GObject     *object,
Matthias Clasen's avatar
Matthias Clasen committed
1077 1078 1079
                                guint        prop_id,
                                GValue      *value,
                                GParamSpec  *pspec)
1080
{
1081
  GtkCellAreaBox *box = GTK_CELL_AREA_BOX (object);
1082

1083 1084
  switch (prop_id)
    {
1085 1086 1087
    case PROP_ORIENTATION:
      g_value_set_enum (value, box->priv->orientation);
      break;
1088 1089 1090 1091 1092 1093 1094
    case PROP_SPACING:
      g_value_set_int (value, gtk_cell_area_box_get_spacing (box));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
1095 1096 1097 1098 1099
}

/*************************************************************
 *                    GtkCellAreaClass                       *
 *************************************************************/
Matthias Clasen's avatar
Matthias Clasen committed
1100
static void
1101
gtk_cell_area_box_add (GtkCellArea        *area,
Matthias Clasen's avatar
Matthias Clasen committed
1102
                       GtkCellRenderer    *renderer)
1103
{
1104
  gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area),
1105
                                renderer, FALSE, FALSE, TRUE);
1106 1107 1108 1109
}

static void
gtk_cell_area_box_remove (GtkCellArea        *area,
Matthias Clasen's avatar
Matthias Clasen committed
1110
                          GtkCellRenderer    *renderer)
1111
{
1112 1113 1114 1115
  GtkCellAreaBox        *box  = GTK_CELL_AREA_BOX (area);
  GtkCellAreaBoxPrivate *priv = box->priv;
  GList                 *node;

1116 1117 1118
  if (priv->last_focus_cell == renderer)
    priv->last_focus_cell = NULL;

Matthias Clasen's avatar
Matthias Clasen committed
1119 1120
  node = g_list_find_custom (priv->cells, renderer,
                             (GCompareFunc)cell_info_find);
1121 1122 1123 1124

  if (node)
    {
      CellInfo *info = node->data;
1125

1126 1127 1128
      cell_info_free (info);

      priv->cells = g_list_delete_link (priv->cells, node);
1129

1130
      /* Reconstruct cell groups */
1131
      cell_groups_rebuild (box);
1132 1133 1134
    }
  else
    g_warning ("Trying to remove a cell renderer that is not present GtkCellAreaBox");
1135 1136 1137
}

static void
1138
gtk_cell_area_box_foreach (GtkCellArea        *area,
Matthias Clasen's avatar
Matthias Clasen committed
1139 1140
                           GtkCellCallback     callback,
                           gpointer            callback_data)
1141
{
1142 1143 1144 1145 1146 1147 1148
  GtkCellAreaBox        *box  = GTK_CELL_AREA_BOX (area);
  GtkCellAreaBoxPrivate *priv = box->priv;
  GList                 *list;

  for (list = priv->cells; list; list = list->next)
    {
      CellInfo *info = list->data;
1149

1150
      if (callback (info->renderer, callback_data))
Matthias Clasen's avatar
Matthias Clasen committed
1151
        break;
1152
    }
1153 1154
}

1155
static void
1156
gtk_cell_area_box_foreach_alloc (GtkCellArea          *area,
Matthias Clasen's avatar
Matthias Clasen committed
1157 1158 1159 1160 1161 1162
                                 GtkCellAreaContext   *context,
                                 GtkWidget            *widget,
                                 const GdkRectangle   *cell_area,
                                 const GdkRectangle   *background_area,
                                 GtkCellAllocCallback  callback,
                                 gpointer              callback_data)
1163 1164 1165
{
  GtkCellAreaBox        *box      = GTK_CELL_AREA_BOX (area);
  GtkCellAreaBoxPrivate *priv     = box->priv;
1166
  GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
1167
  GSList                *allocated_cells, *l;
1168
  GdkRectangle           cell_alloc, cell_background;
1169 1170 1171
  gboolean               rtl;

  rtl = (priv->orientation == GTK_ORIENTATION_HORIZONTAL &&
Matthias Clasen's avatar
Matthias Clasen committed
1172
         gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
1173

1174
  cell_alloc = *cell_area;
1175 1176

  /* Get a list of cells with allocation sizes decided regardless
Matthias Clasen's avatar
Matthias Clasen committed
1177 1178 1179 1180
   * of alignments and pack order etc.
   */
  allocated_cells = get_allocated_cells (box, box_context, widget,
                                         cell_area->width, cell_area->height);
1181 1182 1183

  for (l = allocated_cells; l; l = l->next)
    {
1184
      AllocatedCell *cell = l->data;
1185

1186
      if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
Matthias Clasen's avatar
Matthias Clasen committed
1187 1188 1189 1190
        {
          cell_alloc.x     = cell_area->x + cell->position;
          cell_alloc.width = cell->size;
        }
1191
      else
Matthias Clasen's avatar
Matthias Clasen committed
1192 1193 1194 1195 1196 1197 1198 1199
        {
          cell_alloc.y      = cell_area->y + cell->position;
          cell_alloc.height = cell->size;
        }

      /* Stop iterating over cells if they flow out of the render
       * area, this can happen because the render area can actually
       * be smaller than the requested area (treeview columns can
1200
       * be user resizable and can be resized to be smaller than
Matthias Clasen's avatar
Matthias Clasen committed
1201 1202
       * the actual requested area).
       */
1203
      if (cell_alloc.x > cell_area->x + cell_area->width ||
Matthias Clasen's avatar
Matthias Clasen committed
1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214
          cell_alloc.x + cell_alloc.width < cell_area->x ||
          cell_alloc.y > cell_area->y + cell_area->height)
        break;

      /* Special case for the last cell (or first cell in rtl)...
       * let the last cell consume the remaining space in the area
       * (the last cell is allowed to consume the remaining space if
       * the space given for rendering is actually larger than allocation,
       * this can happen in the expander GtkTreeViewColumn where only the
       * deepest depth column receives the allocation... shallow columns
       * receive more width). */
1215
      if (!l->next)
Matthias Clasen's avatar
Matthias Clasen committed
1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230
        {
          if (rtl)
            {
              /* Fill the leading space for the first cell in the area
               * (still last in the list)
               */
              cell_alloc.width = (cell_alloc.x - cell_area->x) + cell_alloc.width;
              cell_alloc.x     = cell_area->x;
            }
          else
            {
              cell_alloc.width  = cell_area->x + cell_area->width  - cell_alloc.x;
              cell_alloc.height = cell_area->y + cell_area->height - cell_alloc.y;
            }
        }
1231
      else
Matthias Clasen's avatar
Matthias Clasen committed
1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242
        {
          /* If the cell we are rendering doesnt fit into the remaining space,
           * clip it so that the underlying renderer has a chance to deal with
           * it (for instance text renderers get a chance to ellipsize).
           */
          if (cell_alloc.x + cell_alloc.width > cell_area->x + cell_area->width)
            cell_alloc.width = cell_area->x + cell_area->width - cell_alloc.x;

          if (cell_alloc.y + cell_alloc.height > cell_area->y + cell_area->height)
            cell_alloc.height = cell_area->y + cell_area->height - cell_alloc.y;
        }
1243

1244
      /* Add portions of the background_area to the cell_alloc
Matthias Clasen's avatar
Matthias Clasen committed
1245 1246
       * to create the cell_background
       */
1247
      cell_background = cell_alloc;
1248

1249
      if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
Matthias Clasen's avatar
Matthias Clasen committed
1250 1251 1252
        {
          if (l == allocated_cells)
            {
1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263
              /* Add the depth to the first cell */
              if (rtl)
                {
                  cell_background.width += background_area->width - cell_area->width;
                  cell_background.x      = background_area->x + background_area->width - cell_background.width;
                }
              else
                {
                  cell_background.width += cell_area->x - background_area->x;
                  cell_background.x      = background_area->x;
                }
Matthias Clasen's avatar
Matthias Clasen committed
1264
            }
1265

Matthias Clasen's avatar
Matthias Clasen committed
1266
          if (l->next == NULL)
1267 1268 1269 1270 1271 1272 1273 1274 1275
            {
              /* Grant this cell the remaining space */
              int remain = cell_background.x - background_area->x;

              if (rtl)
                cell_background.x -= remain;
              else
                cell_background.width = background_area->width - remain;
            }
1276

Matthias Clasen's avatar
Matthias Clasen committed
1277 1278 1279
          cell_background.y      = background_area->y;
          cell_background.height = background_area->height;
        }
1280
      else
Matthias Clasen's avatar
Matthias Clasen committed
1281 1282 1283 1284 1285 1286
        {
          if (l == allocated_cells)
            {
              cell_background.height += cell_background.y - background_area->y;
              cell_background.y       = background_area->y;
            }
1287

Matthias Clasen's avatar
Matthias Clasen committed
1288 1289 1290
          if (l->next == NULL)
              cell_background.height =
                background_area->height - (cell_background.y - background_area->y);
1291

Matthias Clasen's avatar
Matthias Clasen committed
1292 1293 1294
          cell_background.x     = background_area->x;
          cell_background.width = background_area->width;
        }
1295

1296
      if (callback (cell->renderer, &cell_alloc, &cell_background, callback_data))
Matthias Clasen's avatar
Matthias Clasen committed
1297
        break;
1298 1299
    }

1300 1301
  g_slist_foreach (allocated_cells, (GFunc)allocated_cell_free, NULL);
  g_slist_free (allocated_cells);