gtkbuildable.h \
gtkbutton.h \
gtkcalendar.h \
+ gtkcellarea.h \
+ gtkcellareabox.h \
+ gtkcellareaboxcontext.h \
+ gtkcellareacontext.h \
gtkcelleditable.h \
gtkcelllayout.h \
gtkcellrenderer.h \
gtkbuilderparser.c \
gtkbutton.c \
gtkcalendar.c \
+ gtkcellarea.c \
+ gtkcellareabox.c \
+ gtkcellareaboxcontext.c \
+ gtkcellareacontext.c \
gtkcelleditable.c \
gtkcelllayout.c \
gtkcellrenderer.c \
#include <gtk/gtkbuilder.h>
#include <gtk/gtkbutton.h>
#include <gtk/gtkcalendar.h>
+#include <gtk/gtkcellarea.h>
+#include <gtk/gtkcellareabox.h>
+#include <gtk/gtkcellareacontext.h>
#include <gtk/gtkcelleditable.h>
#include <gtk/gtkcelllayout.h>
#include <gtk/gtkcellrenderer.h>
--- /dev/null
+/* gtkcellarea.c
+ *
+ * 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
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "gtkintl.h"
+#include "gtkcelllayout.h"
+#include "gtkcellarea.h"
+#include "gtkcellareacontext.h"
+#include "gtkmarshalers.h"
+#include "gtkprivate.h"
+
+#include <gobject/gvaluecollector.h>
+
+
+/* GObjectClass */
+static void gtk_cell_area_dispose (GObject *object);
+static void gtk_cell_area_finalize (GObject *object);
+static void gtk_cell_area_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gtk_cell_area_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+/* GtkCellAreaClass */
+static gint gtk_cell_area_real_event (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ GdkEvent *event,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags);
+static void gtk_cell_area_real_apply_attributes (GtkCellArea *area,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gboolean is_expander,
+ gboolean is_expanded);
+static void gtk_cell_area_real_get_preferred_height_for_width (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height);
+static void gtk_cell_area_real_get_preferred_width_for_height (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width);
+static gboolean gtk_cell_area_real_can_focus (GtkCellArea *area);
+static gboolean gtk_cell_area_real_activate (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags);
+
+/* GtkCellLayoutIface */
+static void gtk_cell_area_cell_layout_init (GtkCellLayoutIface *iface);
+static void gtk_cell_area_pack_default (GtkCellLayout *cell_layout,
+ GtkCellRenderer *renderer,
+ gboolean expand);
+static void gtk_cell_area_clear (GtkCellLayout *cell_layout);
+static void gtk_cell_area_add_attribute (GtkCellLayout *cell_layout,
+ GtkCellRenderer *renderer,
+ const gchar *attribute,
+ gint column);
+static void gtk_cell_area_set_cell_data_func (GtkCellLayout *cell_layout,
+ GtkCellRenderer *cell,
+ GtkCellLayoutDataFunc func,
+ gpointer func_data,
+ GDestroyNotify destroy);
+static void gtk_cell_area_clear_attributes (GtkCellLayout *cell_layout,
+ GtkCellRenderer *renderer);
+static void gtk_cell_area_reorder (GtkCellLayout *cell_layout,
+ GtkCellRenderer *cell,
+ gint position);
+static GList *gtk_cell_area_get_cells (GtkCellLayout *cell_layout);
+
+
+/* Used in forall loop to check if a child renderer is present */
+typedef struct {
+ GtkCellRenderer *renderer;
+ gboolean has_renderer;
+} HasRendererCheck;
+
+/* Attribute/Cell metadata */
+typedef struct {
+ const gchar *attribute;
+ gint column;
+} CellAttribute;
+
+typedef struct {
+ GSList *attributes;
+
+ GtkCellLayoutDataFunc func;
+ gpointer data;
+ GDestroyNotify destroy;
+} CellInfo;
+
+static CellInfo *cell_info_new (GtkCellLayoutDataFunc func,
+ gpointer data,
+ GDestroyNotify destroy);
+static void cell_info_free (CellInfo *info);
+static CellAttribute *cell_attribute_new (GtkCellRenderer *renderer,
+ const gchar *attribute,
+ gint column);
+static void cell_attribute_free (CellAttribute *attribute);
+static gint cell_attribute_find (CellAttribute *cell_attribute,
+ const gchar *attribute);
+
+/* Internal functions/signal emissions */
+static void gtk_cell_area_add_editable (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkCellEditable *editable,
+ GdkRectangle *cell_area);
+static void gtk_cell_area_remove_editable (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkCellEditable *editable);
+static void gtk_cell_area_set_edit_widget (GtkCellArea *area,
+ GtkCellEditable *editable);
+static void gtk_cell_area_set_edited_cell (GtkCellArea *area,
+ GtkCellRenderer *renderer);
+
+
+/* Struct to pass data along while looping over
+ * cell renderers to apply attributes
+ */
+typedef struct {
+ GtkCellArea *area;
+ GtkTreeModel *model;
+ GtkTreeIter *iter;
+ gboolean is_expander;
+ gboolean is_expanded;
+} AttributeData;
+
+struct _GtkCellAreaPrivate
+{
+ /* The GtkCellArea bookkeeps any connected
+ * attributes in this hash table.
+ */
+ GHashTable *cell_info;
+
+ /* Current path is saved as a side-effect
+ * of gtk_cell_area_apply_attributes() */
+ gchar *current_path;
+
+ /* Current cell being edited and editable widget used */
+ GtkCellEditable *edit_widget;
+ GtkCellRenderer *edited_cell;
+
+ /* Signal connections to the editable widget */
+ gulong remove_widget_id;
+
+ /* Currently focused cell */
+ GtkCellRenderer *focus_cell;
+
+ /* Tracking which cells are focus siblings of focusable cells */
+ GHashTable *focus_siblings;
+
+ /* Detail string to pass to gtk_paint_*() functions */
+ gchar *style_detail;
+};
+
+enum {
+ PROP_0,
+ PROP_FOCUS_CELL,
+ PROP_EDITED_CELL,
+ PROP_EDIT_WIDGET
+};
+
+enum {
+ SIGNAL_APPLY_ATTRIBUTES,
+ SIGNAL_ADD_EDITABLE,
+ SIGNAL_REMOVE_EDITABLE,
+ SIGNAL_FOCUS_CHANGED,
+ LAST_SIGNAL
+};
+
+/* Keep the paramspec pool internal, no need to deliver notifications
+ * on cells. at least no percieved need for now */
+static GParamSpecPool *cell_property_pool = NULL;
+static guint cell_area_signals[LAST_SIGNAL] = { 0 };
+
+#define PARAM_SPEC_PARAM_ID(pspec) ((pspec)->param_id)
+#define PARAM_SPEC_SET_PARAM_ID(pspec, id) ((pspec)->param_id = (id))
+
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkCellArea, gtk_cell_area, G_TYPE_INITIALLY_UNOWNED,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
+ gtk_cell_area_cell_layout_init));
+
+static void
+gtk_cell_area_init (GtkCellArea *area)
+{
+ GtkCellAreaPrivate *priv;
+
+ area->priv = G_TYPE_INSTANCE_GET_PRIVATE (area,
+ GTK_TYPE_CELL_AREA,
+ GtkCellAreaPrivate);
+ priv = area->priv;
+
+ priv->cell_info = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ (GDestroyNotify)cell_info_free);
+
+ priv->focus_siblings = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ (GDestroyNotify)g_list_free);
+
+ priv->focus_cell = NULL;
+ priv->edited_cell = NULL;
+ priv->edit_widget = NULL;
+
+ priv->remove_widget_id = 0;
+}
+
+static void
+gtk_cell_area_class_init (GtkCellAreaClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ /* GObjectClass */
+ object_class->dispose = gtk_cell_area_dispose;
+ object_class->finalize = gtk_cell_area_finalize;
+ object_class->get_property = gtk_cell_area_get_property;
+ object_class->set_property = gtk_cell_area_set_property;
+
+ /* general */
+ class->add = NULL;
+ class->remove = NULL;
+ class->forall = NULL;
+ class->event = gtk_cell_area_real_event;
+ class->render = NULL;
+ class->apply_attributes = gtk_cell_area_real_apply_attributes;
+
+ /* geometry */
+ class->create_context = NULL;
+ class->get_request_mode = NULL;
+ class->get_preferred_width = NULL;
+ class->get_preferred_height = NULL;
+ class->get_preferred_height_for_width = gtk_cell_area_real_get_preferred_height_for_width;
+ class->get_preferred_width_for_height = gtk_cell_area_real_get_preferred_width_for_height;
+
+ /* focus */
+ class->can_focus = gtk_cell_area_real_can_focus;
+ class->focus = NULL;
+ class->activate = gtk_cell_area_real_activate;
+
+ /* Signals */
+
+ /**
+ * GtkCellArea::apply-attributes:
+ * @area: the #GtkCellArea to apply the attributes to
+ * @model: the #GtkTreeModel to apply the attributes from
+ * @iter: the #GtkTreeIter indicating which row to apply the attributes of
+ * @is_expander: whether the view shows children for this row
+ * @is_expanded: whether the view is currently showing the children of this row
+ *
+ * This signal is emitted whenever applying attributes to @area from @model
+ */
+ cell_area_signals[SIGNAL_APPLY_ATTRIBUTES] =
+ g_signal_new (I_("apply-attributes"),
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GtkCellAreaClass, apply_attributes),
+ NULL, NULL,
+ _gtk_marshal_VOID__OBJECT_BOXED_BOOLEAN_BOOLEAN,
+ G_TYPE_NONE, 4,
+ GTK_TYPE_TREE_MODEL,
+ GTK_TYPE_TREE_ITER,
+ G_TYPE_BOOLEAN,
+ G_TYPE_BOOLEAN);
+
+ /**
+ * GtkCellArea::add-editable:
+ * @area: the #GtkCellArea where editing started
+ * @renderer: the #GtkCellRenderer that started the edited
+ * @editable: the #GtkCellEditable widget to add
+ * @cell_area: the #GtkWidget relative #GdkRectangle coordinates
+ * where @editable should be added
+ * @path: the #GtkTreePath string this edit was initiated for
+ *
+ * Indicates that editing has started on @renderer and that @editable
+ * should be added to the owning cell layouting widget at @cell_area.
+ */
+ cell_area_signals[SIGNAL_ADD_EDITABLE] =
+ g_signal_new (I_("add-editable"),
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0, /* No class closure here */
+ NULL, NULL,
+ _gtk_marshal_VOID__OBJECT_OBJECT_BOXED_STRING,
+ G_TYPE_NONE, 4,
+ GTK_TYPE_CELL_RENDERER,
+ GTK_TYPE_CELL_EDITABLE,
+ GDK_TYPE_RECTANGLE,
+ G_TYPE_STRING);
+
+
+ /**
+ * GtkCellArea::remove-editable:
+ * @area: the #GtkCellArea where editing finished
+ * @renderer: the #GtkCellRenderer that finished editeding
+ * @editable: the #GtkCellEditable widget to remove
+ *
+ * Indicates that editing finished on @renderer and that @editable
+ * should be removed from the owning cell layouting widget.
+ */
+ cell_area_signals[SIGNAL_REMOVE_EDITABLE] =
+ g_signal_new (I_("remove-editable"),
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0, /* No class closure here */
+ NULL, NULL,
+ _gtk_marshal_VOID__OBJECT_OBJECT,
+ G_TYPE_NONE, 2,
+ GTK_TYPE_CELL_RENDERER,
+ GTK_TYPE_CELL_EDITABLE);
+
+ /**
+ * GtkCellArea::focus-changed:
+ * @area: the #GtkCellArea where focus changed
+ * @renderer: the #GtkCellRenderer that has focus
+ * @path: the current #GtkTreePath string set for @area
+ *
+ * Indicates that focus changed on this @area. This signal
+ * is emitted either as a result of focus handling or event
+ * handling.
+ *
+ * It's possible that the signal is emitted even if the
+ * currently focused renderer did not change, this is
+ * because focus may change to the same renderer in the
+ * same cell area for a different row of data.
+ */
+ cell_area_signals[SIGNAL_FOCUS_CHANGED] =
+ g_signal_new (I_("focus-changed"),
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0, /* No class closure here */
+ NULL, NULL,
+ _gtk_marshal_VOID__OBJECT_STRING,
+ G_TYPE_NONE, 2,
+ GTK_TYPE_CELL_RENDERER,
+ G_TYPE_STRING);
+
+ /* Properties */
+ g_object_class_install_property (object_class,
+ PROP_FOCUS_CELL,
+ g_param_spec_object
+ ("focus-cell",
+ P_("Focus Cell"),
+ P_("The cell which currently has focus"),
+ GTK_TYPE_CELL_RENDERER,
+ GTK_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_EDITED_CELL,
+ g_param_spec_object
+ ("edited-cell",
+ P_("Edited Cell"),
+ P_("The cell which is currently being edited"),
+ GTK_TYPE_CELL_RENDERER,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_EDIT_WIDGET,
+ g_param_spec_object
+ ("edit-widget",
+ P_("Edit Widget"),
+ P_("The widget currently editing the edited cell"),
+ GTK_TYPE_CELL_RENDERER,
+ G_PARAM_READABLE));
+
+ /* Pool for Cell Properties */
+ if (!cell_property_pool)
+ cell_property_pool = g_param_spec_pool_new (FALSE);
+
+ g_type_class_add_private (object_class, sizeof (GtkCellAreaPrivate));
+}
+
+/*************************************************************
+ * CellInfo Basics *
+ *************************************************************/
+static CellInfo *
+cell_info_new (GtkCellLayoutDataFunc func,
+ gpointer data,
+ GDestroyNotify destroy)
+{
+ CellInfo *info = g_slice_new0 (CellInfo);
+
+ info->func = func;
+ info->data = data;
+ info->destroy = destroy;
+
+ return info;
+}
+
+static void
+cell_info_free (CellInfo *info)
+{
+ if (info->destroy)
+ info->destroy (info->data);
+
+ g_slist_foreach (info->attributes, (GFunc)cell_attribute_free, NULL);
+ g_slist_free (info->attributes);
+
+ g_slice_free (CellInfo, info);
+}
+
+static CellAttribute *
+cell_attribute_new (GtkCellRenderer *renderer,
+ const gchar *attribute,
+ gint column)
+{
+ GParamSpec *pspec;
+
+ /* Check if the attribute really exists and point to
+ * the property string installed on the cell renderer
+ * class (dont dup the string)
+ */
+ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (renderer), attribute);
+
+ if (pspec)
+ {
+ CellAttribute *cell_attribute = g_slice_new (CellAttribute);
+
+ cell_attribute->attribute = pspec->name;
+ cell_attribute->column = column;
+
+ return cell_attribute;
+ }
+
+ return NULL;
+}
+
+static void
+cell_attribute_free (CellAttribute *attribute)
+{
+ g_slice_free (CellAttribute, attribute);
+}
+
+/* GCompareFunc for g_slist_find_custom() */
+static gint
+cell_attribute_find (CellAttribute *cell_attribute,
+ const gchar *attribute)
+{
+ return g_strcmp0 (cell_attribute->attribute, attribute);
+}
+
+/*************************************************************
+ * GObjectClass *
+ *************************************************************/
+static void
+gtk_cell_area_finalize (GObject *object)
+{
+ GtkCellArea *area = GTK_CELL_AREA (object);
+ GtkCellAreaPrivate *priv = area->priv;
+
+ /* All cell renderers should already be removed at this point,
+ * just kill our (empty) hash tables here.
+ */
+ g_hash_table_destroy (priv->cell_info);
+ g_hash_table_destroy (priv->focus_siblings);
+
+ g_free (priv->current_path);
+
+ G_OBJECT_CLASS (gtk_cell_area_parent_class)->finalize (object);
+}
+
+
+static void
+gtk_cell_area_dispose (GObject *object)
+{
+ /* This removes every cell renderer that may be added to the GtkCellArea,
+ * subclasses should be breaking references to the GtkCellRenderers
+ * at this point.
+ */
+ gtk_cell_layout_clear (GTK_CELL_LAYOUT (object));
+
+ /* Remove any ref to a focused/edited cell */
+ gtk_cell_area_set_focus_cell (GTK_CELL_AREA (object), NULL);
+ gtk_cell_area_set_edited_cell (GTK_CELL_AREA (object), NULL);
+ gtk_cell_area_set_edit_widget (GTK_CELL_AREA (object), NULL);
+
+ G_OBJECT_CLASS (gtk_cell_area_parent_class)->dispose (object);
+}
+
+static void
+gtk_cell_area_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkCellArea *area = GTK_CELL_AREA (object);
+
+ switch (prop_id)
+ {
+ case PROP_FOCUS_CELL:
+ gtk_cell_area_set_focus_cell (area, (GtkCellRenderer *)g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_cell_area_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkCellArea *area = GTK_CELL_AREA (object);
+ GtkCellAreaPrivate *priv = area->priv;
+
+ switch (prop_id)
+ {
+ case PROP_FOCUS_CELL:
+ g_value_set_object (value, priv->focus_cell);
+ break;
+ case PROP_EDITED_CELL:
+ g_value_set_object (value, priv->edited_cell);
+ break;
+ case PROP_EDIT_WIDGET:
+ g_value_set_object (value, priv->edit_widget);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/*************************************************************
+ * GtkCellAreaClass *
+ *************************************************************/
+static gint
+gtk_cell_area_real_event (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ GdkEvent *event,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ GtkCellAreaPrivate *priv = area->priv;
+
+ if (event->type == GDK_KEY_PRESS && (flags & GTK_CELL_RENDERER_FOCUSED) != 0)
+ {
+ GdkEventKey *key_event = (GdkEventKey *)event;
+
+ /* Cancel any edits in progress */
+ if (priv->edited_cell && (key_event->keyval == GDK_KEY_Escape))
+ {
+ gtk_cell_area_stop_editing (area, TRUE);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+apply_cell_attributes (GtkCellRenderer *renderer,
+ CellInfo *info,
+ AttributeData *data)
+{
+ CellAttribute *attribute;
+ GSList *list;
+ GValue value = { 0, };
+ gboolean is_expander;
+ gboolean is_expanded;
+
+ g_object_freeze_notify (G_OBJECT (renderer));
+
+ /* Whether a row expands or is presently expanded can only be
+ * provided by the view (as these states can vary across views
+ * accessing the same model).
+ */
+ g_object_get (renderer, "is-expander", &is_expander, NULL);
+ if (is_expander != data->is_expander)
+ g_object_set (renderer, "is-expander", data->is_expander, NULL);
+
+ g_object_get (renderer, "is-expanded", &is_expanded, NULL);
+ if (is_expanded != data->is_expanded)
+ g_object_set (renderer, "is-expanded", data->is_expanded, NULL);
+
+ /* Apply the attributes directly to the renderer */
+ for (list = info->attributes; list; list = list->next)
+ {
+ attribute = list->data;
+
+ gtk_tree_model_get_value (data->model, data->iter, attribute->column, &value);
+ g_object_set_property (G_OBJECT (renderer), attribute->attribute, &value);
+ g_value_unset (&value);
+ }
+
+ /* Call any GtkCellLayoutDataFunc that may have been set by the user
+ */
+ if (info->func)
+ info->func (GTK_CELL_LAYOUT (data->area), renderer,
+ data->model, data->iter, info->data);
+
+ g_object_thaw_notify (G_OBJECT (renderer));
+}
+
+static void
+gtk_cell_area_real_apply_attributes (GtkCellArea *area,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gboolean is_expander,
+ gboolean is_expanded)
+{
+
+ GtkCellAreaPrivate *priv;
+ AttributeData data;
+ GtkTreePath *path;
+
+ priv = area->priv;
+
+ /* Feed in data needed to apply to every renderer */
+ data.area = area;
+ data.model = tree_model;
+ data.iter = iter;
+ data.is_expander = is_expander;
+ data.is_expanded = is_expanded;
+
+ /* Go over any cells that have attributes or custom GtkCellLayoutDataFuncs and
+ * apply the data from the treemodel */
+ g_hash_table_foreach (priv->cell_info, (GHFunc)apply_cell_attributes, &data);
+
+ /* Update the currently applied path */
+ g_free (priv->current_path);
+ path = gtk_tree_model_get_path (tree_model, iter);
+ priv->current_path = gtk_tree_path_to_string (path);
+ gtk_tree_path_free (path);
+}
+
+static void
+gtk_cell_area_real_get_preferred_height_for_width (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ /* If the area doesnt do height-for-width, fallback on base preferred height */
+ GTK_CELL_AREA_GET_CLASS (area)->get_preferred_width (area, context, widget, minimum_height, natural_height);
+}
+
+static void
+gtk_cell_area_real_get_preferred_width_for_height (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ /* If the area doesnt do width-for-height, fallback on base preferred width */
+ GTK_CELL_AREA_GET_CLASS (area)->get_preferred_width (area, context, widget, minimum_width, natural_width);
+}
+
+static void
+get_can_focus (GtkCellRenderer *renderer,
+ gboolean *can_focus)
+{
+
+ if (gtk_cell_renderer_can_focus (renderer))
+ *can_focus = TRUE;
+}
+
+static gboolean
+gtk_cell_area_real_can_focus (GtkCellArea *area)
+{
+ gboolean can_focus = FALSE;
+
+ /* Checks if any renderer can focus for the currently applied
+ * attributes.
+ *
+ * Subclasses can override this in the case that they are also
+ * rendering widgets as well as renderers.
+ */
+ gtk_cell_area_forall (area, (GtkCellCallback)get_can_focus, &can_focus);
+
+ return can_focus;
+}
+
+static gboolean
+gtk_cell_area_real_activate (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ GtkCellAreaPrivate *priv = area->priv;
+ GdkRectangle background_area;
+
+ if (priv->focus_cell)
+ {
+ /* Get the allocation of the focused cell.
+ */
+ gtk_cell_area_get_cell_allocation (area, context, widget, priv->focus_cell,
+ cell_area, &background_area);
+
+ /* Activate or Edit the currently focused cell
+ *
+ * Currently just not sending an event, renderers afaics dont use
+ * the event argument anyway, worst case is we can synthesize one.
+ */
+ if (gtk_cell_area_activate_cell (area, widget, priv->focus_cell, NULL,
+ &background_area, flags))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*************************************************************
+ * GtkCellLayoutIface *
+ *************************************************************/
+static void
+gtk_cell_area_cell_layout_init (GtkCellLayoutIface *iface)
+{
+ iface->pack_start = gtk_cell_area_pack_default;
+ iface->pack_end = gtk_cell_area_pack_default;
+ iface->clear = gtk_cell_area_clear;
+ iface->add_attribute = gtk_cell_area_add_attribute;
+ iface->set_cell_data_func = gtk_cell_area_set_cell_data_func;
+ iface->clear_attributes = gtk_cell_area_clear_attributes;
+ iface->reorder = gtk_cell_area_reorder;
+ iface->get_cells = gtk_cell_area_get_cells;
+}
+
+static void
+gtk_cell_area_pack_default (GtkCellLayout *cell_layout,
+ GtkCellRenderer *renderer,
+ gboolean expand)
+{
+ gtk_cell_area_add (GTK_CELL_AREA (cell_layout), renderer);
+}
+
+static void
+gtk_cell_area_clear (GtkCellLayout *cell_layout)
+{
+ GtkCellArea *area = GTK_CELL_AREA (cell_layout);
+ GList *l, *cells =
+ gtk_cell_layout_get_cells (cell_layout);
+
+ for (l = cells; l; l = l->next)
+ {
+ GtkCellRenderer *renderer = l->data;
+ gtk_cell_area_remove (area, renderer);
+ }
+
+ g_list_free (cells);
+}
+
+static void
+gtk_cell_area_add_attribute (GtkCellLayout *cell_layout,
+ GtkCellRenderer *renderer,
+ const gchar *attribute,
+ gint column)
+{
+ gtk_cell_area_attribute_connect (GTK_CELL_AREA (cell_layout),
+ renderer, attribute, column);
+}
+
+static void
+gtk_cell_area_set_cell_data_func (GtkCellLayout *cell_layout,
+ GtkCellRenderer *renderer,
+ GtkCellLayoutDataFunc func,
+ gpointer func_data,
+ GDestroyNotify destroy)
+{
+ GtkCellArea *area = GTK_CELL_AREA (cell_layout);
+ GtkCellAreaPrivate *priv = area->priv;
+ CellInfo *info;
+
+ info = g_hash_table_lookup (priv->cell_info, renderer);
+
+ if (info)
+ {
+ if (info->destroy && info->data)
+ info->destroy (info->data);
+
+ if (func)
+ {
+ info->func = func;
+ info->data = func_data;
+ info->destroy = destroy;
+ }
+ else
+ {
+ info->func = NULL;
+ info->data = NULL;
+ info->destroy = NULL;
+ }
+ }
+ else
+ {
+ info = cell_info_new (func, func_data, destroy);
+
+ g_hash_table_insert (priv->cell_info, renderer, info);
+ }
+}
+
+static void
+gtk_cell_area_clear_attributes (GtkCellLayout *cell_layout,
+ GtkCellRenderer *renderer)
+{
+ GtkCellArea *area = GTK_CELL_AREA (cell_layout);
+ GtkCellAreaPrivate *priv = area->priv;
+ CellInfo *info;
+
+ info = g_hash_table_lookup (priv->cell_info, renderer);
+
+ if (info)
+ {
+ g_slist_foreach (info->attributes, (GFunc)cell_attribute_free, NULL);
+ g_slist_free (info->attributes);
+
+ info->attributes = NULL;
+ }
+}
+
+static void
+gtk_cell_area_reorder (GtkCellLayout *cell_layout,
+ GtkCellRenderer *cell,
+ gint position)
+{
+ g_warning ("GtkCellLayout::reorder not implemented for `%s'",
+ g_type_name (G_TYPE_FROM_INSTANCE (cell_layout)));
+}
+
+static void
+accum_cells (GtkCellRenderer *renderer,
+ GList **accum)
+{
+ *accum = g_list_prepend (*accum, renderer);
+}
+
+static GList *
+gtk_cell_area_get_cells (GtkCellLayout *cell_layout)
+{
+ GList *cells = NULL;
+
+ gtk_cell_area_forall (GTK_CELL_AREA (cell_layout),
+ (GtkCellCallback)accum_cells,
+ &cells);
+
+ return g_list_reverse (cells);
+}
+
+
+/*************************************************************
+ * API *
+ *************************************************************/
+
+/**
+ * gtk_cell_area_add:
+ * @area: a #GtkCellArea
+ * @renderer: the #GtkCellRenderer to add to @area
+ *
+ * Adds @renderer to @area with the default child cell properties.
+ */
+void
+gtk_cell_area_add (GtkCellArea *area,
+ GtkCellRenderer *renderer)
+{
+ GtkCellAreaClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+
+ class = GTK_CELL_AREA_GET_CLASS (area);
+
+ if (class->add)
+ class->add (area, renderer);
+ else
+ g_warning ("GtkCellAreaClass::add not implemented for `%s'",
+ g_type_name (G_TYPE_FROM_INSTANCE (area)));
+}
+
+/**
+ * gtk_cell_area_remove:
+ * @area: a #GtkCellArea
+ * @renderer: the #GtkCellRenderer to add to @area
+ *
+ * Removes @renderer from @area.
+ */
+void
+gtk_cell_area_remove (GtkCellArea *area,
+ GtkCellRenderer *renderer)
+{
+ GtkCellAreaClass *class;
+ GtkCellAreaPrivate *priv;
+ GList *renderers, *l;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+
+ class = GTK_CELL_AREA_GET_CLASS (area);
+ priv = area->priv;
+
+ /* Remove any custom attributes and custom cell data func here first */
+ g_hash_table_remove (priv->cell_info, renderer);
+
+ /* Remove focus siblings of this renderer */
+ g_hash_table_remove (priv->focus_siblings, renderer);
+
+ /* Remove this renderer from any focus renderer's sibling list */
+ renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area));
+
+ for (l = renderers; l; l = l->next)
+ {
+ GtkCellRenderer *focus_renderer = l->data;
+
+ if (gtk_cell_area_is_focus_sibling (area, focus_renderer, renderer))
+ {
+ gtk_cell_area_remove_focus_sibling (area, focus_renderer, renderer);
+ break;
+ }
+ }
+
+ g_list_free (renderers);
+
+ if (class->remove)
+ class->remove (area, renderer);
+ else
+ g_warning ("GtkCellAreaClass::remove not implemented for `%s'",
+ g_type_name (G_TYPE_FROM_INSTANCE (area)));
+}
+
+static void
+get_has_renderer (GtkCellRenderer *renderer,
+ HasRendererCheck *check)
+{
+ if (renderer == check->renderer)
+ check->has_renderer = TRUE;
+}
+
+/**
+ * gtk_cell_area_has_renderer:
+ * @area: a #GtkCellArea
+ * @renderer: the #GtkCellRenderer to check
+ *
+ * Checks if @area contains @renderer.
+ *
+ * Returns: %TRUE if @renderer is in the @area.
+ */
+gboolean
+gtk_cell_area_has_renderer (GtkCellArea *area,
+ GtkCellRenderer *renderer)
+{
+ HasRendererCheck check = { renderer, FALSE };
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE);
+ g_return_val_if_fail (GTK_IS_CELL_RENDERER (renderer), FALSE);
+
+ gtk_cell_area_forall (area, (GtkCellCallback)get_has_renderer, &check);
+
+ return check.has_renderer;
+}
+
+/**
+ * gtk_cell_area_forall
+ * @area: a #GtkCellArea
+ * @callback: the #GtkCellCallback to call
+ * @callback_data: user provided data pointer
+ *
+ * Calls @callback for every #GtkCellRenderer in @area.
+ */
+void
+gtk_cell_area_forall (GtkCellArea *area,
+ GtkCellCallback callback,
+ gpointer callback_data)
+{
+ GtkCellAreaClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (callback != NULL);
+
+ class = GTK_CELL_AREA_GET_CLASS (area);
+
+ if (class->forall)
+ class->forall (area, callback, callback_data);
+ else
+ g_warning ("GtkCellAreaClass::forall not implemented for `%s'",
+ g_type_name (G_TYPE_FROM_INSTANCE (area)));
+}
+
+/**
+ * gtk_cell_area_get_cell_allocation:
+ * @area: a #GtkCellArea
+ * @context: the #GtkCellAreaContext used to hold sizes for @area.
+ * @widget: the #GtkWidget that @area is rendering on
+ * @renderer: the #GtkCellRenderer to get the allocation for
+ * @cell_area: the whole allocated area for @area in @widget
+ * for this row
+ * @allocation: where to store the allocation for @renderer
+ *
+ * Derives the allocation of @renderer inside @area if @area
+ * were to be renderered in @cell_area.
+ */
+void
+gtk_cell_area_get_cell_allocation (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ GtkCellRenderer *renderer,
+ const GdkRectangle *cell_area,
+ GdkRectangle *allocation)
+{
+ GtkCellAreaClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+ g_return_if_fail (cell_area != NULL);
+ g_return_if_fail (allocation != NULL);
+
+ class = GTK_CELL_AREA_GET_CLASS (area);
+
+ if (class->get_cell_allocation)
+ class->get_cell_allocation (area, context, widget, renderer, cell_area, allocation);
+ else
+ g_warning ("GtkCellAreaClass::get_cell_allocation not implemented for `%s'",
+ g_type_name (G_TYPE_FROM_INSTANCE (area)));
+}
+
+/**
+ * gtk_cell_area_event:
+ * @area: a #GtkCellArea
+ * @context: the #GtkCellAreaContext for this row of data.
+ * @widget: the #GtkWidget that @area is rendering to
+ * @event: the #GdkEvent to handle
+ * @cell_area: the @widget relative coordinates for @area
+ * @flags: the #GtkCellRendererState for @area in this row.
+ *
+ * Delegates event handling to a #GtkCellArea.
+ *
+ * Returns: %TRUE if the event was handled by @area.
+ */
+gint
+gtk_cell_area_event (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ GdkEvent *event,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ GtkCellAreaClass *class;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), 0);
+ g_return_val_if_fail (GTK_IS_CELL_AREA_CONTEXT (context), 0);
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
+ g_return_val_if_fail (event != NULL, 0);
+ g_return_val_if_fail (cell_area != NULL, 0);
+
+ class = GTK_CELL_AREA_GET_CLASS (area);
+
+ if (class->event)
+ return class->event (area, context, widget, event, cell_area, flags);
+
+ g_warning ("GtkCellAreaClass::event not implemented for `%s'",
+ g_type_name (G_TYPE_FROM_INSTANCE (area)));
+ return 0;
+}
+
+/**
+ * gtk_cell_area_render:
+ * @area: a #GtkCellArea
+ * @context: the #GtkCellAreaContext for this row of data.
+ * @widget: the #GtkWidget that @area is rendering to
+ * @cr: the #cairo_t to render with
+ * @background_area: the @widget relative coordinates for @area's background
+ * @cell_area: the @widget relative coordinates for @area
+ * @flags: the #GtkCellRendererState for @area in this row.
+ * @paint_focus: whether @area should paint focus on focused cells for focused rows or not.
+ *
+ * Renders @area's cells according to @area's layout onto @widget at
+ * the given coordinates.
+ */
+void
+gtk_cell_area_render (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ cairo_t *cr,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags,
+ gboolean paint_focus)
+{
+ GtkCellAreaClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (background_area != NULL);
+ g_return_if_fail (cell_area != NULL);
+
+ class = GTK_CELL_AREA_GET_CLASS (area);
+
+ if (class->render)
+ class->render (area, context, widget, cr, background_area, cell_area, flags, paint_focus);
+ else
+ g_warning ("GtkCellAreaClass::render not implemented for `%s'",
+ g_type_name (G_TYPE_FROM_INSTANCE (area)));
+}
+
+/**
+ * gtk_cell_area_set_style_detail:
+ * @area: a #GtkCellArea
+ * @detail: the #GtkStyle detail string to set
+ *
+ * Sets the detail string used in any gtk_paint_*() functions
+ * used by @area.
+ */
+void
+gtk_cell_area_set_style_detail (GtkCellArea *area,
+ const gchar *detail)
+{
+ GtkCellAreaPrivate *priv;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+
+ priv = area->priv;
+
+ if (g_strcmp0 (priv->style_detail, detail) != 0)
+ {
+ g_free (priv->style_detail);
+ priv->style_detail = g_strdup (detail);
+ }
+}
+
+/**
+ * gtk_cell_area_get_style_detail:
+ * @area: a #GtkCellArea
+ *
+ * Gets the detail string used in any gtk_paint_*() functions
+ * used by @area.
+ *
+ * Returns: the detail string.
+ */
+G_CONST_RETURN gchar *
+gtk_cell_area_get_style_detail (GtkCellArea *area)
+{
+ GtkCellAreaPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL);
+
+ priv = area->priv;
+
+ return priv->style_detail;
+}
+
+/*************************************************************
+ * API: Geometry *
+ *************************************************************/
+/**
+ * gtk_cell_area_create_context:
+ * @area: a #GtkCellArea
+ *
+ * Creates a #GtkCellAreaContext to be used with @area for
+ * all purposes. #GtkCellAreaContext stores geometry information
+ * for rows for which it was operated on, it is important to use
+ * the same context for the same row of data at all times (i.e.
+ * one should render and handle events with the same #GtkCellAreaContext
+ * which was used to request the size of those rows of data).
+ *
+ * Returns: a newly created #GtkCellAreaContext which can be used with @area.
+ */
+GtkCellAreaContext *
+gtk_cell_area_create_context (GtkCellArea *area)
+{
+ GtkCellAreaClass *class;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL);
+
+ class = GTK_CELL_AREA_GET_CLASS (area);
+
+ if (class->create_context)
+ return class->create_context (area);
+
+ g_warning ("GtkCellAreaClass::create_context not implemented for `%s'",
+ g_type_name (G_TYPE_FROM_INSTANCE (area)));
+
+ return NULL;
+}
+
+
+/**
+ * gtk_cell_area_get_request_mode:
+ * @area: a #GtkCellArea
+ *
+ * Gets whether the area prefers a height-for-width layout
+ * or a width-for-height layout.
+ *
+ * Returns: The #GtkSizeRequestMode preferred by @area.
+ *
+ * Since: 3.0
+ */
+GtkSizeRequestMode
+gtk_cell_area_get_request_mode (GtkCellArea *area)
+{
+ GtkCellAreaClass *class;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area),
+ GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH);
+
+ class = GTK_CELL_AREA_GET_CLASS (area);
+
+ if (class->get_request_mode)
+ return class->get_request_mode (area);
+
+ g_warning ("GtkCellAreaClass::get_request_mode not implemented for `%s'",
+ g_type_name (G_TYPE_FROM_INSTANCE (area)));
+
+ return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+}
+
+/**
+ * gtk_cell_area_get_preferred_width:
+ * @area: a #GtkCellArea
+ * @context: the #GtkCellAreaContext to perform this request with
+ * @widget: the #GtkWidget where @area will be rendering
+ * @minimum_width: (out) (allow-none): location to store the minimum width, or %NULL
+ * @natural_width: (out) (allow-none): location to store the natural width, or %NULL
+ *
+ * Retrieves a cell area's initial minimum and natural width.
+ *
+ * @area will store some geometrical information in @context along the way,
+ * when requesting sizes over an arbitrary number of rows, its not important
+ * to check the @minimum_width and @natural_width of this call but rather to
+ * call gtk_cell_area_context_sum_preferred_width() and then consult
+ * gtk_cell_area_context_get_preferred_width().
+ *
+ *
+ * Since: 3.0
+ */
+void
+gtk_cell_area_get_preferred_width (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ GtkCellAreaClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ class = GTK_CELL_AREA_GET_CLASS (area);
+
+ if (class->get_preferred_width)
+ class->get_preferred_width (area, context, widget, minimum_width, natural_width);
+ else
+ g_warning ("GtkCellAreaClass::get_preferred_width not implemented for `%s'",
+ g_type_name (G_TYPE_FROM_INSTANCE (area)));
+}
+
+/**
+ * gtk_cell_area_get_preferred_height_for_width:
+ * @area: a #GtkCellArea
+ * @context: the #GtkCellAreaContext which has already been requested for widths.
+ * @widget: the #GtkWidget where @area will be rendering
+ * @width: the width for which to check the height of this area
+ * @minimum_height: (out) (allow-none): location to store the minimum height, or %NULL
+ * @natural_height: (out) (allow-none): location to store the natural height, or %NULL
+ *
+ * Retrieves a cell area's minimum and natural height if it would be given
+ * the specified @width.
+ *
+ * @area stores some geometrical information in @context along the way
+ * while calling gtk_cell_area_get_preferred_width(), it's important to
+ * perform a series of gtk_cell_area_get_preferred_width() requests with
+ * @context first and then call gtk_cell_area_get_preferred_height_for_width()
+ * on each cell area individually to get the height for width of each
+ * fully requested row.
+ *
+ * If at some point, the width of a single row changes, it should be
+ * requested with gtk_cell_area_get_preferred_width() again and then
+ * the full with of the requested rows checked again after calling
+ * gtk_cell_area_context_sum_preferred_width(), and then the height
+ * for width of each row needs to be requested again.
+ *
+ * Since: 3.0
+ */
+void
+gtk_cell_area_get_preferred_height_for_width (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ GtkCellAreaClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ class = GTK_CELL_AREA_GET_CLASS (area);
+ class->get_preferred_height_for_width (area, context, widget, width, minimum_height, natural_height);
+}
+
+
+/**
+ * gtk_cell_area_get_preferred_height:
+ * @area: a #GtkCellArea
+ * @context: the #GtkCellAreaContext to perform this request with
+ * @widget: the #GtkWidget where @area will be rendering
+ * @minimum_height: (out) (allow-none): location to store the minimum height, or %NULL
+ * @natural_height: (out) (allow-none): location to store the natural height, or %NULL
+ *
+ * Retrieves a cell area's initial minimum and natural height.
+ *
+ * @area will store some geometrical information in @context along the way,
+ * when requesting sizes over an arbitrary number of rows, its not important
+ * to check the @minimum_height and @natural_height of this call but rather to
+ * call gtk_cell_area_context_sum_preferred_height() and then consult
+ * gtk_cell_area_context_get_preferred_height().
+ *
+ * Since: 3.0
+ */
+void
+gtk_cell_area_get_preferred_height (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ GtkCellAreaClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ class = GTK_CELL_AREA_GET_CLASS (area);
+
+ if (class->get_preferred_height)
+ class->get_preferred_height (area, context, widget, minimum_height, natural_height);
+ else
+ g_warning ("GtkCellAreaClass::get_preferred_height not implemented for `%s'",
+ g_type_name (G_TYPE_FROM_INSTANCE (area)));
+}
+
+/**
+ * gtk_cell_area_get_preferred_width_for_height:
+ * @area: a #GtkCellArea
+ * @context: the #GtkCellAreaContext which has already been requested for widths.
+ * @widget: the #GtkWidget where @area will be rendering
+ * @height: the height for which to check the width of this area
+ * @minimum_width: (out) (allow-none): location to store the minimum width, or %NULL
+ * @natural_width: (out) (allow-none): location to store the natural width, or %NULL
+ *
+ * Retrieves a cell area's minimum and natural width if it would be given
+ * the specified @height.
+ *
+ * @area stores some geometrical information in @context along the way
+ * while calling gtk_cell_area_get_preferred_height(), it's important to
+ * perform a series of gtk_cell_area_get_preferred_height() requests with
+ * @context first and then call gtk_cell_area_get_preferred_width_for_height()
+ * on each cell area individually to get the height for width of each
+ * fully requested row.
+ *
+ * If at some point, the width of a single row changes, it should be
+ * requested with gtk_cell_area_get_preferred_width() again and then
+ * the full with of the requested rows checked again after calling
+ * gtk_cell_area_context_sum_preferred_width(), and then the height
+ * for width of each row needs to be requested again.
+ *
+ * Since: 3.0
+ */
+void
+gtk_cell_area_get_preferred_width_for_height (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ GtkCellAreaClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ class = GTK_CELL_AREA_GET_CLASS (area);
+ class->get_preferred_width_for_height (area, context, widget, height, minimum_width, natural_width);
+}
+
+/*************************************************************
+ * API: Attributes *
+ *************************************************************/
+
+/**
+ * gtk_cell_area_attribute_connect:
+ * @area: a #GtkCellArea
+ * @renderer: the #GtkCellRenderer to connect an attribute for
+ * @attribute: the attribute name
+ * @column: the #GtkTreeModel column to fetch attribute values from
+ *
+ * Connects an @attribute to apply values from @column for the
+ * #GtkTreeModel in use.
+ */
+void
+gtk_cell_area_attribute_connect (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *attribute,
+ gint column)
+{
+ GtkCellAreaPrivate *priv;
+ CellInfo *info;
+ CellAttribute *cell_attribute;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+ g_return_if_fail (attribute != NULL);
+ g_return_if_fail (gtk_cell_area_has_renderer (area, renderer));
+
+ priv = area->priv;
+ info = g_hash_table_lookup (priv->cell_info, renderer);
+
+ if (!info)
+ {
+ info = cell_info_new (NULL, NULL, NULL);
+
+ g_hash_table_insert (priv->cell_info, renderer, info);
+ }
+ else
+ {
+ GSList *node;
+
+ /* Check we are not adding the same attribute twice */
+ if ((node = g_slist_find_custom (info->attributes, attribute,
+ (GCompareFunc)cell_attribute_find)) != NULL)
+ {
+ cell_attribute = node->data;
+
+ g_warning ("Cannot connect attribute `%s' for cell renderer class `%s' "
+ "since `%s' is already attributed to column %d",
+ attribute,
+ g_type_name (G_TYPE_FROM_INSTANCE (area)),
+ attribute, cell_attribute->column);
+ return;
+ }
+ }
+
+ cell_attribute = cell_attribute_new (renderer, attribute, column);
+
+ if (!cell_attribute)
+ {
+ g_warning ("Cannot connect attribute `%s' for cell renderer class `%s' "
+ "since attribute does not exist",
+ attribute,
+ g_type_name (G_TYPE_FROM_INSTANCE (area)));
+ return;
+ }
+
+ info->attributes = g_slist_prepend (info->attributes, cell_attribute);
+}
+
+/**
+ * gtk_cell_area_attribute_disconnect:
+ * @area: a #GtkCellArea
+ * @renderer: the #GtkCellRenderer to disconnect an attribute for
+ * @attribute: the attribute name
+ *
+ * Disconnects @attribute for the @renderer in @area so that
+ * attribute will no longer be updated with values from the
+ * model.
+ */
+void
+gtk_cell_area_attribute_disconnect (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *attribute)
+{
+ GtkCellAreaPrivate *priv;
+ CellInfo *info;
+ CellAttribute *cell_attribute;
+ GSList *node;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+ g_return_if_fail (attribute != NULL);
+ g_return_if_fail (gtk_cell_area_has_renderer (area, renderer));
+
+ priv = area->priv;
+ info = g_hash_table_lookup (priv->cell_info, renderer);
+
+ if (info)
+ {
+ node = g_slist_find_custom (info->attributes, attribute,
+ (GCompareFunc)cell_attribute_find);
+ if (node)
+ {
+ cell_attribute = node->data;
+
+ cell_attribute_free (cell_attribute);
+
+ info->attributes = g_slist_delete_link (info->attributes, node);
+ }
+ }
+}
+
+/**
+ * gtk_cell_area_apply_attributes
+ * @area: a #GtkCellArea
+ * @tree_model: a #GtkTreeModel to pull values from
+ * @iter: the #GtkTreeIter in @tree_model to apply values for
+ * @is_expander: whether @iter has children
+ * @is_expanded: whether @iter is expanded in the view and
+ * children are visible
+ *
+ * Applies any connected attributes to the renderers in
+ * @area by pulling the values from @tree_model.
+ */
+void
+gtk_cell_area_apply_attributes (GtkCellArea *area,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gboolean is_expander,
+ gboolean is_expanded)
+{
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_TREE_MODEL (tree_model));
+ g_return_if_fail (iter != NULL);
+
+ g_signal_emit (area, cell_area_signals[SIGNAL_APPLY_ATTRIBUTES], 0,
+ tree_model, iter, is_expander, is_expanded);
+}
+
+/**
+ * gtk_cell_area_get_current_path_string:
+ * @area: a #GtkCellArea
+ *
+ * Gets the current #GtkTreePath string for the currently
+ * applied #GtkTreeIter, this is implicitly updated when
+ * gtk_cell_area_apply_attributes() is called and can be
+ * used to interact with renderers from #GtkCellArea
+ * subclasses.
+ */
+const gchar *
+gtk_cell_area_get_current_path_string (GtkCellArea *area)
+{
+ GtkCellAreaPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL);
+
+ priv = area->priv;
+
+ return priv->current_path;
+}
+
+
+/*************************************************************
+ * API: Cell Properties *
+ *************************************************************/
+/**
+ * gtk_cell_area_class_install_cell_property:
+ * @aclass: a #GtkCellAreaClass
+ * @property_id: the id for the property
+ * @pspec: the #GParamSpec for the property
+ *
+ * Installs a cell property on a cell area class.
+ */
+void
+gtk_cell_area_class_install_cell_property (GtkCellAreaClass *aclass,
+ guint property_id,
+ GParamSpec *pspec)
+{
+ g_return_if_fail (GTK_IS_CELL_AREA_CLASS (aclass));
+ g_return_if_fail (G_IS_PARAM_SPEC (pspec));
+ if (pspec->flags & G_PARAM_WRITABLE)
+ g_return_if_fail (aclass->set_cell_property != NULL);
+ if (pspec->flags & G_PARAM_READABLE)
+ g_return_if_fail (aclass->get_cell_property != NULL);
+ g_return_if_fail (property_id > 0);
+ g_return_if_fail (PARAM_SPEC_PARAM_ID (pspec) == 0); /* paranoid */
+ g_return_if_fail ((pspec->flags & (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY)) == 0);
+
+ if (g_param_spec_pool_lookup (cell_property_pool, pspec->name, G_OBJECT_CLASS_TYPE (aclass), TRUE))
+ {
+ g_warning (G_STRLOC ": class `%s' already contains a cell property named `%s'",
+ G_OBJECT_CLASS_NAME (aclass), pspec->name);
+ return;
+ }
+ g_param_spec_ref (pspec);
+ g_param_spec_sink (pspec);
+ PARAM_SPEC_SET_PARAM_ID (pspec, property_id);
+ g_param_spec_pool_insert (cell_property_pool, pspec, G_OBJECT_CLASS_TYPE (aclass));
+}
+
+/**
+ * gtk_cell_area_class_find_cell_property:
+ * @aclass: a #GtkCellAreaClass
+ * @property_name: the name of the child property to find
+ * @returns: (allow-none): the #GParamSpec of the child property or %NULL if @aclass has no
+ * child property with that name.
+ *
+ * Finds a cell property of a cell area class by name.
+ */
+GParamSpec*
+gtk_cell_area_class_find_cell_property (GtkCellAreaClass *aclass,
+ const gchar *property_name)
+{
+ g_return_val_if_fail (GTK_IS_CELL_AREA_CLASS (aclass), NULL);
+ g_return_val_if_fail (property_name != NULL, NULL);
+
+ return g_param_spec_pool_lookup (cell_property_pool,
+ property_name,
+ G_OBJECT_CLASS_TYPE (aclass),
+ TRUE);
+}
+
+/**
+ * gtk_cell_area_class_list_cell_properties:
+ * @aclass: a #GtkCellAreaClass
+ * @n_properties: location to return the number of cell properties found
+ * @returns: a newly allocated %NULL-terminated array of #GParamSpec*.
+ * The array must be freed with g_free().
+ *
+ * Returns all cell properties of a cell area class.
+ */
+GParamSpec**
+gtk_cell_area_class_list_cell_properties (GtkCellAreaClass *aclass,
+ guint *n_properties)
+{
+ GParamSpec **pspecs;
+ guint n;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA_CLASS (aclass), NULL);
+
+ pspecs = g_param_spec_pool_list (cell_property_pool,
+ G_OBJECT_CLASS_TYPE (aclass),
+ &n);
+ if (n_properties)
+ *n_properties = n;
+
+ return pspecs;
+}
+
+/**
+ * gtk_cell_area_add_with_properties:
+ * @area: a #GtkCellArea
+ * @renderer: a #GtkCellRenderer to be placed inside @area
+ * @first_prop_name: the name of the first cell property to set
+ * @Varargs: a %NULL-terminated list of property names and values, starting
+ * with @first_prop_name
+ *
+ * Adds @renderer to @area, setting cell properties at the same time.
+ * See gtk_cell_area_add() and gtk_cell_area_child_set() for more details.
+ **/
+void
+gtk_cell_area_add_with_properties (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *first_prop_name,
+ ...)
+{
+ GtkCellAreaClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+
+ class = GTK_CELL_AREA_GET_CLASS (area);
+
+ if (class->add)
+ {
+ va_list var_args;
+
+ class->add (area, renderer);
+
+ va_start (var_args, first_prop_name);
+ gtk_cell_area_cell_set_valist (area, renderer, first_prop_name, var_args);
+ va_end (var_args);
+ }
+ else
+ g_warning ("GtkCellAreaClass::add not implemented for `%s'",
+ g_type_name (G_TYPE_FROM_INSTANCE (area)));
+}
+
+/**
+ * gtk_cell_area_cell_set:
+ * @area: a #GtkCellArea
+ * @renderer: a #GtkCellRenderer which is a cell inside @area
+ * @first_prop_name: the name of the first cell property to set
+ * @Varargs: a %NULL-terminated list of property names and values, starting
+ * with @first_prop_name
+ *
+ * Sets one or more cell properties for @cell in @area.
+ **/
+void
+gtk_cell_area_cell_set (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *first_prop_name,
+ ...)
+{
+ va_list var_args;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+
+ va_start (var_args, first_prop_name);
+ gtk_cell_area_cell_set_valist (area, renderer, first_prop_name, var_args);
+ va_end (var_args);
+}
+
+/**
+ * gtk_cell_area_cell_get:
+ * @area: a #GtkCellArea
+ * @renderer: a #GtkCellRenderer which is inside @area
+ * @first_prop_name: the name of the first cell property to get
+ * @Varargs: return location for the first cell property, followed
+ * optionally by more name/return location pairs, followed by %NULL
+ *
+ * Gets the values of one or more cell properties for @renderer in @area.
+ **/
+void
+gtk_cell_area_cell_get (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *first_prop_name,
+ ...)
+{
+ va_list var_args;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+
+ va_start (var_args, first_prop_name);
+ gtk_cell_area_cell_get_valist (area, renderer, first_prop_name, var_args);
+ va_end (var_args);
+}
+
+static inline void
+area_get_cell_property (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GParamSpec *pspec,
+ GValue *value)
+{
+ GtkCellAreaClass *class = g_type_class_peek (pspec->owner_type);
+
+ class->get_cell_property (area, renderer, PARAM_SPEC_PARAM_ID (pspec), value, pspec);
+}
+
+static inline void
+area_set_cell_property (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GParamSpec *pspec,
+ const GValue *value)
+{
+ GValue tmp_value = { 0, };
+ GtkCellAreaClass *class = g_type_class_peek (pspec->owner_type);
+
+ /* provide a copy to work from, convert (if necessary) and validate */
+ g_value_init (&tmp_value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+ if (!g_value_transform (value, &tmp_value))
+ g_warning ("unable to set cell property `%s' of type `%s' from value of type `%s'",
+ pspec->name,
+ g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)),
+ G_VALUE_TYPE_NAME (value));
+ else if (g_param_value_validate (pspec, &tmp_value) && !(pspec->flags & G_PARAM_LAX_VALIDATION))
+ {
+ gchar *contents = g_strdup_value_contents (value);
+
+ g_warning ("value \"%s\" of type `%s' is invalid for property `%s' of type `%s'",
+ contents,
+ G_VALUE_TYPE_NAME (value),
+ pspec->name,
+ g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)));
+ g_free (contents);
+ }
+ else
+ {
+ class->set_cell_property (area, renderer, PARAM_SPEC_PARAM_ID (pspec), &tmp_value, pspec);
+ }
+ g_value_unset (&tmp_value);
+}
+
+/**
+ * gtk_cell_area_cell_set_valist:
+ * @area: a #GtkCellArea
+ * @renderer: a #GtkCellRenderer which inside @area
+ * @first_property_name: the name of the first cell property to set
+ * @var_args: a %NULL-terminated list of property names and values, starting
+ * with @first_prop_name
+ *
+ * Sets one or more cell properties for @renderer in @area.
+ **/
+void
+gtk_cell_area_cell_set_valist (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *first_property_name,
+ va_list var_args)
+{
+ const gchar *name;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+
+ name = first_property_name;
+ while (name)
+ {
+ GValue value = { 0, };
+ gchar *error = NULL;
+ GParamSpec *pspec =
+ g_param_spec_pool_lookup (cell_property_pool, name,
+ G_OBJECT_TYPE (area), TRUE);
+ if (!pspec)
+ {
+ g_warning ("%s: cell area class `%s' has no cell property named `%s'",
+ G_STRLOC, G_OBJECT_TYPE_NAME (area), name);
+ break;
+ }
+ if (!(pspec->flags & G_PARAM_WRITABLE))
+ {
+ g_warning ("%s: cell property `%s' of cell area class `%s' is not writable",
+ G_STRLOC, pspec->name, G_OBJECT_TYPE_NAME (area));
+ break;
+ }
+
+ g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+ G_VALUE_COLLECT (&value, var_args, 0, &error);
+ if (error)
+ {
+ g_warning ("%s: %s", G_STRLOC, error);
+ g_free (error);
+
+ /* we purposely leak the value here, it might not be
+ * in a sane state if an error condition occoured
+ */
+ break;
+ }
+ area_set_cell_property (area, renderer, pspec, &value);
+ g_value_unset (&value);
+ name = va_arg (var_args, gchar*);
+ }
+}
+
+/**
+ * gtk_cell_area_cell_get_valist:
+ * @area: a #GtkCellArea
+ * @renderer: a #GtkCellRenderer inside @area
+ * @first_property_name: the name of the first property to get
+ * @var_args: return location for the first property, followed
+ * optionally by more name/return location pairs, followed by %NULL
+ *
+ * Gets the values of one or more cell properties for @renderer in @area.
+ **/
+void
+gtk_cell_area_cell_get_valist (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *first_property_name,
+ va_list var_args)
+{
+ const gchar *name;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+
+ name = first_property_name;
+ while (name)
+ {
+ GValue value = { 0, };
+ GParamSpec *pspec;
+ gchar *error;
+
+ pspec = g_param_spec_pool_lookup (cell_property_pool, name,
+ G_OBJECT_TYPE (area), TRUE);
+ if (!pspec)
+ {
+ g_warning ("%s: cell area class `%s' has no cell property named `%s'",
+ G_STRLOC, G_OBJECT_TYPE_NAME (area), name);
+ break;
+ }
+ if (!(pspec->flags & G_PARAM_READABLE))
+ {
+ g_warning ("%s: cell property `%s' of cell area class `%s' is not readable",
+ G_STRLOC, pspec->name, G_OBJECT_TYPE_NAME (area));
+ break;
+ }
+
+ g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+ area_get_cell_property (area, renderer, pspec, &value);
+ G_VALUE_LCOPY (&value, var_args, 0, &error);
+ if (error)
+ {
+ g_warning ("%s: %s", G_STRLOC, error);
+ g_free (error);
+ g_value_unset (&value);
+ break;
+ }
+ g_value_unset (&value);
+ name = va_arg (var_args, gchar*);
+ }
+}
+
+/**
+ * gtk_cell_area_cell_set_property:
+ * @area: a #GtkCellArea
+ * @renderer: a #GtkCellRenderer inside @area
+ * @property_name: the name of the cell property to set
+ * @value: the value to set the cell property to
+ *
+ * Sets a cell property for @renderer in @area.
+ **/
+void
+gtk_cell_area_cell_set_property (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *property_name,
+ const GValue *value)
+{
+ GParamSpec *pspec;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+ g_return_if_fail (property_name != NULL);
+ g_return_if_fail (G_IS_VALUE (value));
+
+ pspec = g_param_spec_pool_lookup (cell_property_pool, property_name,
+ G_OBJECT_TYPE (area), TRUE);
+ if (!pspec)
+ g_warning ("%s: cell area class `%s' has no cell property named `%s'",
+ G_STRLOC, G_OBJECT_TYPE_NAME (area), property_name);
+ else if (!(pspec->flags & G_PARAM_WRITABLE))
+ g_warning ("%s: cell property `%s' of cell area class `%s' is not writable",
+ G_STRLOC, pspec->name, G_OBJECT_TYPE_NAME (area));
+ else
+ {
+ area_set_cell_property (area, renderer, pspec, value);
+ }
+}
+
+/**
+ * gtk_cell_area_cell_get_property:
+ * @area: a #GtkCellArea
+ * @renderer: a #GtkCellRenderer inside @area
+ * @property_name: the name of the property to get
+ * @value: a location to return the value
+ *
+ * Gets the value of a cell property for @renderer in @area.
+ **/
+void
+gtk_cell_area_cell_get_property (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *property_name,
+ GValue *value)
+{
+ GParamSpec *pspec;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+ g_return_if_fail (property_name != NULL);
+ g_return_if_fail (G_IS_VALUE (value));
+
+ pspec = g_param_spec_pool_lookup (cell_property_pool, property_name,
+ G_OBJECT_TYPE (area), TRUE);
+ if (!pspec)
+ g_warning ("%s: cell area class `%s' has no cell property named `%s'",
+ G_STRLOC, G_OBJECT_TYPE_NAME (area), property_name);
+ else if (!(pspec->flags & G_PARAM_READABLE))
+ g_warning ("%s: cell property `%s' of cell area class `%s' is not readable",
+ G_STRLOC, pspec->name, G_OBJECT_TYPE_NAME (area));
+ else
+ {
+ GValue *prop_value, tmp_value = { 0, };
+
+ /* auto-conversion of the callers value type
+ */
+ if (G_VALUE_TYPE (value) == G_PARAM_SPEC_VALUE_TYPE (pspec))
+ {
+ g_value_reset (value);
+ prop_value = value;
+ }
+ else if (!g_value_type_transformable (G_PARAM_SPEC_VALUE_TYPE (pspec), G_VALUE_TYPE (value)))
+ {
+ g_warning ("can't retrieve cell property `%s' of type `%s' as value of type `%s'",
+ pspec->name,
+ g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)),
+ G_VALUE_TYPE_NAME (value));
+ return;
+ }
+ else
+ {
+ g_value_init (&tmp_value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+ prop_value = &tmp_value;
+ }
+
+ area_get_cell_property (area, renderer, pspec, prop_value);
+
+ if (prop_value != value)
+ {
+ g_value_transform (prop_value, value);
+ g_value_unset (&tmp_value);
+ }
+ }
+}
+
+/*************************************************************
+ * API: Focus *
+ *************************************************************/
+
+/**
+ * gtk_cell_area_can_focus:
+ * @area: a #GtkCellArea
+ *
+ * Returns whether the area can receive keyboard focus,
+ * after applying new attributes to @area.
+ *
+ * Returns: whether @area can receive focus.
+ */
+gboolean
+gtk_cell_area_can_focus (GtkCellArea *area)
+{
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE);
+
+ return GTK_CELL_AREA_GET_CLASS (area)->can_focus (area);
+}
+
+/**
+ * gtk_cell_area_focus:
+ * @area: a #GtkCellArea
+ * @direction: the #GtkDirectionType
+ *
+ * This should be called by the @area's owning layout widget
+ * when focus is to be passed to @area, or moved within @area
+ * for a given @direction and row data.
+ *
+ * Implementing #GtkCellArea classes should implement this
+ * method to receive and navigate focus in it's own way particular
+ * to how it lays out cells.
+ *
+ * Returns: %TRUE if focus remains inside @area as a result of this call.
+ */
+gboolean
+gtk_cell_area_focus (GtkCellArea *area,
+ GtkDirectionType direction)
+{
+ GtkCellAreaClass *class;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE);
+
+ class = GTK_CELL_AREA_GET_CLASS (area);
+
+ if (class->focus)
+ return class->focus (area, direction);
+
+ g_warning ("GtkCellAreaClass::focus not implemented for `%s'",
+ g_type_name (G_TYPE_FROM_INSTANCE (area)));
+
+ return FALSE;
+}
+
+/**
+ * gtk_cell_area_activate:
+ * @area: a #GtkCellArea
+ * @context: the #GtkCellAreaContext in context with the current row data
+ * @widget: the #GtkWidget that @area is rendering on
+ * @cell_area: the size and location of @area relative to @widget's allocation
+ * @flags: the #GtkCellRendererState flags for @area for this row of data.
+ *
+ * Activates @area, usually by activating the currently focused
+ * cell, however some subclasses which embed widgets in the area
+ * can also activate a widget if it currently has the focus.
+ *
+ * Returns: Whether @area was successfully activated.
+ */
+gboolean
+gtk_cell_area_activate (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE);
+
+ return GTK_CELL_AREA_GET_CLASS (area)->activate (area, context, widget, cell_area, flags);
+}
+
+
+/**
+ * gtk_cell_area_set_focus_cell:
+ * @area: a #GtkCellArea
+ * @focus_cell: the #GtkCellRenderer to give focus to
+ *
+ * This is generally called from #GtkCellArea implementations
+ * either gtk_cell_area_grab_focus() or gtk_cell_area_update_focus()
+ * is called. It's also up to the #GtkCellArea implementation
+ * to update the focused cell when receiving events from
+ * gtk_cell_area_event() appropriately.
+ */
+void
+gtk_cell_area_set_focus_cell (GtkCellArea *area,
+ GtkCellRenderer *renderer)
+{
+ GtkCellAreaPrivate *priv;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (renderer == NULL || GTK_IS_CELL_RENDERER (renderer));
+
+ priv = area->priv;
+
+ if (priv->focus_cell != renderer)
+ {
+ if (priv->focus_cell)
+ g_object_unref (priv->focus_cell);
+
+ priv->focus_cell = renderer;
+
+ if (priv->focus_cell)
+ g_object_ref (priv->focus_cell);
+
+ g_object_notify (G_OBJECT (area), "focus-cell");
+ }
+
+ /* Signal that the current focus renderer for this path changed
+ * (it may be that the focus cell did not change, but the row
+ * may have changed so we need to signal it) */
+ g_signal_emit (area, cell_area_signals[SIGNAL_FOCUS_CHANGED], 0,
+ priv->focus_cell, priv->current_path);
+
+}
+
+/**
+ * gtk_cell_area_get_focus_cell:
+ * @area: a #GtkCellArea
+ *
+ * Retrieves the currently focused cell for @area
+ *
+ * Returns: the currently focused cell in @area.
+ */
+GtkCellRenderer *
+gtk_cell_area_get_focus_cell (GtkCellArea *area)
+{
+ GtkCellAreaPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL);
+
+ priv = area->priv;
+
+ return priv->focus_cell;
+}
+
+
+/*************************************************************
+ * API: Focus Siblings *
+ *************************************************************/
+
+/**
+ * gtk_cell_area_add_focus_sibling:
+ * @area: a #GtkCellArea
+ * @renderer: the #GtkCellRenderer expected to have focus
+ * @sibling: the #GtkCellRenderer to add to @renderer's focus area
+ *
+ * Adds @sibling to @renderer's focusable area, focus will be drawn
+ * around @renderer and all of it's siblings if @renderer can
+ * focus for a given row.
+ *
+ * Events handled by focus siblings can also activate the given
+ * focusable @renderer.
+ */
+void
+gtk_cell_area_add_focus_sibling (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkCellRenderer *sibling)
+{
+ GtkCellAreaPrivate *priv;
+ GList *siblings;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (sibling));
+ g_return_if_fail (renderer != sibling);
+ g_return_if_fail (gtk_cell_area_has_renderer (area, renderer));
+ g_return_if_fail (gtk_cell_area_has_renderer (area, sibling));
+ g_return_if_fail (!gtk_cell_area_is_focus_sibling (area, renderer, sibling));
+
+ /* XXX We should also check that sibling is not in any other renderer's sibling
+ * list already, a renderer can be sibling of only one focusable renderer
+ * at a time.
+ */
+
+ priv = area->priv;
+
+ siblings = g_hash_table_lookup (priv->focus_siblings, renderer);
+
+ if (siblings)
+ siblings = g_list_append (siblings, sibling);
+ else
+ {
+ siblings = g_list_append (siblings, sibling);
+ g_hash_table_insert (priv->focus_siblings, renderer, siblings);
+ }
+}
+
+/**
+ * gtk_cell_area_remove_focus_sibling:
+ * @area: a #GtkCellArea
+ * @renderer: the #GtkCellRenderer expected to have focus
+ * @sibling: the #GtkCellRenderer to remove from @renderer's focus area
+ *
+ * Removes @sibling from @renderer's focus sibling list
+ * (see gtk_cell_area_add_focus_sibling()).
+ */
+void
+gtk_cell_area_remove_focus_sibling (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkCellRenderer *sibling)
+{
+ GtkCellAreaPrivate *priv;
+ GList *siblings;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (sibling));
+ g_return_if_fail (gtk_cell_area_is_focus_sibling (area, renderer, sibling));
+
+ priv = area->priv;
+
+ siblings = g_hash_table_lookup (priv->focus_siblings, renderer);
+
+ siblings = g_list_copy (siblings);
+ siblings = g_list_remove (siblings, sibling);
+
+ if (!siblings)
+ g_hash_table_remove (priv->focus_siblings, renderer);
+ else
+ g_hash_table_insert (priv->focus_siblings, renderer, siblings);
+}
+
+/**
+ * gtk_cell_area_is_focus_sibling:
+ * @area: a #GtkCellArea
+ * @renderer: the #GtkCellRenderer expected to have focus
+ * @sibling: the #GtkCellRenderer to check against @renderer's sibling list
+ *
+ * Returns %TRUE if @sibling is one of @renderer's focus siblings
+ * (see gtk_cell_area_add_focus_sibling()).
+ */
+gboolean
+gtk_cell_area_is_focus_sibling (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkCellRenderer *sibling)
+{
+ GtkCellAreaPrivate *priv;
+ GList *siblings, *l;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE);
+ g_return_val_if_fail (GTK_IS_CELL_RENDERER (renderer), FALSE);
+ g_return_val_if_fail (GTK_IS_CELL_RENDERER (sibling), FALSE);
+
+ priv = area->priv;
+
+ siblings = g_hash_table_lookup (priv->focus_siblings, renderer);
+
+ for (l = siblings; l; l = l->next)
+ {
+ GtkCellRenderer *a_sibling = l->data;
+
+ if (a_sibling == sibling)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * gtk_cell_area_get_focus_siblings:
+ * @area: a #GtkCellArea
+ * @renderer: the #GtkCellRenderer expected to have focus
+ *
+ * Gets the focus sibling cell renderers for @renderer.
+ *
+ * Returns: A #GList of #GtkCellRenderers. The returned list is internal and should not be freed.
+ */
+const GList *
+gtk_cell_area_get_focus_siblings (GtkCellArea *area,
+ GtkCellRenderer *renderer)
+{
+ GtkCellAreaPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL);
+ g_return_val_if_fail (GTK_IS_CELL_RENDERER (renderer), NULL);
+
+ priv = area->priv;
+
+ return g_hash_table_lookup (priv->focus_siblings, renderer);
+}
+
+/**
+ * gtk_cell_area_get_focus_from_sibling:
+ * @area: a #GtkCellArea
+ * @renderer: the #GtkCellRenderer
+ *
+ * Gets the #GtkCellRenderer which is expected to be focusable
+ * for which @renderer is, or may be a sibling.
+ *
+ * This is handy for #GtkCellArea subclasses when handling events,
+ * after determining the renderer at the event location it can
+ * then chose to activate the focus cell for which the event
+ * cell may have been a sibling.
+ *
+ * Returns: the #GtkCellRenderer for which @renderer is a sibling, or %NULL.
+ */
+GtkCellRenderer *
+gtk_cell_area_get_focus_from_sibling (GtkCellArea *area,
+ GtkCellRenderer *renderer)
+{
+ GtkCellRenderer *ret_renderer = NULL;
+ GList *renderers, *l;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL);
+ g_return_val_if_fail (GTK_IS_CELL_RENDERER (renderer), NULL);
+
+ renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area));
+
+ for (l = renderers; l; l = l->next)
+ {
+ GtkCellRenderer *a_renderer = l->data;
+ const GList *list;
+
+ for (list = gtk_cell_area_get_focus_siblings (area, a_renderer);
+ list; list = list->next)
+ {
+ GtkCellRenderer *sibling_renderer = list->data;
+
+ if (sibling_renderer == renderer)
+ {
+ ret_renderer = a_renderer;
+ break;
+ }
+ }
+ }
+ g_list_free (renderers);
+
+ return ret_renderer;
+}
+
+/*************************************************************
+ * API: Cell Activation/Editing *
+ *************************************************************/
+static void
+gtk_cell_area_add_editable (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkCellEditable *editable,
+ GdkRectangle *cell_area)
+{
+ g_signal_emit (area, cell_area_signals[SIGNAL_ADD_EDITABLE], 0,
+ renderer, editable, cell_area, area->priv->current_path);
+}
+
+static void
+gtk_cell_area_remove_editable (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkCellEditable *editable)
+{
+ g_signal_emit (area, cell_area_signals[SIGNAL_REMOVE_EDITABLE], 0, renderer, editable);
+}
+
+static void
+cell_area_remove_widget_cb (GtkCellEditable *editable,
+ GtkCellArea *area)
+{
+ GtkCellAreaPrivate *priv = area->priv;
+
+ g_assert (priv->edit_widget == editable);
+ g_assert (priv->edited_cell != NULL);
+
+ gtk_cell_area_remove_editable (area, priv->edited_cell, priv->edit_widget);
+
+ /* Now that we're done with editing the widget and it can be removed,
+ * remove our references to the widget and disconnect handlers */
+ gtk_cell_area_set_edited_cell (area, NULL);
+ gtk_cell_area_set_edit_widget (area, NULL);
+}
+
+static void
+gtk_cell_area_set_edited_cell (GtkCellArea *area,
+ GtkCellRenderer *renderer)
+{
+ GtkCellAreaPrivate *priv;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (renderer == NULL || GTK_IS_CELL_RENDERER (renderer));
+
+ priv = area->priv;
+
+ if (priv->edited_cell != renderer)
+ {
+ if (priv->edited_cell)
+ g_object_unref (priv->edited_cell);
+
+ priv->edited_cell = renderer;
+
+ if (priv->edited_cell)
+ g_object_ref (priv->edited_cell);
+
+ g_object_notify (G_OBJECT (area), "edited-cell");
+ }
+}
+
+static void
+gtk_cell_area_set_edit_widget (GtkCellArea *area,
+ GtkCellEditable *editable)
+{
+ GtkCellAreaPrivate *priv;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (editable == NULL || GTK_IS_CELL_EDITABLE (editable));
+
+ priv = area->priv;
+
+ if (priv->edit_widget != editable)
+ {
+ if (priv->edit_widget)
+ {
+ g_signal_handler_disconnect (priv->edit_widget, priv->remove_widget_id);
+
+ g_object_unref (priv->edit_widget);
+ }
+
+ priv->edit_widget = editable;
+
+ if (priv->edit_widget)
+ {
+ priv->remove_widget_id =
+ g_signal_connect (priv->edit_widget, "remove-widget",
+ G_CALLBACK (cell_area_remove_widget_cb), area);
+
+ g_object_ref (priv->edit_widget);
+ }
+
+ g_object_notify (G_OBJECT (area), "edit-widget");
+ }
+}
+
+/**
+ * gtk_cell_area_get_edited_cell:
+ * @area: a #GtkCellArea
+ *
+ * Gets the #GtkCellRenderer in @area that is currently
+ * being edited.
+ *
+ * Returns: The currently edited #GtkCellRenderer
+ */
+GtkCellRenderer *
+gtk_cell_area_get_edited_cell (GtkCellArea *area)
+{
+ GtkCellAreaPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL);
+
+ priv = area->priv;
+
+ return priv->edited_cell;
+}
+
+/**
+ * gtk_cell_area_get_edit_widget:
+ * @area: a #GtkCellArea
+ *
+ * Gets the #GtkCellEditable widget currently used
+ * to edit the currently edited cell.
+ *
+ * Returns: The currently active #GtkCellEditable widget
+ */
+GtkCellEditable *
+gtk_cell_area_get_edit_widget (GtkCellArea *area)
+{
+ GtkCellAreaPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL);
+
+ priv = area->priv;
+
+ return priv->edit_widget;
+}
+
+/**
+ * gtk_cell_area_activate_cell:
+ * @area: a #GtkCellArea
+ * @widget: the #GtkWidget that @area is rendering onto
+ * @renderer: the #GtkCellRenderer in @area to activate
+ * @event: the #GdkEvent for which cell activation should occur
+ * @cell_area: the #GdkRectangle in @widget relative coordinates
+ * of @renderer for the current row.
+ * @flags: the #GtkCellRendererState for @renderer
+ *
+ * This is used by #GtkCellArea subclasses when handling events
+ * to activate cells, the base #GtkCellArea class activates cells
+ * for keyboard events for free in it's own GtkCellArea->activate()
+ * implementation.
+ *
+ * Returns: whether cell activation was successful
+ */
+gboolean
+gtk_cell_area_activate_cell (GtkCellArea *area,
+ GtkWidget *widget,
+ GtkCellRenderer *renderer,
+ GdkEvent *event,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ GtkCellRendererMode mode;
+ GdkRectangle inner_area;
+ GtkCellAreaPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE);
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
+ g_return_val_if_fail (GTK_IS_CELL_RENDERER (renderer), FALSE);
+ g_return_val_if_fail (cell_area != NULL, FALSE);
+
+ priv = area->priv;
+
+ /* Remove margins from the background area to produce the cell area.
+ *
+ * XXX Maybe have to do some rtl mode treatment here...
+ */
+ gtk_cell_area_inner_cell_area (area, widget, cell_area, &inner_area);
+
+ g_object_get (renderer, "mode", &mode, NULL);
+
+ if (mode == GTK_CELL_RENDERER_MODE_ACTIVATABLE)
+ {
+ if (gtk_cell_renderer_activate (renderer,
+ event, widget,
+ priv->current_path,
+ cell_area,
+ &inner_area,
+ flags))
+ return TRUE;
+ }
+ else if (mode == GTK_CELL_RENDERER_MODE_EDITABLE)
+ {
+ GtkCellEditable *editable_widget;
+
+ editable_widget =
+ gtk_cell_renderer_start_editing (renderer,
+ event, widget,
+ priv->current_path,
+ cell_area,
+ &inner_area,
+ flags);
+
+ if (editable_widget != NULL)
+ {
+ GdkRectangle edit_area;
+
+ g_return_val_if_fail (GTK_IS_CELL_EDITABLE (editable_widget), FALSE);
+
+ gtk_cell_area_set_edited_cell (area, renderer);
+ gtk_cell_area_set_edit_widget (area, editable_widget);
+
+ gtk_cell_area_aligned_cell_area (area, widget, renderer, &inner_area, &edit_area);
+
+ /* Signal that editing started so that callers can get
+ * a handle on the editable_widget */
+ gtk_cell_area_add_editable (area, priv->focus_cell, editable_widget, &edit_area);
+
+ /* If the signal was successfully handled start the editing */
+ if (gtk_widget_get_parent (GTK_WIDGET (editable_widget)))
+ {
+ gtk_cell_editable_start_editing (editable_widget, NULL);
+ gtk_widget_grab_focus (GTK_WIDGET (editable_widget));
+ }
+ else
+ {
+ /* Otherwise clear the editing state and fire a warning */
+ gtk_cell_area_set_edited_cell (area, NULL);
+ gtk_cell_area_set_edit_widget (area, NULL);
+
+ g_warning ("GtkCellArea::add-editable fired in the dark, no cell editing was started.");
+ }
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * gtk_cell_area_stop_editing:
+ * @area: a #GtkCellArea
+ * @canceled: whether editing was canceled.
+ *
+ * Explicitly stops the editing of the currently
+ * edited cell (see gtk_cell_area_get_edited_cell()).
+ *
+ * If @canceled is %TRUE, the cell renderer will emit
+ * the ::editing-canceled signal.
+ */
+void
+gtk_cell_area_stop_editing (GtkCellArea *area,
+ gboolean canceled)
+{
+ GtkCellAreaPrivate *priv;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+
+ priv = area->priv;
+
+ if (priv->edited_cell)
+ {
+ GtkCellEditable *edit_widget = g_object_ref (priv->edit_widget);
+ GtkCellRenderer *edit_cell = g_object_ref (priv->edited_cell);
+
+ /* Stop editing of the cell renderer */
+ gtk_cell_renderer_stop_editing (priv->edited_cell, canceled);
+
+ /* Remove any references to the editable widget */
+ gtk_cell_area_set_edited_cell (area, NULL);
+ gtk_cell_area_set_edit_widget (area, NULL);
+
+ /* Send the remove-widget signal explicitly (this is done after setting
+ * the edit cell/widget NULL to avoid feedback)
+ */
+ gtk_cell_area_remove_editable (area, edit_cell, edit_widget);
+ g_object_unref (edit_cell);
+ g_object_unref (edit_widget);
+ }
+}
+
+/*************************************************************
+ * API: Convenience for area implementations *
+ *************************************************************/
+
+void
+gtk_cell_area_inner_cell_area (GtkCellArea *area,
+ GtkWidget *widget,
+ const GdkRectangle *cell_area,
+ GdkRectangle *inner_area)
+{
+ gint focus_line_width;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+ g_return_if_fail (cell_area != NULL);
+ g_return_if_fail (inner_area != NULL);
+
+ gtk_widget_style_get (widget, "focus-line-width", &focus_line_width, NULL);
+
+ *inner_area = *cell_area;
+
+ inner_area->x += focus_line_width;
+ inner_area->width -= focus_line_width * 2;
+ inner_area->y += focus_line_width;
+ inner_area->height -= focus_line_width * 2;
+}
+
+void
+gtk_cell_area_aligned_cell_area (GtkCellArea *area,
+ GtkWidget *widget,
+ GtkCellRenderer *renderer,
+ const GdkRectangle *cell_area,
+ GdkRectangle *aligned_area)
+{
+ GtkCellAreaPrivate *priv;
+ gint opposite_size, x_offset, y_offset;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+ g_return_if_fail (cell_area != NULL);
+ g_return_if_fail (aligned_area != NULL);
+
+ priv = area->priv;
+
+ *aligned_area = *cell_area;
+
+ /* Trim up the aligned size */
+ if (gtk_cell_renderer_get_request_mode (renderer) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
+ {
+ gtk_cell_renderer_get_preferred_height_for_width (renderer, widget,
+ aligned_area->width,
+ NULL, &opposite_size);
+
+ aligned_area->height = MIN (opposite_size, aligned_area->height);
+ }
+ else
+ {
+ gtk_cell_renderer_get_preferred_width_for_height (renderer, widget,
+ aligned_area->height,
+ NULL, &opposite_size);
+
+ aligned_area->width = MIN (opposite_size, aligned_area->width);
+ }
+
+ /* offset the cell position */
+ _gtk_cell_renderer_calc_offset (renderer, cell_area,
+ gtk_widget_get_direction (widget),
+ aligned_area->width,
+ aligned_area->height,
+ &x_offset, &y_offset);
+
+ aligned_area->x += x_offset;
+ aligned_area->y += y_offset;
+}
+
+void
+gtk_cell_area_request_renderer (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkOrientation orientation,
+ GtkWidget *widget,
+ gint for_size,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GtkCellAreaPrivate *priv;
+ gint focus_line_width;
+
+ g_return_if_fail (GTK_IS_CELL_AREA (area));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+ g_return_if_fail (minimum_size != NULL);
+ g_return_if_fail (natural_size != NULL);
+
+ priv = area->priv;
+
+ gtk_widget_style_get (widget, "focus-line-width", &focus_line_width, NULL);
+
+ focus_line_width *= 2;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (for_size < 0)
+ gtk_cell_renderer_get_preferred_width (renderer, widget, minimum_size, natural_size);
+ else
+ {
+ for_size = MAX (0, for_size - focus_line_width);
+
+ gtk_cell_renderer_get_preferred_width_for_height (renderer, widget, for_size,
+ minimum_size, natural_size);
+ }
+ }
+ else /* GTK_ORIENTATION_VERTICAL */
+ {
+ if (for_size < 0)
+ gtk_cell_renderer_get_preferred_height (renderer, widget, minimum_size, natural_size);
+ else
+ {
+ for_size = MAX (0, for_size - focus_line_width);
+
+ gtk_cell_renderer_get_preferred_height_for_width (renderer, widget, for_size,
+ minimum_size, natural_size);
+ }
+ }
+
+ *minimum_size += focus_line_width;
+ *natural_size += focus_line_width;
+}
--- /dev/null
+/* gtkcellarea.h
+ *
+ * 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
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#ifndef __GTK_CELL_AREA_H__
+#define __GTK_CELL_AREA_H__
+
+#include <gtk/gtkcellrenderer.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtktreemodel.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_CELL_AREA (gtk_cell_area_get_type ())
+#define GTK_CELL_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CELL_AREA, GtkCellArea))
+#define GTK_CELL_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_CELL_AREA, GtkCellAreaClass))
+#define GTK_IS_CELL_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CELL_AREA))
+#define GTK_IS_CELL_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_CELL_AREA))
+#define GTK_CELL_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_CELL_AREA, GtkCellAreaClass))
+
+typedef struct _GtkCellArea GtkCellArea;
+typedef struct _GtkCellAreaClass GtkCellAreaClass;
+typedef struct _GtkCellAreaPrivate GtkCellAreaPrivate;
+typedef struct _GtkCellAreaContext GtkCellAreaContext;
+
+/**
+ * GtkCellCallback:
+ * @renderer: the cell renderer to operate on
+ * @data: user-supplied data
+ *
+ * The type of the callback functions used for iterating over
+ * the cell renderers of a #GtkCellArea, see gtk_cell_area_forall().
+ */
+typedef void (*GtkCellCallback) (GtkCellRenderer *renderer,
+ gpointer data);
+
+
+struct _GtkCellArea
+{
+ GInitiallyUnowned parent_instance;
+
+ GtkCellAreaPrivate *priv;
+};
+
+struct _GtkCellAreaClass
+{
+ GInitiallyUnownedClass parent_class;
+
+ /* vtable - not signals */
+
+ /* Basic methods */
+ void (* add) (GtkCellArea *area,
+ GtkCellRenderer *renderer);
+ void (* remove) (GtkCellArea *area,
+ GtkCellRenderer *renderer);
+ void (* forall) (GtkCellArea *area,
+ GtkCellCallback callback,
+ gpointer callback_data);
+ void (* get_cell_allocation) (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ GtkCellRenderer *renderer,
+ const GdkRectangle *cell_area,
+ GdkRectangle *allocation);
+ gint (* event) (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ GdkEvent *event,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags);
+ void (* render) (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ cairo_t *cr,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags,
+ gboolean paint_focus);
+ void (* apply_attributes) (GtkCellArea *area,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gboolean is_expander,
+ gboolean is_expanded);
+
+ /* Geometry */
+ GtkCellAreaContext *(* create_context) (GtkCellArea *area);
+ GtkSizeRequestMode (* get_request_mode) (GtkCellArea *area);
+ void (* get_preferred_width) (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint *minimum_width,
+ gint *natural_width);
+ void (* get_preferred_height_for_width) (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height);
+ void (* get_preferred_height) (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint *minimum_height,
+ gint *natural_height);
+ void (* get_preferred_width_for_height) (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width);
+
+ /* Cell Properties */
+ void (* set_cell_property) (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+ void (* get_cell_property) (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+ /* Focus */
+ gboolean (* can_focus) (GtkCellArea *area);
+ gboolean (* focus) (GtkCellArea *area,
+ GtkDirectionType direction);
+ gboolean (* activate) (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags);
+
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+ void (*_gtk_reserved5) (void);
+ void (*_gtk_reserved6) (void);
+ void (*_gtk_reserved7) (void);
+ void (*_gtk_reserved8) (void);
+};
+
+GType gtk_cell_area_get_type (void) G_GNUC_CONST;
+
+/* Basic methods */
+void gtk_cell_area_add (GtkCellArea *area,
+ GtkCellRenderer *renderer);
+void gtk_cell_area_remove (GtkCellArea *area,
+ GtkCellRenderer *renderer);
+gboolean gtk_cell_area_has_renderer (GtkCellArea *area,
+ GtkCellRenderer *renderer);
+void gtk_cell_area_forall (GtkCellArea *area,
+ GtkCellCallback callback,
+ gpointer callback_data);
+void gtk_cell_area_get_cell_allocation (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ GtkCellRenderer *renderer,
+ const GdkRectangle *cell_area,
+ GdkRectangle *allocation);
+gint gtk_cell_area_event (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ GdkEvent *event,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags);
+void gtk_cell_area_render (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ cairo_t *cr,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags,
+ gboolean paint_focus);
+void gtk_cell_area_set_style_detail (GtkCellArea *area,
+ const gchar *detail);
+G_CONST_RETURN gchar *gtk_cell_area_get_style_detail (GtkCellArea *area);
+
+/* Geometry */
+GtkCellAreaContext *gtk_cell_area_create_context (GtkCellArea *area);
+GtkSizeRequestMode gtk_cell_area_get_request_mode (GtkCellArea *area);
+void gtk_cell_area_get_preferred_width (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size);
+void gtk_cell_area_get_preferred_height_for_width (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height);
+void gtk_cell_area_get_preferred_height (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size);
+void gtk_cell_area_get_preferred_width_for_height (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width);
+G_CONST_RETURN gchar *gtk_cell_area_get_current_path_string (GtkCellArea *area);
+
+
+/* Attributes */
+void gtk_cell_area_apply_attributes (GtkCellArea *area,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gboolean is_expander,
+ gboolean is_expanded);
+void gtk_cell_area_attribute_connect (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *attribute,
+ gint column);
+void gtk_cell_area_attribute_disconnect (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *attribute);
+
+/* Cell Properties */
+void gtk_cell_area_class_install_cell_property (GtkCellAreaClass *aclass,
+ guint property_id,
+ GParamSpec *pspec);
+GParamSpec* gtk_cell_area_class_find_cell_property (GtkCellAreaClass *aclass,
+ const gchar *property_name);
+GParamSpec** gtk_cell_area_class_list_cell_properties (GtkCellAreaClass *aclass,
+ guint *n_properties);
+void gtk_cell_area_add_with_properties (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *first_prop_name,
+ ...) G_GNUC_NULL_TERMINATED;
+void gtk_cell_area_cell_set (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *first_prop_name,
+ ...) G_GNUC_NULL_TERMINATED;
+void gtk_cell_area_cell_get (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *first_prop_name,
+ ...) G_GNUC_NULL_TERMINATED;
+void gtk_cell_area_cell_set_valist (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *first_property_name,
+ va_list var_args);
+void gtk_cell_area_cell_get_valist (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *first_property_name,
+ va_list var_args);
+void gtk_cell_area_cell_set_property (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *property_name,
+ const GValue *value);
+void gtk_cell_area_cell_get_property (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *property_name,
+ GValue *value);
+
+#define GTK_CELL_AREA_WARN_INVALID_CHILD_PROPERTY_ID(object, property_id, pspec) \
+ G_OBJECT_WARN_INVALID_PSPEC ((object), "cell property id", (property_id), (pspec))
+
+
+/* Focus */
+gboolean gtk_cell_area_can_focus (GtkCellArea *area);
+gboolean gtk_cell_area_focus (GtkCellArea *area,
+ GtkDirectionType direction);
+gboolean gtk_cell_area_activate (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags);
+void gtk_cell_area_set_focus_cell (GtkCellArea *area,
+ GtkCellRenderer *renderer);
+GtkCellRenderer *gtk_cell_area_get_focus_cell (GtkCellArea *area);
+
+
+/* Focus siblings */
+void gtk_cell_area_add_focus_sibling (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkCellRenderer *sibling);
+void gtk_cell_area_remove_focus_sibling (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkCellRenderer *sibling);
+gboolean gtk_cell_area_is_focus_sibling (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkCellRenderer *sibling);
+G_CONST_RETURN GList *gtk_cell_area_get_focus_siblings (GtkCellArea *area,
+ GtkCellRenderer *renderer);
+GtkCellRenderer *gtk_cell_area_get_focus_from_sibling (GtkCellArea *area,
+ GtkCellRenderer *renderer);
+
+/* Cell Activation/Editing */
+GtkCellRenderer *gtk_cell_area_get_edited_cell (GtkCellArea *area);
+GtkCellEditable *gtk_cell_area_get_edit_widget (GtkCellArea *area);
+gboolean gtk_cell_area_activate_cell (GtkCellArea *area,
+ GtkWidget *widget,
+ GtkCellRenderer *renderer,
+ GdkEvent *event,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags);
+void gtk_cell_area_stop_editing (GtkCellArea *area,
+ gboolean canceled);
+
+/* Functions for area implementations */
+
+/* Distinguish the inner cell area from the whole requested area including margins */
+void gtk_cell_area_inner_cell_area (GtkCellArea *area,
+ GtkWidget *widget,
+ const GdkRectangle *cell_area,
+ GdkRectangle *inner_area);
+
+/* Aligns a cell renderer into cell_area by requesting it's size ... used for focus and cell edit areas */
+void gtk_cell_area_aligned_cell_area (GtkCellArea *area,
+ GtkWidget *widget,
+ GtkCellRenderer *renderer,
+ const GdkRectangle *cell_area,
+ GdkRectangle *aligned_area);
+
+
+/* Request the size of a cell while respecting the cell margins (requests are margin inclusive) */
+void gtk_cell_area_request_renderer (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkOrientation orientation,
+ GtkWidget *widget,
+ gint for_size,
+ gint *minimum_size,
+ gint *natural_size);
+
+G_END_DECLS
+
+#endif /* __GTK_CELL_AREA_H__ */
--- /dev/null
+/* gtkcellareabox.c
+ *
+ * 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
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+#include "gtkintl.h"
+#include "gtkorientable.h"
+#include "gtkcelllayout.h"
+#include "gtkcellareabox.h"
+#include "gtkcellareaboxcontext.h"
+#include "gtkprivate.h"
+
+
+/* GObjectClass */
+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,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gtk_cell_area_box_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+/* GtkCellAreaClass */
+static void gtk_cell_area_box_add (GtkCellArea *area,
+ GtkCellRenderer *renderer);
+static void gtk_cell_area_box_remove (GtkCellArea *area,
+ GtkCellRenderer *renderer);
+static void gtk_cell_area_box_forall (GtkCellArea *area,
+ GtkCellCallback callback,
+ gpointer callback_data);
+static void gtk_cell_area_box_get_cell_allocation (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ GtkCellRenderer *renderer,
+ const GdkRectangle *cell_area,
+ GdkRectangle *allocation);
+static gint gtk_cell_area_box_event (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ GdkEvent *event,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags);
+static void gtk_cell_area_box_render (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ cairo_t *cr,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags,
+ gboolean paint_focus);
+static void gtk_cell_area_box_set_cell_property (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gtk_cell_area_box_get_cell_property (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+static GtkCellAreaContext *gtk_cell_area_box_create_context (GtkCellArea *area);
+static GtkSizeRequestMode gtk_cell_area_box_get_request_mode (GtkCellArea *area);
+static void gtk_cell_area_box_get_preferred_width (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint *minimum_width,
+ gint *natural_width);
+static void gtk_cell_area_box_get_preferred_height (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint *minimum_height,
+ gint *natural_height);
+static void gtk_cell_area_box_get_preferred_height_for_width (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height);
+static void gtk_cell_area_box_get_preferred_width_for_height (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width);
+static gboolean gtk_cell_area_box_focus (GtkCellArea *area,
+ GtkDirectionType direction);
+
+/* GtkCellLayoutIface */
+static void gtk_cell_area_box_cell_layout_init (GtkCellLayoutIface *iface);
+static void gtk_cell_area_box_layout_pack_start (GtkCellLayout *cell_layout,
+ GtkCellRenderer *renderer,
+ gboolean expand);
+static void gtk_cell_area_box_layout_pack_end (GtkCellLayout *cell_layout,
+ GtkCellRenderer *renderer,
+ gboolean expand);
+static void gtk_cell_area_box_layout_reorder (GtkCellLayout *cell_layout,
+ GtkCellRenderer *renderer,
+ gint position);
+
+
+/* CellInfo/CellGroup metadata handling and convenience functions */
+typedef struct {
+ GtkCellRenderer *renderer;
+
+ guint expand : 1; /* Whether the cell expands */
+ guint pack : 1; /* Whether the cell is packed from the start or end */
+ guint align : 1; /* Whether to align this cell's position with adjacent rows */
+} CellInfo;
+
+typedef struct {
+ GList *cells;
+
+ guint id : 8;
+ guint n_cells : 8;
+ guint expand_cells : 8;
+} CellGroup;
+
+typedef struct {
+ GtkCellRenderer *renderer;
+
+ gint position;
+ gint size;
+} AllocatedCell;
+
+static CellInfo *cell_info_new (GtkCellRenderer *renderer,
+ GtkPackType pack,
+ gboolean expand,
+ gboolean align);
+static void cell_info_free (CellInfo *info);
+static gint cell_info_find (CellInfo *info,
+ GtkCellRenderer *renderer);
+
+static AllocatedCell *allocated_cell_new (GtkCellRenderer *renderer,
+ gint position,
+ gint size);
+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,
+ GtkCellAreaBoxContext *dead_context);
+static void flush_contexts (GtkCellAreaBox *box);
+static void init_context_groups (GtkCellAreaBox *box);
+static void init_context_group (GtkCellAreaBox *box,
+ GtkCellAreaBoxContext *context);
+static GSList *get_allocated_cells (GtkCellAreaBox *box,
+ GtkCellAreaBoxContext *context,
+ GtkWidget *widget);
+
+
+struct _GtkCellAreaBoxPrivate
+{
+ GtkOrientation orientation;
+
+ GList *cells;
+ GArray *groups;
+
+ GSList *contexts;
+
+ gint spacing;
+};
+
+enum {
+ PROP_0,
+ PROP_ORIENTATION,
+ PROP_SPACING
+};
+
+enum {
+ CELL_PROP_0,
+ CELL_PROP_EXPAND,
+ CELL_PROP_ALIGN,
+ CELL_PROP_PACK_TYPE
+};
+
+G_DEFINE_TYPE_WITH_CODE (GtkCellAreaBox, gtk_cell_area_box, GTK_TYPE_CELL_AREA,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
+ gtk_cell_area_box_cell_layout_init)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL));
+
+#define OPPOSITE_ORIENTATION(orientation) \
+ ((orientation) == GTK_ORIENTATION_HORIZONTAL ? \
+ GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL)
+
+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;
+ priv->groups = g_array_new (FALSE, TRUE, sizeof (CellGroup));
+ priv->cells = NULL;
+ priv->contexts = NULL;
+ priv->spacing = 0;
+}
+
+static void
+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 */
+ area_class->add = gtk_cell_area_box_add;
+ area_class->remove = gtk_cell_area_box_remove;
+ area_class->forall = gtk_cell_area_box_forall;
+ area_class->get_cell_allocation = gtk_cell_area_box_get_cell_allocation;
+ area_class->event = gtk_cell_area_box_event;
+ area_class->render = gtk_cell_area_box_render;
+ area_class->set_cell_property = gtk_cell_area_box_set_cell_property;
+ area_class->get_cell_property = gtk_cell_area_box_get_cell_property;
+
+ area_class->create_context = gtk_cell_area_box_create_context;
+ 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;
+
+ area_class->focus = gtk_cell_area_box_focus;
+
+ /* Properties */
+ g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
+
+ g_object_class_install_property (object_class,
+ PROP_SPACING,
+ g_param_spec_int ("spacing",
+ P_("Spacing"),
+ P_("Space which is inserted between cells"),
+ 0,
+ G_MAXINT,
+ 0,
+ GTK_PARAM_READWRITE));
+
+ /* Cell Properties */
+ gtk_cell_area_class_install_cell_property (area_class,
+ CELL_PROP_EXPAND,
+ g_param_spec_boolean
+ ("expand",
+ P_("Expand"),
+ P_("Whether the cell expands"),
+ FALSE,
+ GTK_PARAM_READWRITE));
+
+ gtk_cell_area_class_install_cell_property (area_class,
+ CELL_PROP_ALIGN,
+ g_param_spec_boolean
+ ("align",
+ P_("Align"),
+ P_("Whether cell should align with adjacent rows"),
+ TRUE,
+ GTK_PARAM_READWRITE));
+
+ gtk_cell_area_class_install_cell_property (area_class,
+ 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));
+
+ g_type_class_add_private (object_class, sizeof (GtkCellAreaBoxPrivate));
+}
+
+
+/*************************************************************
+ * CellInfo/CellGroup basics and convenience functions *
+ *************************************************************/
+static CellInfo *
+cell_info_new (GtkCellRenderer *renderer,
+ GtkPackType pack,
+ gboolean expand,
+ gboolean align)
+{
+ CellInfo *info = g_slice_new (CellInfo);
+
+ info->renderer = g_object_ref_sink (renderer);
+ info->pack = pack;
+ info->expand = expand;
+ info->align = align;
+
+ 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,
+ GtkCellRenderer *renderer)
+{
+ return (info->renderer == renderer) ? 0 : -1;
+}
+
+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;
+
+ 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, the first group is implied */
+ if (info->align && 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++;
+
+ /* A group expands if it contains any expand cells */
+ if (info->expand)
+ group_ptr->expand_cells++;
+ }
+
+ 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, i;
+
+ expand_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);
+ }
+
+ /* This call implies flushing the request info */
+ gtk_cell_area_box_init_groups (context, priv->groups->len, expand_groups);
+ g_free (expand_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
+flush_contexts (GtkCellAreaBox *box)
+{
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GSList *l;
+
+ /* When the box layout changes, contexts need to
+ * be flushed and sizes for the box get requested again
+ */
+ for (l = priv->contexts; l; l = l->next)
+ {
+ GtkCellAreaContext *context = l->data;
+
+ gtk_cell_area_context_flush (context);
+ }
+}
+
+/* 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)
+{
+ const 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;
+
+ group_allocs = gtk_cell_area_box_context_get_orientation_allocs (context, &n_allocs);
+ if (!group_allocs)
+ {
+ g_warning ("Trying to operate on an unallocated GtkCellAreaContext, "
+ "GtkCellAreaBox requires that the context be allocated at least "
+ "in the orientation of the box");
+ return NULL;
+ }
+
+ for (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 =
+ allocated_cell_new (info->renderer, group_allocs[i].position, group_allocs[i].size);
+
+ allocated_cells = g_slist_prepend (allocated_cells, cell);
+ }
+ else
+ {
+ GtkRequestedSize *sizes;
+ gint avail_size, 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;
+
+ /* Offset the allocation to the group position and allocate into
+ * the group's available size */
+ position = group_allocs[i].position;
+ avail_size = group_allocs[i].size;
+
+ 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, -1,
+ &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;
+ avail_size = gtk_distribute_natural_allocation (avail_size, visible_cells, sizes);
+
+ /* 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--;
+ }
+ }
+
+ cell = allocated_cell_new (info->renderer, position, sizes[j].minimum_size);
+
+ allocated_cells = g_slist_prepend (allocated_cells, cell);
+
+ position += sizes[j].minimum_size;
+ position += priv->spacing;
+ }
+
+ g_free (sizes);
+ }
+ }
+
+ /* 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);
+}
+
+/*************************************************************
+ * GObjectClass *
+ *************************************************************/
+static void
+gtk_cell_area_box_finalize (GObject *object)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (object);
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GSList *l;
+
+ /* 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);
+
+ g_slist_free (priv->contexts);
+ priv->contexts = NULL;
+
+ /* Free the cell grouping info */
+ cell_groups_clear (box);
+ g_array_free (priv->groups, TRUE);
+
+ 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,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (object);
+
+ switch (prop_id)
+ {
+ case PROP_ORIENTATION:
+ box->priv->orientation = g_value_get_enum (value);
+
+ /* Notify that size needs to be requested again */
+ flush_contexts (box);
+ break;
+ 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;
+ }
+}
+
+static void
+gtk_cell_area_box_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (object);
+
+ switch (prop_id)
+ {
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, box->priv->orientation);
+ break;
+ 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;
+ }
+}
+
+/*************************************************************
+ * GtkCellAreaClass *
+ *************************************************************/
+static void
+gtk_cell_area_box_add (GtkCellArea *area,
+ GtkCellRenderer *renderer)
+{
+ gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area),
+ renderer, FALSE, TRUE);
+}
+
+static void
+gtk_cell_area_box_remove (GtkCellArea *area,
+ GtkCellRenderer *renderer)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GList *node;
+
+ node = g_list_find_custom (priv->cells, renderer,
+ (GCompareFunc)cell_info_find);
+
+ if (node)
+ {
+ CellInfo *info = node->data;
+
+ cell_info_free (info);
+
+ priv->cells = g_list_delete_link (priv->cells, node);
+
+ /* Reconstruct cell groups */
+ cell_groups_rebuild (box);
+ }
+ else
+ g_warning ("Trying to remove a cell renderer that is not present GtkCellAreaBox");
+}
+
+static void
+gtk_cell_area_box_forall (GtkCellArea *area,
+ GtkCellCallback callback,
+ gpointer callback_data)
+{
+ 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;
+
+ callback (info->renderer, callback_data);
+ }
+}
+
+static void
+gtk_cell_area_box_get_cell_allocation (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ GtkCellRenderer *renderer,
+ const GdkRectangle *cell_area,
+ GdkRectangle *allocation)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ GSList *allocated_cells, *l;
+
+ *allocation = *cell_area;
+
+ /* Get a list of cells with allocation sizes decided regardless
+ * of alignments and pack order etc. */
+ allocated_cells = get_allocated_cells (box, box_context, widget);
+
+ for (l = allocated_cells; l; l = l->next)
+ {
+ AllocatedCell *cell = l->data;
+
+ if (cell->renderer == renderer)
+ {
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ allocation->x = cell_area->x + cell->position;
+ allocation->width = cell->size;
+ }
+ else
+ {
+ allocation->y = cell_area->y + cell->position;
+ allocation->height = cell->size;
+ }
+
+ break;
+ }
+ }
+
+ g_slist_foreach (allocated_cells, (GFunc)allocated_cell_free, NULL);
+ g_slist_free (allocated_cells);
+}
+
+enum {
+ FOCUS_NONE,
+ FOCUS_PREV,
+ FOCUS_NEXT
+};
+
+static gint
+gtk_cell_area_box_event (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ GdkEvent *event,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ gint retval;
+
+ /* First let the parent class handle activation of cells via keystrokes */
+ retval =
+ GTK_CELL_AREA_CLASS (gtk_cell_area_box_parent_class)->event (area, context, widget,
+ event, cell_area, flags);
+
+ if (retval)
+ return retval;
+
+ /* Also detect mouse events, for mouse events we need to allocate the renderers
+ * and find which renderer needs to be activated.
+ */
+ if (event->type == GDK_BUTTON_PRESS)
+ {
+ GdkEventButton *button_event = (GdkEventButton *)event;
+
+ if (button_event->button == 1)
+ {
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ GSList *allocated_cells, *l;
+ GdkRectangle cell_background, inner_area;
+ gint event_x, event_y;
+
+ /* We may need some semantics to tell us the offset of the event
+ * window we are handling events for (i.e. GtkTreeView has a bin_window) */
+ event_x = button_event->x;
+ event_y = button_event->y;
+
+ cell_background = *cell_area;
+
+ /* Get a list of cells with allocation sizes decided regardless
+ * of alignments and pack order etc. */
+ allocated_cells = get_allocated_cells (box, box_context, widget);
+
+ for (l = allocated_cells; l; l = l->next)
+ {
+ AllocatedCell *cell = l->data;
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ cell_background.x = cell_area->x + cell->position;
+ cell_background.width = cell->size;
+ }
+ else
+ {
+ cell_background.y = cell_area->y + cell->position;
+ cell_background.height = cell->size;
+ }
+
+ /* Remove margins from the background area to produce the cell area
+ */
+ gtk_cell_area_inner_cell_area (area, widget, &cell_background, &inner_area);
+
+ if (event_x >= inner_area.x && event_x <= inner_area.x + inner_area.width &&
+ event_y >= inner_area.y && event_y <= inner_area.y + inner_area.height)
+ {
+ GtkCellRenderer *event_renderer = NULL;
+
+ if (gtk_cell_renderer_can_focus (cell->renderer))
+ event_renderer = cell->renderer;
+ else
+ {
+ GtkCellRenderer *focus_renderer;
+
+ /* A renderer can have focus siblings but that renderer might not be
+ * focusable for every row... so we go on to check can_focus here. */
+ focus_renderer = gtk_cell_area_get_focus_from_sibling (area, cell->renderer);
+
+ if (focus_renderer && gtk_cell_renderer_can_focus (focus_renderer))
+ event_renderer = focus_renderer;
+ }
+
+ if (event_renderer)
+ {
+ if (gtk_cell_area_get_edited_cell (area))
+ {
+ /* XXX Was it really canceled in this case ? */
+ gtk_cell_area_stop_editing (area, TRUE);
+ gtk_cell_area_set_focus_cell (area, event_renderer);
+ retval = TRUE;
+ }
+ else
+ {
+ /* If we are activating via a focus sibling, we need to fix the
+ * cell area */
+ if (event_renderer != cell->renderer)
+ gtk_cell_area_inner_cell_area (area, widget, cell_area, &cell_background);
+
+ gtk_cell_area_set_focus_cell (area, event_renderer);
+
+ retval = gtk_cell_area_activate_cell (area, widget, event_renderer,
+ event, &cell_background, flags);
+ }
+ break;
+ }
+ }
+ }
+ g_slist_foreach (allocated_cells, (GFunc)allocated_cell_free, NULL);
+ g_slist_free (allocated_cells);
+ }
+ }
+
+ return retval;
+}
+
+static void
+gtk_cell_area_box_render (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ cairo_t *cr,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags,
+ gboolean paint_focus)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ GSList *allocated_cells, *l;
+ GdkRectangle cell_background, inner_area;
+ GtkCellRenderer *focus_cell = NULL;
+ GdkRectangle focus_rect = { 0, };
+ gboolean first_focus_cell = TRUE;
+
+ if (flags & GTK_CELL_RENDERER_FOCUSED)
+ {
+ focus_cell = gtk_cell_area_get_focus_cell (area);
+ flags &= ~GTK_CELL_RENDERER_FOCUSED;
+ }
+
+ cell_background = *cell_area;
+
+ /* Get a list of cells with allocation sizes decided regardless
+ * of alignments and pack order etc. */
+ allocated_cells = get_allocated_cells (box, box_context, widget);
+
+ for (l = allocated_cells; l; l = l->next)
+ {
+ AllocatedCell *cell = l->data;
+ GtkCellRendererState cell_fields = 0;
+ GdkRectangle render_background;
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ cell_background.x = cell_area->x + cell->position;
+ cell_background.width = cell->size;
+ }
+ else
+ {
+ cell_background.y = cell_area->y + cell->position;
+ cell_background.height = cell->size;
+ }
+
+ /* Remove margins from the background area to produce the cell area
+ */
+ gtk_cell_area_inner_cell_area (area, widget, &cell_background, &inner_area);
+
+ /* Add portions of the background_area to the cell_background
+ * to create the render_background */
+ render_background = cell_background;
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (l == allocated_cells)
+ {
+ render_background.width += render_background.x - background_area->x;
+ render_background.x = background_area->x;
+ }
+
+ if (l->next == NULL)
+ render_background.width =
+ background_area->width - (render_background.x - background_area->x);
+
+ render_background.y = background_area->y;
+ render_background.height = background_area->height;
+ }
+ else
+ {
+ if (l == allocated_cells)
+ {
+ render_background.height += render_background.y - background_area->y;
+ render_background.y = background_area->y;
+ }
+
+ if (l->next == NULL)
+ render_background.height =
+ background_area->height - (render_background.y - background_area->y);
+
+ render_background.x = background_area->x;
+ render_background.width = background_area->width;
+ }
+
+ if (focus_cell &&
+ (cell->renderer == focus_cell ||
+ gtk_cell_area_is_focus_sibling (area, focus_cell, cell->renderer)))
+ {
+ cell_fields |= GTK_CELL_RENDERER_FOCUSED;
+
+ if (paint_focus)
+ {
+ GdkRectangle cell_focus;
+
+ gtk_cell_area_aligned_cell_area (area, widget, cell->renderer, &inner_area, &cell_focus);
+
+ /* Accumulate the focus rectangle for all focus siblings */
+ if (first_focus_cell)
+ {
+ focus_rect = cell_focus;
+ first_focus_cell = FALSE;
+ }
+ else
+ gdk_rectangle_union (&focus_rect, &cell_focus, &focus_rect);
+ }
+ }
+
+ /* We have to do some per-cell considerations for the 'flags'
+ * for focus handling */
+ gtk_cell_renderer_render (cell->renderer, cr, widget,
+ &render_background, &inner_area,
+ flags | cell_fields);
+ }
+
+ if (paint_focus && focus_rect.width != 0 && focus_rect.height != 0)
+ {
+ GtkStateType renderer_state =
+ flags & GTK_CELL_RENDERER_SELECTED ? GTK_STATE_SELECTED :
+ (flags & GTK_CELL_RENDERER_PRELIT ? GTK_STATE_PRELIGHT :
+ (flags & GTK_CELL_RENDERER_INSENSITIVE ? GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL));
+
+ gtk_paint_focus (gtk_widget_get_style (widget), cr,
+ renderer_state, widget,
+ gtk_cell_area_get_style_detail (area),
+ focus_rect.x, focus_rect.y,
+ focus_rect.width, focus_rect.height);
+ }
+
+
+ g_slist_foreach (allocated_cells, (GFunc)allocated_cell_free, NULL);
+ g_slist_free (allocated_cells);
+}
+
+static void
+gtk_cell_area_box_set_cell_property (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GList *node;
+ CellInfo *info;
+ gboolean rebuild = FALSE;
+ gboolean val;
+ GtkPackType pack_type;
+
+ node = g_list_find_custom (priv->cells, renderer,
+ (GCompareFunc)cell_info_find);
+ if (!node)
+ return;
+
+ info = node->data;
+
+ switch (prop_id)
+ {
+ case CELL_PROP_EXPAND:
+ val = g_value_get_boolean (value);
+
+ if (info->expand != val)
+ {
+ info->expand = val;
+ rebuild = TRUE;
+ }
+ break;
+
+ case CELL_PROP_ALIGN:
+ val = g_value_get_boolean (value);
+
+ if (info->align != val)
+ {
+ info->align = val;
+ rebuild = TRUE;
+ }
+ break;
+
+ case CELL_PROP_PACK_TYPE:
+ pack_type = g_value_get_enum (value);
+
+ if (info->pack != pack_type)
+ {
+ info->pack = pack_type;
+ rebuild = TRUE;
+ }
+ break;
+ default:
+ GTK_CELL_AREA_WARN_INVALID_CHILD_PROPERTY_ID (area, prop_id, pspec);
+ break;
+ }
+
+ /* Groups need to be rebuilt */
+ if (rebuild)
+ cell_groups_rebuild (box);
+}
+
+static void
+gtk_cell_area_box_get_cell_property (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GList *node;
+ CellInfo *info;
+
+ node = g_list_find_custom (priv->cells, renderer,
+ (GCompareFunc)cell_info_find);
+ if (!node)
+ return;
+
+ info = node->data;
+
+ switch (prop_id)
+ {
+ case CELL_PROP_EXPAND:
+ g_value_set_boolean (value, info->expand);
+ break;
+
+ case CELL_PROP_ALIGN:
+ g_value_set_boolean (value, info->align);
+ break;
+
+ case CELL_PROP_PACK_TYPE:
+ g_value_set_enum (value, info->pack);
+ break;
+ default:
+ GTK_CELL_AREA_WARN_INVALID_CHILD_PROPERTY_ID (area, prop_id, pspec);
+ break;
+ }
+}
+
+
+static GtkCellAreaContext *
+gtk_cell_area_box_create_context (GtkCellArea *area)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GtkCellAreaContext *context =
+ (GtkCellAreaContext *)g_object_new (GTK_TYPE_CELL_AREA_BOX_CONTEXT,
+ "area", area, NULL);
+
+ priv->contexts = g_slist_prepend (priv->contexts, context);
+
+ g_object_weak_ref (G_OBJECT (context), (GWeakNotify)context_weak_notify, box);
+
+ /* Tell the new group about our cell layout */
+ init_context_group (box, GTK_CELL_AREA_BOX_CONTEXT (context));
+
+ return context;
+}
+
+static GtkSizeRequestMode
+gtk_cell_area_box_get_request_mode (GtkCellArea *area)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
+ GtkCellAreaBoxPrivate *priv = box->priv;
+
+ return (priv->orientation) == GTK_ORIENTATION_HORIZONTAL ?
+ GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH :
+ GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
+}
+
+static void
+compute_size (GtkCellAreaBox *box,
+ GtkOrientation orientation,
+ GtkCellAreaBoxContext *context,
+ GtkWidget *widget,
+ gint for_size,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GtkCellArea *area = GTK_CELL_AREA (box);
+ GList *list;
+ gint i;
+ gint min_size = 0;
+ gint nat_size = 0;
+
+ for (i = 0; i < priv->groups->len; i++)
+ {
+ CellGroup *group = &g_array_index (priv->groups, CellGroup, i);
+ gint group_min_size = 0;
+ gint group_nat_size = 0;
+
+ for (list = group->cells; list; list = list->next)
+ {
+ CellInfo *info = list->data;
+ gint renderer_min_size, renderer_nat_size;
+
+ if (!gtk_cell_renderer_get_visible (info->renderer))
+ continue;
+
+ gtk_cell_area_request_renderer (area, info->renderer, orientation, widget, for_size,
+ &renderer_min_size, &renderer_nat_size);
+
+ if (orientation == priv->orientation)
+ {
+ if (min_size > 0)
+ {
+ min_size += priv->spacing;
+ nat_size += priv->spacing;
+ }
+
+ if (group_min_size > 0)
+ {
+ group_min_size += priv->spacing;
+ group_nat_size += priv->spacing;
+ }
+
+ min_size += renderer_min_size;
+ nat_size += renderer_nat_size;
+ group_min_size += renderer_min_size;
+ group_nat_size += renderer_nat_size;
+ }
+ else
+ {
+ min_size = MAX (min_size, renderer_min_size);
+ nat_size = MAX (nat_size, renderer_nat_size);
+ group_min_size = MAX (group_min_size, renderer_min_size);
+ group_nat_size = MAX (group_nat_size, renderer_nat_size);
+ }
+ }
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (for_size < 0)
+ gtk_cell_area_box_context_push_group_width (context, group->id, group_min_size, group_nat_size);
+ else
+ gtk_cell_area_box_context_push_group_width_for_height (context, group->id, for_size,
+ group_min_size, group_nat_size);
+ }
+ else
+ {
+ if (for_size < 0)
+ gtk_cell_area_box_context_push_group_height (context, group->id, group_min_size, group_nat_size);
+ else
+ gtk_cell_area_box_context_push_group_height_for_width (context, group->id, for_size,
+ group_min_size, group_nat_size);
+ }
+ }
+
+ *minimum_size = min_size;
+ *natural_size = nat_size;
+}
+
+GtkRequestedSize *
+get_group_sizes (GtkCellArea *area,
+ CellGroup *group,
+ GtkOrientation orientation,
+ GtkWidget *widget,
+ gint *n_sizes)
+{
+ GtkRequestedSize *sizes;
+ GList *l;
+ gint i;
+
+ *n_sizes = count_visible_cells (group, NULL);
+ sizes = g_new (GtkRequestedSize, *n_sizes);
+
+ for (l = group->cells, i = 0; l; l = l->next)
+ {
+ CellInfo *info = l->data;
+
+ if (!gtk_cell_renderer_get_visible (info->renderer))
+ continue;
+
+ sizes[i].data = info;
+
+ gtk_cell_area_request_renderer (area, info->renderer,
+ orientation, widget, -1,
+ &sizes[i].minimum_size,
+ &sizes[i].natural_size);
+
+ i++;
+ }
+
+ return sizes;
+}
+
+static void
+compute_group_size_for_opposing_orientation (GtkCellAreaBox *box,
+ CellGroup *group,
+ GtkWidget *widget,
+ gint for_size,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GtkCellArea *area = GTK_CELL_AREA (box);
+
+ /* Exception for single cell groups */
+ if (group->n_cells == 1)
+ {
+ CellInfo *info = group->cells->data;
+
+ gtk_cell_area_request_renderer (area, info->renderer,
+ OPPOSITE_ORIENTATION (priv->orientation),
+ widget, for_size, minimum_size, natural_size);
+ }
+ else
+ {
+ GtkRequestedSize *orientation_sizes;
+ CellInfo *info;
+ gint n_sizes, i;
+ gint avail_size = for_size;
+ gint extra_size, extra_extra;
+ gint min_size = 0, nat_size = 0;
+
+ orientation_sizes = get_group_sizes (area, group, priv->orientation, widget, &n_sizes);
+
+ /* First naturally allocate the cells in the group into the for_size */
+ avail_size -= (n_sizes - 1) * priv->spacing;
+ for (i = 0; i < n_sizes; i++)
+ avail_size -= orientation_sizes[i].minimum_size;
+
+ avail_size = gtk_distribute_natural_allocation (avail_size, n_sizes, orientation_sizes);
+
+ /* Calculate/distribute expand for cells */
+ if (group->expand_cells > 0)
+ {
+ extra_size = avail_size / group->expand_cells;
+ extra_extra = avail_size % group->expand_cells;
+ }
+ else
+ extra_size = extra_extra = 0;
+
+ for (i = 0; i < n_sizes; i++)
+ {
+ gint cell_min, cell_nat;
+
+ info = orientation_sizes[i].data;
+
+ if (info->expand)
+ {
+ orientation_sizes[i].minimum_size += extra_size;
+ if (extra_extra)
+ {
+ orientation_sizes[i].minimum_size++;
+ extra_extra--;
+ }
+ }
+
+ gtk_cell_area_request_renderer (area, info->renderer,
+ OPPOSITE_ORIENTATION (priv->orientation),
+ widget,
+ orientation_sizes[i].minimum_size,
+ &cell_min, &cell_nat);
+
+ min_size = MAX (min_size, cell_min);
+ nat_size = MAX (nat_size, cell_nat);
+ }
+
+ *minimum_size = min_size;
+ *natural_size = nat_size;
+
+ g_free (orientation_sizes);
+ }
+}
+
+static void
+compute_size_for_opposing_orientation (GtkCellAreaBox *box,
+ GtkCellAreaBoxContext *context,
+ GtkWidget *widget,
+ gint for_size,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ CellGroup *group;
+ GtkRequestedSize *orientation_sizes;
+ gint n_groups, n_expand_groups, i;
+ gint avail_size = for_size;
+ gint extra_size, extra_extra;
+ gint min_size = 0, nat_size = 0;
+
+ n_expand_groups = count_expand_groups (box);
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ orientation_sizes = gtk_cell_area_box_context_get_widths (context, &n_groups);
+ else
+ orientation_sizes = gtk_cell_area_box_context_get_heights (context, &n_groups);
+
+ /* First start by naturally allocating space among groups of cells */
+ avail_size -= (n_groups - 1) * priv->spacing;
+ for (i = 0; i < n_groups; i++)
+ avail_size -= orientation_sizes[i].minimum_size;
+
+ avail_size = gtk_distribute_natural_allocation (avail_size, n_groups, orientation_sizes);
+
+ /* Calculate/distribute expand for groups */
+ if (n_expand_groups > 0)
+ {
+ extra_size = avail_size / n_expand_groups;
+ extra_extra = avail_size % n_expand_groups;
+ }
+ else
+ extra_size = extra_extra = 0;
+
+ /* Now we need to naturally allocate sizes for cells in each group
+ * and push the height-for-width for each group accordingly while accumulating
+ * the overall height-for-width for this row.
+ */
+ for (i = 0; i < n_groups; i++)
+ {
+ gint group_min, group_nat;
+ gint group_idx = GPOINTER_TO_INT (orientation_sizes[i].data);
+
+ group = &g_array_index (priv->groups, CellGroup, group_idx);
+
+ if (group->expand_cells > 0)
+ {
+ orientation_sizes[i].minimum_size += extra_size;
+ if (extra_extra)
+ {
+ orientation_sizes[i].minimum_size++;
+ extra_extra--;
+ }
+ }
+
+ /* Now we have the allocation for the group, request it's height-for-width */
+ compute_group_size_for_opposing_orientation (box, group, widget,
+ orientation_sizes[i].minimum_size,
+ &group_min, &group_nat);
+
+ min_size = MAX (min_size, group_min);
+ nat_size = MAX (nat_size, group_nat);
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ gtk_cell_area_box_context_push_group_height_for_width (context, group_idx, for_size,
+ group_min, group_nat);
+ }
+ else
+ {
+ gtk_cell_area_box_context_push_group_width_for_height (context, group_idx, for_size,
+ group_min, group_nat);
+ }
+ }
+
+ *minimum_size = min_size;
+ *natural_size = nat_size;
+
+ g_free (orientation_sizes);
+}
+
+
+
+static void
+gtk_cell_area_box_get_preferred_width (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
+ GtkCellAreaBoxContext *box_context;
+ gint min_width, nat_width;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (context));
+
+ box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+
+ /* Compute the size of all renderers for current row data,
+ * bumping cell alignments in the context along the way */
+ compute_size (box, GTK_ORIENTATION_HORIZONTAL,
+ box_context, widget, -1, &min_width, &nat_width);
+
+ if (minimum_width)
+ *minimum_width = min_width;
+
+ if (natural_width)
+ *natural_width = nat_width;
+}
+
+static void
+gtk_cell_area_box_get_preferred_height (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
+ GtkCellAreaBoxContext *box_context;
+ gint min_height, nat_height;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (context));
+
+ box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+
+ /* Compute the size of all renderers for current row data,
+ * bumping cell alignments in the context along the way */
+ compute_size (box, GTK_ORIENTATION_VERTICAL,
+ box_context, widget, -1, &min_height, &nat_height);
+
+ if (minimum_height)
+ *minimum_height = min_height;
+
+ if (natural_height)
+ *natural_height = nat_height;
+}
+
+static void
+gtk_cell_area_box_get_preferred_height_for_width (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
+ GtkCellAreaBoxContext *box_context;
+ GtkCellAreaBoxPrivate *priv;
+ gint min_height, nat_height;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (context));
+
+ box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ priv = box->priv;
+
+ if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+ {
+ /* Add up vertical requests of height for width and push the overall
+ * cached sizes for alignments */
+ compute_size (box, priv->orientation, box_context, widget, width, &min_height, &nat_height);
+ }
+ else
+ {
+ /* Juice: virtually allocate cells into the for_width using the
+ * alignments and then return the overall height for that width, and cache it */
+ compute_size_for_opposing_orientation (box, box_context, widget, width, &min_height, &nat_height);
+ }
+
+ if (minimum_height)
+ *minimum_height = min_height;
+
+ if (natural_height)
+ *natural_height = nat_height;
+}
+
+static void
+gtk_cell_area_box_get_preferred_width_for_height (GtkCellArea *area,
+ GtkCellAreaContext *context,
+ GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
+ GtkCellAreaBoxContext *box_context;
+ GtkCellAreaBoxPrivate *priv;
+ gint min_width, nat_width;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (context));
+
+ box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ priv = box->priv;
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ /* Add up horizontal requests of width for height and push the overall
+ * cached sizes for alignments */
+ compute_size (box, priv->orientation, box_context, widget, height, &min_width, &nat_width);
+ }
+ else
+ {
+ /* Juice: horizontally allocate cells into the for_height using the
+ * alignments and then return the overall width for that height, and cache it */
+ compute_size_for_opposing_orientation (box, box_context, widget, height, &min_width, &nat_width);
+ }
+
+ if (minimum_width)
+ *minimum_width = min_width;
+
+ if (natural_width)
+ *natural_width = nat_width;
+}
+
+static gboolean
+gtk_cell_area_box_focus (GtkCellArea *area,
+ GtkDirectionType direction)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ gint cycle = FOCUS_NONE;
+ gboolean cycled_focus = FALSE;
+ GtkCellRenderer *focus_cell;
+
+ focus_cell = gtk_cell_area_get_focus_cell (area);
+
+ switch (direction)
+ {
+ case GTK_DIR_TAB_FORWARD:
+ cycle = FOCUS_NEXT;
+ break;
+ case GTK_DIR_TAB_BACKWARD:
+ cycle = FOCUS_PREV;
+ break;
+ case GTK_DIR_UP:
+ if (priv->orientation == GTK_ORIENTATION_VERTICAL || !focus_cell)
+ cycle = FOCUS_PREV;
+ break;
+ case GTK_DIR_DOWN:
+ if (priv->orientation == GTK_ORIENTATION_VERTICAL || !focus_cell)
+ cycle = FOCUS_NEXT;
+ break;
+ case GTK_DIR_LEFT:
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL || !focus_cell)
+ cycle = FOCUS_PREV;
+ break;
+ case GTK_DIR_RIGHT:
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL || !focus_cell)
+ cycle = FOCUS_NEXT;
+ break;
+ default:
+ break;
+ }
+
+ if (cycle != FOCUS_NONE)
+ {
+ gboolean found_cell = FALSE;
+ GList *list;
+ gint i;
+
+ /* If there is no focused cell, focus on the first (or last) one in the list */
+ if (!focus_cell)
+ found_cell = TRUE;
+
+ for (i = (cycle == FOCUS_NEXT) ? 0 : priv->groups->len -1;
+ cycled_focus == FALSE && i >= 0 && i < priv->groups->len;
+ i = (cycle == FOCUS_NEXT) ? i + 1 : i - 1)
+ {
+ CellGroup *group = &g_array_index (priv->groups, CellGroup, i);
+
+ for (list = (cycle == FOCUS_NEXT) ? g_list_first (group->cells) : g_list_last (group->cells);
+ list; list = (cycle == FOCUS_NEXT) ? list->next : list->prev)
+ {
+ CellInfo *info = list->data;
+
+ if (info->renderer == focus_cell)
+ found_cell = TRUE;
+ else if (found_cell)
+ {
+ if (gtk_cell_renderer_can_focus (info->renderer))
+ {
+ gtk_cell_area_set_focus_cell (area, info->renderer);
+
+ cycled_focus = TRUE;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (!cycled_focus)
+ gtk_cell_area_set_focus_cell (area, NULL);
+
+ return cycled_focus;
+}
+
+
+/*************************************************************
+ * GtkCellLayoutIface *
+ *************************************************************/
+static void
+gtk_cell_area_box_cell_layout_init (GtkCellLayoutIface *iface)
+{
+ iface->pack_start = gtk_cell_area_box_layout_pack_start;
+ iface->pack_end = gtk_cell_area_box_layout_pack_end;
+ iface->reorder = gtk_cell_area_box_layout_reorder;
+}
+
+static void
+gtk_cell_area_box_layout_pack_start (GtkCellLayout *cell_layout,
+ GtkCellRenderer *renderer,
+ gboolean expand)
+{
+ gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (cell_layout), renderer, expand, TRUE);
+}
+
+static void
+gtk_cell_area_box_layout_pack_end (GtkCellLayout *cell_layout,
+ GtkCellRenderer *renderer,
+ gboolean expand)
+{
+ gtk_cell_area_box_pack_end (GTK_CELL_AREA_BOX (cell_layout), renderer, expand, TRUE);
+}
+
+static void
+gtk_cell_area_box_layout_reorder (GtkCellLayout *cell_layout,
+ GtkCellRenderer *renderer,
+ gint position)
+{
+ GtkCellAreaBox *box = GTK_CELL_AREA_BOX (cell_layout);
+ GtkCellAreaBoxPrivate *priv = box->priv;
+ GList *node;
+ CellInfo *info;
+
+ node = g_list_find_custom (priv->cells, renderer,
+ (GCompareFunc)cell_info_find);
+
+ if (node)
+ {
+ info = node->data;
+
+ priv->cells = g_list_delete_link (priv->cells, node);
+ priv->cells = g_list_insert (priv->cells, info, position);
+
+ cell_groups_rebuild (box);
+ }
+}
+
+/*************************************************************
+ * API *
+ *************************************************************/
+GtkCellArea *
+gtk_cell_area_box_new (void)
+{
+ return (GtkCellArea *)g_object_new (GTK_TYPE_CELL_AREA_BOX, NULL);
+}
+
+void
+gtk_cell_area_box_pack_start (GtkCellAreaBox *box,
+ GtkCellRenderer *renderer,
+ gboolean expand,
+ gboolean align)
+{
+ GtkCellAreaBoxPrivate *priv;
+ CellInfo *info;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX (box));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+
+ priv = box->priv;
+
+ if (g_list_find_custom (priv->cells, renderer,
+ (GCompareFunc)cell_info_find))
+ {
+ g_warning ("Refusing to add the same cell renderer to a GtkCellAreaBox twice");
+ return;
+ }
+
+ info = cell_info_new (renderer, GTK_PACK_START, expand, align);
+
+ priv->cells = g_list_append (priv->cells, info);
+
+ cell_groups_rebuild (box);
+}
+
+void
+gtk_cell_area_box_pack_end (GtkCellAreaBox *box,
+ GtkCellRenderer *renderer,
+ gboolean expand,
+ gboolean align)
+{
+ GtkCellAreaBoxPrivate *priv;
+ CellInfo *info;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX (box));
+ g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
+
+ priv = box->priv;
+
+ if (g_list_find_custom (priv->cells, renderer,
+ (GCompareFunc)cell_info_find))
+ {
+ g_warning ("Refusing to add the same cell renderer to a GtkCellArea twice");
+ return;
+ }
+
+ info = cell_info_new (renderer, GTK_PACK_END, expand, align);
+
+ priv->cells = g_list_append (priv->cells, info);
+
+ cell_groups_rebuild (box);
+}
+
+gint
+gtk_cell_area_box_get_spacing (GtkCellAreaBox *box)
+{
+ g_return_val_if_fail (GTK_IS_CELL_AREA_BOX (box), 0);
+
+ return box->priv->spacing;
+}
+
+void
+gtk_cell_area_box_set_spacing (GtkCellAreaBox *box,
+ gint spacing)
+{
+ GtkCellAreaBoxPrivate *priv;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX (box));
+
+ priv = box->priv;
+
+ if (priv->spacing != spacing)
+ {
+ priv->spacing = spacing;
+
+ g_object_notify (G_OBJECT (box), "spacing");
+
+ /* Notify that size needs to be requested again */
+ flush_contexts (box);
+ }
+}
--- /dev/null
+/* gtkcellareabox.h
+ *
+ * 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
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#ifndef __GTK_CELL_AREA_BOX_H__
+#define __GTK_CELL_AREA_BOX_H__
+
+#include <gtk/gtkcellarea.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_CELL_AREA_BOX (gtk_cell_area_box_get_type ())
+#define GTK_CELL_AREA_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CELL_AREA_BOX, GtkCellAreaBox))
+#define GTK_CELL_AREA_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_CELL_AREA_BOX, GtkCellAreaBoxClass))
+#define GTK_IS_CELL_AREA_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CELL_AREA_BOX))
+#define GTK_IS_CELL_AREA_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_CELL_AREA_BOX))
+#define GTK_CELL_AREA_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_CELL_AREA_BOX, GtkCellAreaBoxClass))
+
+typedef struct _GtkCellAreaBox GtkCellAreaBox;
+typedef struct _GtkCellAreaBoxClass GtkCellAreaBoxClass;
+typedef struct _GtkCellAreaBoxPrivate GtkCellAreaBoxPrivate;
+
+struct _GtkCellAreaBox
+{
+ GtkCellArea parent_instance;
+
+ GtkCellAreaBoxPrivate *priv;
+};
+
+struct _GtkCellAreaBoxClass
+{
+ GtkCellAreaClass parent_class;
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+};
+
+GType gtk_cell_area_box_get_type (void) G_GNUC_CONST;
+
+GtkCellArea *gtk_cell_area_box_new (void);
+void gtk_cell_area_box_pack_start (GtkCellAreaBox *box,
+ GtkCellRenderer *renderer,
+ gboolean expand,
+ gboolean align);
+void gtk_cell_area_box_pack_end (GtkCellAreaBox *box,
+ GtkCellRenderer *renderer,
+ gboolean expand,
+ gboolean align);
+gint gtk_cell_area_box_get_spacing (GtkCellAreaBox *box);
+void gtk_cell_area_box_set_spacing (GtkCellAreaBox *box,
+ gint spacing);
+
+
+G_END_DECLS
+
+#endif /* __GTK_CELL_AREA_BOX_H__ */
--- /dev/null
+/* gtkcellareaboxcontext.c
+ *
+ * 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
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+#include "gtkintl.h"
+#include "gtkcellareabox.h"
+#include "gtkcellareaboxcontext.h"
+#include "gtkorientable.h"
+
+/* GObjectClass */
+static void gtk_cell_area_box_context_finalize (GObject *object);
+
+/* GtkCellAreaContextClass */
+static void gtk_cell_area_box_context_flush_preferred_width (GtkCellAreaContext *context);
+static void gtk_cell_area_box_context_flush_preferred_height_for_width (GtkCellAreaContext *context,
+ gint width);
+static void gtk_cell_area_box_context_flush_preferred_height (GtkCellAreaContext *context);
+static void gtk_cell_area_box_context_flush_preferred_width_for_height (GtkCellAreaContext *context,
+ gint height);
+static void gtk_cell_area_box_context_flush_allocation (GtkCellAreaContext *context);
+static void gtk_cell_area_box_context_sum_preferred_width (GtkCellAreaContext *context);
+static void gtk_cell_area_box_context_sum_preferred_height_for_width (GtkCellAreaContext *context,
+ gint width);
+static void gtk_cell_area_box_context_sum_preferred_height (GtkCellAreaContext *context);
+static void gtk_cell_area_box_context_sum_preferred_width_for_height (GtkCellAreaContext *context,
+ gint height);
+static void gtk_cell_area_box_context_allocate_width (GtkCellAreaContext *context,
+ gint width);
+static void gtk_cell_area_box_context_allocate_height (GtkCellAreaContext *context,
+ gint height);
+
+static void free_cache_array (GArray *array);
+
+/* CachedSize management */
+typedef struct {
+ gint min_size;
+ gint nat_size;
+} CachedSize;
+
+typedef struct {
+ gint min_size;
+ gint nat_size;
+ gboolean expand;
+} BaseSize;
+
+struct _GtkCellAreaBoxContextPrivate
+{
+ /* Table of per renderer CachedSizes */
+ GArray *base_widths;
+ GArray *base_heights;
+
+ /* Table of per height/width hash tables of per renderer CachedSizes */
+ GHashTable *widths;
+ GHashTable *heights;
+
+ /* Allocation info for this context if any */
+ gint alloc_width;
+ gint alloc_height;
+ gint n_orientation_allocs;
+ GtkCellAreaBoxAllocation *orientation_allocs;
+};
+
+G_DEFINE_TYPE (GtkCellAreaBoxContext, gtk_cell_area_box_context, GTK_TYPE_CELL_AREA_CONTEXT);
+
+static void
+free_cache_array (GArray *array)
+{
+ g_array_free (array, TRUE);
+}
+
+static void
+gtk_cell_area_box_context_init (GtkCellAreaBoxContext *box_context)
+{
+ GtkCellAreaBoxContextPrivate *priv;
+
+ box_context->priv = G_TYPE_INSTANCE_GET_PRIVATE (box_context,
+ GTK_TYPE_CELL_AREA_BOX_CONTEXT,
+ GtkCellAreaBoxContextPrivate);
+ priv = box_context->priv;
+
+ priv->base_widths = g_array_new (FALSE, TRUE, sizeof (BaseSize));
+ priv->base_heights = g_array_new (FALSE, TRUE, sizeof (BaseSize));
+
+ priv->widths = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify)free_cache_array);
+ priv->heights = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify)free_cache_array);
+
+ priv->alloc_width = 0;
+ priv->alloc_height = 0;
+ priv->orientation_allocs = NULL;
+ priv->n_orientation_allocs = 0;
+}
+
+static void
+gtk_cell_area_box_context_class_init (GtkCellAreaBoxContextClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkCellAreaContextClass *context_class = GTK_CELL_AREA_CONTEXT_CLASS (class);
+
+ /* GObjectClass */
+ object_class->finalize = gtk_cell_area_box_context_finalize;
+
+ context_class->flush_preferred_width = gtk_cell_area_box_context_flush_preferred_width;
+ context_class->flush_preferred_height_for_width = gtk_cell_area_box_context_flush_preferred_height_for_width;
+ context_class->flush_preferred_height = gtk_cell_area_box_context_flush_preferred_height;
+ context_class->flush_preferred_width_for_height = gtk_cell_area_box_context_flush_preferred_width_for_height;
+ context_class->flush_allocation = gtk_cell_area_box_context_flush_allocation;
+
+ context_class->sum_preferred_width = gtk_cell_area_box_context_sum_preferred_width;
+ context_class->sum_preferred_height_for_width = gtk_cell_area_box_context_sum_preferred_height_for_width;
+ context_class->sum_preferred_height = gtk_cell_area_box_context_sum_preferred_height;
+ context_class->sum_preferred_width_for_height = gtk_cell_area_box_context_sum_preferred_width_for_height;
+
+ context_class->allocate_width = gtk_cell_area_box_context_allocate_width;
+ context_class->allocate_height = gtk_cell_area_box_context_allocate_height;
+
+ g_type_class_add_private (object_class, sizeof (GtkCellAreaBoxContextPrivate));
+}
+
+/*************************************************************
+ * GObjectClass *
+ *************************************************************/
+static void
+gtk_cell_area_box_context_finalize (GObject *object)
+{
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (object);
+ GtkCellAreaBoxContextPrivate *priv = box_context->priv;
+
+ g_array_free (priv->base_widths, TRUE);
+ g_array_free (priv->base_heights, TRUE);
+ g_hash_table_destroy (priv->widths);
+ g_hash_table_destroy (priv->heights);
+
+ g_free (priv->orientation_allocs);
+
+ G_OBJECT_CLASS (gtk_cell_area_box_context_parent_class)->finalize (object);
+}
+
+/*************************************************************
+ * GtkCellAreaContextClass *
+ *************************************************************/
+static void
+gtk_cell_area_box_context_flush_preferred_width (GtkCellAreaContext *context)
+{
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ GtkCellAreaBoxContextPrivate *priv = box_context->priv;
+ gint i;
+
+ for (i = 0; i < priv->base_widths->len; i++)
+ {
+ BaseSize *size = &g_array_index (priv->base_widths, BaseSize, i);
+
+ size->min_size = 0;
+ size->nat_size = 0;
+ }
+
+ GTK_CELL_AREA_CONTEXT_CLASS
+ (gtk_cell_area_box_context_parent_class)->flush_preferred_width (context);
+}
+
+static void
+gtk_cell_area_box_context_flush_preferred_height_for_width (GtkCellAreaContext *context,
+ gint width)
+{
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ GtkCellAreaBoxContextPrivate *priv = box_context->priv;
+
+ /* Flush all sizes for special -1 value */
+ if (width < 0)
+ g_hash_table_remove_all (priv->heights);
+ else
+ g_hash_table_remove (priv->heights, GINT_TO_POINTER (width));
+
+ GTK_CELL_AREA_CONTEXT_CLASS
+ (gtk_cell_area_box_context_parent_class)->flush_preferred_height_for_width (context, width);
+}
+
+static void
+gtk_cell_area_box_context_flush_preferred_height (GtkCellAreaContext *context)
+{
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ GtkCellAreaBoxContextPrivate *priv = box_context->priv;
+ gint i;
+
+ for (i = 0; i < priv->base_heights->len; i++)
+ {
+ BaseSize *size = &g_array_index (priv->base_heights, BaseSize, i);
+
+ size->min_size = 0;
+ size->nat_size = 0;
+ }
+
+ GTK_CELL_AREA_CONTEXT_CLASS
+ (gtk_cell_area_box_context_parent_class)->flush_preferred_height (context);
+}
+
+static void
+gtk_cell_area_box_context_flush_preferred_width_for_height (GtkCellAreaContext *context,
+ gint height)
+{
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ GtkCellAreaBoxContextPrivate *priv = box_context->priv;
+
+ /* Flush all sizes for special -1 value */
+ if (height < 0)
+ g_hash_table_remove_all (priv->widths);
+ else
+ g_hash_table_remove (priv->widths, GINT_TO_POINTER (height));
+
+ GTK_CELL_AREA_CONTEXT_CLASS
+ (gtk_cell_area_box_context_parent_class)->flush_preferred_width_for_height (context, height);
+}
+
+static void
+gtk_cell_area_box_context_flush_allocation (GtkCellAreaContext *context)
+{
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ GtkCellAreaBoxContextPrivate *priv = box_context->priv;
+
+ g_free (priv->orientation_allocs);
+ priv->orientation_allocs = NULL;
+ priv->n_orientation_allocs = 0;
+}
+
+static void
+gtk_cell_area_box_context_sum_preferred_width (GtkCellAreaContext *context)
+{
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ GtkCellAreaBoxContextPrivate *priv = box_context->priv;
+ GtkCellArea *area;
+ GtkOrientation orientation;
+ gint spacing, i;
+ gint min_size = 0, nat_size = 0;
+
+ area = gtk_cell_area_context_get_area (context);
+ spacing = gtk_cell_area_box_get_spacing (GTK_CELL_AREA_BOX (area));
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (area));
+
+ for (i = 0; i < priv->base_widths->len; i++)
+ {
+ BaseSize *size = &g_array_index (priv->base_widths, BaseSize, i);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ /* Dont add spacing for 0 size groups, they can be 0 size because
+ * they contain only invisible cells for this round of requests
+ */
+ if (min_size > 0 && size->nat_size > 0)
+ {
+ min_size += spacing;
+ nat_size += spacing;
+ }
+
+ min_size += size->min_size;
+ nat_size += size->nat_size;
+ }
+ else
+ {
+ min_size = MAX (min_size, size->min_size);
+ nat_size = MAX (nat_size, size->nat_size);
+ }
+ }
+
+ gtk_cell_area_context_push_preferred_width (context, min_size, nat_size);
+}
+
+static void
+gtk_cell_area_box_context_sum_preferred_height_for_width (GtkCellAreaContext *context,
+ gint width)
+{
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ GtkCellAreaBoxContextPrivate *priv = box_context->priv;
+ GArray *group_array;
+ GtkCellArea *area;
+ GtkOrientation orientation;
+ gint spacing, i;
+ gint min_size = 0, nat_size = 0;
+
+ group_array = g_hash_table_lookup (priv->heights, GINT_TO_POINTER (width));
+
+ if (group_array)
+ {
+ area = gtk_cell_area_context_get_area (context);
+ spacing = gtk_cell_area_box_get_spacing (GTK_CELL_AREA_BOX (area));
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (area));
+
+ for (i = 0; i < group_array->len; i++)
+ {
+ CachedSize *size = &g_array_index (group_array, CachedSize, i);
+
+ if (orientation == GTK_ORIENTATION_VERTICAL)
+ {
+ /* Dont add spacing for 0 size groups, they can be 0 size because
+ * they contain only invisible cells for this round of requests
+ */
+ if (min_size > 0 && size->nat_size > 0)
+ {
+ min_size += spacing;
+ nat_size += spacing;
+ }
+
+ min_size += size->min_size;
+ nat_size += size->nat_size;
+ }
+ else
+ {
+ min_size = MAX (min_size, size->min_size);
+ nat_size = MAX (nat_size, size->nat_size);
+ }
+ }
+
+ gtk_cell_area_context_push_preferred_height_for_width (context, width, min_size, nat_size);
+ }
+}
+
+static void
+gtk_cell_area_box_context_sum_preferred_height (GtkCellAreaContext *context)
+{
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ GtkCellAreaBoxContextPrivate *priv = box_context->priv;
+ GtkCellArea *area;
+ GtkOrientation orientation;
+ gint spacing, i;
+ gint min_size = 0, nat_size = 0;
+
+ area = gtk_cell_area_context_get_area (context);
+ spacing = gtk_cell_area_box_get_spacing (GTK_CELL_AREA_BOX (area));
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (area));
+
+ for (i = 0; i < priv->base_heights->len; i++)
+ {
+ BaseSize *size = &g_array_index (priv->base_heights, BaseSize, i);
+
+ if (orientation == GTK_ORIENTATION_VERTICAL)
+ {
+ /* Dont add spacing for 0 size groups, they can be 0 size because
+ * they contain only invisible cells for this round of requests
+ */
+ if (min_size > 0 && size->nat_size > 0)
+ {
+ min_size += spacing;
+ nat_size += spacing;
+ }
+
+ min_size += size->min_size;
+ nat_size += size->nat_size;
+ }
+ else
+ {
+ min_size = MAX (min_size, size->min_size);
+ nat_size = MAX (nat_size, size->nat_size);
+ }
+ }
+
+ gtk_cell_area_context_push_preferred_height (context, min_size, nat_size);
+}
+
+static void
+gtk_cell_area_box_context_sum_preferred_width_for_height (GtkCellAreaContext *context,
+ gint height)
+{
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ GtkCellAreaBoxContextPrivate *priv = box_context->priv;
+ GArray *group_array;
+ GtkCellArea *area;
+ GtkOrientation orientation;
+ gint spacing, i;
+ gint min_size = 0, nat_size = 0;
+
+ group_array = g_hash_table_lookup (priv->widths, GINT_TO_POINTER (height));
+
+ if (group_array)
+ {
+ area = gtk_cell_area_context_get_area (context);
+ spacing = gtk_cell_area_box_get_spacing (GTK_CELL_AREA_BOX (area));
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (area));
+
+ for (i = 0; i < group_array->len; i++)
+ {
+ CachedSize *size = &g_array_index (group_array, CachedSize, i);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ /* Dont add spacing for 0 size groups, they can be 0 size because
+ * they contain only invisible cells for this round of requests
+ */
+ if (min_size > 0 && size->nat_size > 0)
+ {
+ min_size += spacing;
+ nat_size += spacing;
+ }
+
+ min_size += size->min_size;
+ nat_size += size->nat_size;
+ }
+ else
+ {
+ min_size = MAX (min_size, size->min_size);
+ nat_size = MAX (nat_size, size->nat_size);
+ }
+ }
+
+ gtk_cell_area_context_push_preferred_width_for_height (context, height, min_size, nat_size);
+ }
+}
+
+static GtkRequestedSize *
+gtk_cell_area_box_context_get_requests (GtkCellAreaBoxContext *box_context,
+ GtkOrientation orientation,
+ gint *n_requests)
+{
+ GtkCellAreaBoxContextPrivate *priv;
+ GtkRequestedSize *requests;
+ GArray *base_array;
+ BaseSize *size;
+ gint visible_groups = 0;
+ gint i, j;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context), NULL);
+
+ priv = box_context->priv;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ base_array = priv->base_widths;
+ else
+ base_array = priv->base_heights;
+
+ for (i = 0; i < base_array->len; i++)
+ {
+ size = &g_array_index (base_array, BaseSize, i);
+
+ if (size->nat_size > 0)
+ visible_groups++;
+ }
+
+ requests = g_new (GtkRequestedSize, visible_groups);
+
+ for (j = 0, i = 0; i < base_array->len; i++)
+ {
+ size = &g_array_index (base_array, BaseSize, i);
+
+ if (size->nat_size > 0)
+ {
+ requests[j].data = GINT_TO_POINTER (i);
+ requests[j].minimum_size = size->min_size;
+ requests[j].natural_size = size->nat_size;
+ j++;
+ }
+ }
+
+ if (n_requests)
+ *n_requests = visible_groups;
+
+ return requests;
+}
+
+static GtkCellAreaBoxAllocation *
+allocate_for_orientation (GtkCellAreaBoxContext *context,
+ GtkOrientation orientation,
+ gint spacing,
+ gint size,
+ gint *n_allocs)
+{
+ GtkCellAreaBoxContextPrivate *priv = context->priv;
+ GtkRequestedSize *orientation_sizes;
+ GtkCellAreaBoxAllocation *allocs;
+ gint n_expand_groups = 0;
+ gint i, n_groups, position;
+ gint extra_size, extra_extra;
+ gint avail_size = size;
+
+ orientation_sizes =
+ gtk_cell_area_box_context_get_requests (context, orientation, &n_groups);
+
+ /* Count groups that expand */
+ for (i = 0; i < n_groups; i++)
+ {
+ BaseSize *size;
+ gint group_idx = GPOINTER_TO_INT (orientation_sizes[i].data);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ size = &g_array_index (priv->base_widths, BaseSize, group_idx);
+ else
+ size = &g_array_index (priv->base_heights, BaseSize, group_idx);
+
+ if (size->expand)
+ n_expand_groups++;
+ }
+
+ /* First start by naturally allocating space among groups */
+ avail_size -= (n_groups - 1) * spacing;
+ for (i = 0; i < n_groups; i++)
+ avail_size -= orientation_sizes[i].minimum_size;
+
+ avail_size = gtk_distribute_natural_allocation (avail_size, n_groups, orientation_sizes);
+
+ /* Calculate/distribute expand for groups */
+ if (n_expand_groups > 0)
+ {
+ extra_size = avail_size / n_expand_groups;
+ extra_extra = avail_size % n_expand_groups;
+ }
+ else
+ extra_size = extra_extra = 0;
+
+ allocs = g_new (GtkCellAreaBoxAllocation, n_groups);
+
+ for (position = 0, i = 0; i < n_groups; i++)
+ {
+ BaseSize *base_size = &g_array_index (priv->base_widths, BaseSize, i);
+
+ allocs[i].group_idx = GPOINTER_TO_INT (orientation_sizes[i].data);
+ allocs[i].position = position;
+ allocs[i].size = orientation_sizes[i].minimum_size;
+
+ if (base_size->expand)
+ {
+ allocs[i].size += extra_size;
+ if (extra_extra)
+ {
+ allocs[i].size++;
+ extra_extra--;
+ }
+ }
+
+ position += allocs[i].size;
+ position += spacing;
+ }
+
+ if (n_allocs)
+ *n_allocs = n_groups;
+
+ g_free (orientation_sizes);
+
+ return allocs;
+}
+
+static void
+gtk_cell_area_box_context_allocate_width (GtkCellAreaContext *context,
+ gint width)
+{
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ GtkCellAreaBoxContextPrivate *priv = box_context->priv;
+ GtkCellArea *area;
+ GtkOrientation orientation;
+
+ area = gtk_cell_area_context_get_area (context);
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (area));
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ gint spacing = gtk_cell_area_box_get_spacing (GTK_CELL_AREA_BOX (area));
+
+ g_free (priv->orientation_allocs);
+ priv->orientation_allocs = allocate_for_orientation (box_context, orientation, spacing, width,
+ &priv->n_orientation_allocs);
+ }
+
+ GTK_CELL_AREA_CONTEXT_CLASS (gtk_cell_area_box_context_parent_class)->allocate_width (context, width);
+}
+
+static void
+gtk_cell_area_box_context_allocate_height (GtkCellAreaContext *context,
+ gint height)
+{
+ GtkCellAreaBoxContext *box_context = GTK_CELL_AREA_BOX_CONTEXT (context);
+ GtkCellAreaBoxContextPrivate *priv = box_context->priv;
+ GtkCellArea *area;
+ GtkOrientation orientation;
+
+ area = gtk_cell_area_context_get_area (context);
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (area));
+
+ if (orientation == GTK_ORIENTATION_VERTICAL)
+ {
+ gint spacing = gtk_cell_area_box_get_spacing (GTK_CELL_AREA_BOX (area));
+
+ g_free (priv->orientation_allocs);
+ priv->orientation_allocs = allocate_for_orientation (box_context, orientation, spacing, height,
+ &priv->n_orientation_allocs);
+ }
+
+ GTK_CELL_AREA_CONTEXT_CLASS (gtk_cell_area_box_context_parent_class)->allocate_height (context, height);
+}
+
+/*************************************************************
+ * API *
+ *************************************************************/
+void
+gtk_cell_area_box_init_groups (GtkCellAreaBoxContext *box_context,
+ guint n_groups,
+ gboolean *expand_groups)
+{
+ GtkCellAreaBoxContextPrivate *priv;
+ gint i;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
+ g_return_if_fail (n_groups == 0 || expand_groups != NULL);
+
+ /* When the group dimensions change, all info must be flushed
+ * Note this already clears the min/nat values on the BaseSizes
+ */
+ gtk_cell_area_context_flush (GTK_CELL_AREA_CONTEXT (box_context));
+
+ priv = box_context->priv;
+ g_array_set_size (priv->base_widths, n_groups);
+ g_array_set_size (priv->base_heights, n_groups);
+
+ /* Now set the expand info */
+ for (i = 0; i < n_groups; i++)
+ {
+ BaseSize *base_width = &g_array_index (priv->base_widths, BaseSize, i);
+ BaseSize *base_height = &g_array_index (priv->base_heights, BaseSize, i);
+
+ base_width->expand = expand_groups[i];
+ base_height->expand = expand_groups[i];
+ }
+}
+
+void
+gtk_cell_area_box_context_push_group_width (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint minimum_width,
+ gint natural_width)
+{
+ GtkCellAreaBoxContextPrivate *priv;
+ BaseSize *size;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
+
+ priv = box_context->priv;
+ g_return_if_fail (group_idx < priv->base_widths->len);
+
+ size = &g_array_index (priv->base_widths, BaseSize, group_idx);
+ size->min_size = MAX (size->min_size, minimum_width);
+ size->nat_size = MAX (size->nat_size, natural_width);
+}
+
+void
+gtk_cell_area_box_context_push_group_height_for_width (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint for_width,
+ gint minimum_height,
+ gint natural_height)
+{
+ GtkCellAreaBoxContextPrivate *priv;
+ GArray *group_array;
+ CachedSize *size;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
+
+ priv = box_context->priv;
+ g_return_if_fail (group_idx < priv->base_widths->len);
+
+ group_array = g_hash_table_lookup (priv->heights, GINT_TO_POINTER (for_width));
+ if (!group_array)
+ {
+ group_array = g_array_new (FALSE, TRUE, sizeof (CachedSize));
+ g_array_set_size (group_array, priv->base_heights->len);
+
+ g_hash_table_insert (priv->heights, GINT_TO_POINTER (for_width), group_array);
+ }
+
+ size = &g_array_index (group_array, CachedSize, group_idx);
+ size->min_size = MAX (size->min_size, minimum_height);
+ size->nat_size = MAX (size->nat_size, natural_height);
+}
+
+void
+gtk_cell_area_box_context_push_group_height (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint minimum_height,
+ gint natural_height)
+{
+ GtkCellAreaBoxContextPrivate *priv;
+ BaseSize *size;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
+
+ priv = box_context->priv;
+ g_return_if_fail (group_idx < priv->base_heights->len);
+
+ size = &g_array_index (priv->base_heights, BaseSize, group_idx);
+ size->min_size = MAX (size->min_size, minimum_height);
+ size->nat_size = MAX (size->nat_size, natural_height);
+}
+
+void
+gtk_cell_area_box_context_push_group_width_for_height (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint for_height,
+ gint minimum_width,
+ gint natural_width)
+{
+ GtkCellAreaBoxContextPrivate *priv;
+ GArray *group_array;
+ CachedSize *size;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
+
+ priv = box_context->priv;
+ g_return_if_fail (group_idx < priv->base_widths->len);
+
+ group_array = g_hash_table_lookup (priv->widths, GINT_TO_POINTER (for_height));
+ if (!group_array)
+ {
+ group_array = g_array_new (FALSE, TRUE, sizeof (CachedSize));
+ g_array_set_size (group_array, priv->base_heights->len);
+
+ g_hash_table_insert (priv->widths, GINT_TO_POINTER (for_height), group_array);
+ }
+
+ size = &g_array_index (group_array, CachedSize, group_idx);
+ size->min_size = MAX (size->min_size, minimum_width);
+ size->nat_size = MAX (size->nat_size, natural_width);
+}
+
+void
+gtk_cell_area_box_context_get_group_width (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ GtkCellAreaBoxContextPrivate *priv;
+ BaseSize *size;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
+
+ priv = box_context->priv;
+ g_return_if_fail (group_idx < priv->base_widths->len);
+
+ size = &g_array_index (priv->base_widths, BaseSize, group_idx);
+
+ if (minimum_width)
+ *minimum_width = size->min_size;
+
+ if (natural_width)
+ *natural_width = size->nat_size;
+}
+
+void
+gtk_cell_area_box_context_get_group_height_for_width (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint for_width,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ GtkCellAreaBoxContextPrivate *priv;
+ GArray *group_array;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
+
+ priv = box_context->priv;
+ g_return_if_fail (group_idx < priv->base_widths->len);
+
+ group_array = g_hash_table_lookup (priv->heights, GINT_TO_POINTER (for_width));
+
+ if (group_array)
+ {
+ CachedSize *size = &g_array_index (group_array, CachedSize, group_idx);
+
+ if (minimum_height)
+ *minimum_height = size->min_size;
+
+ if (natural_height)
+ *natural_height = size->nat_size;
+ }
+ else
+ {
+ if (minimum_height)
+ *minimum_height = -1;
+
+ if (natural_height)
+ *natural_height = -1;
+ }
+}
+
+void
+gtk_cell_area_box_context_get_group_height (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ GtkCellAreaBoxContextPrivate *priv;
+ BaseSize *size;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
+
+ priv = box_context->priv;
+ g_return_if_fail (group_idx < priv->base_heights->len);
+
+ size = &g_array_index (priv->base_heights, BaseSize, group_idx);
+
+ if (minimum_height)
+ *minimum_height = size->min_size;
+
+ if (natural_height)
+ *natural_height = size->nat_size;
+}
+
+void
+gtk_cell_area_box_context_get_group_width_for_height (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint for_height,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ GtkCellAreaBoxContextPrivate *priv;
+ GArray *group_array;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (box_context));
+
+ priv = box_context->priv;
+ g_return_if_fail (group_idx < priv->base_widths->len);
+
+ group_array = g_hash_table_lookup (priv->widths, GINT_TO_POINTER (for_height));
+
+ if (group_array)
+ {
+ CachedSize *size = &g_array_index (group_array, CachedSize, group_idx);
+
+ if (minimum_width)
+ *minimum_width = size->min_size;
+
+ if (natural_width)
+ *natural_width = size->nat_size;
+ }
+ else
+ {
+ if (minimum_width)
+ *minimum_width = -1;
+
+ if (natural_width)
+ *natural_width = -1;
+ }
+}
+
+GtkRequestedSize *
+gtk_cell_area_box_context_get_widths (GtkCellAreaBoxContext *box_context,
+ gint *n_widths)
+{
+ return gtk_cell_area_box_context_get_requests (box_context, GTK_ORIENTATION_HORIZONTAL, n_widths);
+}
+
+GtkRequestedSize *
+gtk_cell_area_box_context_get_heights (GtkCellAreaBoxContext *box_context,
+ gint *n_heights)
+{
+ return gtk_cell_area_box_context_get_requests (box_context, GTK_ORIENTATION_VERTICAL, n_heights);
+}
+
+G_CONST_RETURN GtkCellAreaBoxAllocation *
+gtk_cell_area_box_context_get_orientation_allocs (GtkCellAreaBoxContext *context,
+ gint *n_allocs)
+{
+ GtkCellAreaBoxContextPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA_BOX_CONTEXT (context), NULL);
+
+ priv = context->priv;
+
+ *n_allocs = priv->n_orientation_allocs;
+
+ return priv->orientation_allocs;
+}
--- /dev/null
+/* gtkcellareaboxcontext.h
+ *
+ * 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
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#ifndef __GTK_CELL_AREA_BOX_CONTEXT_H__
+#define __GTK_CELL_AREA_BOX_CONTEXT_H__
+
+#include <gtk/gtkcellareacontext.h>
+#include <gtk/gtkcellareabox.h>
+#include <gtk/gtkcellrenderer.h>
+#include <gtk/gtksizerequest.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_CELL_AREA_BOX_CONTEXT (gtk_cell_area_box_context_get_type ())
+#define GTK_CELL_AREA_BOX_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CELL_AREA_BOX_CONTEXT, GtkCellAreaBoxContext))
+#define GTK_CELL_AREA_BOX_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_CELL_AREA_BOX_CONTEXT, GtkCellAreaBoxContextClass))
+#define GTK_IS_CELL_AREA_BOX_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CELL_AREA_BOX_CONTEXT))
+#define GTK_IS_CELL_AREA_BOX_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_CELL_AREA_BOX_CONTEXT))
+#define GTK_CELL_AREA_BOX_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_CELL_AREA_BOX_CONTEXT, GtkCellAreaBoxContextClass))
+
+typedef struct _GtkCellAreaBoxContext GtkCellAreaBoxContext;
+typedef struct _GtkCellAreaBoxContextClass GtkCellAreaBoxContextClass;
+typedef struct _GtkCellAreaBoxContextPrivate GtkCellAreaBoxContextPrivate;
+
+struct _GtkCellAreaBoxContext
+{
+ GtkCellAreaContext parent_instance;
+
+ GtkCellAreaBoxContextPrivate *priv;
+};
+
+struct _GtkCellAreaBoxContextClass
+{
+ GtkCellAreaContextClass parent_class;
+
+};
+
+GType gtk_cell_area_box_context_get_type (void) G_GNUC_CONST;
+
+
+/* Initialize group array dimensions */
+void gtk_cell_area_box_init_groups (GtkCellAreaBoxContext *box_context,
+ guint n_groups,
+ gboolean *expand_groups);
+
+/* Update cell-group sizes */
+void gtk_cell_area_box_context_push_group_width (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint minimum_width,
+ gint natural_width);
+
+void gtk_cell_area_box_context_push_group_height_for_width (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint for_width,
+ gint minimum_height,
+ gint natural_height);
+
+void gtk_cell_area_box_context_push_group_height (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint minimum_height,
+ gint natural_height);
+
+void gtk_cell_area_box_context_push_group_width_for_height (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint for_height,
+ gint minimum_width,
+ gint natural_width);
+
+/* Fetch cell-group sizes */
+void gtk_cell_area_box_context_get_group_width (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint *minimum_width,
+ gint *natural_width);
+
+void gtk_cell_area_box_context_get_group_height_for_width (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint for_width,
+ gint *minimum_height,
+ gint *natural_height);
+
+void gtk_cell_area_box_context_get_group_height (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint *minimum_height,
+ gint *natural_height);
+
+void gtk_cell_area_box_context_get_group_width_for_height (GtkCellAreaBoxContext *box_context,
+ gint group_idx,
+ gint for_height,
+ gint *minimum_width,
+ gint *natural_width);
+
+GtkRequestedSize *gtk_cell_area_box_context_get_widths (GtkCellAreaBoxContext *box_context,
+ gint *n_widths);
+GtkRequestedSize *gtk_cell_area_box_context_get_heights (GtkCellAreaBoxContext *box_context,
+ gint *n_heights);
+
+/* Private context/area interaction */
+typedef struct {
+ gint group_idx; /* Groups containing only invisible cells are not allocated */
+ gint position; /* Relative group allocation position in the orientation of the box */
+ gint size; /* Full allocated size of the cells in this group spacing inclusive */
+} GtkCellAreaBoxAllocation;
+
+G_CONST_RETURN GtkCellAreaBoxAllocation *
+gtk_cell_area_box_context_get_orientation_allocs (GtkCellAreaBoxContext *context,
+ gint *n_allocs);
+
+G_END_DECLS
+
+#endif /* __GTK_CELL_AREA_BOX_CONTEXT_H__ */
--- /dev/null
+/* gtkcellareacontext.c
+ *
+ * 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
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+#include "gtkintl.h"
+#include "gtkmarshalers.h"
+#include "gtkcellareacontext.h"
+#include "gtkprivate.h"
+
+/* GObjectClass */
+static void gtk_cell_area_context_finalize (GObject *object);
+static void gtk_cell_area_context_dispose (GObject *object);
+static void gtk_cell_area_context_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gtk_cell_area_context_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+/* GtkCellAreaContextClass */
+static void gtk_cell_area_context_real_flush_preferred_width (GtkCellAreaContext *context);
+static void gtk_cell_area_context_real_flush_preferred_height_for_width (GtkCellAreaContext *context,
+ gint width);
+static void gtk_cell_area_context_real_flush_preferred_height (GtkCellAreaContext *context);
+static void gtk_cell_area_context_real_flush_preferred_width_for_height (GtkCellAreaContext *context,
+ gint height);
+static void gtk_cell_area_context_real_flush_allocation (GtkCellAreaContext *context);
+static void gtk_cell_area_context_real_allocate_width (GtkCellAreaContext *context,
+ gint width);
+static void gtk_cell_area_context_real_allocate_height (GtkCellAreaContext *context,
+ gint height);
+
+/* CachedSize management */
+typedef struct {
+ gint min_size;
+ gint nat_size;
+} CachedSize;
+
+static CachedSize *cached_size_new (gint min_size, gint nat_size);
+static void cached_size_free (CachedSize *size);
+
+struct _GtkCellAreaContextPrivate
+{
+ GtkCellArea *cell_area;
+
+ gint min_width;
+ gint nat_width;
+ gint min_height;
+ gint nat_height;
+ gint alloc_width;
+ gint alloc_height;
+
+ GHashTable *widths;
+ GHashTable *heights;
+};
+
+enum {
+ PROP_0,
+ PROP_CELL_AREA,
+ PROP_MIN_WIDTH,
+ PROP_NAT_WIDTH,
+ PROP_MIN_HEIGHT,
+ PROP_NAT_HEIGHT
+};
+
+enum {
+ SIGNAL_WIDTH_CHANGED,
+ SIGNAL_HEIGHT_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint cell_area_context_signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GtkCellAreaContext, gtk_cell_area_context, G_TYPE_OBJECT);
+
+static void
+gtk_cell_area_context_init (GtkCellAreaContext *context)
+{
+ GtkCellAreaContextPrivate *priv;
+
+ context->priv = G_TYPE_INSTANCE_GET_PRIVATE (context,
+ GTK_TYPE_CELL_AREA_CONTEXT,
+ GtkCellAreaContextPrivate);
+ priv = context->priv;
+
+ priv->min_width = -1;
+ priv->nat_width = -1;
+ priv->min_height = -1;
+ priv->nat_height = -1;
+ priv->widths = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify)cached_size_free);
+ priv->heights = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify)cached_size_free);
+}
+
+static void
+gtk_cell_area_context_class_init (GtkCellAreaContextClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ /* GObjectClass */
+ object_class->finalize = gtk_cell_area_context_finalize;
+ object_class->dispose = gtk_cell_area_context_dispose;
+ object_class->get_property = gtk_cell_area_context_get_property;
+ object_class->set_property = gtk_cell_area_context_set_property;
+
+ /* GtkCellAreaContextClass */
+ class->flush_preferred_width = gtk_cell_area_context_real_flush_preferred_width;
+ class->flush_preferred_height_for_width = gtk_cell_area_context_real_flush_preferred_height_for_width;
+ class->flush_preferred_height = gtk_cell_area_context_real_flush_preferred_height;
+ class->flush_preferred_width_for_height = gtk_cell_area_context_real_flush_preferred_width_for_height;
+ class->flush_allocation = gtk_cell_area_context_real_flush_allocation;
+
+ class->sum_preferred_width = NULL;
+ class->sum_preferred_height_for_width = NULL;
+ class->sum_preferred_height = NULL;
+ class->sum_preferred_width_for_height = NULL;
+
+ class->allocate_width = gtk_cell_area_context_real_allocate_width;
+ class->allocate_height = gtk_cell_area_context_real_allocate_height;
+
+ cell_area_context_signals[SIGNAL_HEIGHT_CHANGED] =
+ g_signal_new (I_("height-changed"),
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0, /* Class offset (just a notification, no class handler) */
+ NULL, NULL,
+ _gtk_marshal_VOID__INT_INT_INT,
+ G_TYPE_NONE, 3,
+ G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
+
+ cell_area_context_signals[SIGNAL_WIDTH_CHANGED] =
+ g_signal_new (I_("width-changed"),
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0, /* Class offset (just a notification, no class handler) */
+ NULL, NULL,
+ _gtk_marshal_VOID__INT_INT_INT,
+ G_TYPE_NONE, 3,
+ G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
+
+ g_object_class_install_property (object_class,
+ PROP_CELL_AREA,
+ g_param_spec_object ("area",
+ P_("Area"),
+ P_("The Cell Area this context was created for"),
+ GTK_TYPE_CELL_AREA,
+ GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class,
+ PROP_MIN_WIDTH,
+ g_param_spec_int ("minimum-width",
+ P_("Minimum Width"),
+ P_("Minimum cached width"),
+ -1,
+ G_MAXINT,
+ -1,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_NAT_WIDTH,
+ g_param_spec_int ("natural-width",
+ P_("Minimum Width"),
+ P_("Minimum cached width"),
+ -1,
+ G_MAXINT,
+ -1,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_MIN_HEIGHT,
+ g_param_spec_int ("minimum-height",
+ P_("Minimum Height"),
+ P_("Minimum cached height"),
+ -1,
+ G_MAXINT,
+ -1,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_NAT_HEIGHT,
+ g_param_spec_int ("natural-height",
+ P_("Minimum Height"),
+ P_("Minimum cached height"),
+ -1,
+ G_MAXINT,
+ -1,
+ G_PARAM_READABLE));
+
+ g_type_class_add_private (object_class, sizeof (GtkCellAreaContextPrivate));
+}
+
+
+
+/*************************************************************
+ * Cached Sizes *
+ *************************************************************/
+static CachedSize *
+cached_size_new (gint min_size,
+ gint nat_size)
+{
+ CachedSize *size = g_slice_new (CachedSize);
+
+ size->min_size = min_size;
+ size->nat_size = nat_size;
+
+ return size;
+}
+
+static void
+cached_size_free (CachedSize *size)
+{
+ g_slice_free (CachedSize, size);
+}
+
+/*************************************************************
+ * GObjectClass *
+ *************************************************************/
+static void
+gtk_cell_area_context_finalize (GObject *object)
+{
+ GtkCellAreaContext *context = GTK_CELL_AREA_CONTEXT (object);
+ GtkCellAreaContextPrivate *priv = context->priv;
+
+ g_hash_table_destroy (priv->widths);
+ g_hash_table_destroy (priv->heights);
+
+ G_OBJECT_CLASS (gtk_cell_area_context_parent_class)->finalize (object);
+}
+
+static void
+gtk_cell_area_context_dispose (GObject *object)
+{
+ GtkCellAreaContext *context = GTK_CELL_AREA_CONTEXT (object);
+ GtkCellAreaContextPrivate *priv = context->priv;
+
+ if (priv->cell_area)
+ {
+ g_object_unref (priv->cell_area);
+
+ priv->cell_area = NULL;
+ }
+
+ G_OBJECT_CLASS (gtk_cell_area_context_parent_class)->dispose (object);
+}
+
+static void
+gtk_cell_area_context_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkCellAreaContext *context = GTK_CELL_AREA_CONTEXT (object);
+ GtkCellAreaContextPrivate *priv = context->priv;
+
+ switch (prop_id)
+ {
+ case PROP_CELL_AREA:
+ priv->cell_area = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_cell_area_context_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkCellAreaContext *context = GTK_CELL_AREA_CONTEXT (object);
+ GtkCellAreaContextPrivate *priv = context->priv;
+
+ switch (prop_id)
+ {
+ case PROP_CELL_AREA:
+ g_value_set_object (value, priv->cell_area);
+ break;
+ case PROP_MIN_WIDTH:
+ g_value_set_int (value, priv->min_width);
+ break;
+ case PROP_NAT_WIDTH:
+ g_value_set_int (value, priv->nat_width);
+ break;
+ case PROP_MIN_HEIGHT:
+ g_value_set_int (value, priv->min_height);
+ break;
+ case PROP_NAT_HEIGHT:
+ g_value_set_int (value, priv->nat_height);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/*************************************************************
+ * GtkCellAreaContextClass *
+ *************************************************************/
+static void
+gtk_cell_area_context_real_flush_preferred_width (GtkCellAreaContext *context)
+{
+ GtkCellAreaContextPrivate *priv = context->priv;
+
+ priv->min_width = -1;
+ priv->nat_width = -1;
+
+ g_object_freeze_notify (G_OBJECT (context));
+ g_object_notify (G_OBJECT (context), "minimum-width");
+ g_object_notify (G_OBJECT (context), "natural-width");
+ g_object_thaw_notify (G_OBJECT (context));
+}
+
+static void
+notify_invalid_height (gpointer width_ptr,
+ CachedSize *size,
+ GtkCellAreaContext *context)
+{
+ gint width = GPOINTER_TO_INT (width_ptr);
+
+ /* Notify size invalidated */
+ g_signal_emit (context, cell_area_context_signals[SIGNAL_HEIGHT_CHANGED],
+ 0, width, -1, -1);
+}
+
+static void
+gtk_cell_area_context_real_flush_preferred_height_for_width (GtkCellAreaContext *context,
+ gint width)
+{
+ GtkCellAreaContextPrivate *priv = context->priv;
+
+ /* Flush all sizes for special -1 value */
+ if (width < 0)
+ {
+ g_hash_table_foreach (priv->heights, (GHFunc)notify_invalid_height, context);
+ g_hash_table_remove_all (priv->heights);
+ }
+ else
+ {
+ g_hash_table_remove (priv->heights, GINT_TO_POINTER (width));
+
+ /* Notify size invalidated */
+ g_signal_emit (context, cell_area_context_signals[SIGNAL_HEIGHT_CHANGED],
+ 0, width, -1, -1);
+ }
+}
+
+static void
+gtk_cell_area_context_real_flush_preferred_height (GtkCellAreaContext *context)
+{
+ GtkCellAreaContextPrivate *priv = context->priv;
+
+ priv->min_height = -1;
+ priv->nat_height = -1;
+
+ g_object_freeze_notify (G_OBJECT (context));
+ g_object_notify (G_OBJECT (context), "minimum-height");
+ g_object_notify (G_OBJECT (context), "natural-height");
+ g_object_thaw_notify (G_OBJECT (context));
+}
+
+static void
+notify_invalid_width (gpointer height_ptr,
+ CachedSize *size,
+ GtkCellAreaContext *context)
+{
+ gint height = GPOINTER_TO_INT (height_ptr);
+
+ /* Notify size invalidated */
+ g_signal_emit (context, cell_area_context_signals[SIGNAL_WIDTH_CHANGED],
+ 0, height, -1, -1);
+}
+
+static void
+gtk_cell_area_context_real_flush_preferred_width_for_height (GtkCellAreaContext *context,
+ gint height)
+{
+ GtkCellAreaContextPrivate *priv = context->priv;
+
+ /* Flush all sizes for special -1 value */
+ if (height < 0)
+ {
+ g_hash_table_foreach (priv->widths, (GHFunc)notify_invalid_width, context);
+ g_hash_table_remove_all (priv->widths);
+ }
+ else
+ {
+ g_hash_table_remove (priv->widths, GINT_TO_POINTER (height));
+
+ /* Notify size invalidated */
+ g_signal_emit (context, cell_area_context_signals[SIGNAL_WIDTH_CHANGED],
+ 0, height, -1, -1);
+ }
+}
+
+static void
+gtk_cell_area_context_real_flush_allocation (GtkCellAreaContext *context)
+{
+ GtkCellAreaContextPrivate *priv = context->priv;
+
+ priv->alloc_width = 0;
+ priv->alloc_height = 0;
+}
+
+static void
+gtk_cell_area_context_real_allocate_width (GtkCellAreaContext *context,
+ gint width)
+{
+ GtkCellAreaContextPrivate *priv = context->priv;
+
+ priv->alloc_width = width;
+}
+
+static void
+gtk_cell_area_context_real_allocate_height (GtkCellAreaContext *context,
+ gint height)
+{
+ GtkCellAreaContextPrivate *priv = context->priv;
+
+ priv->alloc_height = height;
+}
+
+
+/*************************************************************
+ * API *
+ *************************************************************/
+GtkCellArea *
+gtk_cell_area_context_get_area (GtkCellAreaContext *context)
+{
+ GtkCellAreaContextPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_CELL_AREA_CONTEXT (context), NULL);
+
+ priv = context->priv;
+
+ return priv->cell_area;
+}
+
+void
+gtk_cell_area_context_flush (GtkCellAreaContext *context)
+{
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ gtk_cell_area_context_flush_preferred_width (context);
+ gtk_cell_area_context_flush_preferred_height_for_width (context, -1);
+ gtk_cell_area_context_flush_preferred_height (context);
+ gtk_cell_area_context_flush_preferred_width_for_height (context, -1);
+ gtk_cell_area_context_flush_allocation (context);
+}
+
+void
+gtk_cell_area_context_flush_preferred_width (GtkCellAreaContext *context)
+{
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->flush_preferred_width (context);
+}
+
+void
+gtk_cell_area_context_flush_preferred_height_for_width (GtkCellAreaContext *context,
+ gint for_width)
+{
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->flush_preferred_height_for_width (context, for_width);
+}
+
+void
+gtk_cell_area_context_flush_preferred_height (GtkCellAreaContext *context)
+{
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->flush_preferred_height (context);
+}
+
+void
+gtk_cell_area_context_flush_preferred_width_for_height (GtkCellAreaContext *context,
+ gint for_height)
+{
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->flush_preferred_width_for_height (context, for_height);
+}
+
+void
+gtk_cell_area_context_flush_allocation (GtkCellAreaContext *context)
+{
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ GTK_CELL_AREA_CONTEXT_GET_CLASS (context)->flush_allocation (context);
+}
+
+void
+gtk_cell_area_context_sum_preferred_width (GtkCellAreaContext *context)
+{
+ GtkCellAreaContextClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ class = GTK_CELL_AREA_CONTEXT_GET_CLASS (context);
+
+ if (class->sum_preferred_width)
+ class->sum_preferred_width (context);
+}
+
+void
+gtk_cell_area_context_sum_preferred_height_for_width (GtkCellAreaContext *context,
+ gint for_width)
+{
+ GtkCellAreaContextClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ class = GTK_CELL_AREA_CONTEXT_GET_CLASS (context);
+
+ if (class->sum_preferred_height_for_width)
+ class->sum_preferred_height_for_width (context, for_width);
+}
+
+void
+gtk_cell_area_context_sum_preferred_height (GtkCellAreaContext *context)
+{
+ GtkCellAreaContextClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ class = GTK_CELL_AREA_CONTEXT_GET_CLASS (context);
+
+ if (class->sum_preferred_height)
+ class->sum_preferred_height (context);
+}
+
+void
+gtk_cell_area_context_sum_preferred_width_for_height (GtkCellAreaContext *context,
+ gint for_height)
+{
+ GtkCellAreaContextClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ class = GTK_CELL_AREA_CONTEXT_GET_CLASS (context);
+
+ if (class->sum_preferred_width_for_height)
+ class->sum_preferred_width_for_height (context, for_height);
+}
+
+void
+gtk_cell_area_context_allocate_width (GtkCellAreaContext *context,
+ gint width)
+{
+ GtkCellAreaContextClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ class = GTK_CELL_AREA_CONTEXT_GET_CLASS (context);
+
+ class->allocate_width (context, width);
+}
+
+void
+gtk_cell_area_context_allocate_height (GtkCellAreaContext *context,
+ gint height)
+{
+ GtkCellAreaContextClass *class;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ class = GTK_CELL_AREA_CONTEXT_GET_CLASS (context);
+
+ class->allocate_height (context, height);
+}
+
+void
+gtk_cell_area_context_get_preferred_width (GtkCellAreaContext *context,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ GtkCellAreaContextPrivate *priv;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ priv = context->priv;
+
+ if (minimum_width)
+ *minimum_width = priv->min_width;
+
+ if (natural_width)
+ *natural_width = priv->nat_width;
+}
+
+void
+gtk_cell_area_context_get_preferred_height_for_width (GtkCellAreaContext *context,
+ gint for_width,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ GtkCellAreaContextPrivate *priv;
+ CachedSize *size;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ priv = context->priv;
+
+ size = g_hash_table_lookup (priv->heights, GINT_TO_POINTER (for_width));
+
+ if (size)
+ {
+ if (minimum_height)
+ *minimum_height = size->min_size;
+
+ if (natural_height)
+ *natural_height = size->nat_size;
+ }
+ else
+ {
+ if (minimum_height)
+ *minimum_height = -1;
+
+ if (natural_height)
+ *natural_height = -1;
+ }
+}
+
+void
+gtk_cell_area_context_get_preferred_height (GtkCellAreaContext *context,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ GtkCellAreaContextPrivate *priv;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ priv = context->priv;
+
+ if (minimum_height)
+ *minimum_height = priv->min_height;
+
+ if (natural_height)
+ *natural_height = priv->nat_height;
+}
+
+void
+gtk_cell_area_context_get_preferred_width_for_height (GtkCellAreaContext *context,
+ gint for_height,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ GtkCellAreaContextPrivate *priv;
+ CachedSize *size;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ priv = context->priv;
+
+ size = g_hash_table_lookup (priv->widths, GINT_TO_POINTER (for_height));
+
+ if (size)
+ {
+ if (minimum_width)
+ *minimum_width = size->min_size;
+
+ if (natural_width)
+ *natural_width = size->nat_size;
+ }
+ else
+ {
+ if (minimum_width)
+ *minimum_width = -1;
+
+ if (natural_width)
+ *natural_width = -1;
+ }
+}
+
+void
+gtk_cell_area_context_get_allocation (GtkCellAreaContext *context,
+ gint *width,
+ gint *height)
+{
+ GtkCellAreaContextPrivate *priv;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ priv = context->priv;
+
+ if (width)
+ *width = priv->alloc_width;
+
+ if (height)
+ *height = priv->alloc_height;
+}
+
+void
+gtk_cell_area_context_push_preferred_width (GtkCellAreaContext *context,
+ gint minimum_width,
+ gint natural_width)
+{
+ GtkCellAreaContextPrivate *priv;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ priv = context->priv;
+
+ g_object_freeze_notify (G_OBJECT (context));
+
+ if (minimum_width > priv->min_width)
+ {
+ priv->min_width = minimum_width;
+
+ g_object_notify (G_OBJECT (context), "minimum-width");
+ }
+
+ if (natural_width > priv->nat_width)
+ {
+ priv->nat_width = natural_width;
+
+ g_object_notify (G_OBJECT (context), "natural-width");
+ }
+
+ g_object_thaw_notify (G_OBJECT (context));
+}
+
+void
+gtk_cell_area_context_push_preferred_height_for_width (GtkCellAreaContext *context,
+ gint for_width,
+ gint minimum_height,
+ gint natural_height)
+{
+ GtkCellAreaContextPrivate *priv;
+ CachedSize *size;
+ gboolean changed = FALSE;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ priv = context->priv;
+
+ size = g_hash_table_lookup (priv->heights, GINT_TO_POINTER (for_width));
+
+ if (!size)
+ {
+ size = cached_size_new (minimum_height, natural_height);
+
+ g_hash_table_insert (priv->heights, GINT_TO_POINTER (for_width), size);
+
+ changed = TRUE;
+ }
+ else
+ {
+ if (minimum_height > size->min_size)
+ {
+ size->min_size = minimum_height;
+ changed = TRUE;
+ }
+
+ if (natural_height > size->nat_size)
+ {
+ size->nat_size = natural_height;
+ changed = TRUE;
+ }
+ }
+
+ if (changed)
+ g_signal_emit (context, cell_area_context_signals[SIGNAL_HEIGHT_CHANGED], 0,
+ for_width, size->min_size, size->nat_size);
+}
+
+void
+gtk_cell_area_context_push_preferred_height (GtkCellAreaContext *context,
+ gint minimum_height,
+ gint natural_height)
+{
+ GtkCellAreaContextPrivate *priv;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ priv = context->priv;
+
+ g_object_freeze_notify (G_OBJECT (context));
+
+ if (minimum_height > priv->min_height)
+ {
+ priv->min_height = minimum_height;
+
+ g_object_notify (G_OBJECT (context), "minimum-height");
+ }
+
+ if (natural_height > priv->nat_height)
+ {
+ priv->nat_height = natural_height;
+
+ g_object_notify (G_OBJECT (context), "natural-height");
+ }
+
+ g_object_thaw_notify (G_OBJECT (context));
+}
+
+void
+gtk_cell_area_context_push_preferred_width_for_height (GtkCellAreaContext *context,
+ gint for_height,
+ gint minimum_width,
+ gint natural_width)
+{
+ GtkCellAreaContextPrivate *priv;
+ CachedSize *size;
+ gboolean changed = FALSE;
+
+ g_return_if_fail (GTK_IS_CELL_AREA_CONTEXT (context));
+
+ priv = context->priv;
+
+ size = g_hash_table_lookup (priv->widths, GINT_TO_POINTER (for_height));
+
+ if (!size)
+ {
+ size = cached_size_new (minimum_width, natural_width);
+
+ g_hash_table_insert (priv->widths, GINT_TO_POINTER (for_height), size);
+
+ changed = TRUE;
+ }
+ else
+ {
+ if (minimum_width > size->min_size)
+ {
+ size->min_size = minimum_width;
+ changed = TRUE;
+ }
+
+ if (natural_width > size->nat_size)
+ {
+ size->nat_size = natural_width;
+ changed = TRUE;
+ }
+ }
+
+ if (changed)
+ g_signal_emit (context, cell_area_context_signals[SIGNAL_WIDTH_CHANGED], 0,
+ for_height, size->min_size, size->nat_size);
+}
--- /dev/null
+/* gtkcellareacontext.h
+ *
+ * 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
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#ifndef __GTK_CELL_AREA_CONTEXT_H__
+#define __GTK_CELL_AREA_CONTEXT_H__
+
+#include <gtk/gtkcellarea.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_CELL_AREA_CONTEXT (gtk_cell_area_context_get_type ())
+#define GTK_CELL_AREA_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CELL_AREA_CONTEXT, GtkCellAreaContext))
+#define GTK_CELL_AREA_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_CELL_AREA_CONTEXT, GtkCellAreaContextClass))
+#define GTK_IS_CELL_AREA_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CELL_AREA_CONTEXT))
+#define GTK_IS_CELL_AREA_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_CELL_AREA_CONTEXT))
+#define GTK_CELL_AREA_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_CELL_AREA_CONTEXT, GtkCellAreaContextClass))
+
+typedef struct _GtkCellAreaContextPrivate GtkCellAreaContextPrivate;
+typedef struct _GtkCellAreaContextClass GtkCellAreaContextClass;
+
+struct _GtkCellAreaContext
+{
+ GObject parent_instance;
+
+ GtkCellAreaContextPrivate *priv;
+};
+
+struct _GtkCellAreaContextClass
+{
+ GObjectClass parent_class;
+
+ /* Subclasses can use this to flush their alignments/allocations */
+ void (* flush_preferred_width) (GtkCellAreaContext *context);
+ void (* flush_preferred_height_for_width) (GtkCellAreaContext *context,
+ gint width);
+ void (* flush_preferred_height) (GtkCellAreaContext *context);
+ void (* flush_preferred_width_for_height) (GtkCellAreaContext *context,
+ gint height);
+ void (* flush_allocation) (GtkCellAreaContext *context);
+
+ /* These must be invoked after a series of requests before consulting
+ * the context values, implementors use this to push the overall
+ * requests while acconting for any internal alignments */
+ void (* sum_preferred_width) (GtkCellAreaContext *context);
+ void (* sum_preferred_height_for_width) (GtkCellAreaContext *context,
+ gint width);
+ void (* sum_preferred_height) (GtkCellAreaContext *context);
+ void (* sum_preferred_width_for_height) (GtkCellAreaContext *context,
+ gint height);
+
+ /* Store an allocation value for a GtkCellArea contextual to a range of
+ * treemodel rows */
+ void (* allocate_width) (GtkCellAreaContext *context,
+ gint width);
+ void (* allocate_height) (GtkCellAreaContext *context,
+ gint height);
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+};
+
+GType gtk_cell_area_context_get_type (void) G_GNUC_CONST;
+
+GtkCellArea *gtk_cell_area_context_get_area (GtkCellAreaContext *context);
+
+/* Apis for GtkCellArea clients to flush the cache */
+void gtk_cell_area_context_flush (GtkCellAreaContext *context);
+void gtk_cell_area_context_flush_preferred_width (GtkCellAreaContext *context);
+void gtk_cell_area_context_flush_preferred_height_for_width (GtkCellAreaContext *context,
+ gint for_width);
+void gtk_cell_area_context_flush_preferred_height (GtkCellAreaContext *context);
+void gtk_cell_area_context_flush_preferred_width_for_height (GtkCellAreaContext *context,
+ gint for_height);
+void gtk_cell_area_context_flush_allocation (GtkCellAreaContext *context);
+
+/* Apis for GtkCellArea clients to sum up the results of a series of requests, this
+ * call is required to reduce the processing while calculating the size of each row */
+void gtk_cell_area_context_sum_preferred_width (GtkCellAreaContext *context);
+void gtk_cell_area_context_sum_preferred_height_for_width (GtkCellAreaContext *context,
+ gint for_width);
+void gtk_cell_area_context_sum_preferred_height (GtkCellAreaContext *context);
+void gtk_cell_area_context_sum_preferred_width_for_height (GtkCellAreaContext *context,
+ gint for_height);
+
+/* Apis to set an allocation size in one dimension or another, the subclass specific context
+ * will store allocated positions/sizes for individual cells or groups of cells */
+void gtk_cell_area_context_allocate_width (GtkCellAreaContext *context,
+ gint width);
+void gtk_cell_area_context_allocate_height (GtkCellAreaContext *context,
+ gint height);
+
+/* Apis for GtkCellArea clients to consult cached values for multiple GtkTreeModel rows */
+void gtk_cell_area_context_get_preferred_width (GtkCellAreaContext *context,
+ gint *minimum_width,
+ gint *natural_width);
+void gtk_cell_area_context_get_preferred_height_for_width (GtkCellAreaContext *context,
+ gint for_width,
+ gint *minimum_height,
+ gint *natural_height);
+void gtk_cell_area_context_get_preferred_height (GtkCellAreaContext *context,
+ gint *minimum_height,
+ gint *natural_height);
+void gtk_cell_area_context_get_preferred_width_for_height (GtkCellAreaContext *context,
+ gint for_height,
+ gint *minimum_width,
+ gint *natural_width);
+void gtk_cell_area_context_get_allocation (GtkCellAreaContext *context,
+ gint *width,
+ gint *height);
+
+/* Apis for GtkCellArea implementations to update cached values for multiple GtkTreeModel rows */
+void gtk_cell_area_context_push_preferred_width (GtkCellAreaContext *context,
+ gint minimum_width,
+ gint natural_width);
+void gtk_cell_area_context_push_preferred_height_for_width (GtkCellAreaContext *context,
+ gint for_width,
+ gint minimum_height,
+ gint natural_height);
+void gtk_cell_area_context_push_preferred_height (GtkCellAreaContext *context,
+ gint minimum_height,
+ gint natural_height);
+void gtk_cell_area_context_push_preferred_width_for_height (GtkCellAreaContext *context,
+ gint for_height,
+ gint minimum_width,
+ gint natural_width);
+
+G_END_DECLS
+
+#endif /* __GTK_CELL_AREA_CONTEXT_H__ */
#include <stdlib.h>
#include <errno.h>
#include "gtkcelllayout.h"
+#include "gtkbuilderprivate.h"
#include "gtkintl.h"
return NULL;
}
+/**
+ * gtk_cell_layout_get_area:
+ * @cell_layout: a #GtkCellLayout
+ *
+ * Returns the underlying #GtkCellArea which might be @cell_layout if called on a #GtkCellArea or
+ * might be %NULL if no #GtkCellArea is used by @cell_layout.
+ *
+ * Return value: (transfer none): a list of cell renderers. The list, but not the
+ * renderers has been newly allocated and should be freed with
+ * g_list_free() when no longer needed.
+ *
+ * Since: 3.0
+ */
+GtkCellArea *
+gtk_cell_layout_get_area (GtkCellLayout *cell_layout)
+{
+ GtkCellLayoutIface *iface;
+
+ g_return_val_if_fail (GTK_IS_CELL_LAYOUT (cell_layout), NULL);
+
+ iface = GTK_CELL_LAYOUT_GET_IFACE (cell_layout);
+ if (iface->get_area)
+ return iface->get_area (cell_layout);
+
+ return NULL;
+}
+
+/* Attribute parsing */
typedef struct {
GtkCellLayout *cell_layout;
GtkCellRenderer *renderer;
attributes_text_element,
};
+
+/* Cell packing parsing */
+static void
+gtk_cell_layout_buildable_set_cell_property (GtkCellArea *area,
+ GtkBuilder *builder,
+ GtkCellRenderer *cell,
+ gchar *name,
+ const gchar *value)
+{
+ GParamSpec *pspec;
+ GValue gvalue = { 0, };
+ GError *error = NULL;
+
+ pspec = gtk_cell_area_class_find_cell_property (GTK_CELL_AREA_GET_CLASS (area), name);
+ if (!pspec)
+ {
+ g_warning ("%s does not have a property called %s",
+ g_type_name (G_OBJECT_TYPE (area)), name);
+ return;
+ }
+
+ if (!gtk_builder_value_from_string (builder, pspec, value, &gvalue, &error))
+ {
+ g_warning ("Could not read property %s:%s with value %s of type %s: %s",
+ g_type_name (G_OBJECT_TYPE (area)),
+ name,
+ value,
+ g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)),
+ error->message);
+ g_error_free (error);
+ return;
+ }
+
+ gtk_cell_area_cell_set_property (area, cell, name, &gvalue);
+ g_value_unset (&gvalue);
+}
+
+typedef struct {
+ GtkBuilder *builder;
+ GtkCellLayout *cell_layout;
+ GtkCellRenderer *renderer;
+ gchar *cell_prop_name;
+ gchar *context;
+ gboolean translatable;
+} CellPackingSubParserData;
+
+static void
+cell_packing_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **names,
+ const gchar **values,
+ gpointer user_data,
+ GError **error)
+{
+ CellPackingSubParserData *parser_data = (CellPackingSubParserData*)user_data;
+ guint i;
+
+ if (strcmp (element_name, "property") == 0)
+ {
+ for (i = 0; names[i]; i++)
+ if (strcmp (names[i], "name") == 0)
+ parser_data->cell_prop_name = g_strdup (values[i]);
+ else if (strcmp (names[i], "translatable") == 0)
+ {
+ if (!_gtk_builder_boolean_from_string (values[i],
+ &parser_data->translatable,
+ error))
+ return;
+ }
+ else if (strcmp (names[i], "comments") == 0)
+ ; /* for translators */
+ else if (strcmp (names[i], "context") == 0)
+ parser_data->context = g_strdup (values[i]);
+ else
+ g_warning ("Unsupported attribute for GtkCellLayout Cell "
+ "property: %s\n", names[i]);
+ }
+ else if (strcmp (element_name, "cell-packing") == 0)
+ return;
+ else
+ g_warning ("Unsupported tag for GtkCellLayout: %s\n", element_name);
+}
+
+static void
+cell_packing_text_element (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ CellPackingSubParserData *parser_data = (CellPackingSubParserData*)user_data;
+ GtkCellArea *area;
+ gchar* value;
+
+ if (!parser_data->cell_prop_name)
+ return;
+
+ if (parser_data->translatable && text_len)
+ {
+ const gchar* domain;
+ domain = gtk_builder_get_translation_domain (parser_data->builder);
+
+ value = _gtk_builder_parser_translate (domain,
+ parser_data->context,
+ text);
+ }
+ else
+ {
+ value = g_strdup (text);
+ }
+
+ area = gtk_cell_layout_get_area (parser_data->cell_layout);
+
+ if (!area)
+ {
+ g_warning ("%s does not have an internal GtkCellArea class and cannot apply child cell properties",
+ g_type_name (G_OBJECT_TYPE (parser_data->cell_layout)));
+ return;
+ }
+
+ gtk_cell_layout_buildable_set_cell_property (area,
+ parser_data->builder,
+ parser_data->renderer,
+ parser_data->cell_prop_name,
+ value);
+
+ g_free (parser_data->cell_prop_name);
+ g_free (parser_data->context);
+ g_free (value);
+ parser_data->cell_prop_name = NULL;
+ parser_data->context = NULL;
+ parser_data->translatable = FALSE;
+}
+
+static const GMarkupParser cell_packing_parser =
+ {
+ cell_packing_start_element,
+ NULL,
+ cell_packing_text_element,
+ };
+
gboolean
_gtk_cell_layout_buildable_custom_tag_start (GtkBuildable *buildable,
GtkBuilder *builder,
GMarkupParser *parser,
gpointer *data)
{
- AttributesSubParserData *parser_data;
+ AttributesSubParserData *attr_data;
+ CellPackingSubParserData *packing_data;
if (!child)
return FALSE;
if (strcmp (tagname, "attributes") == 0)
{
- parser_data = g_slice_new0 (AttributesSubParserData);
- parser_data->cell_layout = GTK_CELL_LAYOUT (buildable);
- parser_data->renderer = GTK_CELL_RENDERER (child);
- parser_data->attr_name = NULL;
+ attr_data = g_slice_new0 (AttributesSubParserData);
+ attr_data->cell_layout = GTK_CELL_LAYOUT (buildable);
+ attr_data->renderer = GTK_CELL_RENDERER (child);
+ attr_data->attr_name = NULL;
*parser = attributes_parser;
- *data = parser_data;
+ *data = attr_data;
+ return TRUE;
+ }
+ else if (strcmp (tagname, "cell-packing") == 0)
+ {
+ packing_data = g_slice_new0 (CellPackingSubParserData);
+ packing_data->builder = builder;
+ packing_data->cell_layout = GTK_CELL_LAYOUT (buildable);
+ packing_data->renderer = GTK_CELL_RENDERER (child);
+
+ *parser = cell_packing_parser;
+ *data = packing_data;
return TRUE;
}
const gchar *tagname,
gpointer *data)
{
- AttributesSubParserData *parser_data;
+ AttributesSubParserData *attr_data;
- parser_data = (AttributesSubParserData*)data;
- g_assert (!parser_data->attr_name);
- g_slice_free (AttributesSubParserData, parser_data);
+ if (strcmp (tagname, "attributes") == 0)
+ {
+ attr_data = (AttributesSubParserData*)data;
+ g_assert (!attr_data->attr_name);
+ g_slice_free (AttributesSubParserData, attr_data);
+ return;
+ }
+ else if (strcmp (tagname, "cell-packing") == 0)
+ {
+ g_slice_free (CellPackingSubParserData, (gpointer)data);
+ return;
+ }
}
void
#define __GTK_CELL_LAYOUT_H__
#include <gtk/gtkcellrenderer.h>
+#include <gtk/gtkcellarea.h>
#include <gtk/gtktreeviewcolumn.h>
#include <gtk/gtkbuildable.h>
#include <gtk/gtkbuilder.h>
GtkCellRenderer *cell,
gint position);
GList* (* get_cells) (GtkCellLayout *cell_layout);
+
+ GtkCellArea *(* get_area) (GtkCellLayout *cell_layout);
};
GType gtk_cell_layout_get_type (void) G_GNUC_CONST;
void gtk_cell_layout_reorder (GtkCellLayout *cell_layout,
GtkCellRenderer *cell,
gint position);
+GtkCellArea *gtk_cell_layout_get_area (GtkCellLayout *cell_layout);
+
gboolean _gtk_cell_layout_buildable_custom_tag_start (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
return cell->priv->sensitive;
}
+
+/**
+ * gtk_cell_renderer_can_focus:
+ * @cell: A #GtkCellRenderer
+ *
+ * Checks whether the cell renderer can receive focus.
+ *
+ * Returns: %TRUE if the cell renderer can do anything with keyboard focus
+ *
+ * Since: 3.0
+ */
+gboolean
+gtk_cell_renderer_can_focus (GtkCellRenderer *cell)
+{
+ GtkCellRendererPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_CELL_RENDERER (cell), FALSE);
+
+ priv = cell->priv;
+
+ return (cell->priv->visible &&
+ (cell->priv->mode == GTK_CELL_RENDERER_MODE_EDITABLE ||
+ cell->priv->mode == GTK_CELL_RENDERER_MODE_ACTIVATABLE));
+}
+
+
/**
* gtk_cell_renderer_stop_editing:
* @cell: A #GtkCellRenderer
gboolean sensitive);
gboolean gtk_cell_renderer_get_sensitive (GtkCellRenderer *cell);
+gboolean gtk_cell_renderer_can_focus (GtkCellRenderer *cell);
+
/* For use by cell renderer implementations only */
void gtk_cell_renderer_stop_editing (GtkCellRenderer *cell,
gboolean canceled);
VOID:ENUM,INT
VOID:ENUM,INT,BOOLEAN
VOID:ENUM,BOXED
+VOID:ENUM,STRING
VOID:INT
VOID:INT,BOOLEAN
VOID:INT,INT
VOID:OBJECT,BOOLEAN
VOID:OBJECT,BOXED,BOXED
VOID:OBJECT,BOXED,UINT,UINT
+VOID:OBJECT,BOXED,BOOLEAN,BOOLEAN
VOID:OBJECT,INT
VOID:OBJECT,INT,OBJECT
VOID:OBJECT,INT,INT
VOID:OBJECT,UINT
VOID:OBJECT,UINT,FLAGS
VOID:OBJECT,STRING
+VOID:OBJECT,OBJECT,STRING
VOID:OBJECT,OBJECT,OBJECT
+VOID:OBJECT,OBJECT,BOXED,STRING
VOID:POINTER
VOID:POINTER,INT
VOID:POINTER,BOOLEAN
testexpand \
testexpander \
testvolumebutton \
- testscrolledwindow
+ testscrolledwindow \
+ testcellarea
if USE_X11
noinst_PROGRAMS += testerrors
testtooltips_DEPENDENCIES = $(TEST_DEPS)
testvolumebutton_DEPENDENCIES = $(TEST_DEPS)
testscrolledwindow_DEPENDENCIES = $(TEST_DEPS)
+testcellarea_DEPENDENCIES = $(TEST_DEPS)
testwindows_DEPENDENCIES = $(TEST_DEPS)
testexpand_DEPENDENCIES = $(TEST_DEPS)
testexpander_DEPENDENCIES = $(TEST_DEPS)
testtooltips_LDADD = $(LDADDS)
testvolumebutton_LDADD = $(LDADDS)
testscrolledwindow_LDADD = $(LDADDS)
+testcellarea_LDADD = $(LDADDS)
testwindows_LDADD = $(LDADDS)
testexpand_LDADD = $(LDADDS)
testexpander_LDADD = $(LDADDS)
testscrolledwindow_SOURCES = \
testscrolledwindow.c
+testcellarea_SOURCES = \
+ testcellarea.c \
+ cellareascaffold.c \
+ cellareascaffold.h
+
testoffscreen_SOURCES = \
gtkoffscreenbox.c \
gtkoffscreenbox.h \
--- /dev/null
+/* cellareascaffold.c
+ *
+ * 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
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include "cellareascaffold.h"
+
+/* GObjectClass */
+static void cell_area_scaffold_finalize (GObject *object);
+static void cell_area_scaffold_dispose (GObject *object);
+static void cell_area_scaffold_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void cell_area_scaffold_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+/* GtkWidgetClass */
+static void cell_area_scaffold_realize (GtkWidget *widget);
+static void cell_area_scaffold_unrealize (GtkWidget *widget);
+static gboolean cell_area_scaffold_draw (GtkWidget *widget,
+ cairo_t *cr);
+static void cell_area_scaffold_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+static void cell_area_scaffold_get_preferred_width (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size);
+static void cell_area_scaffold_get_preferred_height_for_width (GtkWidget *widget,
+ gint for_size,
+ gint *minimum_size,
+ gint *natural_size);
+static void cell_area_scaffold_get_preferred_height (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size);
+static void cell_area_scaffold_get_preferred_width_for_height (GtkWidget *widget,
+ gint for_size,
+ gint *minimum_size,
+ gint *natural_size);
+static void cell_area_scaffold_map (GtkWidget *widget);
+static void cell_area_scaffold_unmap (GtkWidget *widget);
+static gint cell_area_scaffold_focus (GtkWidget *widget,
+ GtkDirectionType direction);
+static gboolean cell_area_scaffold_button_press (GtkWidget *widget,
+ GdkEventButton *event);
+
+/* GtkContainerClass */
+static void cell_area_scaffold_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data);
+static void cell_area_scaffold_remove (GtkContainer *container,
+ GtkWidget *child);
+static void cell_area_scaffold_put_edit_widget (CellAreaScaffold *scaffold,
+ GtkWidget *edit_widget,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+
+/* CellAreaScaffoldClass */
+static void cell_area_scaffold_activate (CellAreaScaffold *scaffold);
+
+/* CellArea/GtkTreeModel callbacks */
+static void size_changed_cb (GtkCellAreaContext *context,
+ GParamSpec *pspec,
+ CellAreaScaffold *scaffold);
+static void focus_changed_cb (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *path,
+ CellAreaScaffold *scaffold);
+static void add_editable_cb (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkCellEditable *edit_widget,
+ GdkRectangle *cell_area,
+ const gchar *path,
+ CellAreaScaffold *scaffold);
+static void remove_editable_cb (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkCellEditable *edit_widget,
+ CellAreaScaffold *scaffold);
+static void row_changed_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ CellAreaScaffold *scaffold);
+static void row_inserted_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ CellAreaScaffold *scaffold);
+static void row_deleted_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ CellAreaScaffold *scaffold);
+static void rows_reordered_cb (GtkTreeModel *model,
+ GtkTreePath *parent,
+ GtkTreeIter *iter,
+ gint *new_order,
+ CellAreaScaffold *scaffold);
+
+typedef struct {
+ gint size; /* The size of the row in the scaffold's opposing orientation */
+} RowData;
+
+struct _CellAreaScaffoldPrivate {
+
+ /* Window for catching events and dispatching them to the cell area */
+ GdkWindow *event_window;
+
+ /* The model we're showing data for */
+ GtkTreeModel *model;
+ gulong row_changed_id;
+ gulong row_inserted_id;
+ gulong row_deleted_id;
+ gulong rows_reordered_id;
+
+ /* The area rendering the data and a global context */
+ GtkCellArea *area;
+ GtkCellAreaContext *context;
+
+ /* Cache some info about rows (hieghts etc) */
+ GArray *row_data;
+
+ /* Focus handling */
+ gint focus_row;
+ gulong focus_changed_id;
+
+ /* Check when the underlying area changes the size and
+ * we need to queue a redraw */
+ gulong size_changed_id;
+
+ /* Currently edited widget */
+ GtkWidget *edit_widget;
+ GdkRectangle edit_rect;
+ gulong add_editable_id;
+ gulong remove_editable_id;
+
+
+ gint row_spacing;
+ gint indent;
+};
+
+enum {
+ PROP_0,
+ PROP_ORIENTATION
+};
+
+enum {
+ ACTIVATE,
+ N_SIGNALS
+};
+
+static guint scaffold_signals[N_SIGNALS] = { 0 };
+
+#define DIRECTION_STR(dir) \
+ ((dir) == GTK_DIR_TAB_FORWARD ? "tab forward" : \
+ (dir) == GTK_DIR_TAB_BACKWARD ? "tab backward" : \
+ (dir) == GTK_DIR_UP ? "up" : \
+ (dir) == GTK_DIR_DOWN ? "down" : \
+ (dir) == GTK_DIR_LEFT ? "left" : \
+ (dir) == GTK_DIR_RIGHT ? "right" : "invalid")
+
+G_DEFINE_TYPE_WITH_CODE (CellAreaScaffold, cell_area_scaffold, GTK_TYPE_CONTAINER,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL));
+
+
+static void
+cell_area_scaffold_init (CellAreaScaffold *scaffold)
+{
+ CellAreaScaffoldPrivate *priv;
+
+ scaffold->priv = G_TYPE_INSTANCE_GET_PRIVATE (scaffold,
+ TYPE_CELL_AREA_SCAFFOLD,
+ CellAreaScaffoldPrivate);
+ priv = scaffold->priv;
+
+ priv->area = gtk_cell_area_box_new ();
+ priv->context = gtk_cell_area_create_context (priv->area);
+
+ priv->row_data = g_array_new (FALSE, FALSE, sizeof (RowData));
+
+ gtk_widget_set_has_window (GTK_WIDGET (scaffold), FALSE);
+ gtk_widget_set_can_focus (GTK_WIDGET (scaffold), TRUE);
+
+ priv->size_changed_id =
+ g_signal_connect (priv->context, "notify",
+ G_CALLBACK (size_changed_cb), scaffold);
+
+ priv->focus_changed_id =
+ g_signal_connect (priv->area, "focus-changed",
+ G_CALLBACK (focus_changed_cb), scaffold);
+
+ priv->add_editable_id =
+ g_signal_connect (priv->area, "add-editable",
+ G_CALLBACK (add_editable_cb), scaffold);
+
+ priv->remove_editable_id =
+ g_signal_connect (priv->area, "remove-editable",
+ G_CALLBACK (remove_editable_cb), scaffold);
+}
+
+static void
+cell_area_scaffold_class_init (CellAreaScaffoldClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
+ GtkContainerClass *container_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->dispose = cell_area_scaffold_dispose;
+ gobject_class->finalize = cell_area_scaffold_finalize;
+ gobject_class->get_property = cell_area_scaffold_get_property;
+ gobject_class->set_property = cell_area_scaffold_set_property;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->realize = cell_area_scaffold_realize;
+ widget_class->unrealize = cell_area_scaffold_unrealize;
+ widget_class->draw = cell_area_scaffold_draw;
+ widget_class->size_allocate = cell_area_scaffold_size_allocate;
+ widget_class->get_preferred_width = cell_area_scaffold_get_preferred_width;
+ widget_class->get_preferred_height_for_width = cell_area_scaffold_get_preferred_height_for_width;
+ widget_class->get_preferred_height = cell_area_scaffold_get_preferred_height;
+ widget_class->get_preferred_width_for_height = cell_area_scaffold_get_preferred_width_for_height;
+ widget_class->map = cell_area_scaffold_map;
+ widget_class->unmap = cell_area_scaffold_unmap;
+ widget_class->focus = cell_area_scaffold_focus;
+ widget_class->button_press_event = cell_area_scaffold_button_press;
+
+ container_class = GTK_CONTAINER_CLASS (class);
+ container_class->forall = cell_area_scaffold_forall;
+ container_class->remove = cell_area_scaffold_remove;
+
+ class->activate = cell_area_scaffold_activate;
+
+ g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation");
+
+ scaffold_signals[ACTIVATE] =
+ g_signal_new ("activate",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (CellAreaScaffoldClass, activate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ widget_class->activate_signal = scaffold_signals[ACTIVATE];
+
+
+ g_type_class_add_private (gobject_class, sizeof (CellAreaScaffoldPrivate));
+}
+
+/*********************************************************
+ * GObjectClass *
+ *********************************************************/
+static void
+cell_area_scaffold_finalize (GObject *object)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (object);
+ CellAreaScaffoldPrivate *priv;
+
+ priv = scaffold->priv;
+
+ g_array_free (priv->row_data, TRUE);
+
+ G_OBJECT_CLASS (cell_area_scaffold_parent_class)->finalize (object);
+}
+
+static void
+cell_area_scaffold_dispose (GObject *object)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (object);
+ CellAreaScaffoldPrivate *priv;
+
+ priv = scaffold->priv;
+
+ cell_area_scaffold_set_model (scaffold, NULL);
+
+ if (priv->context)
+ {
+ /* Disconnect signals */
+ g_signal_handler_disconnect (priv->context, priv->size_changed_id);
+
+ g_object_unref (priv->context);
+ priv->context = NULL;
+ priv->size_changed_id = 0;
+ }
+
+ if (priv->area)
+ {
+ /* Disconnect signals */
+ g_signal_handler_disconnect (priv->area, priv->focus_changed_id);
+ g_signal_handler_disconnect (priv->area, priv->add_editable_id);
+ g_signal_handler_disconnect (priv->area, priv->remove_editable_id);
+
+ g_object_unref (priv->area);
+ priv->area = NULL;
+ priv->focus_changed_id = 0;
+ }
+
+ G_OBJECT_CLASS (cell_area_scaffold_parent_class)->dispose (object);
+}
+
+static void
+cell_area_scaffold_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (object);
+ CellAreaScaffoldPrivate *priv;
+
+ priv = scaffold->priv;
+
+ switch (prop_id)
+ {
+ case PROP_ORIENTATION:
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->area),
+ g_value_get_enum (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cell_area_scaffold_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (object);
+ CellAreaScaffoldPrivate *priv;
+
+ priv = scaffold->priv;
+
+ switch (prop_id)
+ {
+ case PROP_ORIENTATION:
+ g_value_set_enum (value,
+ gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area)));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/*********************************************************
+ * GtkWidgetClass *
+ *********************************************************/
+static void
+cell_area_scaffold_realize (GtkWidget *widget)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget);
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+ GtkAllocation allocation;
+ GdkWindow *window;
+ GdkWindowAttr attributes;
+ gint attributes_mask;
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ gtk_widget_set_realized (widget, TRUE);
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.wclass = GDK_INPUT_ONLY;
+ attributes.event_mask = gtk_widget_get_events (widget);
+ attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_KEY_PRESS_MASK |
+ GDK_KEY_RELEASE_MASK);
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+ window = gtk_widget_get_parent_window (widget);
+ gtk_widget_set_window (widget, window);
+ g_object_ref (window);
+
+ priv->event_window = gdk_window_new (window, &attributes, attributes_mask);
+ gdk_window_set_user_data (priv->event_window, widget);
+
+ gtk_widget_style_attach (widget);
+}
+
+static void
+cell_area_scaffold_unrealize (GtkWidget *widget)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget);
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+
+ if (priv->event_window)
+ {
+ gdk_window_set_user_data (priv->event_window, NULL);
+ gdk_window_destroy (priv->event_window);
+ priv->event_window = NULL;
+ }
+
+ GTK_WIDGET_CLASS (cell_area_scaffold_parent_class)->unrealize (widget);
+}
+
+static gboolean
+cell_area_scaffold_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget);
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+ GtkOrientation orientation;
+ GtkTreeIter iter;
+ gboolean valid;
+ GdkRectangle background_area;
+ GdkRectangle render_area;
+ GtkAllocation allocation;
+ gint i = 0;
+ gboolean have_focus;
+ GtkCellRendererState flags;
+
+ if (!priv->model)
+ return FALSE;
+
+ have_focus = gtk_widget_has_focus (widget);
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area));
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ render_area.x = 0;
+ render_area.y = 0;
+ render_area.width = allocation.width;
+ render_area.height = allocation.height;
+
+ background_area = render_area;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ render_area.x = priv->indent;
+ render_area.width -= priv->indent;
+ }
+ else
+ {
+ render_area.y = priv->indent;
+ render_area.height -= priv->indent;
+ }
+
+ valid = gtk_tree_model_get_iter_first (priv->model, &iter);
+ while (valid)
+ {
+ RowData *data = &g_array_index (priv->row_data, RowData, i);
+
+ if (have_focus && i == priv->focus_row)
+ flags = GTK_CELL_RENDERER_FOCUSED;
+ else
+ flags = 0;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ render_area.height = data->size;
+
+ background_area.height = render_area.height;
+ background_area.y = render_area.y;
+
+ if (i == 0)
+ {
+ background_area.height += priv->row_spacing / 2;
+ background_area.height += priv->row_spacing % 2;
+ }
+ else if (i == priv->row_data->len - 1)
+ {
+ background_area.y -= priv->row_spacing / 2;
+ background_area.height += priv->row_spacing / 2;
+ }
+ else
+ {
+ background_area.y -= priv->row_spacing / 2;
+ background_area.height += priv->row_spacing;
+ }
+ }
+ else /* GTK_ORIENTATION_VERTICAL */
+ {
+ render_area.width = data->size;
+
+ background_area.width = render_area.width;
+ background_area.x = render_area.x;
+
+ if (i == 0)
+ {
+ background_area.width += priv->row_spacing / 2;
+ background_area.width += priv->row_spacing % 2;
+ }
+ else if (i == priv->row_data->len - 1)
+ {
+ background_area.x -= priv->row_spacing / 2;
+ background_area.width += priv->row_spacing / 2;
+ }
+ else
+ {
+ background_area.x -= priv->row_spacing / 2;
+ background_area.width += priv->row_spacing;
+ }
+ }
+
+ gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE);
+ gtk_cell_area_render (priv->area, priv->context, widget, cr,
+ &background_area, &render_area, flags,
+ (have_focus && i == priv->focus_row));
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ render_area.y += data->size;
+ render_area.y += priv->row_spacing;
+ }
+ else
+ {
+ render_area.x += data->size;
+ render_area.x += priv->row_spacing;
+ }
+
+ i++;
+ valid = gtk_tree_model_iter_next (priv->model, &iter);
+ }
+
+ /* Draw the edit widget after drawing everything else */
+ GTK_WIDGET_CLASS (cell_area_scaffold_parent_class)->draw (widget, cr);
+
+ return FALSE;
+}
+
+static void
+request_all_base (CellAreaScaffold *scaffold)
+{
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+ GtkWidget *widget = GTK_WIDGET (scaffold);
+ GtkOrientation orientation;
+ GtkTreeIter iter;
+ gboolean valid;
+
+ if (!priv->model)
+ return;
+
+ g_signal_handler_block (priv->context, priv->size_changed_id);
+
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area));
+
+ valid = gtk_tree_model_get_iter_first (priv->model, &iter);
+ while (valid)
+ {
+ gint min, nat;
+
+ gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ gtk_cell_area_get_preferred_width (priv->area, priv->context, widget, &min, &nat);
+ else
+ gtk_cell_area_get_preferred_height (priv->area, priv->context, widget, &min, &nat);
+
+ valid = gtk_tree_model_iter_next (priv->model, &iter);
+ }
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ gtk_cell_area_context_sum_preferred_width (priv->context);
+ else
+ gtk_cell_area_context_sum_preferred_height (priv->context);
+
+ g_signal_handler_unblock (priv->context, priv->size_changed_id);
+}
+
+static void
+get_row_sizes (CellAreaScaffold *scaffold,
+ GArray *array,
+ gint for_size)
+{
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+ GtkWidget *widget = GTK_WIDGET (scaffold);
+ GtkOrientation orientation;
+ GtkTreeIter iter;
+ gboolean valid;
+ gint i = 0;
+
+ if (!priv->model)
+ return;
+
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area));
+
+ valid = gtk_tree_model_get_iter_first (priv->model, &iter);
+ while (valid)
+ {
+ RowData *data = &g_array_index (array, RowData, i);
+
+ gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ gtk_cell_area_get_preferred_height_for_width (priv->area, priv->context, widget,
+ for_size, &data->size, NULL);
+ else
+ gtk_cell_area_get_preferred_width_for_height (priv->area, priv->context, widget,
+ for_size, &data->size, NULL);
+
+ i++;
+ valid = gtk_tree_model_iter_next (priv->model, &iter);
+ }
+}
+
+static void
+cell_area_scaffold_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget);
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+ GtkOrientation orientation;
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ if (gtk_widget_get_realized (widget))
+ gdk_window_move_resize (priv->event_window,
+ allocation->x,
+ allocation->y,
+ allocation->width,
+ allocation->height);
+
+ /* Allocate the child GtkCellEditable widget if one is currently editing a row */
+ if (priv->edit_widget)
+ gtk_widget_size_allocate (priv->edit_widget, &priv->edit_rect);
+
+ if (!priv->model)
+ return;
+
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area));
+
+ /* Cache the per-row sizes and allocate the context */
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ gtk_cell_area_context_allocate_width (priv->context, allocation->width - priv->indent);
+ get_row_sizes (scaffold, priv->row_data, allocation->width - priv->indent);
+ }
+ else
+ {
+ gtk_cell_area_context_allocate_height (priv->context, allocation->height - priv->indent);
+ get_row_sizes (scaffold, priv->row_data, allocation->height - priv->indent);
+ }
+}
+
+
+static void
+cell_area_scaffold_get_preferred_width (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget);
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+ GtkOrientation orientation;
+
+ if (!priv->model)
+ return;
+
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area));
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ request_all_base (scaffold);
+
+ gtk_cell_area_context_get_preferred_width (priv->context, minimum_size, natural_size);
+
+ *minimum_size += priv->indent;
+ *natural_size += priv->indent;
+ }
+ else
+ {
+ gint min_size, nat_size;
+
+ GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_size, &nat_size);
+ GTK_WIDGET_GET_CLASS (widget)->get_preferred_width_for_height (widget, min_size,
+ minimum_size, natural_size);
+ }
+}
+
+static void
+cell_area_scaffold_get_preferred_height_for_width (GtkWidget *widget,
+ gint for_size,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget);
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+ GtkOrientation orientation;
+
+ if (!priv->model)
+ return;
+
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area));
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ GArray *request_array;
+ gint n_rows, i, full_size = 0;
+
+ n_rows = gtk_tree_model_iter_n_children (priv->model, NULL);
+
+ /* Get an array for the contextual request */
+ request_array = g_array_new (FALSE, FALSE, sizeof (RowData));
+ g_array_set_size (request_array, n_rows);
+ memset (request_array->data, 0x0, n_rows * sizeof (RowData));
+
+ /* Gather each contextual size into the request array */
+ get_row_sizes (scaffold, request_array, for_size - priv->indent);
+
+ /* Sum up the size and add some row spacing */
+ for (i = 0; i < n_rows; i++)
+ {
+ RowData *data = &g_array_index (request_array, RowData, i);
+
+ full_size += data->size;
+ }
+
+ full_size += MAX (0, n_rows -1) * priv->row_spacing;
+
+ g_array_free (request_array, TRUE);
+
+ *minimum_size = full_size;
+ *natural_size = full_size;
+ }
+ else
+ {
+ GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, minimum_size, natural_size);
+ }
+}
+
+static void
+cell_area_scaffold_get_preferred_height (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget);
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+ GtkOrientation orientation;
+
+ if (!priv->model)
+ return;
+
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area));
+
+ if (orientation == GTK_ORIENTATION_VERTICAL)
+ {
+ request_all_base (scaffold);
+
+ gtk_cell_area_context_get_preferred_height (priv->context, minimum_size, natural_size);
+
+ *minimum_size += priv->indent;
+ *natural_size += priv->indent;
+ }
+ else
+ {
+ gint min_size, nat_size;
+
+ GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_size, &nat_size);
+ GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget, min_size,
+ minimum_size, natural_size);
+ }
+}
+
+static void
+cell_area_scaffold_get_preferred_width_for_height (GtkWidget *widget,
+ gint for_size,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget);
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+ GtkOrientation orientation;
+
+ if (!priv->model)
+ return;
+
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area));
+
+ if (orientation == GTK_ORIENTATION_VERTICAL)
+ {
+ GArray *request_array;
+ gint n_rows, i, full_size = 0;
+
+ n_rows = gtk_tree_model_iter_n_children (priv->model, NULL);
+
+ /* Get an array for the contextual request */
+ request_array = g_array_new (FALSE, FALSE, sizeof (RowData));
+ g_array_set_size (request_array, n_rows);
+ memset (request_array->data, 0x0, n_rows * sizeof (RowData));
+
+ /* Gather each contextual size into the request array */
+ get_row_sizes (scaffold, request_array, for_size - priv->indent);
+
+ /* Sum up the size and add some row spacing */
+ for (i = 0; i < n_rows; i++)
+ {
+ RowData *data = &g_array_index (request_array, RowData, i);
+
+ full_size += data->size;
+ }
+
+ full_size += MAX (0, n_rows -1) * priv->row_spacing;
+
+ g_array_free (request_array, TRUE);
+
+ *minimum_size = full_size;
+ *natural_size = full_size;
+ }
+ else
+ {
+ GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, minimum_size, natural_size);
+ }
+}
+
+static void
+cell_area_scaffold_map (GtkWidget *widget)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget);
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+
+ GTK_WIDGET_CLASS (cell_area_scaffold_parent_class)->map (widget);
+
+ if (priv->event_window)
+ gdk_window_show (priv->event_window);
+}
+
+static void
+cell_area_scaffold_unmap (GtkWidget *widget)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget);
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+
+ GTK_WIDGET_CLASS (cell_area_scaffold_parent_class)->unmap (widget);
+
+ if (priv->event_window)
+ gdk_window_hide (priv->event_window);
+}
+
+
+static gint
+cell_area_scaffold_focus (GtkWidget *widget,
+ GtkDirectionType direction)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget);
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+ GtkTreeIter iter;
+ gboolean valid;
+ gint focus_row;
+ GtkOrientation orientation;
+ gboolean changed = FALSE;
+
+ /* Grab focus on ourself if we dont already have focus */
+ if (!gtk_widget_has_focus (widget))
+ gtk_widget_grab_focus (widget);
+
+ /* Move focus from cell to cell and row to row */
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area));
+
+ focus_row = priv->focus_row;
+
+ g_signal_handler_block (priv->area, priv->focus_changed_id);
+
+ valid = gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, priv->focus_row);
+ while (valid)
+ {
+ gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE);
+
+ /* If focus stays in the area we dont need to do any more */
+ if (gtk_cell_area_focus (priv->area, direction))
+ {
+ priv->focus_row = focus_row;
+
+ /* XXX A smarter implementation would only invalidate the rectangles where
+ * focus was removed from and new focus was placed */
+ gtk_widget_queue_draw (widget);
+ changed = TRUE;
+ break;
+ }
+ else
+ {
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (direction == GTK_DIR_RIGHT ||
+ direction == GTK_DIR_LEFT)
+ break;
+ else if (direction == GTK_DIR_UP ||
+ direction == GTK_DIR_TAB_BACKWARD)
+ {
+ if (focus_row == 0)
+ break;
+ else
+ {
+ /* XXX A real implementation should check if the
+ * previous row can focus with it's attributes setup */
+ focus_row--;
+ valid = gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, focus_row);
+ }
+ }
+ else /* direction == GTK_DIR_DOWN || GTK_DIR_TAB_FORWARD */
+ {
+ if (focus_row == priv->row_data->len - 1)
+ break;
+ else
+ {
+ /* XXX A real implementation should check if the
+ * previous row can focus with it's attributes setup */
+ focus_row++;
+ valid = gtk_tree_model_iter_next (priv->model, &iter);
+ }
+ }
+ }
+ else /* (orientation == GTK_ORIENTATION_HORIZONTAL) */
+ {
+ if (direction == GTK_DIR_UP ||
+ direction == GTK_DIR_DOWN)
+ break;
+ else if (direction == GTK_DIR_LEFT ||
+ direction == GTK_DIR_TAB_BACKWARD)
+ {
+ if (focus_row == 0)
+ break;
+ else
+ {
+ /* XXX A real implementation should check if the
+ * previous row can focus with it's attributes setup */
+ focus_row--;
+ valid = gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, focus_row);
+ }
+ }
+ else /* direction == GTK_DIR_RIGHT || GTK_DIR_TAB_FORWARD */
+ {
+ if (focus_row == priv->row_data->len - 1)
+ break;
+ else
+ {
+ /* XXX A real implementation should check if the
+ * previous row can focus with it's attributes setup */
+ focus_row++;
+ valid = gtk_tree_model_iter_next (priv->model, &iter);
+ }
+ }
+ }
+ }
+ }
+
+ g_signal_handler_unblock (priv->area, priv->focus_changed_id);
+
+ /* XXX A smarter implementation would only invalidate the rectangles where
+ * focus was removed from and new focus was placed */
+ gtk_widget_queue_draw (widget);
+
+ return changed;
+}
+
+static gboolean
+cell_area_scaffold_button_press (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget);
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+ GtkTreeIter iter;
+ gboolean valid;
+ GtkOrientation orientation;
+ gint i = 0;
+ GdkRectangle event_area;
+ GtkAllocation allocation;
+ gboolean handled = FALSE;
+
+ /* Move focus from cell to cell and row to row */
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area));
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ event_area.x = 0;
+ event_area.y = 0;
+ event_area.width = allocation.width;
+ event_area.height = allocation.height;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ event_area.x = priv->indent;
+ event_area.width -= priv->indent;
+ }
+ else
+ {
+ event_area.y = priv->indent;
+ event_area.height -= priv->indent;
+ }
+
+ valid = gtk_tree_model_get_iter_first (priv->model, &iter);
+ while (valid)
+ {
+ RowData *data = &g_array_index (priv->row_data, RowData, i);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ event_area.height = data->size;
+
+ if (event->y >= event_area.y &&
+ event->y <= event_area.y + event_area.height)
+ {
+ /* XXX A real implementation would assemble GtkCellRendererState flags here */
+ gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE);
+ handled = gtk_cell_area_event (priv->area, priv->context, GTK_WIDGET (scaffold),
+ (GdkEvent *)event, &event_area, 0);
+ break;
+ }
+
+ event_area.y += data->size;
+ event_area.y += priv->row_spacing;
+ }
+ else
+ {
+ event_area.width = data->size;
+
+ if (event->x >= event_area.x &&
+ event->x <= event_area.x + event_area.width)
+ {
+ /* XXX A real implementation would assemble GtkCellRendererState flags here */
+ gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE);
+ handled = gtk_cell_area_event (priv->area, priv->context, GTK_WIDGET (scaffold),
+ (GdkEvent *)event, &event_area, 0);
+ break;
+ }
+
+ event_area.x += data->size;
+ event_area.x += priv->row_spacing;
+ }
+
+ i++;
+ valid = gtk_tree_model_iter_next (priv->model, &iter);
+ }
+
+ return handled;
+}
+
+
+/*********************************************************
+ * GtkContainerClass *
+ *********************************************************/
+static void
+cell_area_scaffold_put_edit_widget (CellAreaScaffold *scaffold,
+ GtkWidget *edit_widget,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
+{
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+
+ priv->edit_rect.x = x;
+ priv->edit_rect.y = y;
+ priv->edit_rect.width = width;
+ priv->edit_rect.height = height;
+ priv->edit_widget = edit_widget;
+
+ gtk_widget_set_parent (edit_widget, GTK_WIDGET (scaffold));
+}
+
+static void
+cell_area_scaffold_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (container);
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+
+ if (priv->edit_widget)
+ (* callback) (priv->edit_widget, callback_data);
+}
+
+static void
+cell_area_scaffold_remove (GtkContainer *container,
+ GtkWidget *child)
+{
+ CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (container);
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+
+ g_return_if_fail (child == priv->edit_widget);
+
+ gtk_widget_unparent (priv->edit_widget);
+ priv->edit_widget = NULL;
+}
+
+/*********************************************************
+ * CellAreaScaffoldClass *
+ *********************************************************/
+static void
+cell_area_scaffold_activate (CellAreaScaffold *scaffold)
+{
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+ GtkWidget *widget = GTK_WIDGET (scaffold);
+ GtkAllocation allocation;
+ GtkOrientation orientation;
+ GdkRectangle cell_area;
+ GtkTreeIter iter;
+ gboolean valid;
+ gint i = 0;
+
+ orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area));
+ gtk_widget_get_allocation (widget, &allocation);
+
+ cell_area.x = 0;
+ cell_area.y = 0;
+ cell_area.width = allocation.width;
+ cell_area.height = allocation.height;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ cell_area.x = priv->indent;
+ cell_area.width -= priv->indent;
+ }
+ else
+ {
+ cell_area.y = priv->indent;
+ cell_area.height -= priv->indent;
+ }
+
+ valid = gtk_tree_model_get_iter_first (priv->model, &iter);
+ while (valid)
+ {
+ RowData *data = &g_array_index (priv->row_data, RowData, i);
+
+ if (i == priv->focus_row)
+ {
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ cell_area.height = data->size;
+ else
+ cell_area.width = data->size;
+
+ gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE);
+ gtk_cell_area_activate (priv->area, priv->context, widget, &cell_area, GTK_CELL_RENDERER_FOCUSED);
+
+ break;
+ }
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ cell_area.y += data->size + priv->row_spacing;
+ else
+ cell_area.x += data->size + priv->row_spacing;
+
+ i++;
+ valid = gtk_tree_model_iter_next (priv->model, &iter);
+ }
+}
+
+/*********************************************************
+ * CellArea/GtkTreeModel callbacks *
+ *********************************************************/
+static void
+size_changed_cb (GtkCellAreaContext *context,
+ GParamSpec *pspec,
+ CellAreaScaffold *scaffold)
+{
+ if (!strcmp (pspec->name, "minimum-width") ||
+ !strcmp (pspec->name, "natural-width") ||
+ !strcmp (pspec->name, "minimum-height") ||
+ !strcmp (pspec->name, "natural-height"))
+ gtk_widget_queue_resize (GTK_WIDGET (scaffold));
+}
+
+static void
+focus_changed_cb (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ const gchar *path,
+ CellAreaScaffold *scaffold)
+{
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+ GtkWidget *widget = GTK_WIDGET (scaffold);
+ GtkTreePath *treepath;
+ gint *indices;
+
+ if (!priv->model)
+ return;
+
+ /* We can be signaled that a renderer lost focus, here
+ * we dont care */
+ if (!renderer)
+ return;
+
+ treepath = gtk_tree_path_new_from_string (path);
+ indices = gtk_tree_path_get_indices (treepath);
+
+ priv->focus_row = indices[0];
+
+ gtk_tree_path_free (treepath);
+
+ /* Make sure we have focus now */
+ if (!gtk_widget_has_focus (widget))
+ gtk_widget_grab_focus (widget);
+
+ gtk_widget_queue_draw (widget);
+}
+
+static void
+add_editable_cb (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkCellEditable *edit_widget,
+ GdkRectangle *cell_area,
+ const gchar *path,
+ CellAreaScaffold *scaffold)
+{
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (GTK_WIDGET (scaffold), &allocation);
+
+ cell_area_scaffold_put_edit_widget (scaffold, GTK_WIDGET (edit_widget),
+ allocation.x + cell_area->x,
+ allocation.y + cell_area->y,
+ cell_area->width, cell_area->height);
+}
+
+static void
+remove_editable_cb (GtkCellArea *area,
+ GtkCellRenderer *renderer,
+ GtkCellEditable *edit_widget,
+ CellAreaScaffold *scaffold)
+{
+ gtk_container_remove (GTK_CONTAINER (scaffold), GTK_WIDGET (edit_widget));
+
+ gtk_widget_grab_focus (GTK_WIDGET (scaffold));
+}
+
+static void
+rebuild_and_flush_internals (CellAreaScaffold *scaffold)
+{
+ CellAreaScaffoldPrivate *priv = scaffold->priv;
+ gint n_rows;
+
+ if (priv->model)
+ {
+ n_rows = gtk_tree_model_iter_n_children (priv->model, NULL);
+
+ /* Clear/reset the array */
+ g_array_set_size (priv->row_data, n_rows);
+ memset (priv->row_data->data, 0x0, n_rows * sizeof (RowData));
+ }
+ else
+ g_array_set_size (priv->row_data, 0);
+
+ /* Data changed, lets flush the context and consequently queue resize and
+ * start everything over again (note this is definitly far from optimized) */
+ gtk_cell_area_context_flush (priv->context);
+}
+
+static void
+row_changed_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ CellAreaScaffold *scaffold)
+{
+ rebuild_and_flush_internals (scaffold);
+}
+
+static void
+row_inserted_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ CellAreaScaffold *scaffold)
+{
+ rebuild_and_flush_internals (scaffold);
+}
+
+static void
+row_deleted_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ CellAreaScaffold *scaffold)
+{
+ rebuild_and_flush_internals (scaffold);
+}
+
+static void
+rows_reordered_cb (GtkTreeModel *model,
+ GtkTreePath *parent,
+ GtkTreeIter *iter,
+ gint *new_order,
+ CellAreaScaffold *scaffold)
+{
+ rebuild_and_flush_internals (scaffold);
+}
+
+/*********************************************************
+ * API *
+ *********************************************************/
+GtkWidget *
+cell_area_scaffold_new (void)
+{
+ return (GtkWidget *)g_object_new (TYPE_CELL_AREA_SCAFFOLD, NULL);
+}
+
+GtkCellArea *
+cell_area_scaffold_get_area (CellAreaScaffold *scaffold)
+{
+ CellAreaScaffoldPrivate *priv;
+
+ g_return_val_if_fail (IS_CELL_AREA_SCAFFOLD (scaffold), NULL);
+
+ priv = scaffold->priv;
+
+ return priv->area;
+}
+
+void
+cell_area_scaffold_set_model (CellAreaScaffold *scaffold,
+ GtkTreeModel *model)
+{
+ CellAreaScaffoldPrivate *priv;
+
+ g_return_if_fail (IS_CELL_AREA_SCAFFOLD (scaffold));
+
+ priv = scaffold->priv;
+
+ if (priv->model != model)
+ {
+ if (priv->model)
+ {
+ g_signal_handler_disconnect (priv->model, priv->row_changed_id);
+ g_signal_handler_disconnect (priv->model, priv->row_inserted_id);
+ g_signal_handler_disconnect (priv->model, priv->row_deleted_id);
+ g_signal_handler_disconnect (priv->model, priv->rows_reordered_id);
+
+ g_object_unref (priv->model);
+ }
+
+ priv->model = model;
+
+ if (priv->model)
+ {
+ g_object_ref (priv->model);
+
+ priv->row_changed_id =
+ g_signal_connect (priv->model, "row-changed",
+ G_CALLBACK (row_changed_cb), scaffold);
+
+ priv->row_inserted_id =
+ g_signal_connect (priv->model, "row-inserted",
+ G_CALLBACK (row_inserted_cb), scaffold);
+
+ priv->row_deleted_id =
+ g_signal_connect (priv->model, "row-deleted",
+ G_CALLBACK (row_deleted_cb), scaffold);
+
+ priv->rows_reordered_id =
+ g_signal_connect (priv->model, "rows-reordered",
+ G_CALLBACK (rows_reordered_cb), scaffold);
+ }
+
+ rebuild_and_flush_internals (scaffold);
+ }
+}
+
+GtkTreeModel *
+cell_area_scaffold_get_model (CellAreaScaffold *scaffold)
+{
+ CellAreaScaffoldPrivate *priv;
+
+ g_return_val_if_fail (IS_CELL_AREA_SCAFFOLD (scaffold), NULL);
+
+ priv = scaffold->priv;
+
+ return priv->model;
+}
+
+
+void
+cell_area_scaffold_set_row_spacing (CellAreaScaffold *scaffold,
+ gint spacing)
+{
+ CellAreaScaffoldPrivate *priv;
+
+ g_return_if_fail (IS_CELL_AREA_SCAFFOLD (scaffold));
+
+ priv = scaffold->priv;
+
+ if (priv->row_spacing != spacing)
+ {
+ priv->row_spacing = spacing;
+ gtk_widget_queue_resize (GTK_WIDGET (scaffold));
+ }
+}
+
+gint
+cell_area_scaffold_get_row_spacing (CellAreaScaffold *scaffold)
+{
+ CellAreaScaffoldPrivate *priv;
+
+ g_return_val_if_fail (IS_CELL_AREA_SCAFFOLD (scaffold), 0);
+
+ priv = scaffold->priv;
+
+ return priv->row_spacing;
+}
+
+void
+cell_area_scaffold_set_indentation (CellAreaScaffold *scaffold,
+ gint indent)
+{
+ CellAreaScaffoldPrivate *priv;
+
+ g_return_if_fail (IS_CELL_AREA_SCAFFOLD (scaffold));
+
+ priv = scaffold->priv;
+
+ if (priv->indent != indent)
+ {
+ priv->indent = indent;
+ gtk_widget_queue_resize (GTK_WIDGET (scaffold));
+ }
+}
+
+gint
+cell_area_scaffold_get_indentation (CellAreaScaffold *scaffold)
+{
+ CellAreaScaffoldPrivate *priv;
+
+ g_return_val_if_fail (IS_CELL_AREA_SCAFFOLD (scaffold), 0);
+
+ priv = scaffold->priv;
+
+ return priv->indent;
+}
+
+
--- /dev/null
+/* cellareascaffold.h
+ *
+ * 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
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __CELL_AREA_SCAFFOLD_H__
+#define __CELL_AREA_SCAFFOLD_H__
+
+#include <gtk/gtk.h>
+
+
+G_BEGIN_DECLS
+
+#define TYPE_CELL_AREA_SCAFFOLD (cell_area_scaffold_get_type ())
+#define CELL_AREA_SCAFFOLD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_CELL_AREA_SCAFFOLD, CellAreaScaffold))
+#define CELL_AREA_SCAFFOLD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_CELL_AREA_SCAFFOLD, CellAreaScaffoldClass))
+#define IS_CELL_AREA_SCAFFOLD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_CELL_AREA_SCAFFOLD))
+#define IS_CELL_AREA_SCAFFOLD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_CELL_AREA_SCAFFOLD))
+#define CELL_AREA_SCAFFOLD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_CELL_AREA_SCAFFOLD, CellAreaScaffoldClass))
+
+
+typedef struct _CellAreaScaffold CellAreaScaffold;
+typedef struct _CellAreaScaffoldClass CellAreaScaffoldClass;
+typedef struct _CellAreaScaffoldPrivate CellAreaScaffoldPrivate;
+
+struct _CellAreaScaffold
+{
+ GtkContainer widget;
+
+ CellAreaScaffoldPrivate *priv;
+};
+
+struct _CellAreaScaffoldClass
+{
+ GtkContainerClass parent_class;
+
+ void (* activate) (CellAreaScaffold *scaffold);
+};
+
+
+GType cell_area_scaffold_get_type (void) G_GNUC_CONST;
+GtkWidget *cell_area_scaffold_new (void);
+
+GtkCellArea *cell_area_scaffold_get_area (CellAreaScaffold *scaffold);
+void cell_area_scaffold_set_model (CellAreaScaffold *scaffold,
+ GtkTreeModel *model);
+GtkTreeModel *cell_area_scaffold_get_model (CellAreaScaffold *scaffold);
+
+void cell_area_scaffold_set_row_spacing (CellAreaScaffold *scaffold,
+ gint spacing);
+gint cell_area_scaffold_get_row_spacing (CellAreaScaffold *scaffold);
+
+void cell_area_scaffold_set_indentation (CellAreaScaffold *scaffold,
+ gint indent);
+gint cell_area_scaffold_get_indentation (CellAreaScaffold *scaffold);
+
+
+
+G_END_DECLS
+
+#endif /* __CELL_AREA_SCAFFOLD_H__ */
--- /dev/null
+#include <gtk/gtk.h>
+#include "cellareascaffold.h"
+
+/*******************************************************
+ * Simple Test *
+ *******************************************************/
+enum {
+ SIMPLE_COLUMN_NAME,
+ SIMPLE_COLUMN_ICON,
+ SIMPLE_COLUMN_DESCRIPTION,
+ N_SIMPLE_COLUMNS
+};
+
+static GtkCellRenderer *cell_1 = NULL, *cell_2 = NULL, *cell_3 = NULL;
+
+static GtkTreeModel *
+simple_list_model (void)
+{
+ GtkTreeIter iter;
+ GtkListStore *store =
+ gtk_list_store_new (N_SIMPLE_COLUMNS,
+ G_TYPE_STRING, /* name text */
+ G_TYPE_STRING, /* icon name */
+ G_TYPE_STRING); /* description text */
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ SIMPLE_COLUMN_NAME, "Alice in wonderland",
+ SIMPLE_COLUMN_ICON, "gtk-execute",
+ SIMPLE_COLUMN_DESCRIPTION,
+ "Twas brillig, and the slithy toves "
+ "did gyre and gimble in the wabe; "
+ "all mimsy were the borogoves, "
+ "and the mome raths outgrabe",
+ -1);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ SIMPLE_COLUMN_NAME, "Marry Poppins",
+ SIMPLE_COLUMN_ICON, "gtk-yes",
+ SIMPLE_COLUMN_DESCRIPTION, "Supercalifragilisticexpialidocious",
+ -1);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ SIMPLE_COLUMN_NAME, "George Bush",
+ SIMPLE_COLUMN_ICON, "gtk-dialog-warning",
+ SIMPLE_COLUMN_DESCRIPTION, "It's a very good question, very direct, "
+ "and I'm not going to answer it",
+ -1);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ SIMPLE_COLUMN_NAME, "Whinnie the pooh",
+ SIMPLE_COLUMN_ICON, "gtk-stop",
+ SIMPLE_COLUMN_DESCRIPTION, "The most wonderful thing about tiggers, "
+ "is tiggers are wonderful things",
+ -1);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ SIMPLE_COLUMN_NAME, "Aleister Crowley",
+ SIMPLE_COLUMN_ICON, "gtk-about",
+ SIMPLE_COLUMN_DESCRIPTION,
+ "Thou shalt do what thou wilt shall be the whole of the law",
+ -1);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ SIMPLE_COLUMN_NAME, "Mark Twain",
+ SIMPLE_COLUMN_ICON, "gtk-quit",
+ SIMPLE_COLUMN_DESCRIPTION,
+ "Giving up smoking is the easiest thing in the world. "
+ "I know because I've done it thousands of times.",
+ -1);
+
+
+ return (GtkTreeModel *)store;
+}
+
+static GtkWidget *
+simple_scaffold (void)
+{
+ GtkTreeModel *model;
+ GtkWidget *scaffold;
+ GtkCellArea *area;
+ GtkCellRenderer *renderer;
+
+ scaffold = cell_area_scaffold_new ();
+ gtk_widget_show (scaffold);
+
+ model = simple_list_model ();
+
+ cell_area_scaffold_set_model (CELL_AREA_SCAFFOLD (scaffold), model);
+
+ area = cell_area_scaffold_get_area (CELL_AREA_SCAFFOLD (scaffold));
+
+ cell_1 = renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, FALSE, FALSE);
+ gtk_cell_area_attribute_connect (area, renderer, "text", SIMPLE_COLUMN_NAME);
+
+ cell_2 = renderer = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (G_OBJECT (renderer), "xalign", 0.0F, NULL);
+ gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, TRUE, FALSE);
+ gtk_cell_area_attribute_connect (area, renderer, "stock-id", SIMPLE_COLUMN_ICON);
+
+ cell_3 = renderer = gtk_cell_renderer_text_new ();
+ g_object_set (G_OBJECT (renderer),
+ "wrap-mode", PANGO_WRAP_WORD,
+ "wrap-width", 215,
+ NULL);
+ gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, FALSE, TRUE);
+ gtk_cell_area_attribute_connect (area, renderer, "text", SIMPLE_COLUMN_DESCRIPTION);
+
+ return scaffold;
+}
+
+static void
+orientation_changed (GtkComboBox *combo,
+ CellAreaScaffold *scaffold)
+{
+ GtkOrientation orientation = gtk_combo_box_get_active (combo);
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (scaffold), orientation);
+}
+
+static void
+align_cell_2_toggled (GtkToggleButton *toggle,
+ CellAreaScaffold *scaffold)
+{
+ GtkCellArea *area = cell_area_scaffold_get_area (scaffold);
+ gboolean align = gtk_toggle_button_get_active (toggle);
+
+ gtk_cell_area_cell_set (area, cell_2, "align", align, NULL);
+}
+
+static void
+align_cell_3_toggled (GtkToggleButton *toggle,
+ CellAreaScaffold *scaffold)
+{
+ GtkCellArea *area = cell_area_scaffold_get_area (scaffold);
+ gboolean align = gtk_toggle_button_get_active (toggle);
+
+ gtk_cell_area_cell_set (area, cell_3, "align", align, NULL);
+}
+
+static void
+expand_cell_1_toggled (GtkToggleButton *toggle,
+ CellAreaScaffold *scaffold)
+{
+ GtkCellArea *area = cell_area_scaffold_get_area (scaffold);
+ gboolean expand = gtk_toggle_button_get_active (toggle);
+
+ gtk_cell_area_cell_set (area, cell_1, "expand", expand, NULL);
+}
+
+static void
+expand_cell_2_toggled (GtkToggleButton *toggle,
+ CellAreaScaffold *scaffold)
+{
+ GtkCellArea *area = cell_area_scaffold_get_area (scaffold);
+ gboolean expand = gtk_toggle_button_get_active (toggle);
+
+ gtk_cell_area_cell_set (area, cell_2, "expand", expand, NULL);
+}
+
+static void
+expand_cell_3_toggled (GtkToggleButton *toggle,
+ CellAreaScaffold *scaffold)
+{
+ GtkCellArea *area = cell_area_scaffold_get_area (scaffold);
+ gboolean expand = gtk_toggle_button_get_active (toggle);
+
+ gtk_cell_area_cell_set (area, cell_3, "expand", expand, NULL);
+}
+
+static void
+simple_cell_area (void)
+{
+ GtkWidget *window, *widget;
+ GtkWidget *scaffold, *frame, *vbox, *hbox;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+ gtk_window_set_title (GTK_WINDOW (window), "CellArea expand and alignments");
+
+ scaffold = simple_scaffold ();
+
+ hbox = gtk_hbox_new (FALSE, 4);
+ frame = gtk_frame_new (NULL);
+ gtk_widget_show (hbox);
+ gtk_widget_show (frame);
+
+ gtk_widget_set_valign (frame, GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (frame, GTK_ALIGN_FILL);
+
+ gtk_container_add (GTK_CONTAINER (frame), scaffold);
+
+ gtk_box_pack_end (GTK_BOX (hbox), frame, TRUE, TRUE, 0);
+
+ /* Now add some controls */
+ vbox = gtk_vbox_new (FALSE, 4);
+ gtk_widget_show (vbox);
+ gtk_box_pack_end (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
+
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Horizontal");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Vertical");
+ gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "changed",
+ G_CALLBACK (orientation_changed), scaffold);
+
+ widget = gtk_check_button_new_with_label ("Align 2nd Cell");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE);
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "toggled",
+ G_CALLBACK (align_cell_2_toggled), scaffold);
+
+ widget = gtk_check_button_new_with_label ("Align 3rd Cell");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE);
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "toggled",
+ G_CALLBACK (align_cell_3_toggled), scaffold);
+
+
+ widget = gtk_check_button_new_with_label ("Expand 1st Cell");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE);
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "toggled",
+ G_CALLBACK (expand_cell_1_toggled), scaffold);
+
+ widget = gtk_check_button_new_with_label ("Expand 2nd Cell");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE);
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "toggled",
+ G_CALLBACK (expand_cell_2_toggled), scaffold);
+
+ widget = gtk_check_button_new_with_label ("Expand 3rd Cell");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE);
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "toggled",
+ G_CALLBACK (expand_cell_3_toggled), scaffold);
+
+ gtk_container_add (GTK_CONTAINER (window), hbox);
+
+ gtk_widget_show (window);
+}
+
+/*******************************************************
+ * Focus Test *
+ *******************************************************/
+static GtkCellRenderer *focus_renderer, *sibling_renderer;
+
+enum {
+ FOCUS_COLUMN_NAME,
+ FOCUS_COLUMN_CHECK,
+ FOCUS_COLUMN_STATIC_TEXT,
+ N_FOCUS_COLUMNS
+};
+
+static GtkTreeModel *
+focus_list_model (void)
+{
+ GtkTreeIter iter;
+ GtkListStore *store =
+ gtk_list_store_new (N_FOCUS_COLUMNS,
+ G_TYPE_STRING, /* name text */
+ G_TYPE_BOOLEAN, /* check */
+ G_TYPE_STRING); /* static text */
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ FOCUS_COLUMN_NAME, "Enter a string",
+ FOCUS_COLUMN_CHECK, TRUE,
+ FOCUS_COLUMN_STATIC_TEXT, "Does it fly ?",
+ -1);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ FOCUS_COLUMN_NAME, "Enter a string",
+ FOCUS_COLUMN_CHECK, FALSE,
+ FOCUS_COLUMN_STATIC_TEXT, "Would you put it in a toaster ?",
+ -1);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ FOCUS_COLUMN_NAME, "Type something",
+ FOCUS_COLUMN_CHECK, FALSE,
+ FOCUS_COLUMN_STATIC_TEXT, "Does it feed on cute kittens ?",
+ -1);
+
+ return (GtkTreeModel *)store;
+}
+
+static void
+cell_toggled (GtkCellRendererToggle *cell_renderer,
+ const gchar *path,
+ CellAreaScaffold *scaffold)
+{
+ GtkTreeModel *model = cell_area_scaffold_get_model (scaffold);
+ GtkTreeIter iter;
+ gboolean active;
+
+ g_print ("Cell toggled !\n");
+
+ if (!gtk_tree_model_get_iter_from_string (model, &iter, path))
+ return;
+
+ gtk_tree_model_get (model, &iter, FOCUS_COLUMN_CHECK, &active, -1);
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter, FOCUS_COLUMN_CHECK, !active, -1);
+}
+
+static void
+cell_edited (GtkCellRendererToggle *cell_renderer,
+ const gchar *path,
+ const gchar *new_text,
+ CellAreaScaffold *scaffold)
+{
+ GtkTreeModel *model = cell_area_scaffold_get_model (scaffold);
+ GtkTreeIter iter;
+
+ g_print ("Cell edited with new text '%s' !\n", new_text);
+
+ if (!gtk_tree_model_get_iter_from_string (model, &iter, path))
+ return;
+
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter, FOCUS_COLUMN_NAME, new_text, -1);
+}
+
+static GtkWidget *
+focus_scaffold (gboolean color_bg, GtkCellRenderer **focus, GtkCellRenderer **sibling)
+{
+ GtkTreeModel *model;
+ GtkWidget *scaffold;
+ GtkCellArea *area;
+ GtkCellRenderer *renderer, *toggle;
+
+ scaffold = cell_area_scaffold_new ();
+ gtk_widget_show (scaffold);
+
+ model = focus_list_model ();
+
+ cell_area_scaffold_set_model (CELL_AREA_SCAFFOLD (scaffold), model);
+
+ area = cell_area_scaffold_get_area (CELL_AREA_SCAFFOLD (scaffold));
+
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
+ gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, TRUE, FALSE);
+ gtk_cell_area_attribute_connect (area, renderer, "text", FOCUS_COLUMN_NAME);
+
+ if (color_bg)
+ g_object_set (G_OBJECT (renderer), "cell-background", "red", NULL);
+
+ g_signal_connect (G_OBJECT (renderer), "edited",
+ G_CALLBACK (cell_edited), scaffold);
+
+ toggle = renderer = gtk_cell_renderer_toggle_new ();
+ g_object_set (G_OBJECT (renderer), "xalign", 0.0F, NULL);
+ gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, FALSE, TRUE);
+ gtk_cell_area_attribute_connect (area, renderer, "active", FOCUS_COLUMN_CHECK);
+
+ if (color_bg)
+ g_object_set (G_OBJECT (renderer), "cell-background", "green", NULL);
+
+ if (focus)
+ *focus = renderer;
+
+ g_signal_connect (G_OBJECT (renderer), "toggled",
+ G_CALLBACK (cell_toggled), scaffold);
+
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (G_OBJECT (renderer),
+ "wrap-mode", PANGO_WRAP_WORD,
+ "wrap-width", 150,
+ NULL);
+
+ if (color_bg)
+ g_object_set (G_OBJECT (renderer), "cell-background", "blue", NULL);
+
+ if (sibling)
+ *sibling = renderer;
+
+ gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, FALSE, TRUE);
+ gtk_cell_area_attribute_connect (area, renderer, "text", FOCUS_COLUMN_STATIC_TEXT);
+
+ gtk_cell_area_add_focus_sibling (area, toggle, renderer);
+
+ return scaffold;
+}
+
+static void
+focus_sibling_toggled (GtkToggleButton *toggle,
+ CellAreaScaffold *scaffold)
+{
+ GtkCellArea *area = cell_area_scaffold_get_area (scaffold);
+ gboolean active = gtk_toggle_button_get_active (toggle);
+
+ if (active)
+ gtk_cell_area_add_focus_sibling (area, focus_renderer, sibling_renderer);
+ else
+ gtk_cell_area_remove_focus_sibling (area, focus_renderer, sibling_renderer);
+
+ gtk_widget_queue_draw (GTK_WIDGET (scaffold));
+}
+
+
+static void
+focus_cell_area (void)
+{
+ GtkWidget *window, *widget;
+ GtkWidget *scaffold, *frame, *vbox, *hbox;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ hbox = gtk_hbox_new (FALSE, 4);
+ gtk_widget_show (hbox);
+
+ gtk_window_set_title (GTK_WINDOW (window), "Focus and editable cells");
+
+ scaffold = focus_scaffold (FALSE, &focus_renderer, &sibling_renderer);
+
+ frame = gtk_frame_new (NULL);
+ gtk_widget_show (frame);
+
+ gtk_widget_set_valign (frame, GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (frame, GTK_ALIGN_FILL);
+
+ gtk_container_add (GTK_CONTAINER (frame), scaffold);
+
+ gtk_box_pack_end (GTK_BOX (hbox), frame, TRUE, TRUE, 0);
+
+ /* Now add some controls */
+ vbox = gtk_vbox_new (FALSE, 4);
+ gtk_widget_show (vbox);
+ gtk_box_pack_end (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
+
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Horizontal");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Vertical");
+ gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "changed",
+ G_CALLBACK (orientation_changed), scaffold);
+
+ widget = gtk_check_button_new_with_label ("Focus Sibling");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE);
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "toggled",
+ G_CALLBACK (focus_sibling_toggled), scaffold);
+
+ gtk_container_add (GTK_CONTAINER (window), hbox);
+
+ gtk_widget_show (window);
+}
+
+
+
+/*******************************************************
+ * Background Area *
+ *******************************************************/
+static void
+cell_spacing_changed (GtkSpinButton *spin_button,
+ CellAreaScaffold *scaffold)
+{
+ GtkCellArea *area = cell_area_scaffold_get_area (scaffold);
+ gint value;
+
+ value = (gint)gtk_spin_button_get_value (spin_button);
+
+ gtk_cell_area_box_set_spacing (GTK_CELL_AREA_BOX (area), value);
+}
+
+static void
+row_spacing_changed (GtkSpinButton *spin_button,
+ CellAreaScaffold *scaffold)
+{
+ gint value;
+
+ value = (gint)gtk_spin_button_get_value (spin_button);
+
+ cell_area_scaffold_set_row_spacing (scaffold, value);
+}
+
+static void
+indentation_changed (GtkSpinButton *spin_button,
+ CellAreaScaffold *scaffold)
+{
+ gint value;
+
+ value = (gint)gtk_spin_button_get_value (spin_button);
+
+ cell_area_scaffold_set_indentation (scaffold, value);
+}
+
+static void
+background_area (void)
+{
+ GtkWidget *window, *widget, *label, *main_vbox;
+ GtkWidget *scaffold, *frame, *vbox, *hbox;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ hbox = gtk_hbox_new (FALSE, 4);
+ main_vbox = gtk_vbox_new (FALSE, 4);
+ gtk_widget_show (hbox);
+ gtk_widget_show (main_vbox);
+ gtk_container_add (GTK_CONTAINER (window), main_vbox);
+
+ gtk_window_set_title (GTK_WINDOW (window), "Background Area");
+
+ label = gtk_label_new ("In this example, row spacing gets devided into the background area, "
+ "column spacing is added between each background area, indentation is "
+ "prepended space distributed to the background area.");
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_width_chars (GTK_LABEL (label), 40);
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (main_vbox), label, FALSE, FALSE, 0);
+
+ scaffold = focus_scaffold (TRUE, NULL, NULL);
+
+ frame = gtk_frame_new (NULL);
+ gtk_widget_show (frame);
+
+ gtk_widget_set_valign (frame, GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (frame, GTK_ALIGN_FILL);
+
+ gtk_container_add (GTK_CONTAINER (frame), scaffold);
+
+ gtk_box_pack_end (GTK_BOX (hbox), frame, TRUE, TRUE, 0);
+
+ /* Now add some controls */
+ vbox = gtk_vbox_new (FALSE, 4);
+ gtk_widget_show (vbox);
+ gtk_box_pack_end (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0);
+
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Horizontal");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Vertical");
+ gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "changed",
+ G_CALLBACK (orientation_changed), scaffold);
+
+ widget = gtk_spin_button_new_with_range (0, 10, 1);
+ label = gtk_label_new ("Cell spacing");
+ hbox = gtk_hbox_new (FALSE, 4);
+ gtk_widget_show (hbox);
+ gtk_widget_show (label);
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "value-changed",
+ G_CALLBACK (cell_spacing_changed), scaffold);
+
+
+ widget = gtk_spin_button_new_with_range (0, 10, 1);
+ label = gtk_label_new ("Row spacing");
+ hbox = gtk_hbox_new (FALSE, 4);
+ gtk_widget_show (hbox);
+ gtk_widget_show (label);
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "value-changed",
+ G_CALLBACK (row_spacing_changed), scaffold);
+
+ widget = gtk_spin_button_new_with_range (0, 30, 1);
+ label = gtk_label_new ("Intentation");
+ hbox = gtk_hbox_new (FALSE, 4);
+ gtk_widget_show (hbox);
+ gtk_widget_show (label);
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+
+ g_signal_connect (G_OBJECT (widget), "value-changed",
+ G_CALLBACK (indentation_changed), scaffold);
+
+ gtk_widget_show (window);
+}
+
+
+
+
+
+
+int
+main (int argc, char *argv[])
+{
+ gtk_init (NULL, NULL);
+
+ simple_cell_area ();
+ focus_cell_area ();
+ background_area ();
+
+ gtk_main ();
+
+ return 0;
+}