Made progress on focus handling.

- Added vfunc to get the allocation of a cell inside an area
 - Superclass GtkCellArea handles activation of focused cells
   by handling key events (as well as editing of editable cells)
 - Added signal "editing-started" to GtkCellArea to signal that
   editing has started (generally signaled from inside event handling)
 - Added properties "focus-cell" and "edited-cell"
This commit is contained in:
Tristan Van Berkom 2010-11-05 22:16:32 +09:00
parent 1ad5fa3e7a
commit 631bdc438c
4 changed files with 396 additions and 13 deletions

View File

@ -130,10 +130,21 @@ typedef struct {
struct _GtkCellAreaPrivate
{
/* 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() */
gchar *current_path;
GtkCellRenderer *edited_cell;
GtkCellRenderer *focus_cell;
guint can_focus : 1;
@ -144,11 +155,14 @@ enum {
PROP_CELL_MARGIN_LEFT,
PROP_CELL_MARGIN_RIGHT,
PROP_CELL_MARGIN_TOP,
PROP_CELL_MARGIN_BOTTOM
PROP_CELL_MARGIN_BOTTOM,
PROP_FOCUS_CELL,
PROP_EDITED_CELL
};
enum {
SIGNAL_FOCUS_LEAVE,
SIGNAL_EDITING_STARTED,
LAST_SIGNAL
};
@ -229,6 +243,17 @@ gtk_cell_area_class_init (GtkCellAreaClass *class)
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);
/* Properties */
g_object_class_install_property (object_class,
@ -275,6 +300,24 @@ gtk_cell_area_class_init (GtkCellAreaClass *class)
0,
GTK_PARAM_READWRITE));
g_object_class_install_property (object_class,
PROP_FOCUS_CELL,
g_param_spec_object
("focus-cell",
P_("Focus Cell"),
P_("The cell which currently has focus"),
GTK_TYPE_CELL_RENDERER,
GTK_PARAM_READWRITE));
g_object_class_install_property (object_class,
PROP_EDITED_CELL,
g_param_spec_object
("edited-cell",
P_("Edited Cell"),
P_("The cell which is currently being edited"),
GTK_TYPE_CELL_RENDERER,
GTK_PARAM_READWRITE));
/* Pool for Cell Properties */
if (!cell_property_pool)
cell_property_pool = g_param_spec_pool_new (FALSE);
@ -366,6 +409,8 @@ gtk_cell_area_finalize (GObject *object)
*/
g_hash_table_destroy (priv->cell_info);
g_free (priv->current_path);
G_OBJECT_CLASS (gtk_cell_area_parent_class)->finalize (object);
}
@ -379,8 +424,9 @@ gtk_cell_area_dispose (GObject *object)
*/
gtk_cell_layout_clear (GTK_CELL_LAYOUT (object));
/* Remove any ref to a focused cell */
/* 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);
G_OBJECT_CLASS (gtk_cell_area_parent_class)->dispose (object);
}
@ -407,6 +453,9 @@ gtk_cell_area_set_property (GObject *object,
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;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -436,6 +485,9 @@ gtk_cell_area_get_property (GObject *object,
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;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -453,16 +505,73 @@ gtk_cell_area_real_event (GtkCellArea *area,
const GdkRectangle *cell_area,
GtkCellRendererState flags)
{
if (event->type == GDK_KEY_PRESS)
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)
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 of Edit the currently focused cell */
/* 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);
return TRUE;
/* 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;
}
}
}
}
@ -662,6 +771,14 @@ gtk_cell_area_get_cells (GtkCellLayout *cell_layout)
/*************************************************************
* API *
*************************************************************/
/**
* gtk_cell_area_add:
* @area: a #GtkCellArea
* @renderer: the #GtkCellRenderer to add to @area
*
* Adds @renderer to @area with the default child cell properties.
*/
void
gtk_cell_area_add (GtkCellArea *area,
GtkCellRenderer *renderer)
@ -680,6 +797,13 @@ gtk_cell_area_add (GtkCellArea *area,
g_type_name (G_TYPE_FROM_INSTANCE (area)));
}
/**
* gtk_cell_area_remove:
* @area: a #GtkCellArea
* @renderer: the #GtkCellRenderer to add to @area
*
* Removes @renderer from @area.
*/
void
gtk_cell_area_remove (GtkCellArea *area,
GtkCellRenderer *renderer)
@ -703,6 +827,14 @@ gtk_cell_area_remove (GtkCellArea *area,
g_type_name (G_TYPE_FROM_INSTANCE (area)));
}
/**
* gtk_cell_area_forall
* @area: a #GtkCellArea
* @callback: the #GtkCellCallback to call
* @callback_data: user provided data pointer
*
* Calls @callback for every #GtkCellRenderer in @area.
*/
void
gtk_cell_area_forall (GtkCellArea *area,
GtkCellCallback callback,
@ -722,6 +854,45 @@ gtk_cell_area_forall (GtkCellArea *area,
g_type_name (G_TYPE_FROM_INSTANCE (area)));
}
/**
* gtk_cell_area_get_cell_allocation:
* @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
*
* Derives the allocation of @renderer inside @area if @area
* were to be renderered in @cell_area.
*/
void
gtk_cell_area_get_cell_allocation (GtkCellArea *area,
GtkCellAreaIter *iter,
GtkWidget *widget,
GtkCellRenderer *renderer,
const GdkRectangle *cell_area,
GdkRectangle *allocation)
{
GtkCellAreaClass *class;
g_return_if_fail (GTK_IS_CELL_AREA (area));
g_return_if_fail (GTK_IS_CELL_AREA_ITER (iter));
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (GTK_IS_CELL_RENDERER (renderer));
g_return_if_fail (cell_area != NULL);
g_return_if_fail (allocation != NULL);
class = GTK_CELL_AREA_GET_CLASS (area);
if (class->get_cell_allocation)
class->get_cell_allocation (area, 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)));
}
gint
gtk_cell_area_event (GtkCellArea *area,
GtkCellAreaIter *iter,
@ -893,6 +1064,17 @@ gtk_cell_area_get_preferred_width_for_height (GtkCellArea *area,
/*************************************************************
* API: Attributes *
*************************************************************/
/**
* gtk_cell_area_attribute_connect:
* @area: a #GtkCellArea
* @renderer: the #GtkCellRenderer to connect an attribute for
* @attribute: the attribute name
* @column: the #GtkTreeModel column to fetch attribute values from
*
* Connects an @attribute to apply values from @column for the
* #GtkTreeModel in use.
*/
void
gtk_cell_area_attribute_connect (GtkCellArea *area,
GtkCellRenderer *renderer,
@ -949,6 +1131,16 @@ gtk_cell_area_attribute_connect (GtkCellArea *area,
info->attributes = g_slist_prepend (info->attributes, cell_attribute);
}
/**
* gtk_cell_area_attribute_disconnect:
* @area: a #GtkCellArea
* @renderer: the #GtkCellRenderer to disconnect an attribute for
* @attribute: the attribute name
*
* Disconnects @attribute for the @renderer in @area so that
* attribute will no longer be updated with values from the
* model.
*/
void
gtk_cell_area_attribute_disconnect (GtkCellArea *area,
GtkCellRenderer *renderer,
@ -1025,6 +1217,18 @@ apply_cell_attributes (GtkCellRenderer *renderer,
g_object_thaw_notify (G_OBJECT (renderer));
}
/**
* gtk_cell_area_apply_attributes
* @area: a #GtkCellArea
* @tree_model: a #GtkTreeModel to pull values from
* @iter: the #GtkTreeIter in @tree_model to apply values for
* @is_expander: whether @iter has children
* @is_expanded: whether @iter is expanded in the view and
* children are visible
*
* Applies any connected attributes to the renderers in
* @area by pulling the values from @tree_model.
*/
void
gtk_cell_area_apply_attributes (GtkCellArea *area,
GtkTreeModel *tree_model,
@ -1034,6 +1238,7 @@ gtk_cell_area_apply_attributes (GtkCellArea *area,
{
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));
@ -1051,8 +1256,37 @@ gtk_cell_area_apply_attributes (GtkCellArea *area,
/* 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);
}
/**
* gtk_cell_area_get_current_path_string:
* @area: a #GtkCellArea
*
* Gets the current #GtkTreePath string for the currently
* applied #GtkTreeIter, this is implicitly updated when
* gtk_cell_area_apply_attributes() is called and can be
* used to interact with renderers from #GtkCellArea
* subclasses.
*/
const gchar *
gtk_cell_area_get_current_path_string (GtkCellArea *area)
{
GtkCellAreaPrivate *priv;
g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL);
priv = area->priv;
return priv->current_path;
}
/*************************************************************
* API: Cell Properties *
*************************************************************/
@ -1566,6 +1800,8 @@ gtk_cell_area_set_focus_cell (GtkCellArea *area,
if (priv->focus_cell)
g_object_ref (priv->focus_cell);
g_object_notify (G_OBJECT (area), "focus-cell");
}
}
@ -1589,6 +1825,43 @@ gtk_cell_area_get_focus_cell (GtkCellArea *area)
return priv->focus_cell;
}
void
gtk_cell_area_set_edited_cell (GtkCellArea *area,
GtkCellRenderer *renderer)
{
GtkCellAreaPrivate *priv;
g_return_if_fail (GTK_IS_CELL_AREA (area));
g_return_if_fail (renderer == NULL || GTK_IS_CELL_RENDERER (renderer));
priv = area->priv;
if (priv->edited_cell != renderer)
{
if (priv->edited_cell)
g_object_unref (priv->edited_cell);
priv->edited_cell = renderer;
if (priv->edited_cell)
g_object_ref (priv->edited_cell);
g_object_notify (G_OBJECT (area), "edited-cell");
}
}
GtkCellRenderer *
gtk_cell_area_get_edited_cell (GtkCellArea *area)
{
GtkCellAreaPrivate *priv;
g_return_val_if_fail (GTK_IS_CELL_AREA (area), NULL);
priv = area->priv;
return priv->edited_cell;
}
/*************************************************************
* API: Margins *
*************************************************************/
@ -1697,6 +1970,21 @@ gtk_cell_area_set_cell_margin_bottom (GtkCellArea *area,
}
/* For convenience in area implementations */
void
gtk_cell_area_editing_started (GtkCellArea *area,
GtkCellRenderer *renderer,
GtkCellEditable *editable)
{
GtkCellAreaPrivate *priv;
g_return_if_fail (GTK_IS_CELL_AREA (area));
priv = area->priv;
g_signal_emit (area, cell_area_signals[SIGNAL_EDITING_STARTED], 0,
renderer, editable, priv->current_path);
}
void
gtk_cell_area_inner_cell_area (GtkCellArea *area,
GdkRectangle *background_area,

View File

@ -79,6 +79,12 @@ struct _GtkCellAreaClass
void (* forall) (GtkCellArea *area,
GtkCellCallback callback,
gpointer callback_data);
void (* get_cell_allocation) (GtkCellArea *area,
GtkCellAreaIter *iter,
GtkWidget *widget,
GtkCellRenderer *renderer,
const GdkRectangle *cell_area,
GdkRectangle *allocation);
gint (* event) (GtkCellArea *area,
GtkCellAreaIter *iter,
GtkWidget *widget,
@ -156,6 +162,12 @@ void gtk_cell_area_remove (GtkCellArea
void gtk_cell_area_forall (GtkCellArea *area,
GtkCellCallback callback,
gpointer callback_data);
void gtk_cell_area_get_cell_allocation (GtkCellArea *area,
GtkCellAreaIter *iter,
GtkWidget *widget,
GtkCellRenderer *renderer,
const GdkRectangle *cell_area,
GdkRectangle *allocation);
gint gtk_cell_area_event (GtkCellArea *area,
GtkCellAreaIter *iter,
GtkWidget *widget,
@ -194,6 +206,8 @@ void gtk_cell_area_get_preferred_width_for_height (GtkCellArea
gint height,
gint *minimum_width,
gint *natural_width);
G_CONST_RETURN gchar *gtk_cell_area_get_current_path_string (GtkCellArea *area);
/* Attributes */
void gtk_cell_area_apply_attributes (GtkCellArea *area,
@ -263,6 +277,9 @@ gboolean gtk_cell_area_get_can_focus (GtkCellArea
void gtk_cell_area_set_focus_cell (GtkCellArea *area,
GtkCellRenderer *renderer);
GtkCellRenderer *gtk_cell_area_get_focus_cell (GtkCellArea *area);
void gtk_cell_area_set_edited_cell (GtkCellArea *area,
GtkCellRenderer *renderer);
GtkCellRenderer *gtk_cell_area_get_edited_cell (GtkCellArea *area);
@ -282,6 +299,11 @@ void gtk_cell_area_set_cell_margin_bottom (GtkCellArea
/* Functions for area implementations */
/* Signal that editing started on the area (fires the "editing-started" signal) */
void gtk_cell_area_editing_started (GtkCellArea *area,
GtkCellRenderer *renderer,
GtkCellEditable *editable);
/* Distinguish the inner cell area from the whole requested area including margins */
void gtk_cell_area_inner_cell_area (GtkCellArea *area,
GdkRectangle *cell_area,

View File

@ -50,6 +50,12 @@ static void gtk_cell_area_box_remove (GtkCellArea
static void gtk_cell_area_box_forall (GtkCellArea *area,
GtkCellCallback callback,
gpointer callback_data);
static void gtk_cell_area_box_get_cell_allocation (GtkCellArea *area,
GtkCellAreaIter *iter,
GtkWidget *widget,
GtkCellRenderer *renderer,
const GdkRectangle *cell_area,
GdkRectangle *allocation);
static gint gtk_cell_area_box_event (GtkCellArea *area,
GtkCellAreaIter *iter,
GtkWidget *widget,
@ -225,13 +231,14 @@ gtk_cell_area_box_class_init (GtkCellAreaBoxClass *class)
object_class->get_property = gtk_cell_area_box_get_property;
/* GtkCellAreaClass */
area_class->add = gtk_cell_area_box_add;
area_class->remove = gtk_cell_area_box_remove;
area_class->forall = gtk_cell_area_box_forall;
area_class->event = gtk_cell_area_box_event;
area_class->render = gtk_cell_area_box_render;
area_class->set_cell_property = gtk_cell_area_box_set_cell_property;
area_class->get_cell_property = gtk_cell_area_box_get_cell_property;
area_class->add = gtk_cell_area_box_add;
area_class->remove = gtk_cell_area_box_remove;
area_class->forall = gtk_cell_area_box_forall;
area_class->get_cell_allocation = gtk_cell_area_box_get_cell_allocation;
area_class->event = gtk_cell_area_box_event;
area_class->render = gtk_cell_area_box_render;
area_class->set_cell_property = gtk_cell_area_box_set_cell_property;
area_class->get_cell_property = gtk_cell_area_box_get_cell_property;
area_class->create_iter = gtk_cell_area_box_create_iter;
area_class->get_request_mode = gtk_cell_area_box_get_request_mode;
@ -811,6 +818,50 @@ gtk_cell_area_box_forall (GtkCellArea *area,
}
}
static void
gtk_cell_area_box_get_cell_allocation (GtkCellArea *area,
GtkCellAreaIter *iter,
GtkWidget *widget,
GtkCellRenderer *renderer,
const GdkRectangle *cell_area,
GdkRectangle *allocation)
{
GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area);
GtkCellAreaBoxPrivate *priv = box->priv;
GtkCellAreaBoxIter *box_iter = GTK_CELL_AREA_BOX_ITER (iter);
GSList *allocated_cells, *l;
*allocation = *cell_area;
/* Get a list of cells with allocation sizes decided regardless
* of alignments and pack order etc. */
allocated_cells = get_allocated_cells (box, box_iter, widget);
for (l = allocated_cells; l; l = l->next)
{
AllocatedCell *cell = l->data;
if (cell->renderer == renderer)
{
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
{
allocation->x = cell_area->x + cell->position;
allocation->width = cell->size;
}
else
{
allocation->y = cell_area->y + cell->position;
allocation->height = cell->size;
}
break;
}
}
g_slist_foreach (allocated_cells, (GFunc)allocated_cell_free, NULL);
g_slist_free (allocated_cells);
}
static gint
gtk_cell_area_box_event (GtkCellArea *area,
GtkCellAreaIter *iter,
@ -819,6 +870,27 @@ gtk_cell_area_box_event (GtkCellArea *area,
const GdkRectangle *cell_area,
GtkCellRendererState flags)
{
gint retval;
/* First let the parent class handle activation of cells via keystrokes */
retval =
GTK_CELL_AREA_CLASS (gtk_cell_area_box_parent_class)->event (area, iter, widget,
event, cell_area, flags);
if (retval)
return retval;
/* Now detect keystrokes that move focus directionally inside the area
* or signal that focus should leave the area in a given direction.
*
* To navigate focus we only need to loop through the groups and
* observe the orientation and push focus along to the next cell
* or signal that focus should leave the area.
*/
/* Also detect mouse events, for mouse events we need to allocate the renderers
* and find which renderer needs to be activated.
*/
return 0;

View File

@ -90,6 +90,7 @@ VOID:OBJECT,STRING,STRING
VOID:OBJECT,UINT
VOID:OBJECT,UINT,FLAGS
VOID:OBJECT,STRING
VOID:OBJECT,OBJECT,STRING
VOID:OBJECT,OBJECT,OBJECT
VOID:POINTER
VOID:POINTER,INT