From 524110f9025b149241747979003a3f243e907891 Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Wed, 10 Nov 2010 22:25:13 +0900 Subject: [PATCH] Focus driving in GtkCellArea now works. - Fixed focus driving in GtkCellArea with refined apis - Added gtk_cell_area_activate() to be called when the area has focus (to activate or start editing the focused cell) - Added support for this in cellareascaffold - testcellarea now watches the "toggled" signal for a toggle renderer and updates the model state accordingly, this currently works with keyboard navigation, however focus is still not painted on cells. --- gtk/gtkcellarea.c | 1 + gtk/gtkcellareabox.c | 32 ++-- tests/cellareascaffold.c | 314 ++++++++++++++++++++++++++++++++------- tests/cellareascaffold.h | 1 + tests/testcellarea.c | 36 ++++- 5 files changed, 312 insertions(+), 72 deletions(-) diff --git a/gtk/gtkcellarea.c b/gtk/gtkcellarea.c index 81d6f826d1..de24277250 100644 --- a/gtk/gtkcellarea.c +++ b/gtk/gtkcellarea.c @@ -675,6 +675,7 @@ gtk_cell_area_real_activate (GtkCellArea *area, &background_area, flags)) return TRUE; } + return FALSE; } diff --git a/gtk/gtkcellareabox.c b/gtk/gtkcellareabox.c index 31946b5e13..66058d8bb3 100644 --- a/gtk/gtkcellareabox.c +++ b/gtk/gtkcellareabox.c @@ -939,11 +939,7 @@ gtk_cell_area_box_render (GtkCellArea *area, } if (cell->renderer == focus_cell) - { - g_print ("Rendering a cell with the focus flag !\n"); - - cell_fields |= GTK_CELL_RENDERER_FOCUSED; - } + cell_fields |= GTK_CELL_RENDERER_FOCUSED; /* Remove margins from the background area to produce the cell area */ @@ -1528,27 +1524,19 @@ gtk_cell_area_box_focus (GtkCellArea *area, cycle = FOCUS_PREV; break; case GTK_DIR_UP: - if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) - return FALSE; - else + if (priv->orientation == GTK_ORIENTATION_VERTICAL || !focus_cell) cycle = FOCUS_PREV; break; case GTK_DIR_DOWN: - if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) - return FALSE; - else + if (priv->orientation == GTK_ORIENTATION_VERTICAL || !focus_cell) cycle = FOCUS_NEXT; break; case GTK_DIR_LEFT: - if (priv->orientation == GTK_ORIENTATION_VERTICAL) - return FALSE; - else + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL || !focus_cell) cycle = FOCUS_PREV; break; case GTK_DIR_RIGHT: - if (priv->orientation == GTK_ORIENTATION_VERTICAL) - return FALSE; - else + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL || !focus_cell) cycle = FOCUS_NEXT; break; default: @@ -1561,12 +1549,12 @@ gtk_cell_area_box_focus (GtkCellArea *area, GList *list; gint i; - /* If there is no focused cell, focus on the first one in the list */ + /* If there is no focused cell, focus on the first (or last) one in the list */ if (!focus_cell) found_cell = TRUE; for (i = (cycle == FOCUS_NEXT) ? 0 : priv->groups->len -1; - i >= 0 && i < priv->groups->len; + cycled_focus == FALSE && i >= 0 && i < priv->groups->len; i = (cycle == FOCUS_NEXT) ? i + 1 : i - 1) { CellGroup *group = &g_array_index (priv->groups, CellGroup, i); @@ -1576,7 +1564,7 @@ gtk_cell_area_box_focus (GtkCellArea *area, { CellInfo *info = list->data; - if (!found_cell && info->renderer == focus_cell) + if (info->renderer == focus_cell) found_cell = TRUE; else if (found_cell) { @@ -1591,6 +1579,10 @@ gtk_cell_area_box_focus (GtkCellArea *area, } } } + + if (!cycled_focus) + gtk_cell_area_set_focus_cell (area, NULL); + return cycled_focus; } diff --git a/tests/cellareascaffold.c b/tests/cellareascaffold.c index 8b7624d48d..c0b72d7873 100644 --- a/tests/cellareascaffold.c +++ b/tests/cellareascaffold.c @@ -59,12 +59,30 @@ static void cell_area_scaffold_get_preferred_width_for_height (GtkWidget gint *natural_size); static gint cell_area_scaffold_focus (GtkWidget *widget, GtkDirectionType direction); -static void cell_area_scaffold_grab_focus (GtkWidget *widget); -/* CellArea callbacks */ +/* CellAreaScaffoldClass */ +static void cell_area_scaffold_activate (CellAreaScaffold *scaffold); + +/* CellArea/GtkTreeModel callbacks */ static void size_changed_cb (GtkCellAreaIter *iter, GParamSpec *pspec, CellAreaScaffold *scaffold); +static void row_changed_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + CellAreaScaffold *scaffold); +static void row_inserted_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + CellAreaScaffold *scaffold); +static void row_deleted_cb (GtkTreeModel *model, + GtkTreePath *path, + CellAreaScaffold *scaffold); +static void rows_reordered_cb (GtkTreeModel *model, + GtkTreePath *parent, + GtkTreeIter *iter, + gint *new_order, + CellAreaScaffold *scaffold); typedef struct { gint size; /* The size of the row in the scaffold's opposing orientation */ @@ -77,6 +95,10 @@ struct _CellAreaScaffoldPrivate { /* The model we're showing data for */ GtkTreeModel *model; + gulong row_changed_id; + gulong row_inserted_id; + gulong row_deleted_id; + gulong rows_reordered_id; /* The area rendering the data and a global iter */ GtkCellArea *area; @@ -91,6 +113,8 @@ struct _CellAreaScaffoldPrivate { /* Check when the underlying area changes the size and * we need to queue a redraw */ gulong size_changed_id; + + }; enum { @@ -98,6 +122,13 @@ enum { PROP_ORIENTATION }; +enum { + ACTIVATE, + N_SIGNALS +}; + +static guint scaffold_signals[N_SIGNALS] = { 0 }; + #define ROW_SPACING 2 #define DIRECTION_STR(dir) \ @@ -157,10 +188,22 @@ cell_area_scaffold_class_init (CellAreaScaffoldClass *class) 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; + + class->activate = cell_area_scaffold_activate; g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation"); + scaffold_signals[ACTIVATE] = + g_signal_new ("activate", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (CellAreaScaffoldClass, activate), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + widget_class->activate_signal = scaffold_signals[ACTIVATE]; + + g_type_class_add_private (gobject_class, sizeof (CellAreaScaffoldPrivate)); } @@ -658,62 +701,166 @@ 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; + gint focus_row; + GtkOrientation orientation; - /* Actually take the focus */ - GTK_WIDGET_CLASS (cell_area_scaffold_parent_class)->grab_focus (widget); + /* Grab focus on ourself if we dont already have focus */ + if (!gtk_widget_has_focus (widget)) + gtk_widget_grab_focus (widget); - if (!priv->model) - return; + /* Move focus from cell to cell and row to row */ + orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area)); + + focus_row = priv->focus_row; + + valid = gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, priv->focus_row); + while (valid) + { + gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); + + /* If focus stays in the area we dont need to do any more */ + if (gtk_cell_area_focus (priv->area, direction)) + { + GtkCellRenderer *renderer = gtk_cell_area_get_focus_cell (priv->area); + + priv->focus_row = focus_row; + + g_print ("focusing in direction %s: focus set on a %s in row %d\n", + DIRECTION_STR (direction), G_OBJECT_TYPE_NAME (renderer), priv->focus_row); + + return TRUE; + } + else + { + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + if (direction == GTK_DIR_RIGHT || + direction == GTK_DIR_LEFT) + break; + else if (direction == GTK_DIR_UP || + direction == GTK_DIR_TAB_BACKWARD) + { + if (focus_row == 0) + break; + else + { + /* XXX A real implementation should check if the + * previous row can focus with it's attributes setup */ + focus_row--; + valid = gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, focus_row); + } + } + else /* direction == GTK_DIR_DOWN || GTK_DIR_TAB_FORWARD */ + { + if (focus_row == priv->row_data->len - 1) + break; + else + { + /* XXX A real implementation should check if the + * previous row can focus with it's attributes setup */ + focus_row++; + valid = gtk_tree_model_iter_next (priv->model, &iter); + } + } + } + else /* (orientation == GTK_ORIENTATION_HORIZONTAL) */ + { + if (direction == GTK_DIR_UP || + direction == GTK_DIR_DOWN) + break; + else if (direction == GTK_DIR_LEFT || + direction == GTK_DIR_TAB_BACKWARD) + { + if (focus_row == 0) + break; + else + { + /* XXX A real implementation should check if the + * previous row can focus with it's attributes setup */ + focus_row--; + valid = gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, focus_row); + } + } + else /* direction == GTK_DIR_RIGHT || GTK_DIR_TAB_FORWARD */ + { + if (focus_row == priv->row_data->len - 1) + break; + else + { + /* XXX A real implementation should check if the + * previous row can focus with it's attributes setup */ + focus_row++; + valid = gtk_tree_model_iter_next (priv->model, &iter); + } + } + } + } + } + + g_print ("focus leaving with no cells in focus (direction %s, focus_row %d)\n", + DIRECTION_STR (direction), priv->focus_row); + + return FALSE; +} + +/********************************************************* + * CellAreaScaffoldClass * + *********************************************************/ +static void +cell_area_scaffold_activate (CellAreaScaffold *scaffold) +{ + CellAreaScaffoldPrivate *priv = scaffold->priv; + GtkWidget *widget = GTK_WIDGET (scaffold); + GtkAllocation allocation; + GtkOrientation orientation; + GdkRectangle cell_area; + GtkTreeIter iter; + gboolean valid; + gint i = 0; + + orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->area)); + gtk_widget_get_allocation (widget, &allocation); + + cell_area.x = 0; + cell_area.y = 0; + cell_area.width = allocation.width; + cell_area.height = allocation.height; - /* Find the first row that can focus and give it focus */ valid = gtk_tree_model_get_iter_first (priv->model, &iter); while (valid) { - i++; + RowData *data = &g_array_index (priv->row_data, RowData, i); - gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); - - if (gtk_cell_area_can_focus (priv->area)) + if (i == priv->focus_row) { - gtk_cell_area_focus (priv->area, GTK_DIR_RIGHT); + if (orientation == GTK_ORIENTATION_HORIZONTAL) + cell_area.height = data->size; + else + cell_area.width = data->size; + + gtk_cell_area_apply_attributes (priv->area, priv->model, &iter, FALSE, FALSE); + gtk_cell_area_activate (priv->area, priv->iter, widget, &cell_area, GTK_CELL_RENDERER_FOCUSED); + break; } + if (orientation == GTK_ORIENTATION_HORIZONTAL) + cell_area.y += data->size + ROW_SPACING; + else + cell_area.x += data->size + ROW_SPACING; + + i++; 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 * + * CellArea/GtkTreeModel callbacks * *********************************************************/ static void size_changed_cb (GtkCellAreaIter *iter, @@ -727,6 +874,64 @@ size_changed_cb (GtkCellAreaIter *iter, gtk_widget_queue_resize (GTK_WIDGET (scaffold)); } +static void +rebuild_and_flush_internals (CellAreaScaffold *scaffold) +{ + CellAreaScaffoldPrivate *priv = scaffold->priv; + gint n_rows; + + if (priv->model) + { + n_rows = gtk_tree_model_iter_n_children (priv->model, NULL); + + /* Clear/reset the array */ + g_array_set_size (priv->row_data, n_rows); + memset (priv->row_data->data, 0x0, n_rows * sizeof (RowData)); + } + else + g_array_set_size (priv->row_data, 0); + + /* Data changed, lets flush the iter and consequently queue resize and + * start everything over again (note this is definitly far from optimized) */ + gtk_cell_area_iter_flush (priv->iter); +} + +static void +row_changed_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + CellAreaScaffold *scaffold) +{ + rebuild_and_flush_internals (scaffold); +} + +static void +row_inserted_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + CellAreaScaffold *scaffold) +{ + rebuild_and_flush_internals (scaffold); +} + +static void +row_deleted_cb (GtkTreeModel *model, + GtkTreePath *path, + CellAreaScaffold *scaffold) +{ + rebuild_and_flush_internals (scaffold); +} + +static void +rows_reordered_cb (GtkTreeModel *model, + GtkTreePath *parent, + GtkTreeIter *iter, + gint *new_order, + CellAreaScaffold *scaffold) +{ + rebuild_and_flush_internals (scaffold); +} + /********************************************************* * API * *********************************************************/ @@ -762,7 +967,11 @@ cell_area_scaffold_set_model (CellAreaScaffold *scaffold, { if (priv->model) { - /* XXX disconnect signals */ + g_signal_handler_disconnect (priv->model, priv->row_changed_id); + g_signal_handler_disconnect (priv->model, priv->row_inserted_id); + g_signal_handler_disconnect (priv->model, priv->row_deleted_id); + g_signal_handler_disconnect (priv->model, priv->rows_reordered_id); + g_object_unref (priv->model); } @@ -770,23 +979,26 @@ cell_area_scaffold_set_model (CellAreaScaffold *scaffold, if (priv->model) { - gint n_rows; - - /* XXX connect signals */ g_object_ref (priv->model); - n_rows = gtk_tree_model_iter_n_children (priv->model, NULL); + priv->row_changed_id = + g_signal_connect (priv->model, "row-changed", + G_CALLBACK (row_changed_cb), scaffold); - /* Clear/reset the array */ - g_array_set_size (priv->row_data, n_rows); - memset (priv->row_data->data, 0x0, n_rows * sizeof (RowData)); - } - else - { - g_array_set_size (priv->row_data, 0); + priv->row_inserted_id = + g_signal_connect (priv->model, "row-inserted", + G_CALLBACK (row_inserted_cb), scaffold); + + priv->row_deleted_id = + g_signal_connect (priv->model, "row-deleted", + G_CALLBACK (row_deleted_cb), scaffold); + + priv->rows_reordered_id = + g_signal_connect (priv->model, "rows-reordered", + G_CALLBACK (rows_reordered_cb), scaffold); } - gtk_cell_area_iter_flush (priv->iter); + rebuild_and_flush_internals (scaffold); } } diff --git a/tests/cellareascaffold.h b/tests/cellareascaffold.h index 2d14098ff8..411ecf8cf9 100644 --- a/tests/cellareascaffold.h +++ b/tests/cellareascaffold.h @@ -52,6 +52,7 @@ struct _CellAreaScaffoldClass { GtkWidgetClass parent_class; + void (* activate) (CellAreaScaffold *scaffold); }; diff --git a/tests/testcellarea.c b/tests/testcellarea.c index b501135f6c..d547f71e3b 100644 --- a/tests/testcellarea.c +++ b/tests/testcellarea.c @@ -287,6 +287,24 @@ focus_list_model (void) return (GtkTreeModel *)store; } +static void +cell_toggled (GtkCellRendererToggle *cell_renderer, + gchar *path, + CellAreaScaffold *scaffold) +{ + GtkTreeModel *model = cell_area_scaffold_get_model (scaffold); + GtkTreeIter iter; + gboolean active; + + g_print ("Cell toggled !\n"); + + if (!gtk_tree_model_get_iter_from_string (model, &iter, path)) + return; + + gtk_tree_model_get (model, &iter, FOCUS_COLUMN_CHECK, &active, -1); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, FOCUS_COLUMN_CHECK, !active, -1); +} + static GtkWidget * focus_scaffold (void) { @@ -315,6 +333,9 @@ focus_scaffold (void) 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); + g_signal_connect (G_OBJECT (renderer), "toggled", + G_CALLBACK (cell_toggled), scaffold); + renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (renderer), "wrap-mode", PANGO_WRAP_WORD, @@ -334,6 +355,8 @@ focus_cell_area (void) GtkWidget *scaffold, *frame, *vbox, *hbox; window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + hbox = gtk_hbox_new (FALSE, 4); + gtk_widget_show (hbox); scaffold = focus_scaffold (); @@ -345,7 +368,18 @@ focus_cell_area (void) gtk_container_add (GTK_CONTAINER (frame), scaffold); - gtk_container_add (GTK_CONTAINER (window), frame); + gtk_box_pack_end (GTK_BOX (hbox), frame, TRUE, TRUE, 0); + + /* Now add some controls */ + vbox = gtk_vbox_new (FALSE, 4); + gtk_widget_show (vbox); + gtk_box_pack_end (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + + widget = gtk_check_button_new_with_label ("check button"); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); + + gtk_container_add (GTK_CONTAINER (window), hbox); gtk_widget_show (window); }