From 4643d90c5faaeacbd099359082b366e86e8d6de2 Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Wed, 10 Nov 2010 19:17:06 +0900 Subject: [PATCH] Committing new (and simplified) focus handling approach for GtkCellArea. Also adding missing file cellareascaffold.h --- gtk/gtkcellarea.c | 282 ++++++++++++++++----------------------- gtk/gtkcellarea.h | 33 +++-- gtk/gtkcellareabox.c | 255 ++++++++++++----------------------- tests/cellareascaffold.c | 200 ++++++++++++++++++++++++--- tests/cellareascaffold.h | 68 ++++++++++ tests/testcellarea.c | 112 ++++++++++++++++ 6 files changed, 574 insertions(+), 376 deletions(-) create mode 100644 tests/cellareascaffold.h diff --git a/gtk/gtkcellarea.c b/gtk/gtkcellarea.c index 4032369131..81d6f826d1 100644 --- a/gtk/gtkcellarea.c +++ b/gtk/gtkcellarea.c @@ -68,7 +68,12 @@ static void gtk_cell_area_real_get_preferred_width_for_height (GtkCellArea gint height, gint *minimum_width, gint *natural_width); -static void gtk_cell_area_real_update_focus (GtkCellArea *area); +static gboolean gtk_cell_area_real_can_focus (GtkCellArea *area); +static gboolean gtk_cell_area_real_activate (GtkCellArea *area, + GtkCellAreaIter *iter, + GtkWidget *widget, + const GdkRectangle *cell_area, + GtkCellRendererState flags); /* GtkCellLayoutIface */ static void gtk_cell_area_cell_layout_init (GtkCellLayoutIface *iface); @@ -168,8 +173,6 @@ struct _GtkCellAreaPrivate /* Currently focused cell */ GtkCellRenderer *focus_cell; - guint can_focus : 1; - }; enum { @@ -184,7 +187,6 @@ enum { }; enum { - SIGNAL_FOCUS_LEAVE, SIGNAL_EDITING_STARTED, SIGNAL_EDITING_CANCELED, SIGNAL_EDITING_DONE, @@ -228,7 +230,6 @@ gtk_cell_area_init (GtkCellArea *area) priv->focus_cell = NULL; priv->edited_cell = NULL; priv->edit_widget = NULL; - priv->can_focus = FALSE; priv->editing_done_id = 0; priv->remove_widget_id = 0; @@ -261,20 +262,11 @@ gtk_cell_area_class_init (GtkCellAreaClass *class) 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->can_focus = gtk_cell_area_real_can_focus; + class->focus = NULL; + class->activate = gtk_cell_area_real_activate; /* 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), @@ -591,32 +583,14 @@ gtk_cell_area_real_event (GtkCellArea *area, const GdkRectangle *cell_area, GtkCellRendererState flags) { + GtkCellAreaPrivate *priv = area->priv; + if (event->type == GDK_KEY_PRESS && (flags & GTK_CELL_RENDERER_FOCUSED) != 0) { - GdkEventKey *key_event = (GdkEventKey *)event; - GtkCellAreaPrivate *priv = area->priv; + GdkEventKey *key_event = (GdkEventKey *)event; - 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)) - { - GdkRectangle background_area; - - /* Get the allocation of the focused cell. - */ - gtk_cell_area_get_cell_allocation (area, iter, widget, priv->focus_cell, - cell_area, &background_area); - - /* Activate or Edit the currently focused cell */ - if (gtk_cell_area_activate_cell (area, widget, priv->focus_cell, event, - &background_area, flags)) - return TRUE; - } - else if (priv->edited_cell && - (key_event->keyval == GDK_KEY_Escape)) + /* Cancel any edits in progress */ + if (priv->edited_cell && (key_event->keyval == GDK_KEY_Escape)) { gtk_cell_area_stop_editing (area, TRUE); return TRUE; @@ -651,32 +625,57 @@ gtk_cell_area_real_get_preferred_width_for_height (GtkCellArea *area, } static void -update_can_focus (GtkCellRenderer *renderer, - gboolean *can_focus) +get_can_focus (GtkCellRenderer *renderer, + gboolean *can_focus) { if (gtk_cell_renderer_can_focus (renderer)) *can_focus = TRUE; } -static void -gtk_cell_area_real_update_focus (GtkCellArea *area) +static gboolean +gtk_cell_area_real_can_focus (GtkCellArea *area) { gboolean can_focus = FALSE; - /* Update the area's can focus flag, if any of the renderers can - * focus then the area can focus. + /* Checks if any renderer can focus for the currently applied + * attributes. * * Subclasses can override this in the case that they are also * rendering widgets as well as renderers. */ - gtk_cell_area_forall (area, (GtkCellCallback)update_can_focus, &can_focus); - gtk_cell_area_set_can_focus (area, can_focus); + gtk_cell_area_forall (area, (GtkCellCallback)get_can_focus, &can_focus); - /* 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 can_focus; +} + +static gboolean +gtk_cell_area_real_activate (GtkCellArea *area, + GtkCellAreaIter *iter, + GtkWidget *widget, + const GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GtkCellAreaPrivate *priv = area->priv; + GdkRectangle background_area; + + if (priv->focus_cell) + { + /* Get the allocation of the focused cell. + */ + gtk_cell_area_get_cell_allocation (area, iter, widget, priv->focus_cell, + cell_area, &background_area); + + /* Activate or Edit the currently focused cell + * + * Currently just not sending an event, renderers afaics dont use + * the event argument anyway, worst case is we can synthesize one. + */ + if (gtk_cell_area_activate_cell (area, widget, priv->focus_cell, NULL, + &background_area, flags)) + return TRUE; + } + return FALSE; } /************************************************************* @@ -1687,134 +1686,80 @@ gtk_cell_area_cell_get_property (GtkCellArea *area, *************************************************************/ /** - * gtk_cell_area_grab_focus: - * @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. - * - * 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. - * - * Implementing #GtkCellArea classes should implement this - * method to receive focus in it's own way particular to - * how it lays out cells. - */ -void -gtk_cell_area_grab_focus (GtkCellArea *area, - GtkDirectionType direction) -{ - GtkCellAreaClass *class; - - g_return_if_fail (GTK_IS_CELL_AREA (area)); - - class = GTK_CELL_AREA_GET_CLASS (area); - - 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))); -} - -/** - * gtk_cell_area_focus_leave: - * @area: a #GtkCellArea - * @direction: the #GtkDirectionType in which focus - * is to leave @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) -{ - GtkCellAreaPrivate *priv; - - g_return_if_fail (GTK_IS_CELL_AREA (area)); - - priv = area->priv; - - g_signal_emit (area, cell_area_signals[SIGNAL_FOCUS_LEAVE], 0, direction, priv->current_path); -} - -/** - * gtk_cell_area_update_focus: - * @area: a #GtkCellArea - * - * Updates focus information on @area for a given - * row of data. - * - * 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. - */ -void -gtk_cell_area_update_focus (GtkCellArea *area) -{ - g_return_if_fail (GTK_IS_CELL_AREA (area)); - - GTK_CELL_AREA_GET_CLASS (area)->update_focus (area); -} - -/** - * gtk_cell_area_set_can_focus: - * @area: a #GtkCellArea - * @can_focus: whether @area can receive focus - * - * This is generally called from GtkCellArea::update_focus() - * implementations to update if the @area can focus after - * applying new row data attributes. - */ -void -gtk_cell_area_set_can_focus (GtkCellArea *area, - gboolean can_focus) -{ - GtkCellAreaPrivate *priv; - - g_return_if_fail (GTK_IS_CELL_AREA (area)); - - priv = area->priv; - - if (priv->can_focus != can_focus) - { - priv->can_focus = can_focus; - } -} - -/** - * gtk_cell_area_get_can_focus: + * gtk_cell_area_can_focus: * @area: a #GtkCellArea * * 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. + * after applying new attributes to @area. * * Returns: whether @area can receive focus. */ gboolean -gtk_cell_area_get_can_focus (GtkCellArea *area) +gtk_cell_area_can_focus (GtkCellArea *area) { - GtkCellAreaPrivate *priv; + g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE); + + return GTK_CELL_AREA_GET_CLASS (area)->can_focus (area); +} + +/** + * gtk_cell_area_focus: + * @area: a #GtkCellArea + * @direction: the #GtkDirectionType + * + * This should be called by the @area's owning layout widget + * when focus is to be passed to @area, or moved within @area + * for a given @direction and row data. + * + * Implementing #GtkCellArea classes should implement this + * method to receive and navigate focus in it's own way particular + * to how it lays out cells. + * + * Returns: %TRUE if focus remains inside @area as a result of this call. + */ +gboolean +gtk_cell_area_focus (GtkCellArea *area, + GtkDirectionType direction) +{ + GtkCellAreaClass *class; g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE); - priv = area->priv; + class = GTK_CELL_AREA_GET_CLASS (area); - return priv->can_focus; + if (class->focus) + return class->focus (area, direction); + + g_warning ("GtkCellAreaClass::focus not implemented for `%s'", + g_type_name (G_TYPE_FROM_INSTANCE (area))); + + return FALSE; +} + +/** + * gtk_cell_area_activate: + * @area: a #GtkCellArea + * @iter: the #GtkCellAreaIter in context with the current row data + * @widget: the #GtkWidget that @area is rendering on + * @cell_area: the size and location of @area relative to @widget's allocation + * @flags: the #GtkCellRendererState flags for @area for this row of data. + * + * Activates @area, usually by activating the currently focused + * cell, however some subclasses which embed widgets in the area + * can also activate a widget if it currently has the focus. + * + * Returns: Whether @area was successfully activated. + */ +gboolean +gtk_cell_area_activate (GtkCellArea *area, + GtkCellAreaIter *iter, + GtkWidget *widget, + const GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + g_return_val_if_fail (GTK_IS_CELL_AREA (area), FALSE); + + return GTK_CELL_AREA_GET_CLASS (area)->activate (area, iter, widget, cell_area, flags); } @@ -2042,7 +1987,6 @@ gtk_cell_area_activate_cell (GtkCellArea *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 (event != NULL, FALSE); g_return_val_if_fail (cell_area != NULL, FALSE); priv = area->priv; diff --git a/gtk/gtkcellarea.h b/gtk/gtkcellarea.h index 0e8a7a9e45..6271e99d32 100644 --- a/gtk/gtkcellarea.h +++ b/gtk/gtkcellarea.h @@ -137,9 +137,15 @@ struct _GtkCellAreaClass GParamSpec *pspec); /* Focus */ - void (* grab_focus) (GtkCellArea *area, + gboolean (* can_focus) (GtkCellArea *area); + gboolean (* focus) (GtkCellArea *area, GtkDirectionType direction); - void (* update_focus) (GtkCellArea *area); + gboolean (* activate) (GtkCellArea *area, + GtkCellAreaIter *iter, + GtkWidget *widget, + const GdkRectangle *cell_area, + GtkCellRendererState flags); + /* Padding for future expansion */ void (*_gtk_reserved1) (void); @@ -265,18 +271,17 @@ void gtk_cell_area_cell_get_property (GtkCellArea /* Focus */ -void gtk_cell_area_grab_focus (GtkCellArea *area, - GtkDirectionType direction); -void gtk_cell_area_focus_leave (GtkCellArea *area, - GtkDirectionType direction); -void gtk_cell_area_update_focus (GtkCellArea *area); -void gtk_cell_area_set_can_focus (GtkCellArea *area, - gboolean can_focus); -gboolean gtk_cell_area_get_can_focus (GtkCellArea *area); -void gtk_cell_area_set_focus_cell (GtkCellArea *area, - GtkCellRenderer *renderer); -GtkCellRenderer *gtk_cell_area_get_focus_cell (GtkCellArea *area); - +gboolean gtk_cell_area_can_focus (GtkCellArea *area); +gboolean gtk_cell_area_focus (GtkCellArea *area, + GtkDirectionType direction); +gboolean gtk_cell_area_activate (GtkCellArea *area, + GtkCellAreaIter *iter, + GtkWidget *widget, + const GdkRectangle *cell_area, + GtkCellRendererState flags); +void gtk_cell_area_set_focus_cell (GtkCellArea *area, + GtkCellRenderer *renderer); +GtkCellRenderer *gtk_cell_area_get_focus_cell (GtkCellArea *area); /* Cell Activation/Editing */ void gtk_cell_area_set_edited_cell (GtkCellArea *area, diff --git a/gtk/gtkcellareabox.c b/gtk/gtkcellareabox.c index 887b6b34f6..31946b5e13 100644 --- a/gtk/gtkcellareabox.c +++ b/gtk/gtkcellareabox.c @@ -102,7 +102,7 @@ static void gtk_cell_area_box_get_preferred_width_for_height (GtkCellArea gint height, gint *minimum_width, gint *natural_width); -static void gtk_cell_area_box_grab_focus (GtkCellArea *area, +static gboolean gtk_cell_area_box_focus (GtkCellArea *area, GtkDirectionType direction); /* GtkCellLayoutIface */ @@ -247,7 +247,7 @@ gtk_cell_area_box_class_init (GtkCellAreaBoxClass *class) area_class->get_preferred_height_for_width = gtk_cell_area_box_get_preferred_height_for_width; area_class->get_preferred_width_for_height = gtk_cell_area_box_get_preferred_width_for_height; - area_class->grab_focus = gtk_cell_area_box_grab_focus; + area_class->focus = gtk_cell_area_box_focus; /* Properties */ g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation"); @@ -869,91 +869,6 @@ enum { FOCUS_NEXT }; -static void -gtk_cell_area_cycle_focus (GtkCellAreaBox *box, - GtkCellRenderer *focus_cell, - GtkDirectionType direction) -{ - GtkCellAreaBoxPrivate *priv = box->priv; - GtkCellArea *area = GTK_CELL_AREA (box); - gint cycle = FOCUS_NONE; - - switch (direction) - { - case GTK_DIR_TAB_FORWARD: - cycle = FOCUS_NEXT; - break; - case GTK_DIR_TAB_BACKWARD: - cycle = FOCUS_PREV; - break; - case GTK_DIR_UP: - if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) - gtk_cell_area_focus_leave (area, direction); - else - cycle = FOCUS_PREV; - break; - case GTK_DIR_DOWN: - if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) - gtk_cell_area_focus_leave (area, direction); - else - cycle = FOCUS_NEXT; - break; - case GTK_DIR_LEFT: - if (priv->orientation == GTK_ORIENTATION_VERTICAL) - gtk_cell_area_focus_leave (area, direction); - else - cycle = FOCUS_PREV; - break; - case GTK_DIR_RIGHT: - if (priv->orientation == GTK_ORIENTATION_VERTICAL) - gtk_cell_area_focus_leave (area, direction); - else - cycle = FOCUS_NEXT; - break; - default: - break; - } - - if (cycle != FOCUS_NONE) - { - gboolean found_cell = FALSE; - gboolean cycled_focus = FALSE; - GList *list; - gint i; - - for (i = (cycle == FOCUS_NEXT) ? 0 : priv->groups->len -1; - i >= 0 && i < priv->groups->len; - i = (cycle == FOCUS_NEXT) ? i + 1 : i - 1) - { - CellGroup *group = &g_array_index (priv->groups, CellGroup, i); - - for (list = (cycle == FOCUS_NEXT) ? g_list_first (group->cells) : g_list_last (group->cells); - list; list = (cycle == FOCUS_NEXT) ? list->next : list->prev) - { - CellInfo *info = list->data; - - if (!found_cell && info->renderer == focus_cell) - found_cell = TRUE; - else if (found_cell) - { - if (gtk_cell_renderer_can_focus (info->renderer)) - { - gtk_cell_area_set_focus_cell (area, info->renderer); - - cycled_focus = TRUE; - break; - } - } - } - } - - /* We cycled right out of the area, signal the parent we - * need to focus out of the area */ - if (!cycled_focus) - gtk_cell_area_focus_leave (area, direction); - } -} - static gint gtk_cell_area_box_event (GtkCellArea *area, GtkCellAreaIter *iter, @@ -972,61 +887,6 @@ gtk_cell_area_box_event (GtkCellArea *area, 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. - */ - if (event->type == GDK_KEY_PRESS && (flags & GTK_CELL_RENDERER_FOCUSED) != 0) - { - GdkEventKey *key_event = (GdkEventKey *)event; - GtkCellRenderer *focus_cell; - - focus_cell = gtk_cell_area_get_focus_cell (area); - - if (focus_cell) - { - GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); - GtkDirectionType direction = GTK_DIR_TAB_FORWARD; - gboolean have_direction = FALSE; - - /* Check modifiers and TAB keys ! */ - switch (key_event->keyval) - { - case GDK_KEY_KP_Up: - case GDK_KEY_Up: - direction = GTK_DIR_UP; - have_direction = TRUE; - break; - case GDK_KEY_KP_Down: - case GDK_KEY_Down: - direction = GTK_DIR_DOWN; - have_direction = TRUE; - break; - case GDK_KEY_KP_Left: - case GDK_KEY_Left: - direction = GTK_DIR_LEFT; - have_direction = TRUE; - break; - case GDK_KEY_KP_Right: - case GDK_KEY_Right: - direction = GTK_DIR_RIGHT; - have_direction = TRUE; - break; - default: - break; - } - - if (have_direction) - { - gtk_cell_area_cycle_focus (box, focus_cell, direction); - return TRUE; - } - } - } - /* Also detect mouse events, for mouse events we need to allocate the renderers * and find which renderer needs to be activated. */ @@ -1048,6 +908,13 @@ gtk_cell_area_box_render (GtkCellArea *area, GtkCellAreaBoxIter *box_iter = GTK_CELL_AREA_BOX_ITER (iter); GSList *allocated_cells, *l; GdkRectangle background_area, inner_area; + GtkCellRenderer *focus_cell = NULL; + + if (flags & GTK_CELL_RENDERER_FOCUSED) + { + focus_cell = gtk_cell_area_get_focus_cell (area); + flags &= ~GTK_CELL_RENDERER_FOCUSED; + } background_area = *cell_area; @@ -1057,7 +924,8 @@ gtk_cell_area_box_render (GtkCellArea *area, for (l = allocated_cells; l; l = l->next) { - AllocatedCell *cell = l->data; + AllocatedCell *cell = l->data; + GtkCellRendererState cell_fields = 0; if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) { @@ -1070,16 +938,22 @@ gtk_cell_area_box_render (GtkCellArea *area, background_area.height = cell->size; } + if (cell->renderer == focus_cell) + { + g_print ("Rendering a cell with the focus flag !\n"); + + cell_fields |= GTK_CELL_RENDERER_FOCUSED; + } + /* Remove margins from the background area to produce the cell area */ gtk_cell_area_inner_cell_area (area, &background_area, &inner_area); - /* XXX We have to do some per-cell considerations for the 'flags' + /* We have to do some per-cell considerations for the 'flags' * for focus handling */ gtk_cell_renderer_render (cell->renderer, cr, widget, &background_area, &inner_area, - flags); - + flags | cell_fields); } g_slist_foreach (allocated_cells, (GFunc)allocated_cell_free, NULL); @@ -1633,52 +1507,91 @@ gtk_cell_area_box_get_preferred_width_for_height (GtkCellArea *area, *natural_width = nat_width; } -static void -gtk_cell_area_box_grab_focus (GtkCellArea *area, - GtkDirectionType direction) +static gboolean +gtk_cell_area_box_focus (GtkCellArea *area, + GtkDirectionType direction) { - GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); - GtkCellAreaBoxPrivate *priv; - gboolean first_cell = FALSE; - gint i; - GList *list; + GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); + GtkCellAreaBoxPrivate *priv = box->priv; + gint cycle = FOCUS_NONE; + gboolean cycled_focus = FALSE; + GtkCellRenderer *focus_cell; - priv = box->priv; + focus_cell = gtk_cell_area_get_focus_cell (area); switch (direction) { case GTK_DIR_TAB_FORWARD: - case GTK_DIR_DOWN: - case GTK_DIR_RIGHT: - first_cell = TRUE; + cycle = FOCUS_NEXT; break; - case GTK_DIR_TAB_BACKWARD: - case GTK_DIR_UP: + cycle = FOCUS_PREV; + break; + case GTK_DIR_UP: + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + return FALSE; + else + cycle = FOCUS_PREV; + break; + case GTK_DIR_DOWN: + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + return FALSE; + else + cycle = FOCUS_NEXT; + break; case GTK_DIR_LEFT: + if (priv->orientation == GTK_ORIENTATION_VERTICAL) + return FALSE; + else + cycle = FOCUS_PREV; + break; + case GTK_DIR_RIGHT: + if (priv->orientation == GTK_ORIENTATION_VERTICAL) + return FALSE; + else + cycle = FOCUS_NEXT; + break; default: - first_cell = FALSE; break; } - for (i = first_cell ? 0 : priv->groups->len -1; - i >= 0 && i < priv->groups->len; - i = first_cell ? i + 1 : i - 1) + if (cycle != FOCUS_NONE) { - CellGroup *group = &g_array_index (priv->groups, CellGroup, i); + gboolean found_cell = FALSE; + GList *list; + gint i; - for (list = first_cell ? g_list_first (group->cells) : g_list_last (group->cells); - list; list = first_cell ? list->next : list->prev) + /* If there is no focused cell, focus on the first one in the list */ + if (!focus_cell) + found_cell = TRUE; + + for (i = (cycle == FOCUS_NEXT) ? 0 : priv->groups->len -1; + i >= 0 && i < priv->groups->len; + i = (cycle == FOCUS_NEXT) ? i + 1 : i - 1) { - CellInfo *info = list->data; + CellGroup *group = &g_array_index (priv->groups, CellGroup, i); - if (gtk_cell_renderer_can_focus (info->renderer)) + for (list = (cycle == FOCUS_NEXT) ? g_list_first (group->cells) : g_list_last (group->cells); + list; list = (cycle == FOCUS_NEXT) ? list->next : list->prev) { - gtk_cell_area_set_focus_cell (area, info->renderer); - break; + CellInfo *info = list->data; + + if (!found_cell && info->renderer == focus_cell) + found_cell = TRUE; + else if (found_cell) + { + if (gtk_cell_renderer_can_focus (info->renderer)) + { + gtk_cell_area_set_focus_cell (area, info->renderer); + + cycled_focus = TRUE; + break; + } + } } } } + return cycled_focus; } diff --git a/tests/cellareascaffold.c b/tests/cellareascaffold.c index 0da496d6ee..8b7624d48d 100644 --- a/tests/cellareascaffold.c +++ b/tests/cellareascaffold.c @@ -37,6 +37,8 @@ static void cell_area_scaffold_get_property (GObject GParamSpec *pspec); /* GtkWidgetClass */ +static void cell_area_scaffold_realize (GtkWidget *widget); +static void cell_area_scaffold_unrealize (GtkWidget *widget); static gboolean cell_area_scaffold_draw (GtkWidget *widget, cairo_t *cr); static void cell_area_scaffold_size_allocate (GtkWidget *widget, @@ -55,8 +57,14 @@ static void cell_area_scaffold_get_preferred_width_for_height (GtkWidget gint for_size, gint *minimum_size, gint *natural_size); +static gint cell_area_scaffold_focus (GtkWidget *widget, + GtkDirectionType direction); +static void cell_area_scaffold_grab_focus (GtkWidget *widget); - +/* CellArea callbacks */ +static void size_changed_cb (GtkCellAreaIter *iter, + GParamSpec *pspec, + CellAreaScaffold *scaffold); typedef struct { gint size; /* The size of the row in the scaffold's opposing orientation */ @@ -64,6 +72,9 @@ typedef struct { struct _CellAreaScaffoldPrivate { + /* Window for catching events and dispatching them to the cell area */ + GdkWindow *event_window; + /* The model we're showing data for */ GtkTreeModel *model; @@ -74,33 +85,33 @@ struct _CellAreaScaffoldPrivate { /* Cache some info about rows (hieghts etc) */ GArray *row_data; + /* Focus handling */ + gint focus_row; + + /* Check when the underlying area changes the size and + * we need to queue a redraw */ gulong size_changed_id; }; - -#define ROW_SPACING 2 - enum { PROP_0, PROP_ORIENTATION }; +#define ROW_SPACING 2 + +#define DIRECTION_STR(dir) \ + ((dir) == GTK_DIR_TAB_FORWARD ? "tab forward" : \ + (dir) == GTK_DIR_TAB_BACKWARD ? "tab backward" : \ + (dir) == GTK_DIR_UP ? "up" : \ + (dir) == GTK_DIR_DOWN ? "down" : \ + (dir) == GTK_DIR_LEFT ? "left" : \ + (dir) == GTK_DIR_RIGHT ? "right" : "invalid") + G_DEFINE_TYPE_WITH_CODE (CellAreaScaffold, cell_area_scaffold, GTK_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)); -static void -size_changed_cb (GtkCellAreaIter *iter, - GParamSpec *pspec, - CellAreaScaffold *scaffold) -{ - if (!strcmp (pspec->name, "minimum-width") || - !strcmp (pspec->name, "natural-width") || - !strcmp (pspec->name, "minimum-height") || - !strcmp (pspec->name, "natural-height")) - gtk_widget_queue_resize (GTK_WIDGET (scaffold)); -} - static void cell_area_scaffold_init (CellAreaScaffold *scaffold) { @@ -114,13 +125,14 @@ cell_area_scaffold_init (CellAreaScaffold *scaffold) priv->area = gtk_cell_area_box_new (); priv->iter = gtk_cell_area_create_iter (priv->area); - priv->size_changed_id = - g_signal_connect (priv->iter, "notify", - G_CALLBACK (size_changed_cb), scaffold); - priv->row_data = g_array_new (FALSE, FALSE, sizeof (RowData)); gtk_widget_set_has_window (GTK_WIDGET (scaffold), FALSE); + gtk_widget_set_can_focus (GTK_WIDGET (scaffold), TRUE); + + priv->size_changed_id = + g_signal_connect (priv->iter, "notify", + G_CALLBACK (size_changed_cb), scaffold); } static void @@ -136,12 +148,16 @@ cell_area_scaffold_class_init (CellAreaScaffoldClass *class) gobject_class->set_property = cell_area_scaffold_set_property; widget_class = GTK_WIDGET_CLASS(class); + widget_class->realize = cell_area_scaffold_realize; + widget_class->unrealize = cell_area_scaffold_unrealize; widget_class->draw = cell_area_scaffold_draw; widget_class->size_allocate = cell_area_scaffold_size_allocate; widget_class->get_preferred_width = cell_area_scaffold_get_preferred_width; widget_class->get_preferred_height_for_width = cell_area_scaffold_get_preferred_height_for_width; widget_class->get_preferred_height = cell_area_scaffold_get_preferred_height; widget_class->get_preferred_width_for_height = cell_area_scaffold_get_preferred_width_for_height; + widget_class->focus = cell_area_scaffold_focus; + widget_class->grab_focus = cell_area_scaffold_grab_focus; g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation"); @@ -186,6 +202,7 @@ cell_area_scaffold_dispose (GObject *object) if (priv->area) { + /* Disconnect signals */ g_object_unref (priv->area); priv->area = NULL; } @@ -239,10 +256,64 @@ cell_area_scaffold_get_property (GObject *object, } } - /********************************************************* * GtkWidgetClass * *********************************************************/ +static void +cell_area_scaffold_realize (GtkWidget *widget) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + GtkAllocation allocation; + GdkWindow *window; + GdkWindowAttr attributes; + gint attributes_mask; + + gtk_widget_get_allocation (widget, &allocation); + + gtk_widget_set_realized (widget, TRUE); + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.wclass = GDK_INPUT_ONLY; + attributes.event_mask = gtk_widget_get_events (widget); + attributes.event_mask |= (GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK); + + attributes_mask = GDK_WA_X | GDK_WA_Y; + + window = gtk_widget_get_parent_window (widget); + gtk_widget_set_window (widget, window); + g_object_ref (window); + + priv->event_window = gdk_window_new (window, + &attributes, attributes_mask); + gdk_window_set_user_data (priv->event_window, widget); + + gtk_widget_style_attach (widget); +} + +static void +cell_area_scaffold_unrealize (GtkWidget *widget) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + + if (priv->event_window) + { + gdk_window_set_user_data (priv->event_window, NULL); + gdk_window_destroy (priv->event_window); + priv->event_window = NULL; + } + + GTK_WIDGET_CLASS (cell_area_scaffold_parent_class)->unrealize (widget); +} + static gboolean cell_area_scaffold_draw (GtkWidget *widget, cairo_t *cr) @@ -255,10 +326,13 @@ cell_area_scaffold_draw (GtkWidget *widget, GdkRectangle render_area; GtkAllocation allocation; gint i = 0; + gboolean have_focus; + GtkCellRendererState flags; if (!priv->model) return FALSE; + have_focus = gtk_widget_has_focus (widget); orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area)); gtk_widget_get_allocation (widget, &allocation); @@ -273,6 +347,11 @@ cell_area_scaffold_draw (GtkWidget *widget, { RowData *data = &g_array_index (priv->row_data, RowData, i); + if (have_focus && i == priv->focus_row) + flags = GTK_CELL_RENDERER_FOCUSED; + else + flags = 0; + if (orientation == GTK_ORIENTATION_HORIZONTAL) { render_area.height = data->size; @@ -283,7 +362,7 @@ cell_area_scaffold_draw (GtkWidget *widget, } gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); - gtk_cell_area_render (priv->area, priv->iter, widget, cr, &render_area, 0); + gtk_cell_area_render (priv->area, priv->iter, widget, cr, &render_area, flags); if (orientation == GTK_ORIENTATION_HORIZONTAL) { @@ -391,6 +470,13 @@ cell_area_scaffold_size_allocate (GtkWidget *widget, gtk_widget_set_allocation (widget, allocation); + if (gtk_widget_get_realized (widget)) + gdk_window_move_resize (priv->event_window, + allocation->x, + allocation->y, + allocation->width, + allocation->height); + orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area)); /* Cache the per-row sizes and allocate the iter */ @@ -569,7 +655,77 @@ cell_area_scaffold_get_preferred_width_for_height (GtkWidget *widget, } } +static gint +cell_area_scaffold_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + g_print ("cell_area_scaffold_focus called for direction %s\n", + DIRECTION_STR (direction)); + /* Grab focus on ourself if we dont already have focus */ + if (!gtk_widget_has_focus (widget)) + { + gtk_widget_grab_focus (widget); + return TRUE; + } + return TRUE; +} + +static void +cell_area_scaffold_grab_focus (GtkWidget *widget) +{ + CellAreaScaffold *scaffold = CELL_AREA_SCAFFOLD (widget); + CellAreaScaffoldPrivate *priv = scaffold->priv; + GtkTreeIter iter; + gboolean valid; + gint i = -1; + + /* Actually take the focus */ + GTK_WIDGET_CLASS (cell_area_scaffold_parent_class)->grab_focus (widget); + + if (!priv->model) + return; + + /* Find the first row that can focus and give it focus */ + valid = gtk_tree_model_get_iter_first (priv->model, &iter); + while (valid) + { + i++; + + gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); + + if (gtk_cell_area_can_focus (priv->area)) + { + gtk_cell_area_focus (priv->area, GTK_DIR_RIGHT); + break; + } + + valid = gtk_tree_model_iter_next (priv->model, &iter); + } + + if (valid && i >= 0) + { + g_print ("Grab focus called, setting focus on row %d\n", i); + + priv->focus_row = i; + gtk_widget_queue_draw (widget); + } +} + +/********************************************************* + * CellArea callbacks * + *********************************************************/ +static void +size_changed_cb (GtkCellAreaIter *iter, + GParamSpec *pspec, + CellAreaScaffold *scaffold) +{ + if (!strcmp (pspec->name, "minimum-width") || + !strcmp (pspec->name, "natural-width") || + !strcmp (pspec->name, "minimum-height") || + !strcmp (pspec->name, "natural-height")) + gtk_widget_queue_resize (GTK_WIDGET (scaffold)); +} /********************************************************* * API * diff --git a/tests/cellareascaffold.h b/tests/cellareascaffold.h new file mode 100644 index 0000000000..2d14098ff8 --- /dev/null +++ b/tests/cellareascaffold.h @@ -0,0 +1,68 @@ +/* cellareascaffold.h + * + * Copyright (C) 2010 Openismus GmbH + * + * Authors: + * Tristan Van Berkom + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CELL_AREA_SCAFFOLD_H__ +#define __CELL_AREA_SCAFFOLD_H__ + +#include + + +G_BEGIN_DECLS + +#define TYPE_CELL_AREA_SCAFFOLD (cell_area_scaffold_get_type ()) +#define CELL_AREA_SCAFFOLD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_CELL_AREA_SCAFFOLD, CellAreaScaffold)) +#define CELL_AREA_SCAFFOLD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_CELL_AREA_SCAFFOLD, CellAreaScaffoldClass)) +#define IS_CELL_AREA_SCAFFOLD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_CELL_AREA_SCAFFOLD)) +#define IS_CELL_AREA_SCAFFOLD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_CELL_AREA_SCAFFOLD)) +#define CELL_AREA_SCAFFOLD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_CELL_AREA_SCAFFOLD, CellAreaScaffoldClass)) + + +typedef struct _CellAreaScaffold CellAreaScaffold; +typedef struct _CellAreaScaffoldClass CellAreaScaffoldClass; +typedef struct _CellAreaScaffoldPrivate CellAreaScaffoldPrivate; + +struct _CellAreaScaffold +{ + GtkWidget widget; + + CellAreaScaffoldPrivate *priv; +}; + +struct _CellAreaScaffoldClass +{ + GtkWidgetClass parent_class; + +}; + + +GType cell_area_scaffold_get_type (void) G_GNUC_CONST; +GtkWidget *cell_area_scaffold_new (void); + +GtkCellArea *cell_area_scaffold_get_area (CellAreaScaffold *scaffold); +void cell_area_scaffold_set_model (CellAreaScaffold *scaffold, + GtkTreeModel *model); +GtkTreeModel *cell_area_scaffold_get_model (CellAreaScaffold *scaffold); + +G_END_DECLS + +#endif /* __CELL_AREA_SCAFFOLD_H__ */ diff --git a/tests/testcellarea.c b/tests/testcellarea.c index 015be304fa..b501135f6c 100644 --- a/tests/testcellarea.c +++ b/tests/testcellarea.c @@ -1,6 +1,9 @@ #include #include "cellareascaffold.h" +/******************************************************* + * Simple Test * + *******************************************************/ enum { SIMPLE_COLUMN_NAME, SIMPLE_COLUMN_ICON, @@ -240,12 +243,121 @@ simple_cell_area (void) gtk_widget_show (window); } +/******************************************************* + * Focus Test * + *******************************************************/ +enum { + FOCUS_COLUMN_NAME, + FOCUS_COLUMN_CHECK, + FOCUS_COLUMN_STATIC_TEXT, + N_FOCUS_COLUMNS +}; + +static GtkTreeModel * +focus_list_model (void) +{ + GtkTreeIter iter; + GtkListStore *store = + gtk_list_store_new (N_FOCUS_COLUMNS, + G_TYPE_STRING, /* name text */ + G_TYPE_BOOLEAN, /* check */ + G_TYPE_STRING); /* static text */ + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + FOCUS_COLUMN_NAME, "Enter a string", + FOCUS_COLUMN_CHECK, TRUE, + FOCUS_COLUMN_STATIC_TEXT, "Does it fly ?", + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + FOCUS_COLUMN_NAME, "Enter a string", + FOCUS_COLUMN_CHECK, FALSE, + FOCUS_COLUMN_STATIC_TEXT, "Would you put it in a toaster ?", + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + FOCUS_COLUMN_NAME, "Type something", + FOCUS_COLUMN_CHECK, FALSE, + FOCUS_COLUMN_STATIC_TEXT, "Does it feed on cute kittens ?", + -1); + + return (GtkTreeModel *)store; +} + +static GtkWidget * +focus_scaffold (void) +{ + GtkTreeModel *model; + GtkWidget *scaffold; + GtkCellArea *area; + GtkCellRenderer *renderer; + + scaffold = cell_area_scaffold_new (); + gtk_widget_show (scaffold); + + model = focus_list_model (); + + cell_area_scaffold_set_model (CELL_AREA_SCAFFOLD (scaffold), model); + + area = cell_area_scaffold_get_area (CELL_AREA_SCAFFOLD (scaffold)); + + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL); + gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, TRUE, FALSE); + gtk_cell_area_attribute_connect (area, renderer, "text", FOCUS_COLUMN_NAME); + + /* Catch signal ... */ + renderer = gtk_cell_renderer_toggle_new (); + g_object_set (G_OBJECT (renderer), "xalign", 0.0F, NULL); + gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, FALSE, TRUE); + gtk_cell_area_attribute_connect (area, renderer, "active", FOCUS_COLUMN_CHECK); + + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), + "wrap-mode", PANGO_WRAP_WORD, + "wrap-width", 150, + NULL); + gtk_cell_area_box_pack_start (GTK_CELL_AREA_BOX (area), renderer, FALSE, TRUE); + gtk_cell_area_attribute_connect (area, renderer, "text", FOCUS_COLUMN_STATIC_TEXT); + + return scaffold; +} + + +static void +focus_cell_area (void) +{ + GtkWidget *window, *widget; + GtkWidget *scaffold, *frame, *vbox, *hbox; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + scaffold = focus_scaffold (); + + frame = gtk_frame_new (NULL); + gtk_widget_show (frame); + + gtk_widget_set_valign (frame, GTK_ALIGN_CENTER); + gtk_widget_set_halign (frame, GTK_ALIGN_FILL); + + gtk_container_add (GTK_CONTAINER (frame), scaffold); + + gtk_container_add (GTK_CONTAINER (window), frame); + + gtk_widget_show (window); +} + + int main (int argc, char *argv[]) { gtk_init (NULL, NULL); simple_cell_area (); + focus_cell_area (); gtk_main ();