+static AllocatedCell *
+allocated_cell_new (GtkCellRenderer *renderer,
+ gint position,
+ gint size)
+{
+ 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);
+}
+
+static GList *
+list_consecutive_cells (GtkCellAreaBox *box)
+{
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GList *l, *consecutive_cells = NULL, *pack_end_cells = NULL;
+ CellInfo *info;
+
+ /* List cells in consecutive order taking their
+ * PACK_START/PACK_END options into account
+ */
+ for (l = priv->cells; l; l = l->next)
+ {
+ info = l->data;
+
+ if (info->pack == GTK_PACK_START)
+ consecutive_cells = g_list_prepend (consecutive_cells, info);
+ }
+
+ for (l = priv->cells; l; l = l->next)
+ {
+ info = l->data;
+
+ if (info->pack == GTK_PACK_END)
+ pack_end_cells = g_list_prepend (pack_end_cells, info);
+ }
+
+ consecutive_cells = g_list_reverse (consecutive_cells);
+ consecutive_cells = g_list_concat (consecutive_cells, pack_end_cells);
+
+ return consecutive_cells;
+}
+
+static void
+cell_groups_clear (GtkCellAreaBox *box)
+{
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ 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)
+{
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ CellGroup group = { 0, };
+ CellGroup *group_ptr;
+ GList *cells, *l;
+ guint id = 0;
+ gboolean last_cell_fixed = FALSE;
+
+ cell_groups_clear (box);
+
+ if (!priv->cells)
+ return;
+
+ cells = list_consecutive_cells (box);
+
+ /* First group is implied */
+ g_array_append_val (priv->groups, group);
+ group_ptr = &g_array_index (priv->groups, CellGroup, id);
+
+ for (l = cells; l; l = l->next)
+ {
+ CellInfo *info = l->data;
+
+ /* 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)
+ {
+ memset (&group, 0x0, sizeof (CellGroup));
+ group.id = ++id;
+
+ g_array_append_val (priv->groups, group);
+ group_ptr = &g_array_index (priv->groups, CellGroup, id);
+ }
+
+ group_ptr->cells = g_list_prepend (group_ptr->cells, info);
+ group_ptr->n_cells++;
+
+ /* Not every group is aligned, some are floating
+ * fixed size cells */
+ if (info->align)
+ group_ptr->align = TRUE;
+
+ /* A group expands if it contains any expand cells */
+ if (info->expand)
+ group_ptr->expand_cells++;
+
+ last_cell_fixed = info->fixed;
+ }
+
+ g_list_free (cells);
+
+ for (id = 0; id < priv->groups->len; id++)
+ {
+ group_ptr = &g_array_index (priv->groups, CellGroup, id);
+
+ group_ptr->cells = g_list_reverse (group_ptr->cells);
+ }
+
+ /* Contexts need to be updated with the new grouping information */
+ init_context_groups (box);
+}
+
+static gint
+count_visible_cells (CellGroup *group,
+ gint *expand_cells)
+{
+ 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))
+ {
+ visible_cells++;
+
+ if (info->expand)
+ n_expand_cells++;
+ }
+ }
+
+ if (expand_cells)
+ *expand_cells = n_expand_cells;
+
+ return visible_cells;
+}
+
+static gint
+count_expand_groups (GtkCellAreaBox *box)
+{
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ gint i;
+ gint expand_groups = 0;
+
+ for (i = 0; i < priv->groups->len; i++)
+ {
+ CellGroup *group = &g_array_index (priv->groups, CellGroup, i);
+
+ if (group->expand_cells > 0)
+ expand_groups++;
+ }
+
+ return expand_groups;
+}
+
+static void
+context_weak_notify (GtkCellAreaBox *box,
+ GtkCellAreaBoxContext *dead_context)
+{
+ GtkCellAreaBoxPrivate *priv = box->priv;
+
+ priv->contexts = g_slist_remove (priv->contexts, dead_context);
+}
+
+static void
+init_context_group (GtkCellAreaBox *box,
+ GtkCellAreaBoxContext *context)
+{
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ gint *expand_groups, *align_groups, i;
+
+ expand_groups = g_new (gboolean, priv->groups->len);
+ align_groups = g_new (gboolean, priv->groups->len);
+
+ for (i = 0; i < priv->groups->len; i++)
+ {
+ CellGroup *group = &g_array_index (priv->groups, CellGroup, i);
+
+ expand_groups[i] = (group->expand_cells > 0);
+ align_groups[i] = group->align;
+ }
+
+ /* This call implies resetting the request info */
+ _gtk_cell_area_box_init_groups (context, priv->groups->len, expand_groups, align_groups);
+ g_free (expand_groups);
+ g_free (align_groups);
+}
+
+static void
+init_context_groups (GtkCellAreaBox *box)
+{
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GSList *l;
+
+ /* When the box's groups are reconstructed,
+ * contexts need to be reinitialized.
+ */
+ for (l = priv->contexts; l; l = l->next)
+ {
+ GtkCellAreaBoxContext *context = l->data;
+
+ init_context_group (box, context);
+ }
+}
+
+static void
+reset_contexts (GtkCellAreaBox *box)
+{
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GSList *l;
+
+ /* When the box layout changes, contexts need to
+ * be reset and sizes for the box get requested again
+ */
+ for (l = priv->contexts; l; l = l->next)
+ {
+ GtkCellAreaContext *context = l->data;
+
+ gtk_cell_area_context_reset (context);
+ }
+}
+
+/* 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,
+ GtkWidget *widget,
+ gint width,
+ gint height)
+{
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GList *cells, *l;
+ GSList *allocated_cells = NULL;
+ GtkRequestedSize *sizes;
+ gint i;
+ gint nvisible = 0, nexpand = 0, group_expand;
+ gint avail_size, extra_size, extra_extra, full_size;
+ gint position = 0, for_size;
+ gboolean rtl;
+
+ if (!priv->cells)
+ return NULL;
+
+ /* For vertical oriented boxes, we just let the cell renderers
+ * realign themselves for rtl
+ */
+ rtl = (priv->orientation == GTK_ORIENTATION_HORIZONTAL &&
+ gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
+
+ 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)
+ {
+ full_size = avail_size = width;
+ for_size = height;
+ }
+ else
+ {
+ full_size = avail_size = height;
+ for_size = width;
+ }
+
+ /* 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))
+ continue;
+
+ gtk_cell_area_request_renderer (GTK_CELL_AREA (box), info->renderer,
+ priv->orientation,
+ widget, for_size,
+ &sizes[i].minimum_size,
+ &sizes[i].natural_size);
+
+ avail_size -= sizes[i].minimum_size;
+
+ sizes[i].data = info;
+
+ i++;
+ }
+
+ /* Naturally distribute the allocation */
+ avail_size -= (nvisible - 1) * priv->spacing;
+ if (avail_size > 0)
+ avail_size = gtk_distribute_natural_allocation (avail_size, nvisible, sizes);
+ else
+ avail_size = 0;
+
+ /* 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)
+ {
+ sizes[i].minimum_size += extra_size;
+ if (extra_extra)
+ {
+ sizes[i].minimum_size++;
+ extra_extra--;
+ }
+ }
+
+ if (rtl)
+ cell = allocated_cell_new (info->renderer,
+ full_size - (position + sizes[i].minimum_size),
+ sizes[i].minimum_size);
+ else
+ cell = allocated_cell_new (info->renderer, position, sizes[i].minimum_size);
+
+ allocated_cells = g_slist_prepend (allocated_cells, cell);
+
+ 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,
+ * we have the correct positions, no need to allocate from left to right
+ */
+ return g_slist_reverse (allocated_cells);
+}
+
+/* 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 *
+get_allocated_cells (GtkCellAreaBox *box,
+ GtkCellAreaBoxContext *context,
+ GtkWidget *widget,
+ gint width,
+ gint height)
+{
+ GtkCellAreaBoxAllocation *group_allocs;
+ GtkCellArea *area = GTK_CELL_AREA (box);
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GList *cell_list;
+ GSList *allocated_cells = NULL;
+ gint i, j, n_allocs, position;
+ gint for_size, full_size;
+ gboolean rtl;
+
+ group_allocs = _gtk_cell_area_box_context_get_orientation_allocs (context, &n_allocs);
+ if (!group_allocs)
+ return allocate_cells_manually (box, widget, width, height);
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ full_size = width;
+ for_size = height;
+ }
+ else
+ {
+ full_size = height;
+ for_size = width;
+ }
+
+ /* For vertical oriented boxes, we just let the cell renderers
+ * realign themselves for rtl
+ */
+ rtl = (priv->orientation == GTK_ORIENTATION_HORIZONTAL &&
+ gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
+
+ for (position = 0, i = 0; i < n_allocs; i++)
+ {
+ /* 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
+ */
+ CellGroup *group = &g_array_index (priv->groups, CellGroup, group_allocs[i].group_idx);
+
+ /* Exception for single cell groups */
+ if (group->n_cells == 1)
+ {
+ CellInfo *info = group->cells->data;
+ AllocatedCell *cell;
+ gint cell_position, cell_size;
+
+ if (!gtk_cell_renderer_get_visible (info->renderer))
+ continue;
+
+ /* 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);
+ }
+
+ if (rtl)
+ cell = allocated_cell_new (info->renderer,
+ full_size - (cell_position + cell_size), cell_size);
+ else
+ cell = allocated_cell_new (info->renderer, cell_position, cell_size);
+
+ position += cell_size;
+ position += priv->spacing;
+
+ allocated_cells = g_slist_prepend (allocated_cells, cell);
+ }
+ else
+ {
+ GtkRequestedSize *sizes;
+ gint avail_size, cell_position;
+ gint visible_cells, expand_cells;
+ gint extra_size, extra_extra;
+
+ visible_cells = count_visible_cells (group, &expand_cells);
+
+ /* If this row has no visible cells in this group, just
+ * skip the allocation
+ */
+ if (visible_cells == 0)
+ continue;
+
+ /* 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;
+ }
+
+ sizes = g_new (GtkRequestedSize, visible_cells);
+
+ for (j = 0, cell_list = group->cells; cell_list; cell_list = cell_list->next)
+ {
+ CellInfo *info = cell_list->data;
+
+ if (!gtk_cell_renderer_get_visible (info->renderer))
+ continue;
+
+ gtk_cell_area_request_renderer (area, info->renderer,
+ priv->orientation,
+ widget, for_size,
+ &sizes[j].minimum_size,
+ &sizes[j].natural_size);
+
+ sizes[j].data = info;
+ avail_size -= sizes[j].minimum_size;
+
+ j++;
+ }
+
+ /* Distribute cells naturally within the group */
+ avail_size -= (visible_cells - 1) * priv->spacing;
+ if (avail_size > 0)
+ avail_size = gtk_distribute_natural_allocation (avail_size, visible_cells, sizes);
+ else
+ avail_size = 0;
+
+ /* 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,
+ full_size - (cell_position + sizes[j].minimum_size),
+ sizes[j].minimum_size);
+ else
+ cell = allocated_cell_new (info->renderer, cell_position, sizes[j].minimum_size);
+
+ allocated_cells = g_slist_prepend (allocated_cells, cell);
+
+ cell_position += sizes[j].minimum_size;
+ cell_position += priv->spacing;
+ }
+
+ g_free (sizes);
+
+ position = cell_position;
+ }
+ }
+
+ g_free (group_allocs);
+
+ /* Note it might not be important to reverse the list here at all,
+ * we have the correct positions, no need to allocate from left to right
+ */
+ return g_slist_reverse (allocated_cells);
+}
+
+
+static void
+gtk_cell_area_box_focus_changed (GtkCellArea *area,
+ GParamSpec *pspec,
+ GtkCellAreaBox *box)
+{
+ if (gtk_cell_area_get_focus_cell (area))
+ box->priv->last_focus_cell = gtk_cell_area_get_focus_cell (area);
+}
+