diff --git a/gtk/gtkcellarea.c b/gtk/gtkcellarea.c index e68fb653df..f170786da7 100644 --- a/gtk/gtkcellarea.c +++ b/gtk/gtkcellarea.c @@ -21,6 +21,289 @@ * Boston, MA 02111-1307, USA. */ +/** + * SECTION:gtkcellarea + * @Short_Description: An abstract class for laying out #GtkCellRenderers + * @Title: GtkCellArea + * + * The #GtkCellArea is an abstract class for laying out #GtkCellRenderers + * onto a given area of a #GtkWidget. + * + * The work of rendering #GtkCellRenderers can be very complicated; it involves + * requesting size for cells, driving keyboard focus from cell to cell, rendering + * the actual cells, painting the focus onto the currently focused cell and finally + * activating cells which are %GTK_CELL_RENDERER_MODE_ACTIVATABLE and editing cells + * which are %GTK_CELL_RENDERER_MODE_EDITABLE. The work is even more complex since + * a cell renderer as opposed to a widget, is used to interact with an arbitrary + * number of #GtkTreeModel rows instead of always displaying the same data. + * + * + * Requesting area sizes + * + * As outlined in GtkWidget's + * geometry management section, GTK+ uses a height-for-width + * geometry managemen 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 #GtkCellArea one needs to calculate + * the size of a handful of rows, this will be done differently by + * different #GtkCellLayout widgets. For instance a #GtkTreeViewColumn + * always lines up the areas from top to bottom while a #GtkIconView + * on the other hand might enforce that areas maintain a fixed width + * and then wrap the area around, thus requesting height for more + * areas when allocated less width. + * + * It's also important for #GtkCellAreas to maintain some cell + * alignments with areas rendered for different rows so that + * a handful of rendered rows can allocate the same size for + * a said cell across rows (and also to make sure to request + * an appropriate size for the largest row after requesting + * a hand full of rows). For this reason the #GtkCellArea + * uses a #GtkCellAreaContext object to store the alignments + * and sizes along the way. + * + * 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 hand full 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: + * + * Requesting the width of a hand full of GtkTreeModel rows. + * + * 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 hand full 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 + * treemodel 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 treemodel 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 the scrollbar + * values while more and mode 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 crued example of how to render all the rows at the root level runs as follows: + * + * Requesting the width of a hand full 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 it's minimum + * or natural height or if the model content is expected to fit inside the layouting + * widget with not scrolled window 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. + * + * 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 when displaying rows from top to bottom. + * + * static void + * 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; + * } + * + * + * + * + * + */ + #include "config.h" #include @@ -275,7 +558,6 @@ gtk_cell_area_class_init (GtkCellAreaClass *class) class->activate = gtk_cell_area_real_activate; /* Signals */ - /** * GtkCellArea::apply-attributes: * @area: the #GtkCellArea to apply the attributes to @@ -372,6 +654,11 @@ gtk_cell_area_class_init (GtkCellAreaClass *class) G_TYPE_STRING); /* Properties */ + /** + * GtkCellArea:focus-cell: + * + * The cell in the area that currently has focus + */ g_object_class_install_property (object_class, PROP_FOCUS_CELL, g_param_spec_object @@ -381,6 +668,14 @@ gtk_cell_area_class_init (GtkCellAreaClass *class) 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(). + */ g_object_class_install_property (object_class, PROP_EDITED_CELL, g_param_spec_object @@ -390,6 +685,14 @@ gtk_cell_area_class_init (GtkCellAreaClass *class) 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(). + */ g_object_class_install_property (object_class, PROP_EDIT_WIDGET, g_param_spec_object @@ -989,7 +1292,7 @@ gtk_cell_area_has_renderer (GtkCellArea *area, } /** - * gtk_cell_area_forall + * gtk_cell_area_forall: * @area: a #GtkCellArea * @callback: the #GtkCellCallback to call * @callback_data: user provided data pointer