diff --git a/gtk/gtkcellarea.c b/gtk/gtkcellarea.c index de24277250..e34c163079 100644 --- a/gtk/gtkcellarea.c +++ b/gtk/gtkcellarea.c @@ -97,6 +97,13 @@ static void gtk_cell_area_reorder (GtkCellLayout gint position); static GList *gtk_cell_area_get_cells (GtkCellLayout *cell_layout); + +/* Used in forall loop to check if a child renderer is present */ +typedef struct { + GtkCellRenderer *renderer; + gboolean has_renderer; +} HasRendererCheck; + /* Attribute/Cell metadata */ typedef struct { const gchar *attribute; @@ -154,6 +161,9 @@ struct _GtkCellAreaPrivate */ GHashTable *cell_info; + /* Tracking which cells are focus siblings of focusable cells */ + GHashTable *focus_siblings; + /* The cell border decides how much space to reserve * around each cell for the background_area */ @@ -222,6 +232,11 @@ gtk_cell_area_init (GtkCellArea *area) NULL, (GDestroyNotify)cell_info_free); + priv->focus_siblings = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify)g_list_free); + priv->cell_border.left = 0; priv->cell_border.right = 0; priv->cell_border.top = 0; @@ -470,9 +485,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); @@ -858,6 +874,7 @@ gtk_cell_area_remove (GtkCellArea *area, { 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)); @@ -868,6 +885,25 @@ gtk_cell_area_remove (GtkCellArea *area, /* Remove any custom attributes and custom cell data func here first */ g_hash_table_remove (priv->cell_info, renderer); + /* 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); + if (class->remove) class->remove (area, renderer); else @@ -875,6 +911,28 @@ gtk_cell_area_remove (GtkCellArea *area, g_type_name (G_TYPE_FROM_INSTANCE (area))); } +static void +get_has_renderer (GtkCellRenderer *renderer, + HasRendererCheck *check) +{ + if (renderer == check->renderer) + check->has_renderer = TRUE; +} + +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_forall (area, (GtkCellCallback)get_has_renderer, &check); + + return check.has_renderer; +} + /** * gtk_cell_area_forall * @area: a #GtkCellArea @@ -972,8 +1030,10 @@ gtk_cell_area_render (GtkCellArea *area, GtkCellAreaIter *iter, GtkWidget *widget, cairo_t *cr, + const GdkRectangle *background_area, const GdkRectangle *cell_area, - GtkCellRendererState flags) + GtkCellRendererState flags, + gboolean paint_focus) { GtkCellAreaClass *class; @@ -981,12 +1041,13 @@ gtk_cell_area_render (GtkCellArea *area, g_return_if_fail (GTK_IS_CELL_AREA_ITER (iter)); 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, iter, 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))); @@ -1136,6 +1197,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); @@ -1202,6 +1264,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); @@ -1821,6 +1884,110 @@ gtk_cell_area_get_focus_cell (GtkCellArea *area) } +/************************************************************* + * API: Focus Siblings * + *************************************************************/ +void +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); + + if (siblings) + siblings = g_list_append (siblings, sibling); + else + { + siblings = g_list_append (siblings, sibling); + g_hash_table_insert (priv->focus_siblings, renderer, siblings); + } +} + +void +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; + + 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); +} + +gboolean +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; + + 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; +} + +const GList * +gtk_cell_area_get_focus_siblings (GtkCellArea *area, + GtkCellRenderer *renderer) +{ + GtkCellAreaPrivate *priv; + + 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; + + return g_hash_table_lookup (priv->focus_siblings, renderer); +} + /************************************************************* * API: Cell Activation/Editing * *************************************************************/ diff --git a/gtk/gtkcellarea.h b/gtk/gtkcellarea.h index 6271e99d32..c1203541e5 100644 --- a/gtk/gtkcellarea.h +++ b/gtk/gtkcellarea.h @@ -95,8 +95,10 @@ struct _GtkCellAreaClass GtkCellAreaIter *iter, GtkWidget *widget, cairo_t *cr, + const GdkRectangle *background_area, const GdkRectangle *cell_area, - GtkCellRendererState flags); + GtkCellRendererState flags, + gboolean paint_focus); /* Geometry */ GtkCellAreaIter *(* create_iter) (GtkCellArea *area); @@ -165,6 +167,8 @@ void gtk_cell_area_add (GtkCellArea GtkCellRenderer *renderer); void gtk_cell_area_remove (GtkCellArea *area, GtkCellRenderer *renderer); +gboolean gtk_cell_area_has_renderer (GtkCellArea *area, + GtkCellRenderer *renderer); void gtk_cell_area_forall (GtkCellArea *area, GtkCellCallback callback, gpointer callback_data); @@ -184,8 +188,10 @@ void gtk_cell_area_render (GtkCellArea GtkCellAreaIter *iter, GtkWidget *widget, cairo_t *cr, + const GdkRectangle *background_area, const GdkRectangle *cell_area, - GtkCellRendererState flags); + GtkCellRendererState flags, + gboolean paint_focus); /* Geometry */ GtkCellAreaIter *gtk_cell_area_create_iter (GtkCellArea *area); @@ -283,6 +289,21 @@ void gtk_cell_area_set_focus_cell (GtkCellArea GtkCellRenderer *renderer); GtkCellRenderer *gtk_cell_area_get_focus_cell (GtkCellArea *area); + +/* Focus siblings */ +void gtk_cell_area_add_focus_sibling (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellRenderer *sibling); +void gtk_cell_area_remove_focus_sibling (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellRenderer *sibling); +gboolean gtk_cell_area_is_focus_sibling (GtkCellArea *area, + GtkCellRenderer *renderer, + GtkCellRenderer *sibling); +G_CONST_RETURN GList *gtk_cell_area_get_focus_siblings (GtkCellArea *area, + GtkCellRenderer *renderer); + + /* Cell Activation/Editing */ void gtk_cell_area_set_edited_cell (GtkCellArea *area, GtkCellRenderer *renderer); diff --git a/gtk/gtkcellareabox.c b/gtk/gtkcellareabox.c index 66058d8bb3..45355aebe5 100644 --- a/gtk/gtkcellareabox.c +++ b/gtk/gtkcellareabox.c @@ -66,8 +66,10 @@ static void gtk_cell_area_box_render (GtkCellArea GtkCellAreaIter *iter, GtkWidget *widget, cairo_t *cr, + const GdkRectangle *background_area, const GdkRectangle *cell_area, - GtkCellRendererState flags); + GtkCellRendererState flags, + gboolean paint_focus); static void gtk_cell_area_box_set_cell_property (GtkCellArea *area, GtkCellRenderer *renderer, guint prop_id, @@ -900,15 +902,19 @@ gtk_cell_area_box_render (GtkCellArea *area, GtkCellAreaIter *iter, GtkWidget *widget, cairo_t *cr, + const GdkRectangle *background_area, const GdkRectangle *cell_area, - GtkCellRendererState flags) + GtkCellRendererState flags, + gboolean paint_focus) { GtkCellAreaBox *box = GTK_CELL_AREA_BOX (area); GtkCellAreaBoxPrivate *priv = box->priv; GtkCellAreaBoxIter *box_iter = GTK_CELL_AREA_BOX_ITER (iter); GSList *allocated_cells, *l; - GdkRectangle background_area, inner_area; + GdkRectangle cell_background, inner_area; GtkCellRenderer *focus_cell = NULL; + GdkRectangle focus_rect = { 0, }; + gboolean first_focus_cell = TRUE; if (flags & GTK_CELL_RENDERER_FOCUSED) { @@ -916,7 +922,7 @@ gtk_cell_area_box_render (GtkCellArea *area, flags &= ~GTK_CELL_RENDERER_FOCUSED; } - background_area = *cell_area; + cell_background = *cell_area; /* Get a list of cells with allocation sizes decided regardless * of alignments and pack order etc. */ @@ -929,29 +935,95 @@ gtk_cell_area_box_render (GtkCellArea *area, if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) { - background_area.x = cell_area->x + cell->position; - background_area.width = cell->size; + cell_background.x = cell_area->x + cell->position; + cell_background.width = cell->size; } else { - background_area.y = cell_area->y + cell->position; - background_area.height = cell->size; + cell_background.y = cell_area->y + cell->position; + cell_background.height = cell->size; } - if (cell->renderer == focus_cell) - 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); + gtk_cell_area_inner_cell_area (area, &cell_background, &inner_area); + + if (focus_cell && + (cell->renderer == focus_cell || + gtk_cell_area_is_focus_sibling (area, focus_cell, cell->renderer))) + { + cell_fields |= GTK_CELL_RENDERER_FOCUSED; + + if (paint_focus) + { + GdkRectangle cell_focus; + gint opposite_size, x_offset, y_offset; + + cell_focus = inner_area; + + /* Trim up the focus size */ + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + { + gtk_cell_renderer_get_preferred_height_for_width (cell->renderer, widget, + cell_focus.width, + NULL, &opposite_size); + + cell_focus.height = MIN (opposite_size, cell_focus.height); + } + else + { + gtk_cell_renderer_get_preferred_width_for_height (cell->renderer, widget, + cell_focus.height, + NULL, &opposite_size); + + cell_focus.width = MIN (opposite_size, cell_focus.width); + } + + /* offset the cell position */ + _gtk_cell_renderer_calc_offset (cell->renderer, &inner_area, GTK_TEXT_DIR_LTR, + cell_focus.width, cell_focus.height, + &x_offset, &y_offset); + + cell_focus.x += x_offset; + cell_focus.y += y_offset; + + /* Accumulate the focus rectangle for all focus siblings */ + if (first_focus_cell) + { + focus_rect = cell_focus; + first_focus_cell = FALSE; + } + else + gdk_rectangle_union (&focus_rect, &cell_focus, &focus_rect); + } + } /* 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, + &cell_background, &inner_area, flags | cell_fields); } + if (paint_focus && focus_rect.width != 0 && focus_rect.height != 0) + { + GtkStateType renderer_state = + flags & GTK_CELL_RENDERER_SELECTED ? GTK_STATE_SELECTED : + (flags & GTK_CELL_RENDERER_PRELIT ? GTK_STATE_PRELIGHT : + (flags & GTK_CELL_RENDERER_INSENSITIVE ? GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL)); + + gtk_paint_focus (gtk_widget_get_style (widget), + cr, renderer_state, + widget, + /* XXX This hint should be a property on GtkCellArea I suppose */ + "treeview", + focus_rect.x, + focus_rect.y, + focus_rect.width, + focus_rect.height); + } + + g_slist_foreach (allocated_cells, (GFunc)allocated_cell_free, NULL); g_slist_free (allocated_cells); } diff --git a/tests/cellareascaffold.c b/tests/cellareascaffold.c index c0b72d7873..6d40be9edd 100644 --- a/tests/cellareascaffold.c +++ b/tests/cellareascaffold.c @@ -405,7 +405,9 @@ 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, flags); + gtk_cell_area_render (priv->area, priv->iter, widget, cr, + &render_area, &render_area, flags, + (have_focus && i == priv->focus_row)); if (orientation == GTK_ORIENTATION_HORIZONTAL) { @@ -726,13 +728,11 @@ cell_area_scaffold_focus (GtkWidget *widget, /* 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); - + + /* XXX A smarter implementation would only invalidate the rectangles where + * focus was removed from and new focus was placed */ + gtk_widget_queue_draw (widget); return TRUE; } else @@ -802,8 +802,9 @@ cell_area_scaffold_focus (GtkWidget *widget, } } - g_print ("focus leaving with no cells in focus (direction %s, focus_row %d)\n", - DIRECTION_STR (direction), priv->focus_row); + /* XXX A smarter implementation would only invalidate the rectangles where + * focus was removed from and new focus was placed */ + gtk_widget_queue_draw (widget); return FALSE; } diff --git a/tests/testcellarea.c b/tests/testcellarea.c index d547f71e3b..0acf2ebbb5 100644 --- a/tests/testcellarea.c +++ b/tests/testcellarea.c @@ -246,6 +246,8 @@ simple_cell_area (void) /******************************************************* * Focus Test * *******************************************************/ +static GtkCellRenderer *focus_renderer, *sibling_renderer; + enum { FOCUS_COLUMN_NAME, FOCUS_COLUMN_CHECK, @@ -328,7 +330,7 @@ focus_scaffold (void) gtk_cell_area_attribute_connect (area, renderer, "text", FOCUS_COLUMN_NAME); /* Catch signal ... */ - renderer = gtk_cell_renderer_toggle_new (); + focus_renderer = 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); @@ -336,7 +338,7 @@ focus_scaffold (void) g_signal_connect (G_OBJECT (renderer), "toggled", G_CALLBACK (cell_toggled), scaffold); - renderer = gtk_cell_renderer_text_new (); + sibling_renderer = renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (renderer), "wrap-mode", PANGO_WRAP_WORD, "wrap-width", 150, @@ -347,6 +349,21 @@ focus_scaffold (void) return scaffold; } +static void +focus_sibling_toggled (GtkToggleButton *toggle, + CellAreaScaffold *scaffold) +{ + GtkCellArea *area = cell_area_scaffold_get_area (scaffold); + gboolean active = gtk_toggle_button_get_active (toggle); + + if (active) + gtk_cell_area_add_focus_sibling (area, focus_renderer, sibling_renderer); + else + gtk_cell_area_remove_focus_sibling (area, focus_renderer, sibling_renderer); + + gtk_widget_queue_draw (GTK_WIDGET (scaffold)); +} + static void focus_cell_area (void) @@ -375,10 +392,13 @@ focus_cell_area (void) gtk_widget_show (vbox); gtk_box_pack_end (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); - widget = gtk_check_button_new_with_label ("check button"); + widget = gtk_check_button_new_with_label ("Focus Sibling"); gtk_widget_show (widget); gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (widget), "toggled", + G_CALLBACK (focus_sibling_toggled), scaffold); + gtk_container_add (GTK_CONTAINER (window), hbox); gtk_widget_show (window);