X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkcellarea.c;h=df9c70968a0ce52235da79cda714a05ea0c76a60;hb=3b2182e711ace4a2d6843848208224cd7525d530;hp=ccc1b71bc9579875cd1b0132196af8eb09b48111;hpb=631bdc438c67debf5d7fab51cd655ac296ab32d3;p=~andy%2Fgtk diff --git a/gtk/gtkcellarea.c b/gtk/gtkcellarea.c index ccc1b71bc..df9c70968 100644 --- a/gtk/gtkcellarea.c +++ b/gtk/gtkcellarea.c @@ -16,9 +16,340 @@ * 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. + * License along with this library. If not, see . + */ + +/** + * SECTION:gtkcellarea + * @Short_Description: An abstract class for laying out GtkCellRenderers + * @Title: GtkCellArea + * + * The #GtkCellArea is an abstract class for #GtkCellLayout widgets + * (also referred to as "layouting widgets") to interface with an + * arbitrary number of #GtkCellRenderers and interact with the user + * for a given #GtkTreeModel row. + * + * The cell area handles events, focus navigation, drawing and + * size requests and allocations for a given row of data. + * + * Usually users dont have to interact with the #GtkCellArea directly + * unless they are implementing a cell-layouting widget themselves. + * + * + * Requesting area sizes + * + * As outlined in GtkWidget's + * geometry management section, GTK+ uses a height-for-width + * geometry management system to compute the sizes of widgets and user + * interfaces. #GtkCellArea uses the same semantics to calculate the + * size of an area for an arbitrary number of #GtkTreeModel rows. + * + * When requesting the size of a cell area one needs to calculate + * the size for a handful of rows, and this will be done differently by + * different layouting widgets. For instance a #GtkTreeViewColumn + * always lines up the areas from top to bottom while a #GtkIconView + * on the other hand might enforce that all areas received the same + * width and wrap the areas around, requesting height for more cell + * areas when allocated less width. + * + * It's also important for areas to maintain some cell + * alignments with areas rendered for adjacent rows (cells can + * appear "columnized" inside an area even when the size of + * cells are different in each row). For this reason the #GtkCellArea + * uses a #GtkCellAreaContext object to store the alignments + * and sizes along the way (as well as the overall largest minimum + * and natural size for all the rows which have been calculated + * with the said context). + * + * The #GtkCellAreaContext is an opaque object specific to the + * #GtkCellArea which created it (see gtk_cell_area_create_context()). + * The owning cell-layouting widget can create as many contexts as + * it wishes to calculate sizes of rows which should receive the + * same size in at least one orientation (horizontally or vertically), + * However, it's important that the same #GtkCellAreaContext which + * was used to request the sizes for a given #GtkTreeModel row be + * used when rendering or processing events for that row. + * + * In order to request the width of all the rows at the root level + * of a #GtkTreeModel one would do the following: + * + * Requesting the width of a handful of GtkTreeModel rows + * + * GtkTreeIter iter; + * gint minimum_width; + * gint natural_width; + * + * valid = gtk_tree_model_get_iter_first (model, &iter); + * while (valid) + * { + * gtk_cell_area_apply_attributes (area, model, &iter, FALSE, FALSE); + * gtk_cell_area_get_preferred_width (area, context, widget, NULL, NULL); + * + * valid = gtk_tree_model_iter_next (model, &iter); + * } + * gtk_cell_area_context_get_preferred_width (context, &minimum_width, &natural_width); + * + * + * Note that in this example it's not important to observe the + * returned minimum and natural width of the area for each row + * unless the cell-layouting object is actually interested in the + * widths of individual rows. The overall width is however stored + * in the accompanying #GtkCellAreaContext object and can be consulted + * at any time. + * + * This can be useful since #GtkCellLayout widgets usually have to + * support requesting and rendering rows in treemodels with an + * exceedingly large amount of rows. The #GtkCellLayout widget in + * that case would calculate the required width of the rows in an + * idle or timeout source (see g_timeout_add()) and when the widget + * is requested its actual width in #GtkWidgetClass.get_preferred_width() + * it can simply consult the width accumulated so far in the + * #GtkCellAreaContext object. + * + * A simple example where rows are rendered from top to bottom and + * take up the full width of the layouting widget would look like: + * + * A typical get_preferred_width() implementation + * + * static void + * foo_get_preferred_width (GtkWidget *widget, + * gint *minimum_size, + * gint *natural_size) + * { + * Foo *foo = FOO (widget); + * FooPrivate *priv = foo->priv; + * + * foo_ensure_at_least_one_handfull_of_rows_have_been_requested (foo); + * + * gtk_cell_area_context_get_preferred_width (priv->context, minimum_size, natural_size); + * } + * + * + * In the above example the Foo widget has to make sure that some + * row sizes have been calculated (the amount of rows that Foo judged + * was appropriate to request space for in a single timeout iteration) + * before simply returning the amount of space required by the area via + * the #GtkCellAreaContext. + * + * Requesting the height for width (or width for height) of an area is + * a similar task except in this case the #GtkCellAreaContext does not + * store the data (actually, it does not know how much space the layouting + * widget plans to allocate it for every row. It's up to the layouting + * widget to render each row of data with the appropriate height and + * width which was requested by the #GtkCellArea). + * + * In order to request the height for width of all the rows at the + * root level of a #GtkTreeModel one would do the following: + * + * Requesting the height for width of a handful of GtkTreeModel rows + * + * GtkTreeIter iter; + * gint minimum_height; + * gint natural_height; + * gint full_minimum_height = 0; + * gint full_natural_height = 0; + * + * valid = gtk_tree_model_get_iter_first (model, &iter); + * while (valid) + * { + * gtk_cell_area_apply_attributes (area, model, &iter, FALSE, FALSE); + * gtk_cell_area_get_preferred_height_for_width (area, context, widget, + * width, &minimum_height, &natural_height); + * + * if (width_is_for_allocation) + * cache_row_height (&iter, minimum_height, natural_height); + * + * full_minimum_height += minimum_height; + * full_natural_height += natural_height; + * + * valid = gtk_tree_model_iter_next (model, &iter); + * } + * + * + * Note that in the above example we would need to cache the heights + * returned for each row so that we would know what sizes to render the + * areas for each row. However we would only want to really cache the + * heights if the request is intended for the layouting widgets real + * allocation. + * + * In some cases the layouting widget is requested the height for an + * arbitrary for_width, this is a special case for layouting widgets + * who need to request size for tens of thousands of rows. For this + * case it's only important that the layouting widget calculate + * one reasonably sized chunk of rows and return that height + * synchronously. The reasoning here is that any layouting widget is + * at least capable of synchronously calculating enough height to fill + * the screen height (or scrolled window height) in response to a single + * call to #GtkWidgetClass.get_preferred_height_for_width(). Returning + * a perfect height for width that is larger than the screen area is + * inconsequential since after the layouting receives an allocation + * from a scrolled window it simply continues to drive the scrollbar + * values while more and more height is required for the row heights + * that are calculated in the background. + * + * + * + * Rendering Areas + * + * Once area sizes have been aquired at least for the rows in the + * visible area of the layouting widget they can be rendered at + * #GtkWidgetClass.draw() time. + * + * A crude example of how to render all the rows at the root level + * runs as follows: + * + * Requesting the width of a handful of GtkTreeModel rows + * + * GtkAllocation allocation; + * GdkRectangle cell_area = { 0, }; + * GtkTreeIter iter; + * gint minimum_width; + * gint natural_width; + * + * gtk_widget_get_allocation (widget, &allocation); + * cell_area.width = allocation.width; + * + * valid = gtk_tree_model_get_iter_first (model, &iter); + * while (valid) + * { + * cell_area.height = get_cached_height_for_row (&iter); + * + * gtk_cell_area_apply_attributes (area, model, &iter, FALSE, FALSE); + * gtk_cell_area_render (area, context, widget, cr, + * &cell_area, &cell_area, state_flags, FALSE); + * + * cell_area.y += cell_area.height; + * + * valid = gtk_tree_model_iter_next (model, &iter); + * } + * + * + * Note that the cached height in this example really depends on how + * the layouting widget works. The layouting widget might decide to + * give every row its minimum or natural height or, if the model content + * is expected to fit inside the layouting widget without scrolling, it + * would make sense to calculate the allocation for each row at + * #GtkWidget::size-allocate time using gtk_distribute_natural_allocation(). + * + * + * + * Handling Events and Driving Keyboard Focus + * + * Passing events to the area is as simple as handling events on any + * normal widget and then passing them to the gtk_cell_area_event() + * API as they come in. Usually #GtkCellArea is only interested in + * button events, however some customized derived areas can be implemented + * who are interested in handling other events. Handling an event can + * trigger the #GtkCellArea::focus-changed signal to fire; as well as + * #GtkCellArea::add-editable in the case that an editable cell was + * clicked and needs to start editing. You can call + * gtk_cell_area_stop_editing() at any time to cancel any cell editing + * that is currently in progress. + * + * The #GtkCellArea drives keyboard focus from cell to cell in a way + * similar to #GtkWidget. For layouting widgets that support giving + * focus to cells it's important to remember to pass %GTK_CELL_RENDERER_FOCUSED + * to the area functions for the row that has focus and to tell the + * area to paint the focus at render time. + * + * Layouting widgets that accept focus on cells should implement the + * #GtkWidgetClass.focus() virtual method. The layouting widget is always + * responsible for knowing where #GtkTreeModel rows are rendered inside + * the widget, so at #GtkWidgetClass.focus() time the layouting widget + * should use the #GtkCellArea methods to navigate focus inside the area + * and then observe the GtkDirectionType to pass the focus to adjacent + * rows and areas. + * + * A basic example of how the #GtkWidgetClass.focus() virtual method + * should be implemented: + * + * Implementing keyboard focus navigation + * + * static gboolean + * foo_focus (GtkWidget *widget, + * GtkDirectionType direction) + * { + * Foo *foo = FOO (widget); + * FooPrivate *priv = foo->priv; + * gint focus_row; + * gboolean have_focus = FALSE; + * + * focus_row = priv->focus_row; + * + * if (!gtk_widget_has_focus (widget)) + * gtk_widget_grab_focus (widget); + * + * 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 (gtk_cell_area_focus (priv->area, direction)) + * { + * priv->focus_row = focus_row; + * have_focus = TRUE; + * break; + * } + * else + * { + * 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 + * { + * focus_row--; + * valid = gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, focus_row); + * } + * } + * else + * { + * if (focus_row == last_row) + * break; + * else + * { + * focus_row++; + * valid = gtk_tree_model_iter_next (priv->model, &iter); + * } + * } + * } + * } + * return have_focus; + * } + * + * + * Note that the layouting widget is responsible for matching the + * GtkDirectionType values to the way it lays out its cells. + * + * + * + * Cell Properties + * + * The #GtkCellArea introduces cell properties + * for #GtkCellRenderers in very much the same way that #GtkContainer + * introduces child properties + * for #GtkWidgets. This provides some general interfaces for defining + * the relationship cell areas have with their cells. For instance in a + * #GtkCellAreaBox a cell might "expand" and receive extra space when + * the area is allocated more than its full natural request, or a cell + * might be configured to "align" with adjacent rows which were requested + * and rendered with the same #GtkCellAreaContext. + * + * Use gtk_cell_area_class_install_cell_property() to install cell + * properties for a cell area class and gtk_cell_area_class_find_cell_property() + * or gtk_cell_area_class_list_cell_properties() to get information about + * existing cell properties. + * + * To set the value of a cell property, use gtk_cell_area_cell_set_property(), + * gtk_cell_area_cell_set() or gtk_cell_area_cell_set_valist(). To obtain + * the value of a cell property, use gtk_cell_area_cell_get_property(), + * gtk_cell_area_cell_get() or gtk_cell_area_cell_get_valist(). + * + * */ #include "config.h" @@ -30,7 +361,7 @@ #include "gtkintl.h" #include "gtkcelllayout.h" #include "gtkcellarea.h" -#include "gtkcellareaiter.h" +#include "gtkcellareacontext.h" #include "gtkmarshalers.h" #include "gtkprivate.h" @@ -41,56 +372,147 @@ 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); + guint prop_id, + const GValue *value, + GParamSpec *pspec); static void gtk_cell_area_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); + guint prop_id, + GValue *value, + GParamSpec *pspec); /* GtkCellAreaClass */ +static void gtk_cell_area_real_add (GtkCellArea *area, + GtkCellRenderer *renderer); +static void gtk_cell_area_real_remove (GtkCellArea *area, + GtkCellRenderer *renderer); +static void gtk_cell_area_real_foreach (GtkCellArea *area, + GtkCellCallback callback, + gpointer callback_data); +static void gtk_cell_area_real_foreach_alloc (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + const GdkRectangle *background_area, + GtkCellAllocCallback callback, + gpointer callback_data); static gint gtk_cell_area_real_event (GtkCellArea *area, - GtkCellAreaIter *iter, - GtkWidget *widget, - GdkEvent *event, - const GdkRectangle *cell_area, - GtkCellRendererState flags); -static void gtk_cell_area_real_get_preferred_height_for_width (GtkCellArea *area, - GtkCellAreaIter *iter, + GtkCellAreaContext *context, + GtkWidget *widget, + GdkEvent *event, + const GdkRectangle *cell_area, + GtkCellRendererState flags); +static void gtk_cell_area_real_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_real_apply_attributes (GtkCellArea *area, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gboolean is_expander, + gboolean is_expanded); + +static GtkCellAreaContext *gtk_cell_area_real_create_context (GtkCellArea *area); +static GtkCellAreaContext *gtk_cell_area_real_copy_context (GtkCellArea *area, + GtkCellAreaContext *context); +static GtkSizeRequestMode gtk_cell_area_real_get_request_mode (GtkCellArea *area); +static void gtk_cell_area_real_get_preferred_width (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint *minimum_width, + gint *natural_width); +static void gtk_cell_area_real_get_preferred_height (GtkCellArea *area, + GtkCellAreaContext *context, GtkWidget *widget, - gint width, gint *minimum_height, gint *natural_height); +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, - GtkCellAreaIter *iter, - GtkWidget *widget, - gint height, - gint *minimum_width, - gint *natural_width); -static void gtk_cell_area_real_update_focus (GtkCellArea *area); + GtkCellAreaContext *context, + GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width); +static gboolean gtk_cell_area_real_is_activatable (GtkCellArea *area); +static gboolean gtk_cell_area_real_activate (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + GtkCellRendererState flags, + gboolean edit_only); +static gboolean gtk_cell_area_real_focus (GtkCellArea *area, + GtkDirectionType direction); /* 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); + 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); + 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); + GtkCellRenderer *cell, + GtkCellLayoutDataFunc func, + gpointer func_data, + GDestroyNotify destroy); static void gtk_cell_area_clear_attributes (GtkCellLayout *cell_layout, - GtkCellRenderer *renderer); + GtkCellRenderer *renderer); static void gtk_cell_area_reorder (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gint position); + GtkCellRenderer *cell, + gint position); static GList *gtk_cell_area_get_cells (GtkCellLayout *cell_layout); +static GtkCellArea *gtk_cell_area_get_area (GtkCellLayout *cell_layout); + +/* GtkBuildableIface */ +static void gtk_cell_area_buildable_init (GtkBuildableIface *iface); +static void gtk_cell_area_buildable_custom_tag_end (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + gpointer *data); + +/* Used in foreach loop to check if a child renderer is present */ +typedef struct { + GtkCellRenderer *renderer; + gboolean has_renderer; +} HasRendererCheck; + +/* Used in foreach loop to get a cell's allocation */ +typedef struct { + GtkCellRenderer *renderer; + GdkRectangle allocation; +} RendererAllocationData; + +/* Used in foreach loop to render cells */ +typedef struct { + GtkCellArea *area; + GtkWidget *widget; + cairo_t *cr; + GdkRectangle focus_rect; + GtkCellRendererState render_flags; + guint paint_focus : 1; + guint focus_all : 1; + guint first_focus : 1; +} CellRenderData; + +/* Used in foreach loop to get a cell by position */ +typedef struct { + gint x; + gint y; + GtkCellRenderer *renderer; + GdkRectangle cell_area; +} CellByPositionData; /* Attribute/Cell metadata */ typedef struct { @@ -99,25 +521,40 @@ typedef struct { } CellAttribute; typedef struct { - GSList *attributes; + GSList *attributes; GtkCellLayoutDataFunc func; gpointer data; GDestroyNotify destroy; + GtkCellLayout *proxy; } CellInfo; static CellInfo *cell_info_new (GtkCellLayoutDataFunc func, - gpointer data, - GDestroyNotify destroy); + gpointer data, + GDestroyNotify destroy); static void cell_info_free (CellInfo *info); static CellAttribute *cell_attribute_new (GtkCellRenderer *renderer, - const gchar *attribute, - gint column); + const gchar *attribute, + gint column); static void cell_attribute_free (CellAttribute *attribute); static gint cell_attribute_find (CellAttribute *cell_attribute, - const gchar *attribute); - -/* Struct to pass data along while looping over + const gchar *attribute); + +/* Internal functions/signal emissions */ +static void gtk_cell_area_add_editable (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *editable, + const 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 { @@ -130,54 +567,59 @@ typedef struct { struct _GtkCellAreaPrivate { - /* The GtkCellArea bookkeeps any connected + /* The GtkCellArea bookkeeps any connected * attributes in this hash table. */ GHashTable *cell_info; - /* The cell border decides how much space to reserve - * around each cell for the background_area - */ - GtkBorder cell_border; - /* Current path is saved as a side-effect - * of gtk_cell_area_apply_attributes() */ + * 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; - guint can_focus : 1; + /* Tracking which cells are focus siblings of focusable cells */ + GHashTable *focus_siblings; }; enum { PROP_0, - PROP_CELL_MARGIN_LEFT, - PROP_CELL_MARGIN_RIGHT, - PROP_CELL_MARGIN_TOP, - PROP_CELL_MARGIN_BOTTOM, PROP_FOCUS_CELL, - PROP_EDITED_CELL + PROP_EDITED_CELL, + PROP_EDIT_WIDGET }; enum { - SIGNAL_FOCUS_LEAVE, - SIGNAL_EDITING_STARTED, + 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 */ + * on cells. at least no perceived 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)); + G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT, + gtk_cell_area_cell_layout_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, + gtk_cell_area_buildable_init)) static void gtk_cell_area_init (GtkCellArea *area) @@ -185,28 +627,32 @@ gtk_cell_area_init (GtkCellArea *area) GtkCellAreaPrivate *priv; area->priv = G_TYPE_INSTANCE_GET_PRIVATE (area, - GTK_TYPE_CELL_AREA, - GtkCellAreaPrivate); + 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->cell_info = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify)cell_info_free); - priv->cell_border.left = 0; - priv->cell_border.right = 0; - priv->cell_border.top = 0; - priv->cell_border.bottom = 0; + 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 +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; @@ -214,109 +660,188 @@ gtk_cell_area_class_init (GtkCellAreaClass *class) 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->add = gtk_cell_area_real_add; + class->remove = gtk_cell_area_real_remove; + class->foreach = gtk_cell_area_real_foreach; + class->foreach_alloc = gtk_cell_area_real_foreach_alloc; + class->event = gtk_cell_area_real_event; + class->render = gtk_cell_area_real_render; + class->apply_attributes = gtk_cell_area_real_apply_attributes; /* geometry */ - class->create_iter = NULL; - class->get_request_mode = NULL; - class->get_preferred_width = NULL; - class->get_preferred_height = NULL; + class->create_context = gtk_cell_area_real_create_context; + class->copy_context = gtk_cell_area_real_copy_context; + class->get_request_mode = gtk_cell_area_real_get_request_mode; + class->get_preferred_width = gtk_cell_area_real_get_preferred_width; + class->get_preferred_height = gtk_cell_area_real_get_preferred_height; 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->grab_focus = NULL; - class->update_focus = gtk_cell_area_real_update_focus; + class->is_activatable = gtk_cell_area_real_is_activatable; + class->activate = gtk_cell_area_real_activate; + class->focus = gtk_cell_area_real_focus; /* Signals */ - cell_area_signals[SIGNAL_FOCUS_LEAVE] = - g_signal_new (I_("focus-leave"), - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - 0, /* Class offset (just a notification, no class handler) */ - NULL, NULL, - _gtk_marshal_VOID__ENUM_STRING, - G_TYPE_NONE, 2, - GTK_TYPE_DIRECTION_TYPE, G_TYPE_STRING); - - cell_area_signals[SIGNAL_EDITING_STARTED] = - g_signal_new (I_("editing-started"), - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - 0, /* No class closure here */ - NULL, NULL, - _gtk_marshal_VOID__OBJECT_OBJECT_STRING, - G_TYPE_NONE, 3, - GTK_TYPE_CELL_RENDERER, - GTK_TYPE_CELL_EDITABLE, - G_TYPE_STRING); + /** + * 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 + * + * Since: 3.0 + */ + 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); + g_signal_set_va_marshaller (cell_area_signals[SIGNAL_APPLY_ATTRIBUTES], G_TYPE_FROM_CLASS (class), + _gtk_marshal_VOID__OBJECT_BOXED_BOOLEAN_BOOLEANv); + + /** + * 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. + * + * Since: 3.0 + */ + 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. + * + * Since: 3.0 + */ + 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. + * + * Since: 3.0 + */ + 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_CELL_MARGIN_LEFT, - g_param_spec_int - ("cell-margin-left", - P_("Margin on Left"), - P_("Pixels of extra space on the left side of each cell"), - 0, - G_MAXINT16, - 0, - GTK_PARAM_READWRITE)); - - g_object_class_install_property (object_class, - PROP_CELL_MARGIN_RIGHT, - g_param_spec_int - ("cell-margin-right", - P_("Margin on Right"), - P_("Pixels of extra space on the right side of each cell"), - 0, - G_MAXINT16, - 0, - GTK_PARAM_READWRITE)); - - g_object_class_install_property (object_class, - PROP_CELL_MARGIN_TOP, - g_param_spec_int - ("cell-margin-top", - P_("Margin on Top"), - P_("Pixels of extra space on the top side of each cell"), - 0, - G_MAXINT16, - 0, - GTK_PARAM_READWRITE)); - - g_object_class_install_property (object_class, - PROP_CELL_MARGIN_BOTTOM, - g_param_spec_int - ("cell-margin-bottom", - P_("Margin on Bottom"), - P_("Pixels of extra space on the bottom side of each cell"), - 0, - G_MAXINT16, - 0, - GTK_PARAM_READWRITE)); - + /** + * GtkCellArea:focus-cell: + * + * The cell in the area that currently has focus + * + * Since: 3.0 + */ 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)); - + ("focus-cell", + P_("Focus Cell"), + P_("The cell which currently has focus"), + GTK_TYPE_CELL_RENDERER, + GTK_PARAM_READWRITE)); + + /** + * GtkCellArea:edited-cell: + * + * The cell in the area that is currently edited + * + * This property is read-only and only changes as + * a result of a call gtk_cell_area_activate_cell(). + * + * Since: 3.0 + */ 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, - GTK_PARAM_READWRITE)); + ("edited-cell", + P_("Edited Cell"), + P_("The cell which is currently being edited"), + GTK_TYPE_CELL_RENDERER, + G_PARAM_READABLE)); + + /** + * GtkCellArea:edit-widget: + * + * The widget currently editing the edited cell + * + * This property is read-only and only changes as + * a result of a call gtk_cell_area_activate_cell(). + * + * Since: 3.0 + */ + 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_EDITABLE, + G_PARAM_READABLE)); /* Pool for Cell Properties */ if (!cell_property_pool) @@ -330,15 +855,14 @@ gtk_cell_area_class_init (GtkCellAreaClass *class) *************************************************************/ static CellInfo * cell_info_new (GtkCellLayoutDataFunc func, - gpointer data, - GDestroyNotify destroy) + gpointer data, + GDestroyNotify destroy) { - CellInfo *info = g_slice_new (CellInfo); - - info->attributes = NULL; - info->func = func; - info->data = data; - info->destroy = destroy; + CellInfo *info = g_slice_new0 (CellInfo); + + info->func = func; + info->data = data; + info->destroy = destroy; return info; } @@ -357,14 +881,14 @@ cell_info_free (CellInfo *info) static CellAttribute * cell_attribute_new (GtkCellRenderer *renderer, - const gchar *attribute, - gint column) + 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) + * class (dont dup the string) */ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (renderer), attribute); @@ -390,7 +914,7 @@ cell_attribute_free (CellAttribute *attribute) /* GCompareFunc for g_slist_find_custom() */ static gint cell_attribute_find (CellAttribute *cell_attribute, - const gchar *attribute) + const gchar *attribute) { return g_strcmp0 (cell_attribute->attribute, attribute); } @@ -405,9 +929,10 @@ gtk_cell_area_finalize (GObject *object) GtkCellAreaPrivate *priv = area->priv; /* All cell renderers should already be removed at this point, - * just kill our hash table here. + * 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); @@ -419,7 +944,7 @@ 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 + * subclasses should be breaking references to the GtkCellRenderers * at this point. */ gtk_cell_layout_clear (GTK_CELL_LAYOUT (object)); @@ -427,32 +952,21 @@ gtk_cell_area_dispose (GObject *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) + guint prop_id, + const GValue *value, + GParamSpec *pspec) { GtkCellArea *area = GTK_CELL_AREA (object); switch (prop_id) { - case PROP_CELL_MARGIN_LEFT: - gtk_cell_area_set_cell_margin_left (area, g_value_get_int (value)); - break; - case PROP_CELL_MARGIN_RIGHT: - gtk_cell_area_set_cell_margin_right (area, g_value_get_int (value)); - break; - case PROP_CELL_MARGIN_TOP: - gtk_cell_area_set_cell_margin_top (area, g_value_get_int (value)); - break; - case PROP_CELL_MARGIN_BOTTOM: - gtk_cell_area_set_cell_margin_bottom (area, g_value_get_int (value)); - break; case PROP_FOCUS_CELL: gtk_cell_area_set_focus_cell (area, (GtkCellRenderer *)g_value_get_object (value)); break; @@ -464,30 +978,24 @@ gtk_cell_area_set_property (GObject *object, static void gtk_cell_area_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) + guint prop_id, + GValue *value, + GParamSpec *pspec) { GtkCellArea *area = GTK_CELL_AREA (object); GtkCellAreaPrivate *priv = area->priv; switch (prop_id) { - case PROP_CELL_MARGIN_LEFT: - g_value_set_int (value, priv->cell_border.left); - break; - case PROP_CELL_MARGIN_RIGHT: - g_value_set_int (value, priv->cell_border.right); - break; - case PROP_CELL_MARGIN_TOP: - g_value_set_int (value, priv->cell_border.top); - break; - case PROP_CELL_MARGIN_BOTTOM: - g_value_set_int (value, priv->cell_border.bottom); - break; 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; @@ -497,276 +1005,631 @@ gtk_cell_area_get_property (GObject *object, /************************************************************* * GtkCellAreaClass * *************************************************************/ -static gint -gtk_cell_area_real_event (GtkCellArea *area, - GtkCellAreaIter *iter, - GtkWidget *widget, - GdkEvent *event, - const GdkRectangle *cell_area, - GtkCellRendererState flags) +static void +gtk_cell_area_real_add (GtkCellArea *area, + GtkCellRenderer *renderer) { - if (event->type == GDK_KEY_PRESS && (flags & GTK_CELL_RENDERER_FOCUSED) != 0) - { - GdkEventKey *key_event = (GdkEventKey *)event; - GtkCellAreaPrivate *priv = area->priv; - - if (priv->focus_cell && - (key_event->keyval == GDK_KEY_space || - key_event->keyval == GDK_KEY_KP_Space || - key_event->keyval == GDK_KEY_Return || - key_event->keyval == GDK_KEY_ISO_Enter || - key_event->keyval == GDK_KEY_KP_Enter)) - { - /* Activate or Edit the currently focused cell */ - GtkCellRendererMode mode; - GdkRectangle background_area; - GdkRectangle inner_area; - - /* Get the allocation of the focused cell. - */ - gtk_cell_area_get_cell_allocation (area, iter, widget, priv->focus_cell, - cell_area, &background_area); - - /* Remove margins from the background area to produce the cell area. - */ - gtk_cell_area_inner_cell_area (area, &background_area, &inner_area); - - /* XXX Need to do some extra right-to-left casing either here - * or inside the above called apis. - */ - - g_object_get (priv->focus_cell, "mode", &mode, NULL); - - if (mode == GTK_CELL_RENDERER_MODE_ACTIVATABLE) - { - if (gtk_cell_renderer_activate (priv->focus_cell, - event, widget, - priv->current_path, - &background_area, - &inner_area, - flags)) - return TRUE; - } - else if (mode == GTK_CELL_RENDERER_MODE_EDITABLE) - { - GtkCellEditable *editable_widget; - - editable_widget = - gtk_cell_renderer_start_editing (priv->focus_cell, - event, widget, - priv->current_path, - &background_area, - &inner_area, - flags); - - if (editable_widget != NULL) - { - g_return_val_if_fail (GTK_IS_CELL_EDITABLE (editable_widget), FALSE); - - gtk_cell_area_set_edited_cell (area, priv->focus_cell); - - /* Signal that editing started so that callers can get - * a handle on the editable_widget */ - gtk_cell_area_editing_started (area, priv->focus_cell, editable_widget); - - return TRUE; - } - } - } - } - - return FALSE; + g_warning ("GtkCellAreaClass::add not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); } -static void -gtk_cell_area_real_get_preferred_height_for_width (GtkCellArea *area, - GtkCellAreaIter *iter, - GtkWidget *widget, - gint width, - gint *minimum_height, - gint *natural_height) +static void +gtk_cell_area_real_remove (GtkCellArea *area, + GtkCellRenderer *renderer) { - /* If the area doesnt do height-for-width, fallback on base preferred height */ - GTK_CELL_AREA_GET_CLASS (area)->get_preferred_width (area, iter, widget, minimum_height, natural_height); + g_warning ("GtkCellAreaClass::remove not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); } static void -gtk_cell_area_real_get_preferred_width_for_height (GtkCellArea *area, - GtkCellAreaIter *iter, - GtkWidget *widget, - gint height, - gint *minimum_width, - gint *natural_width) +gtk_cell_area_real_foreach (GtkCellArea *area, + GtkCellCallback callback, + gpointer callback_data) { - /* If the area doesnt do width-for-height, fallback on base preferred width */ - GTK_CELL_AREA_GET_CLASS (area)->get_preferred_width (area, iter, widget, minimum_width, natural_width); + g_warning ("GtkCellAreaClass::foreach not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); } static void -update_can_focus (GtkCellRenderer *renderer, - gboolean *can_focus) +gtk_cell_area_real_foreach_alloc (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + const GdkRectangle *background_area, + GtkCellAllocCallback callback, + gpointer callback_data) { - - if (gtk_cell_renderer_can_focus (renderer)) - *can_focus = TRUE; + g_warning ("GtkCellAreaClass::foreach_alloc not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); } -static void -gtk_cell_area_real_update_focus (GtkCellArea *area) +static gint +gtk_cell_area_real_event (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + GdkEvent *event, + const GdkRectangle *cell_area, + GtkCellRendererState flags) { - gboolean can_focus = FALSE; + GtkCellAreaPrivate *priv = area->priv; + gboolean retval = FALSE; - /* Update the area's can focus flag, if any of the renderers can - * focus then the area can focus. - * - * Subclasses can override this in the case that they are also - * rendering widgets as well as renderers. - */ - gtk_cell_area_forall (area, (GtkCellCallback)update_can_focus, &can_focus); - gtk_cell_area_set_can_focus (area, can_focus); + 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); + retval = TRUE; + } + } + else if (event->type == GDK_BUTTON_PRESS) + { + GdkEventButton *button_event = (GdkEventButton *)event; + + if (button_event->button == GDK_BUTTON_PRIMARY) + { + GtkCellRenderer *renderer = NULL; + GtkCellRenderer *focus_renderer; + GdkRectangle alloc_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; + + /* Dont try to search for an event coordinate that is not in the area, that will + * trigger a runtime warning. + */ + if (event_x >= cell_area->x && event_x <= cell_area->x + cell_area->width && + event_y >= cell_area->y && event_y <= cell_area->y + cell_area->height) + renderer = + gtk_cell_area_get_cell_at_position (area, context, widget, + cell_area, event_x, event_y, + &alloc_area); + + if (renderer) + { + focus_renderer = gtk_cell_area_get_focus_from_sibling (area, renderer); + if (!focus_renderer) + focus_renderer = renderer; + + /* If we're already editing, cancel it and set focus */ + 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, focus_renderer); + retval = TRUE; + } + else + { + /* If we are activating via a focus sibling, + * we need to fetch the right cell area for the real event renderer */ + if (focus_renderer != renderer) + gtk_cell_area_get_cell_allocation (area, context, widget, focus_renderer, + cell_area, &alloc_area); + + gtk_cell_area_set_focus_cell (area, focus_renderer); + retval = gtk_cell_area_activate_cell (area, widget, focus_renderer, + event, &alloc_area, flags); + } + } + } + } - /* Unset the currently focused cell if the area can not receive - * focus for the given row data */ - if (!can_focus) - gtk_cell_area_set_focus_cell (area, NULL); + return retval; } -/************************************************************* - * GtkCellLayoutIface * - *************************************************************/ -static void -gtk_cell_area_cell_layout_init (GtkCellLayoutIface *iface) +static gboolean +render_cell (GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + const GdkRectangle *cell_background, + CellRenderData *data) { - 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; -} + GtkCellRenderer *focus_cell; + GtkCellRendererState flags; + GdkRectangle inner_area; -static void -gtk_cell_area_pack_default (GtkCellLayout *cell_layout, - GtkCellRenderer *renderer, - gboolean expand) -{ - gtk_cell_area_add (GTK_CELL_AREA (cell_layout), renderer); -} + focus_cell = gtk_cell_area_get_focus_cell (data->area); + flags = data->render_flags; -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); + gtk_cell_area_inner_cell_area (data->area, data->widget, cell_area, &inner_area); - for (l = cells; l; l = l->next) + if ((flags & GTK_CELL_RENDERER_FOCUSED) && + (data->focus_all || + (focus_cell && + (renderer == focus_cell || + gtk_cell_area_is_focus_sibling (data->area, focus_cell, renderer))))) { - GtkCellRenderer *renderer = l->data; - gtk_cell_area_remove (area, renderer); + gint focus_line_width; + GdkRectangle cell_focus; + + gtk_cell_renderer_get_aligned_area (renderer, data->widget, flags, &inner_area, &cell_focus); + + gtk_widget_style_get (data->widget, + "focus-line-width", &focus_line_width, + NULL); + + /* The focus rectangle is located around the aligned area of the cell */ + cell_focus.x -= focus_line_width; + cell_focus.y -= focus_line_width; + cell_focus.width += 2 * focus_line_width; + cell_focus.height += 2 * focus_line_width; + + if (data->first_focus) + { + data->first_focus = FALSE; + data->focus_rect = cell_focus; + } + else + { + gdk_rectangle_union (&data->focus_rect, &cell_focus, &data->focus_rect); + } } - g_list_free (cells); -} + gtk_cell_renderer_render (renderer, data->cr, data->widget, + cell_background, &inner_area, flags); -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); + return FALSE; } 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; +gtk_cell_area_real_render (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + cairo_t *cr, + const GdkRectangle *background_area, + const GdkRectangle *cell_area, + GtkCellRendererState flags, + gboolean paint_focus) +{ + CellRenderData render_data = + { + area, + widget, + cr, + { 0, }, + flags, + paint_focus, + FALSE, TRUE + }; + + /* Make sure we dont paint a focus rectangle while there + * is an editable widget in play + */ + if (gtk_cell_area_get_edited_cell (area)) + render_data.paint_focus = FALSE; - info = g_hash_table_lookup (priv->cell_info, renderer); + if (!gtk_widget_has_visible_focus (widget)) + render_data.paint_focus = FALSE; - if (info) - { - if (info->destroy && info->data) - info->destroy (info->data); + /* If no cell can activate but the caller wants focus painted, + * then we paint focus around all cells */ + if ((flags & GTK_CELL_RENDERER_FOCUSED) != 0 && paint_focus && + !gtk_cell_area_is_activatable (area)) + render_data.focus_all = TRUE; - if (func) - { - info->func = func; - info->data = func_data; - info->destroy = destroy; - } - else - { - info->func = NULL; - info->data = NULL; - info->destroy = NULL; - } - } - else + gtk_cell_area_foreach_alloc (area, context, widget, cell_area, background_area, + (GtkCellAllocCallback)render_cell, &render_data); + + if (render_data.paint_focus && + render_data.focus_rect.width != 0 && + render_data.focus_rect.height != 0) { - info = cell_info_new (func, func_data, destroy); + GtkStyleContext *style_context; + GtkStateFlags renderer_state = 0; - g_hash_table_insert (priv->cell_info, renderer, info); + style_context = gtk_widget_get_style_context (widget); + gtk_style_context_save (style_context); + + renderer_state = gtk_cell_renderer_get_state (NULL, widget, flags); + gtk_style_context_set_state (style_context, renderer_state); + + cairo_save (cr); + + gdk_cairo_rectangle (cr, background_area); + cairo_clip (cr); + + gtk_render_focus (style_context, cr, + render_data.focus_rect.x, render_data.focus_rect.y, + render_data.focus_rect.width, render_data.focus_rect.height); + + gtk_style_context_restore (style_context); + cairo_restore (cr); } } static void -gtk_cell_area_clear_attributes (GtkCellLayout *cell_layout, - GtkCellRenderer *renderer) +apply_cell_attributes (GtkCellRenderer *renderer, + CellInfo *info, + AttributeData *data) { - GtkCellArea *area = GTK_CELL_AREA (cell_layout); - GtkCellAreaPrivate *priv = area->priv; - CellInfo *info; + CellAttribute *attribute; + GSList *list; + GValue value = G_VALUE_INIT; + gboolean is_expander; + gboolean is_expanded; - info = g_hash_table_lookup (priv->cell_info, renderer); + g_object_freeze_notify (G_OBJECT (renderer)); - if (info) + /* 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) { - g_slist_foreach (info->attributes, (GFunc)cell_attribute_free, NULL); - g_slist_free (info->attributes); + attribute = list->data; - info->attributes = NULL; + 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); } -} -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))); + /* Call any GtkCellLayoutDataFunc that may have been set by the user + */ + if (info->func) + info->func (info->proxy ? info->proxy : GTK_CELL_LAYOUT (data->area), renderer, + data->model, data->iter, info->data); + + g_object_thaw_notify (G_OBJECT (renderer)); } static void -accum_cells (GtkCellRenderer *renderer, - GList **accum) +gtk_cell_area_real_apply_attributes (GtkCellArea *area, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gboolean is_expander, + gboolean is_expanded) { - *accum = g_list_prepend (*accum, renderer); -} -static GList * -gtk_cell_area_get_cells (GtkCellLayout *cell_layout) -{ - GList *cells = NULL; + 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 GtkCellAreaContext * +gtk_cell_area_real_create_context (GtkCellArea *area) +{ + g_warning ("GtkCellAreaClass::create_context not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); + + return NULL; +} + +static GtkCellAreaContext * +gtk_cell_area_real_copy_context (GtkCellArea *area, + GtkCellAreaContext *context) +{ + g_warning ("GtkCellAreaClass::copy_context not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); + + return NULL; +} + +static GtkSizeRequestMode +gtk_cell_area_real_get_request_mode (GtkCellArea *area) +{ + /* By default cell areas are height-for-width. */ + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +static void +gtk_cell_area_real_get_preferred_width (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint *minimum_width, + gint *natural_width) +{ + g_warning ("GtkCellAreaClass::get_preferred_width not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); +} + +static void +gtk_cell_area_real_get_preferred_height (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + gint *minimum_height, + gint *natural_height) +{ + g_warning ("GtkCellAreaClass::get_preferred_height not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); +} + +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_height (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 gboolean +get_is_activatable (GtkCellRenderer *renderer, + gboolean *activatable) +{ + + if (gtk_cell_renderer_is_activatable (renderer)) + *activatable = TRUE; + + return *activatable; +} + +static gboolean +gtk_cell_area_real_is_activatable (GtkCellArea *area) +{ + gboolean activatable = 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_foreach (area, (GtkCellCallback)get_is_activatable, &activatable); + + return activatable; +} + +static gboolean +gtk_cell_area_real_activate (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + GtkCellRendererState flags, + gboolean edit_only) +{ + GtkCellAreaPrivate *priv = area->priv; + GdkRectangle renderer_area; + GtkCellRenderer *activate_cell = NULL; + GtkCellRendererMode mode; + + if (priv->focus_cell) + { + g_object_get (priv->focus_cell, "mode", &mode, NULL); + + if (gtk_cell_renderer_get_visible (priv->focus_cell) && + (edit_only ? mode == GTK_CELL_RENDERER_MODE_EDITABLE : + mode != GTK_CELL_RENDERER_MODE_INERT)) + activate_cell = priv->focus_cell; + } + else + { + GList *cells, *l; + + /* GtkTreeView sometimes wants to activate a cell when no + * cells are in focus. + */ + cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area)); + for (l = cells; l && !activate_cell; l = l->next) + { + GtkCellRenderer *renderer = l->data; + + g_object_get (renderer, "mode", &mode, NULL); + + if (gtk_cell_renderer_get_visible (renderer) && + (edit_only ? mode == GTK_CELL_RENDERER_MODE_EDITABLE : + mode != GTK_CELL_RENDERER_MODE_INERT)) + activate_cell = renderer; + } + g_list_free (cells); + } + + if (activate_cell) + { + /* Get the allocation of the focused cell. + */ + gtk_cell_area_get_cell_allocation (area, context, widget, activate_cell, + cell_area, &renderer_area); + + /* Activate or Edit the 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, activate_cell, NULL, + &renderer_area, flags)) + return TRUE; + } + + return FALSE; +} + +static gboolean +gtk_cell_area_real_focus (GtkCellArea *area, + GtkDirectionType direction) +{ + g_warning ("GtkCellAreaClass::focus not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); + 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; + iface->get_area = gtk_cell_area_get_area; +} + +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); + + _gtk_cell_area_set_cell_data_func_with_proxy (area, renderer, (GFunc)func, func_data, destroy, NULL); +} + +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 gboolean +accum_cells (GtkCellRenderer *renderer, + GList **accum) +{ + *accum = g_list_prepend (*accum, renderer); + + return FALSE; +} - gtk_cell_area_forall (GTK_CELL_AREA (cell_layout), - (GtkCellCallback)accum_cells, - &cells); +static GList * +gtk_cell_area_get_cells (GtkCellLayout *cell_layout) +{ + GList *cells = NULL; + + gtk_cell_area_foreach (GTK_CELL_AREA (cell_layout), + (GtkCellCallback)accum_cells, + &cells); return g_list_reverse (cells); } +static GtkCellArea * +gtk_cell_area_get_area (GtkCellLayout *cell_layout) +{ + return GTK_CELL_AREA (cell_layout); +} + +/************************************************************* + * GtkBuildableIface * + *************************************************************/ +static void +gtk_cell_area_buildable_init (GtkBuildableIface *iface) +{ + iface->add_child = _gtk_cell_layout_buildable_add_child; + iface->custom_tag_start = _gtk_cell_layout_buildable_custom_tag_start; + iface->custom_tag_end = gtk_cell_area_buildable_custom_tag_end; +} + +static void +gtk_cell_area_buildable_custom_tag_end (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + gpointer *data) +{ + /* Just ignore the boolean return from here */ + _gtk_cell_layout_buildable_custom_tag_end (buildable, builder, child, tagname, data); +} /************************************************************* * API * @@ -778,133 +1641,183 @@ gtk_cell_area_get_cells (GtkCellLayout *cell_layout) * @renderer: the #GtkCellRenderer to add to @area * * Adds @renderer to @area with the default child cell properties. + * + * Since: 3.0 */ void gtk_cell_area_add (GtkCellArea *area, - GtkCellRenderer *renderer) + 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_GET_CLASS (area)->add (area, renderer); } /** * gtk_cell_area_remove: * @area: a #GtkCellArea - * @renderer: the #GtkCellRenderer to add to @area + * @renderer: the #GtkCellRenderer to remove from @area * * Removes @renderer from @area. + * + * Since: 3.0 */ void gtk_cell_area_remove (GtkCellArea *area, - GtkCellRenderer *renderer) + 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); - if (class->remove) - class->remove (area, renderer); - else - g_warning ("GtkCellAreaClass::remove not implemented for `%s'", - g_type_name (G_TYPE_FROM_INSTANCE (area))); + /* 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); + + GTK_CELL_AREA_GET_CLASS (area)->remove (area, renderer); +} + +static gboolean +get_has_renderer (GtkCellRenderer *renderer, + HasRendererCheck *check) +{ + if (renderer == check->renderer) + check->has_renderer = TRUE; + + return check->has_renderer; +} + +/** + * gtk_cell_area_has_renderer: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer to check + * + * Checks if @area contains @renderer. + * + * Return value: %TRUE if @renderer is in the @area. + * + * Since: 3.0 + */ +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_foreach (area, (GtkCellCallback)get_has_renderer, &check); + + return check.has_renderer; } /** - * gtk_cell_area_forall + * gtk_cell_area_foreach: * @area: a #GtkCellArea - * @callback: the #GtkCellCallback to call + * @callback: (scope call): the #GtkCellCallback to call * @callback_data: user provided data pointer * * Calls @callback for every #GtkCellRenderer in @area. + * + * Since: 3.0 */ void -gtk_cell_area_forall (GtkCellArea *area, - GtkCellCallback callback, - gpointer callback_data) +gtk_cell_area_foreach (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_CLASS (area)->foreach (area, callback, callback_data); } /** - * gtk_cell_area_get_cell_allocation: + * gtk_cell_area_foreach_alloc: * @area: a #GtkCellArea - * @iter: the #GtkCellAreaIter 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 + * @context: the #GtkCellAreaContext for this row of data. + * @widget: the #GtkWidget that @area is rendering to + * @cell_area: the @widget relative coordinates and size for @area + * @background_area: the @widget relative coordinates of the background area + * @callback: (scope call): the #GtkCellAllocCallback to call + * @callback_data: user provided data pointer * - * Derives the allocation of @renderer inside @area if @area - * were to be renderered in @cell_area. + * Calls @callback for every #GtkCellRenderer in @area with the + * allocated rectangle inside @cell_area. + * + * Since: 3.0 */ void -gtk_cell_area_get_cell_allocation (GtkCellArea *area, - GtkCellAreaIter *iter, - GtkWidget *widget, - GtkCellRenderer *renderer, - const GdkRectangle *cell_area, - GdkRectangle *allocation) +gtk_cell_area_foreach_alloc (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + const GdkRectangle *background_area, + GtkCellAllocCallback callback, + gpointer callback_data) { - GtkCellAreaClass *class; - g_return_if_fail (GTK_IS_CELL_AREA (area)); - g_return_if_fail (GTK_IS_CELL_AREA_ITER (iter)); + 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); + g_return_if_fail (callback != NULL); - if (class->get_cell_allocation) - class->get_cell_allocation (area, iter, 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_GET_CLASS (area)->foreach_alloc (area, context, widget, + cell_area, background_area, + callback, callback_data); } +/** + * 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. + * + * Return value: %TRUE if the event was handled by @area. + * + * Since: 3.0 + */ gint gtk_cell_area_event (GtkCellArea *area, - GtkCellAreaIter *iter, - GtkWidget *widget, - GdkEvent *event, - const GdkRectangle *cell_area, - GtkCellRendererState flags) + 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_ITER (iter), 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); @@ -912,107 +1825,311 @@ gtk_cell_area_event (GtkCellArea *area, class = GTK_CELL_AREA_GET_CLASS (area); if (class->event) - return class->event (area, iter, widget, event, cell_area, flags); + 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))); + 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. + * + * Since: 3.0 + */ void gtk_cell_area_render (GtkCellArea *area, - GtkCellAreaIter *iter, - GtkWidget *widget, - cairo_t *cr, - const GdkRectangle *cell_area, - GtkCellRendererState flags) + 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_ITER (iter)); + 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, iter, widget, cr, cell_area, flags); + 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))); + g_warning ("GtkCellAreaClass::render not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); } -/************************************************************* - * API: Geometry * - *************************************************************/ -GtkCellAreaIter * -gtk_cell_area_create_iter (GtkCellArea *area) +static gboolean +get_cell_allocation (GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + const GdkRectangle *cell_background, + RendererAllocationData *data) { - GtkCellAreaClass *class; + if (data->renderer == renderer) + data->allocation = *cell_area; - g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + return (data->renderer == renderer); +} - class = GTK_CELL_AREA_GET_CLASS (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: (out): where to store the allocation for @renderer + * + * Derives the allocation of @renderer inside @area if @area + * were to be renderered in @cell_area. + * + * Since: 3.0 + */ +void +gtk_cell_area_get_cell_allocation (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + GdkRectangle *allocation) +{ + RendererAllocationData data = { renderer, { 0, } }; - if (class->create_iter) - return class->create_iter (area); + 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); - g_warning ("GtkCellAreaClass::create_iter not implemented for `%s'", - g_type_name (G_TYPE_FROM_INSTANCE (area))); - - return NULL; + gtk_cell_area_foreach_alloc (area, context, widget, cell_area, cell_area, + (GtkCellAllocCallback)get_cell_allocation, &data); + + *allocation = data.allocation; } +static gboolean +get_cell_by_position (GtkCellRenderer *renderer, + const GdkRectangle *cell_area, + const GdkRectangle *cell_background, + CellByPositionData *data) +{ + if (data->x >= cell_area->x && data->x < cell_area->x + cell_area->width && + data->y >= cell_area->y && data->y < cell_area->y + cell_area->height) + { + data->renderer = renderer; + data->cell_area = *cell_area; + } -GtkSizeRequestMode -gtk_cell_area_get_request_mode (GtkCellArea *area) + return (data->renderer != NULL); +} + +/** + * gtk_cell_area_get_cell_at_position: + * @area: a #GtkCellArea + * @context: the #GtkCellAreaContext used to hold sizes for @area. + * @widget: the #GtkWidget that @area is rendering on + * @cell_area: the whole allocated area for @area in @widget + * for this row + * @x: the x position + * @y: the y position + * @alloc_area: (out) (allow-none): where to store the inner allocated area of the + * returned cell renderer, or %NULL. + * + * Gets the #GtkCellRenderer at @x and @y coordinates inside @area and optionally + * returns the full cell allocation for it inside @cell_area. + * + * Return value: (transfer none): the #GtkCellRenderer at @x and @y. + * + * Since: 3.0 + */ +GtkCellRenderer * +gtk_cell_area_get_cell_at_position (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + gint x, + gint y, + GdkRectangle *alloc_area) { - GtkCellAreaClass *class; + CellByPositionData data = { x, y, NULL, { 0, } }; - g_return_val_if_fail (GTK_IS_CELL_AREA (area), - GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH); + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + g_return_val_if_fail (GTK_IS_CELL_AREA_CONTEXT (context), NULL); + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + g_return_val_if_fail (cell_area != NULL, NULL); + g_return_val_if_fail (x >= cell_area->x && x <= cell_area->x + cell_area->width, NULL); + g_return_val_if_fail (y >= cell_area->y && y <= cell_area->y + cell_area->height, NULL); - class = GTK_CELL_AREA_GET_CLASS (area); + gtk_cell_area_foreach_alloc (area, context, widget, cell_area, cell_area, + (GtkCellAllocCallback)get_cell_by_position, &data); - if (class->get_request_mode) - return class->get_request_mode (area); + if (alloc_area) + *alloc_area = data.cell_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; + return data.renderer; +} + +/************************************************************* + * 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). + * + * Return value: (transfer full): a newly created #GtkCellAreaContext which can be used with @area. + * + * Since: 3.0 + */ +GtkCellAreaContext * +gtk_cell_area_create_context (GtkCellArea *area) +{ + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + + return GTK_CELL_AREA_GET_CLASS (area)->create_context (area); +} + +/** + * gtk_cell_area_copy_context: + * @area: a #GtkCellArea + * @context: the #GtkCellAreaContext to copy + * + * This is sometimes needed for cases where rows need to share + * alignments in one orientation but may be separately grouped + * in the opposing orientation. + * + * For instance, #GtkIconView creates all icons (rows) to have + * the same width and the cells theirin to have the same + * horizontal alignments. However each row of icons may have + * a separate collective height. #GtkIconView uses this to + * request the heights of each row based on a context which + * was already used to request all the row widths that are + * to be displayed. + * + * Return value: (transfer full): a newly created #GtkCellAreaContext copy of @context. + * + * Since: 3.0 + */ +GtkCellAreaContext * +gtk_cell_area_copy_context (GtkCellArea *area, + GtkCellAreaContext *context) +{ + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + g_return_val_if_fail (GTK_IS_CELL_AREA_CONTEXT (context), NULL); + + return GTK_CELL_AREA_GET_CLASS (area)->copy_context (area, context); +} + +/** + * 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. + * + * Return value: The #GtkSizeRequestMode preferred by @area. + * + * Since: 3.0 + */ +GtkSizeRequestMode +gtk_cell_area_get_request_mode (GtkCellArea *area) +{ + g_return_val_if_fail (GTK_IS_CELL_AREA (area), + GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH); + + return GTK_CELL_AREA_GET_CLASS (area)->get_request_mode (area); } +/** + * 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, it's not important + * to check the @minimum_width and @natural_width of this call but rather to + * consult gtk_cell_area_context_get_preferred_width() after a series of + * requests. + * + * Since: 3.0 + */ void gtk_cell_area_get_preferred_width (GtkCellArea *area, - GtkCellAreaIter *iter, - GtkWidget *widget, - gint *minimum_size, - gint *natural_size) + 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, iter, widget, minimum_size, natural_size); - else - g_warning ("GtkCellAreaClass::get_preferred_width not implemented for `%s'", - g_type_name (G_TYPE_FROM_INSTANCE (area))); + GTK_CELL_AREA_GET_CLASS (area)->get_preferred_width (area, context, widget, + minimum_width, natural_width); } +/** + * 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 width of the requested rows checked again with + * gtk_cell_area_context_get_preferred_width(). + * + * Since: 3.0 + */ void gtk_cell_area_get_preferred_height_for_width (GtkCellArea *area, - GtkCellAreaIter *iter, - GtkWidget *widget, - gint width, - gint *minimum_height, - gint *natural_height) + GtkCellAreaContext *context, + GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height) { GtkCellAreaClass *class; @@ -1020,37 +2137,75 @@ gtk_cell_area_get_preferred_height_for_width (GtkCellArea *area, g_return_if_fail (GTK_IS_WIDGET (widget)); class = GTK_CELL_AREA_GET_CLASS (area); - class->get_preferred_height_for_width (area, iter, widget, width, minimum_height, natural_height); + 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, it's not important + * to check the @minimum_height and @natural_height of this call but rather to + * consult gtk_cell_area_context_get_preferred_height() after a series of + * requests. + * + * Since: 3.0 + */ void gtk_cell_area_get_preferred_height (GtkCellArea *area, - GtkCellAreaIter *iter, - GtkWidget *widget, - gint *minimum_size, - gint *natural_size) + 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, iter, widget, minimum_size, natural_size); - else - g_warning ("GtkCellAreaClass::get_preferred_height not implemented for `%s'", - g_type_name (G_TYPE_FROM_INSTANCE (area))); + GTK_CELL_AREA_GET_CLASS (area)->get_preferred_height (area, context, widget, + minimum_height, natural_height); } +/** + * 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 height of a single row changes, it should be + * requested with gtk_cell_area_get_preferred_height() again and then + * the full height of the requested rows checked again with + * gtk_cell_area_context_get_preferred_height(). + * + * Since: 3.0 + */ void gtk_cell_area_get_preferred_width_for_height (GtkCellArea *area, - GtkCellAreaIter *iter, - GtkWidget *widget, - gint height, - gint *minimum_width, - gint *natural_width) + GtkCellAreaContext *context, + GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width) { GtkCellAreaClass *class; @@ -1058,7 +2213,7 @@ gtk_cell_area_get_preferred_width_for_height (GtkCellArea *area, g_return_if_fail (GTK_IS_WIDGET (widget)); class = GTK_CELL_AREA_GET_CLASS (area); - class->get_preferred_width_for_height (area, iter, widget, height, minimum_width, natural_width); + class->get_preferred_width_for_height (area, context, widget, height, minimum_width, natural_width); } /************************************************************* @@ -1074,13 +2229,15 @@ gtk_cell_area_get_preferred_width_for_height (GtkCellArea *area, * * Connects an @attribute to apply values from @column for the * #GtkTreeModel in use. + * + * Since: 3.0 */ void gtk_cell_area_attribute_connect (GtkCellArea *area, - GtkCellRenderer *renderer, - const gchar *attribute, - gint column) -{ + GtkCellRenderer *renderer, + const gchar *attribute, + gint column) +{ GtkCellAreaPrivate *priv; CellInfo *info; CellAttribute *cell_attribute; @@ -1088,6 +2245,7 @@ gtk_cell_area_attribute_connect (GtkCellArea *area, 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); @@ -1104,17 +2262,17 @@ gtk_cell_area_attribute_connect (GtkCellArea *area, /* 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; - } + (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_OBJECT_TYPE_NAME (renderer), + attribute, cell_attribute->column); + return; + } } cell_attribute = cell_attribute_new (renderer, attribute, column); @@ -1122,9 +2280,9 @@ gtk_cell_area_attribute_connect (GtkCellArea *area, 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))); + "since attribute does not exist", + attribute, + G_OBJECT_TYPE_NAME (renderer)); return; } @@ -1140,11 +2298,13 @@ gtk_cell_area_attribute_connect (GtkCellArea *area, * Disconnects @attribute for the @renderer in @area so that * attribute will no longer be updated with values from the * model. + * + * Since: 3.0 */ -void +void gtk_cell_area_attribute_disconnect (GtkCellArea *area, - GtkCellRenderer *renderer, - const gchar *attribute) + GtkCellRenderer *renderer, + const gchar *attribute) { GtkCellAreaPrivate *priv; CellInfo *info; @@ -1154,6 +2314,7 @@ gtk_cell_area_attribute_disconnect (GtkCellArea *area, 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); @@ -1161,107 +2322,45 @@ gtk_cell_area_attribute_disconnect (GtkCellArea *area, if (info) { node = g_slist_find_custom (info->attributes, attribute, - (GCompareFunc)cell_attribute_find); + (GCompareFunc)cell_attribute_find); if (node) - { - cell_attribute = node->data; - - cell_attribute_free (cell_attribute); - - info->attributes = g_slist_delete_link (info->attributes, node); - } - } -} - -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); + { + cell_attribute = node->data; - /* Apply the attributes directly to the renderer */ - for (list = info->attributes; list; list = list->next) - { - attribute = list->data; + cell_attribute_free (cell_attribute); - 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); + info->attributes = g_slist_delete_link (info->attributes, node); + } } - - /* 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)); } /** - * gtk_cell_area_apply_attributes + * gtk_cell_area_apply_attributes: * @area: a #GtkCellArea - * @tree_model: a #GtkTreeModel to pull values from + * @tree_model: the #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 + * Applies any connected attributes to the renderers in * @area by pulling the values from @tree_model. + * + * Since: 3.0 */ void gtk_cell_area_apply_attributes (GtkCellArea *area, - GtkTreeModel *tree_model, - GtkTreeIter *iter, - gboolean is_expander, - gboolean is_expanded) + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gboolean is_expander, + gboolean is_expanded) { - GtkCellAreaPrivate *priv; - AttributeData data; - GtkTreePath *path; - 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); - 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); + g_signal_emit (area, cell_area_signals[SIGNAL_APPLY_ATTRIBUTES], 0, + tree_model, iter, is_expander, is_expanded); } /** @@ -1273,6 +2372,12 @@ gtk_cell_area_apply_attributes (GtkCellArea *area, * gtk_cell_area_apply_attributes() is called and can be * used to interact with renderers from #GtkCellArea * subclasses. + * + * Return value: The current #GtkTreePath string for the current + * attributes applied to @area. This string belongs to the area and + * should not be freed. + * + * Since: 3.0 */ const gchar * gtk_cell_area_get_current_path_string (GtkCellArea *area) @@ -1290,10 +2395,20 @@ gtk_cell_area_get_current_path_string (GtkCellArea *area) /************************************************************* * 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. + * + * Since: 3.0 + */ void gtk_cell_area_class_install_cell_property (GtkCellAreaClass *aclass, - guint property_id, - GParamSpec *pspec) + guint property_id, + GParamSpec *pspec) { g_return_if_fail (GTK_IS_CELL_AREA_CLASS (aclass)); g_return_if_fail (G_IS_PARAM_SPEC (pspec)); @@ -1308,7 +2423,7 @@ gtk_cell_area_class_install_cell_property (GtkCellAreaClass *aclass, 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); + G_OBJECT_CLASS_NAME (aclass), pspec->name); return; } g_param_spec_ref (pspec); @@ -1317,22 +2432,47 @@ gtk_cell_area_class_install_cell_property (GtkCellAreaClass *aclass, 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 + * + * Finds a cell property of a cell area class by name. + * + * Return value: (transfer none): the #GParamSpec of the child property + * or %NULL if @aclass has no child property with that name. + * + * Since: 3.0 + */ GParamSpec* gtk_cell_area_class_find_cell_property (GtkCellAreaClass *aclass, - const gchar *property_name) + 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); + 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 all cell properties of a cell area class. + * + * Return value: (array length=n_properties) (transfer container): a newly + * allocated %NULL-terminated array of #GParamSpec*. The array + * must be freed with g_free(). + * + * Since: 3.0 + */ GParamSpec** -gtk_cell_area_class_list_cell_properties (GtkCellAreaClass *aclass, - guint *n_properties) +gtk_cell_area_class_list_cell_properties (GtkCellAreaClass *aclass, + guint *n_properties) { GParamSpec **pspecs; guint n; @@ -1340,19 +2480,32 @@ gtk_cell_area_class_list_cell_properties (GtkCellAreaClass *aclass, 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); + 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 + * @...: 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_cell_set() for more details. + * + * Since: 3.0 + */ void gtk_cell_area_add_with_properties (GtkCellArea *area, - GtkCellRenderer *renderer, - const gchar *first_prop_name, - ...) + GtkCellRenderer *renderer, + const gchar *first_prop_name, + ...) { GtkCellAreaClass *class; @@ -1372,15 +2525,27 @@ gtk_cell_area_add_with_properties (GtkCellArea *area, va_end (var_args); } else - g_warning ("GtkCellAreaClass::add not implemented for `%s'", - g_type_name (G_TYPE_FROM_INSTANCE (area))); + 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 + * @...: a %NULL-terminated list of property names and values, starting + * with @first_prop_name + * + * Sets one or more cell properties for @cell in @area. + * + * Since: 3.0 + */ void gtk_cell_area_cell_set (GtkCellArea *area, - GtkCellRenderer *renderer, - const gchar *first_prop_name, - ...) + GtkCellRenderer *renderer, + const gchar *first_prop_name, + ...) { va_list var_args; @@ -1392,11 +2557,23 @@ gtk_cell_area_cell_set (GtkCellArea *area, 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 + * @...: 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. + * + * Since: 3.0 + */ void gtk_cell_area_cell_get (GtkCellArea *area, - GtkCellRenderer *renderer, - const gchar *first_prop_name, - ...) + GtkCellRenderer *renderer, + const gchar *first_prop_name, + ...) { va_list var_args; @@ -1410,40 +2587,40 @@ gtk_cell_area_cell_get (GtkCellArea *area, static inline void area_get_cell_property (GtkCellArea *area, - GtkCellRenderer *renderer, - GParamSpec *pspec, - GValue *value) + 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) + GtkCellRenderer *renderer, + GParamSpec *pspec, + const GValue *value) { - GValue tmp_value = { 0, }; + GValue tmp_value = G_VALUE_INIT; 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)); + 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))); + contents, + G_VALUE_TYPE_NAME (value), + pspec->name, + g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec))); g_free (contents); } else @@ -1453,11 +2630,23 @@ area_set_cell_property (GtkCellArea *area, 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. + * + * Since: 3.0 + */ void gtk_cell_area_cell_set_valist (GtkCellArea *area, - GtkCellRenderer *renderer, - const gchar *first_property_name, - va_list var_args) + GtkCellRenderer *renderer, + const gchar *first_property_name, + va_list var_args) { const gchar *name; @@ -1467,47 +2656,59 @@ gtk_cell_area_cell_set_valist (GtkCellArea *area, name = first_property_name; while (name) { - GValue value = { 0, }; + GValue value = G_VALUE_INIT; gchar *error = NULL; - GParamSpec *pspec = - g_param_spec_pool_lookup (cell_property_pool, name, - G_OBJECT_TYPE (area), TRUE); + 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; - } + { + 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); + { + 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_COLLECT_INIT (&value, G_PARAM_SPEC_VALUE_TYPE (pspec), + 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; - } + { + 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. + * + * Since: 3.0 + */ void gtk_cell_area_cell_get_valist (GtkCellArea *area, - GtkCellRenderer *renderer, - const gchar *first_property_name, - va_list var_args) + GtkCellRenderer *renderer, + const gchar *first_property_name, + va_list var_args) { const gchar *name; @@ -1517,45 +2718,56 @@ gtk_cell_area_cell_get_valist (GtkCellArea *area, name = first_property_name; while (name) { - GValue value = { 0, }; + GValue value = G_VALUE_INIT; GParamSpec *pspec; gchar *error; pspec = g_param_spec_pool_lookup (cell_property_pool, name, - G_OBJECT_TYPE (area), TRUE); + 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; - } + { + 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_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_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. + * + * Since: 3.0 + */ void gtk_cell_area_cell_set_property (GtkCellArea *area, - GtkCellRenderer *renderer, - const gchar *property_name, - const GValue *value) + GtkCellRenderer *renderer, + const gchar *property_name, + const GValue *value) { GParamSpec *pspec; @@ -1563,26 +2775,37 @@ gtk_cell_area_cell_set_property (GtkCellArea *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); + 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); + 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)); + 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. + * + * Since: 3.0 + */ void gtk_cell_area_cell_get_property (GtkCellArea *area, - GtkCellRenderer *renderer, - const gchar *property_name, - GValue *value) + GtkCellRenderer *renderer, + const gchar *property_name, + GValue *value) { GParamSpec *pspec; @@ -1590,244 +2813,449 @@ gtk_cell_area_cell_get_property (GtkCellArea *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); + 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); + 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)); + G_STRLOC, pspec->name, G_OBJECT_TYPE_NAME (area)); else { - GValue *prop_value, tmp_value = { 0, }; + GValue *prop_value, tmp_value = G_VALUE_INIT; /* 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; - } + { + 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; - } + { + 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; - } + { + 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); - } + { + g_value_transform (prop_value, value); + g_value_unset (&tmp_value); + } } } -/************************************************************* - * API: Focus * - *************************************************************/ +/************************************************************* + * API: Focus * + *************************************************************/ + +/** + * gtk_cell_area_is_activatable: + * @area: a #GtkCellArea + * + * Returns whether the area can do anything when activated, + * after applying new attributes to @area. + * + * Return value: whether @area can do anything when activated. + * + * Since: 3.0 + */ +gboolean +gtk_cell_area_is_activatable (GtkCellArea *area) +{ + g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE); + + return GTK_CELL_AREA_GET_CLASS (area)->is_activatable (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 its own way particular + * to how it lays out cells. + * + * Return value: %TRUE if focus remains inside @area as a result of this call. + * + * Since: 3.0 + */ +gboolean +gtk_cell_area_focus (GtkCellArea *area, + GtkDirectionType direction) +{ + g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE); + + return GTK_CELL_AREA_GET_CLASS (area)->focus (area, direction); +} + +/** + * 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. + * @edit_only: if %TRUE then only cell renderers that are %GTK_CELL_RENDERER_MODE_EDITABLE + * will be activated. + * + * 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. + * + * Return value: Whether @area was successfully activated. + * + * Since: 3.0 + */ +gboolean +gtk_cell_area_activate (GtkCellArea *area, + GtkCellAreaContext *context, + GtkWidget *widget, + const GdkRectangle *cell_area, + GtkCellRendererState flags, + gboolean edit_only) +{ + 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, edit_only); +} + + +/** + * gtk_cell_area_set_focus_cell: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer to give focus to + * + * Explicitly sets the currently focused cell to @renderer. + * + * This is generally called by implementations of + * #GtkCellAreaClass.focus() or #GtkCellAreaClass.event(), + * however it can also be used to implement functions such + * as gtk_tree_view_set_cursor_on_cell(). + * + * Since: 3.0 + */ +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_grab_focus: + * gtk_cell_area_get_focus_cell: * @area: a #GtkCellArea - * @direction: the #GtkDirectionType from which focus came * - * This should be called by the @area's owning layout widget - * when focus should be passed to @area for a given row data. + * Retrieves the currently focused cell for @area * - * Note that after applying new attributes for @area that - * gtk_cell_area_update_focus() should be called and - * gtk_cell_area_can_focus() should be checked before trying - * to pass focus to @area. + * Return value: (transfer none): the currently focused cell in @area. * - * Implementing #GtkCellArea classes should implement this - * method to receive focus in it's own way particular to - * how it lays out cells. + * Since: 3.0 */ -void -gtk_cell_area_grab_focus (GtkCellArea *area, - GtkDirectionType direction) +GtkCellRenderer * +gtk_cell_area_get_focus_cell (GtkCellArea *area) { - GtkCellAreaClass *class; + GtkCellAreaPrivate *priv; - g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); - class = GTK_CELL_AREA_GET_CLASS (area); + priv = area->priv; - if (class->grab_focus) - class->grab_focus (area, direction); - else - g_warning ("GtkCellAreaClass::grab_focus not implemented for `%s'", - g_type_name (G_TYPE_FROM_INSTANCE (area))); + return priv->focus_cell; } -/** - * gtk_cell_area_focus_leave: - * @area: a #GtkCellArea - * @direction: the #GtkDirectionType in which focus - * is to leave @area - * @path: the current #GtkTreePath string for the - * event which was handled by @area - * - * Notifies that focus is to leave @area in the - * given @direction. - * - * This is called by #GtkCellArea implementations upon - * handling a key event that caused focus to leave the - * cell. The resulting signal can be handled by the - * owning layouting widget to decide which new @area - * to pass focus to and from what @direction. Or to - * pass focus along to an entirely new data row. - */ -void -gtk_cell_area_focus_leave (GtkCellArea *area, - GtkDirectionType direction, - const gchar *path) -{ - g_return_if_fail (GTK_IS_CELL_AREA (area)); - g_signal_emit (area, cell_area_signals[SIGNAL_FOCUS_LEAVE], 0, direction, path); -} +/************************************************************* + * API: Focus Siblings * + *************************************************************/ /** - * gtk_cell_area_update_focus: + * 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 * - * Updates focus information on @area for a given - * row of data. + * Adds @sibling to @renderer's focusable area, focus will be drawn + * around @renderer and all of its siblings if @renderer can + * focus for a given row. * - * After calling gtk_cell_area_apply_attributes() to - * the @area this method should be called to update - * information about whether the @area can focus and - * which is the cell currently in focus. + * Events handled by focus siblings can also activate the given + * focusable @renderer. + * + * Since: 3.0 */ void -gtk_cell_area_update_focus (GtkCellArea *area) +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); - GTK_CELL_AREA_GET_CLASS (area)->update_focus (area); + 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_set_can_focus: + * gtk_cell_area_remove_focus_sibling: * @area: a #GtkCellArea - * @can_focus: whether @area can receive focus + * @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()). * - * This is generally called from GtkCellArea::update_focus() - * implementations to update if the @area can focus after - * applying new row data attributes. + * Since: 3.0 */ void -gtk_cell_area_set_can_focus (GtkCellArea *area, - gboolean can_focus) +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; - if (priv->can_focus != can_focus) - { - priv->can_focus = can_focus; - } + 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_get_can_focus: + * 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 whether the area can receive keyboard focus, - * after applying new attributes to @area, - * gtk_cell_area_update_focus() needs to be called before - * calling this method. + * Returns whether @sibling is one of @renderer's focus siblings + * (see gtk_cell_area_add_focus_sibling()). * - * Returns: whether @area can receive focus. + * Return value: %TRUE if @sibling is a focus sibling of @renderer + * + * Since: 3.0 */ gboolean -gtk_cell_area_get_can_focus (GtkCellArea *area) +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; - return priv->can_focus; -} + 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_set_focus_cell: + * gtk_cell_area_get_focus_siblings: * @area: a #GtkCellArea - * @focus_cell: the #GtkCellRenderer to give focus to + * @renderer: the #GtkCellRenderer expected to have focus + * + * Gets the focus sibling cell renderers for @renderer. * - * 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. + * Return value: (element-type GtkCellRenderer) (transfer none): A #GList of #GtkCellRenderers. + * The returned list is internal and should not be freed. + * + * Since: 3.0 */ -void -gtk_cell_area_set_focus_cell (GtkCellArea *area, - GtkCellRenderer *renderer) +const GList * +gtk_cell_area_get_focus_siblings (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)); + 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; - 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"); - } + return g_hash_table_lookup (priv->focus_siblings, renderer); } /** - * gtk_cell_area_get_focus_cell: + * gtk_cell_area_get_focus_from_sibling: * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer * - * Retrieves the currently focused cell for @area + * 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. + * + * Return value: (transfer none): the #GtkCellRenderer for which @renderer + * is a sibling, or %NULL. * - * Returns: the currently focused cell in @area. + * Since: 3.0 */ GtkCellRenderer * -gtk_cell_area_get_focus_cell (GtkCellArea *area) +gtk_cell_area_get_focus_from_sibling (GtkCellArea *area, + GtkCellRenderer *renderer) { - GtkCellAreaPrivate *priv; + 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); - priv = area->priv; + renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (area)); - return priv->focus_cell; + 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; } -void +/************************************************************* + * API: Cell Activation/Editing * + *************************************************************/ +static void +gtk_cell_area_add_editable (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellEditable *editable, + const 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) + GtkCellRenderer *renderer) { GtkCellAreaPrivate *priv; @@ -1839,121 +3267,217 @@ gtk_cell_area_set_edited_cell (GtkCellArea *area, if (priv->edited_cell != renderer) { if (priv->edited_cell) - g_object_unref (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_ref (priv->edited_cell); g_object_notify (G_OBJECT (area), "edited-cell"); } } -GtkCellRenderer * -gtk_cell_area_get_edited_cell (GtkCellArea *area) +static void +gtk_cell_area_set_edit_widget (GtkCellArea *area, + GtkCellEditable *editable) { GtkCellAreaPrivate *priv; - g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (editable == NULL || GTK_IS_CELL_EDITABLE (editable)); priv = area->priv; - return priv->edited_cell; -} - -/************************************************************* - * API: Margins * - *************************************************************/ -gint -gtk_cell_area_get_cell_margin_left (GtkCellArea *area) -{ - g_return_val_if_fail (GTK_IS_CELL_AREA (area), 0); - - return area->priv->cell_border.left; -} + if (priv->edit_widget != editable) + { + if (priv->edit_widget) + { + g_signal_handler_disconnect (priv->edit_widget, priv->remove_widget_id); -void -gtk_cell_area_set_cell_margin_left (GtkCellArea *area, - gint margin) -{ - GtkCellAreaPrivate *priv; + g_object_unref (priv->edit_widget); + } - g_return_if_fail (GTK_IS_CELL_AREA (area)); + priv->edit_widget = editable; - priv = area->priv; + if (priv->edit_widget) + { + priv->remove_widget_id = + g_signal_connect (priv->edit_widget, "remove-widget", + G_CALLBACK (cell_area_remove_widget_cb), area); - if (priv->cell_border.left != margin) - { - priv->cell_border.left = margin; + g_object_ref (priv->edit_widget); + } - g_object_notify (G_OBJECT (area), "margin-left"); + g_object_notify (G_OBJECT (area), "edit-widget"); } } -gint -gtk_cell_area_get_cell_margin_right (GtkCellArea *area) -{ - g_return_val_if_fail (GTK_IS_CELL_AREA (area), 0); - - return area->priv->cell_border.right; -} - -void -gtk_cell_area_set_cell_margin_right (GtkCellArea *area, - gint margin) +/** + * gtk_cell_area_get_edited_cell: + * @area: a #GtkCellArea + * + * Gets the #GtkCellRenderer in @area that is currently + * being edited. + * + * Return value: (transfer none): The currently edited #GtkCellRenderer + * + * Since: 3.0 + */ +GtkCellRenderer * +gtk_cell_area_get_edited_cell (GtkCellArea *area) { GtkCellAreaPrivate *priv; - g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); priv = area->priv; - if (priv->cell_border.right != margin) - { - priv->cell_border.right = margin; - - g_object_notify (G_OBJECT (area), "margin-right"); - } + return priv->edited_cell; } -gint -gtk_cell_area_get_cell_margin_top (GtkCellArea *area) +/** + * gtk_cell_area_get_edit_widget: + * @area: a #GtkCellArea + * + * Gets the #GtkCellEditable widget currently used + * to edit the currently edited cell. + * + * Return value: (transfer none): The currently active #GtkCellEditable widget + * + * Since: 3.0 + */ +GtkCellEditable * +gtk_cell_area_get_edit_widget (GtkCellArea *area) { - g_return_val_if_fail (GTK_IS_CELL_AREA (area), 0); + GtkCellAreaPrivate *priv; + + g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL); + + priv = area->priv; - return area->priv->cell_border.top; + return priv->edit_widget; } -void -gtk_cell_area_set_cell_margin_top (GtkCellArea *area, - gint margin) -{ +/** + * 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 its own GtkCellArea->activate() + * implementation. + * + * Return value: whether cell activation was successful + * + * Since: 3.0 + */ +gboolean +gtk_cell_area_activate_cell (GtkCellArea *area, + GtkWidget *widget, + GtkCellRenderer *renderer, + GdkEvent *event, + const GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GtkCellRendererMode mode; GtkCellAreaPrivate *priv; - g_return_if_fail (GTK_IS_CELL_AREA (area)); + 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; - if (priv->cell_border.top != margin) - { - priv->cell_border.top = margin; + if (!gtk_cell_renderer_get_sensitive (renderer)) + return FALSE; - g_object_notify (G_OBJECT (area), "margin-top"); - } -} + g_object_get (renderer, "mode", &mode, NULL); -gint -gtk_cell_area_get_cell_margin_bottom (GtkCellArea *area) -{ - g_return_val_if_fail (GTK_IS_CELL_AREA (area), 0); + if (mode == GTK_CELL_RENDERER_MODE_ACTIVATABLE) + { + if (gtk_cell_renderer_activate (renderer, + event, widget, + priv->current_path, + cell_area, + cell_area, + flags)) + return TRUE; + } + else if (mode == GTK_CELL_RENDERER_MODE_EDITABLE) + { + GtkCellEditable *editable_widget; + GdkRectangle inner_area; + + gtk_cell_area_inner_cell_area (area, widget, cell_area, &inner_area); + + editable_widget = + gtk_cell_renderer_start_editing (renderer, + event, widget, + priv->current_path, + &inner_area, + &inner_area, + flags); + + if (editable_widget != NULL) + { + 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); + + /* 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, cell_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 area->priv->cell_border.bottom; + return FALSE; } +/** + * gtk_cell_area_stop_editing: + * @area: a #GtkCellArea + * @canceled: whether editing was canceled. + * + * Explicitly stops the editing of the currently edited cell. + * + * If @canceled is %TRUE, the currently edited cell renderer + * will emit the ::editing-canceled signal, otherwise the + * the ::editing-done signal will be emitted on the current + * edit widget. + * + * See gtk_cell_area_get_edited_cell() and gtk_cell_area_get_edit_widget(). + * + * Since: 3.0 + */ void -gtk_cell_area_set_cell_margin_bottom (GtkCellArea *area, - gint margin) +gtk_cell_area_stop_editing (GtkCellArea *area, + gboolean canceled) { GtkCellAreaPrivate *priv; @@ -1961,61 +3485,104 @@ gtk_cell_area_set_cell_margin_bottom (GtkCellArea *area, priv = area->priv; - if (priv->cell_border.bottom != margin) + if (priv->edited_cell) { - priv->cell_border.bottom = margin; + GtkCellEditable *edit_widget = g_object_ref (priv->edit_widget); + GtkCellRenderer *edit_cell = g_object_ref (priv->edited_cell); - g_object_notify (G_OBJECT (area), "margin-bottom"); - } -} - -/* For convenience in area implementations */ -void -gtk_cell_area_editing_started (GtkCellArea *area, - GtkCellRenderer *renderer, - GtkCellEditable *editable) -{ - GtkCellAreaPrivate *priv; + /* Stop editing of the cell renderer */ + gtk_cell_renderer_stop_editing (priv->edited_cell, canceled); - g_return_if_fail (GTK_IS_CELL_AREA (area)); + /* When editing is explicitly halted either + * the "editing-canceled" signal is emitted on the cell + * renderer or the "editing-done" signal on the GtkCellEditable widget + */ + if (!canceled) + gtk_cell_editable_editing_done (edit_widget); - priv = area->priv; + /* Remove any references to the editable widget */ + gtk_cell_area_set_edited_cell (area, NULL); + gtk_cell_area_set_edit_widget (area, NULL); - g_signal_emit (area, cell_area_signals[SIGNAL_EDITING_STARTED], 0, - renderer, editable, priv->current_path); + /* 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 * + *************************************************************/ + +/** + * gtk_cell_area_inner_cell_area: + * @area: a #GtkCellArea + * @widget: the #GtkWidget that @area is rendering onto + * @cell_area: the @widget relative coordinates where one of @area's cells + * is to be placed + * @inner_area: (out): the return location for the inner cell area + * + * This is a convenience function for #GtkCellArea implementations + * to get the inner area where a given #GtkCellRenderer will be + * rendered. It removes any padding previously added by gtk_cell_area_request_renderer(). + * + * Since: 3.0 + */ void gtk_cell_area_inner_cell_area (GtkCellArea *area, - GdkRectangle *background_area, - GdkRectangle *cell_area) + GtkWidget *widget, + const GdkRectangle *cell_area, + GdkRectangle *inner_area) { - GtkCellAreaPrivate *priv; + gint focus_line_width; g_return_if_fail (GTK_IS_CELL_AREA (area)); - g_return_if_fail (background_area != NULL); + g_return_if_fail (GTK_IS_WIDGET (widget)); g_return_if_fail (cell_area != NULL); + g_return_if_fail (inner_area != NULL); - priv = area->priv; + gtk_widget_style_get (widget, "focus-line-width", &focus_line_width, NULL); - *cell_area = *background_area; + *inner_area = *cell_area; - cell_area->x += priv->cell_border.left; - cell_area->width -= (priv->cell_border.left + priv->cell_border.right); - cell_area->y += priv->cell_border.top; - cell_area->height -= (priv->cell_border.top + priv->cell_border.bottom); + 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; } +/** + * gtk_cell_area_request_renderer: + * @area: a #GtkCellArea + * @renderer: the #GtkCellRenderer to request size for + * @orientation: the #GtkOrientation in which to request size + * @widget: the #GtkWidget that @area is rendering onto + * @for_size: the allocation contextual size to request for, or -1 if + * the base request for the orientation is to be returned. + * @minimum_size: (out) (allow-none): location to store the minimum size, or %NULL + * @natural_size: (out) (allow-none): location to store the natural size, or %NULL + * + * This is a convenience function for #GtkCellArea implementations + * to request size for cell renderers. It's important to use this + * function to request size and then use gtk_cell_area_inner_cell_area() + * at render and event time since this function will add padding + * around the cell for focus painting. + * + * Since: 3.0 + */ void gtk_cell_area_request_renderer (GtkCellArea *area, - GtkCellRenderer *renderer, - GtkOrientation orientation, - GtkWidget *widget, - gint for_size, - gint *minimum_size, - gint *natural_size) + 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)); @@ -2023,36 +3590,86 @@ gtk_cell_area_request_renderer (GtkCellArea *area, 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); + gtk_cell_renderer_get_preferred_width (renderer, widget, minimum_size, natural_size); else - { - for_size = MAX (0, for_size - (priv->cell_border.top + priv->cell_border.bottom)); + { + 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); - } - - *minimum_size += (priv->cell_border.left + priv->cell_border.right); - *natural_size += (priv->cell_border.left + priv->cell_border.right); + 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); + gtk_cell_renderer_get_preferred_height (renderer, widget, minimum_size, natural_size); else - { - for_size = MAX (0, for_size - (priv->cell_border.left + priv->cell_border.right)); + { + 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; +} + +void +_gtk_cell_area_set_cell_data_func_with_proxy (GtkCellArea *area, + GtkCellRenderer *cell, + GFunc func, + gpointer func_data, + GDestroyNotify destroy, + gpointer proxy) +{ + GtkCellAreaPrivate *priv; + CellInfo *info; + + g_return_if_fail (GTK_IS_CELL_AREA (area)); + g_return_if_fail (GTK_IS_CELL_RENDERER (cell)); + + priv = area->priv; + + info = g_hash_table_lookup (priv->cell_info, cell); + + /* Note we do not take a reference to the proxy, the proxy is a GtkCellLayout + * that is forwarding its implementation to a delegate GtkCellArea therefore + * its life-cycle is longer than the area's life cycle. + */ + if (info) + { + if (info->destroy && info->data) + info->destroy (info->data); - gtk_cell_renderer_get_preferred_height_for_width (renderer, widget, for_size, - minimum_size, natural_size); + if (func) + { + info->func = (GtkCellLayoutDataFunc)func; + info->data = func_data; + info->destroy = destroy; + info->proxy = proxy; + } + else + { + info->func = NULL; + info->data = NULL; + info->destroy = NULL; + info->proxy = NULL; } + } + else + { + info = cell_info_new ((GtkCellLayoutDataFunc)func, func_data, destroy); + info->proxy = proxy; - *minimum_size += (priv->cell_border.top + priv->cell_border.bottom); - *natural_size += (priv->cell_border.top + priv->cell_border.bottom); + g_hash_table_insert (priv->cell_info, cell, info); } }