From d20d7c54d38d7a8617eed2c5b8b82755470ceafa Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 6 May 2012 05:39:58 +0200 Subject: [PATCH] iconview: Redo layouting --- gtk/a11y/gtkiconviewaccessible.c | 8 +- gtk/gtkiconview.c | 390 ++++++++++--------------------- gtk/gtkiconviewprivate.h | 3 - 3 files changed, 129 insertions(+), 272 deletions(-) diff --git a/gtk/a11y/gtkiconviewaccessible.c b/gtk/a11y/gtkiconviewaccessible.c index 40333b7b8c..bbb1a689cc 100644 --- a/gtk/a11y/gtkiconviewaccessible.c +++ b/gtk/a11y/gtkiconviewaccessible.c @@ -23,6 +23,7 @@ #include "gtk/gtkadjustment.h" #include "gtk/gtkiconviewprivate.h" +#include "gtk/gtkcellareacontext.h" #include "gtk/gtkcellrendererpixbuf.h" #include "gtk/gtkcellrenderertext.h" #include "gtk/gtkpango.h" @@ -219,12 +220,11 @@ get_pixbuf_box (GtkIconView *icon_view, GdkRectangle *box) { GetPixbufBoxData data = { { 0, }, FALSE }; - GtkCellAreaContext *context; - - context = g_ptr_array_index (icon_view->priv->row_contexts, item->row); _gtk_icon_view_set_cell_data (icon_view, item); - gtk_cell_area_foreach_alloc (icon_view->priv->cell_area, context, + gtk_cell_area_context_allocate (icon_view->priv->cell_area_context, item->cell_area.width, item->cell_area.height); + gtk_cell_area_foreach_alloc (icon_view->priv->cell_area, + icon_view->priv->cell_area_context, GTK_WIDGET (icon_view), &item->cell_area, &item->cell_area, (GtkCellAllocCallback)get_pixbuf_foreach, &data); diff --git a/gtk/gtkiconview.c b/gtk/gtkiconview.c index d06f6af60e..7aaf158d4d 100644 --- a/gtk/gtkiconview.c +++ b/gtk/gtkiconview.c @@ -219,7 +219,6 @@ static gboolean gtk_icon_view_item_hit_test (GtkIco gint width, gint height); static gboolean gtk_icon_view_unselect_all_internal (GtkIconView *icon_view); -static void gtk_icon_view_cache_widths (GtkIconView *icon_view); static void gtk_icon_view_update_rubberband (gpointer data); static void gtk_icon_view_item_invalidate_size (GtkIconViewItem *item); static void gtk_icon_view_invalidate_sizes (GtkIconView *icon_view); @@ -263,9 +262,6 @@ static void gtk_icon_view_remove_editable (GtkCel GtkCellRenderer *renderer, GtkCellEditable *editable, GtkIconView *icon_view); -static void gtk_icon_view_context_changed (GtkCellAreaContext *context, - GParamSpec *pspec, - GtkIconView *icon_view); static void update_text_cell (GtkIconView *icon_view); static void update_pixbuf_cell (GtkIconView *icon_view); @@ -975,9 +971,6 @@ gtk_icon_view_init (GtkIconView *icon_view) icon_view->priv->item_padding = 6; icon_view->priv->draw_focus = TRUE; - - icon_view->priv->row_contexts = - g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); } /* GObject methods */ @@ -1010,19 +1003,10 @@ gtk_icon_view_dispose (GObject *object) if (priv->cell_area_context) { - g_signal_handler_disconnect (priv->cell_area_context, priv->context_changed_id); - priv->context_changed_id = 0; - g_object_unref (priv->cell_area_context); priv->cell_area_context = NULL; } - if (priv->row_contexts) - { - g_ptr_array_free (priv->row_contexts, TRUE); - priv->row_contexts = NULL; - } - if (priv->cell_area) { gtk_cell_area_stop_editing (icon_view->priv->cell_area, TRUE); @@ -2018,18 +2002,6 @@ gtk_icon_view_remove_editable (GtkCellArea *area, gtk_tree_path_free (path); } -static void -gtk_icon_view_context_changed (GtkCellAreaContext *context, - GParamSpec *pspec, - GtkIconView *icon_view) -{ - if (!strcmp (pspec->name, "minimum-width") || - !strcmp (pspec->name, "natural-width") || - !strcmp (pspec->name, "minimum-height") || - !strcmp (pspec->name, "natural-height")) - gtk_icon_view_invalidate_sizes (icon_view); -} - /** * gtk_icon_view_set_cursor: * @icon_view: A #GtkIconView @@ -2077,11 +2049,10 @@ gtk_icon_view_set_cursor (GtkIconView *icon_view, if (start_editing && icon_view->priv->cell_area) { - GtkCellAreaContext *context; - - context = g_ptr_array_index (icon_view->priv->row_contexts, item->row); _gtk_icon_view_set_cell_data (icon_view, item); - gtk_cell_area_activate (icon_view->priv->cell_area, context, + gtk_cell_area_context_allocate (icon_view->priv->cell_area_context, item->cell_area.width, item->cell_area.height); + gtk_cell_area_activate (icon_view->priv->cell_area, + icon_view->priv->cell_area_context, GTK_WIDGET (icon_view), &item->cell_area, 0 /* XXX flags */, TRUE); } @@ -2233,12 +2204,10 @@ gtk_icon_view_button_press (GtkWidget *widget, if (cell != NULL && gtk_cell_renderer_is_activatable (cell)) { - GtkCellAreaContext *context; - - context = g_ptr_array_index (icon_view->priv->row_contexts, item->row); - _gtk_icon_view_set_cell_data (icon_view, item); - gtk_cell_area_activate (icon_view->priv->cell_area, context, + gtk_cell_area_context_allocate (icon_view->priv->cell_area_context, item->cell_area.width, item->cell_area.height); + gtk_cell_area_activate (icon_view->priv->cell_area, + icon_view->priv->cell_area_context, GTK_WIDGET (icon_view), &item->cell_area, 0/* XXX flags */, FALSE); } @@ -2508,17 +2477,16 @@ gtk_icon_view_item_hit_test (GtkIconView *icon_view, gint height) { HitTestData data = { { x, y, width, height }, FALSE }; - GtkCellAreaContext *context; GdkRectangle *item_area = &item->cell_area; if (MIN (x + width, item_area->x + item_area->width) - MAX (x, item_area->x) <= 0 || MIN (y + height, item_area->y + item_area->height) - MAX (y, item_area->y) <= 0) return FALSE; - context = g_ptr_array_index (icon_view->priv->row_contexts, item->row); - _gtk_icon_view_set_cell_data (icon_view, item); - gtk_cell_area_foreach_alloc (icon_view->priv->cell_area, context, + gtk_cell_area_context_allocate (icon_view->priv->cell_area_context, item->cell_area.width, item->cell_area.height); + gtk_cell_area_foreach_alloc (icon_view->priv->cell_area, + icon_view->priv->cell_area_context, GTK_WIDGET (icon_view), item_area, item_area, (GtkCellAllocCallback)hit_test, &data); @@ -2578,15 +2546,16 @@ static gboolean gtk_icon_view_real_activate_cursor_item (GtkIconView *icon_view) { GtkTreePath *path; - GtkCellAreaContext *context; if (!icon_view->priv->cursor_item) return FALSE; - context = g_ptr_array_index (icon_view->priv->row_contexts, icon_view->priv->cursor_item->row); - _gtk_icon_view_set_cell_data (icon_view, icon_view->priv->cursor_item); - gtk_cell_area_activate (icon_view->priv->cell_area, context, + gtk_cell_area_context_allocate (icon_view->priv->cell_area_context, + icon_view->priv->cursor_item->cell_area.width, + icon_view->priv->cursor_item->cell_area.height); + gtk_cell_area_activate (icon_view->priv->cell_area, + icon_view->priv->cell_area_context, GTK_WIDGET (icon_view), &icon_view->priv->cursor_item->cell_area, 0 /* XXX flags */, @@ -2768,110 +2737,6 @@ gtk_icon_view_adjustment_changed (GtkAdjustment *adjustment, } } -static GList * -gtk_icon_view_layout_single_row (GtkIconView *icon_view, - GList *first_item, - gint item_width, - gint row, - gint *y, - gint *maximum_width) -{ - GtkAllocation allocation; - GtkCellAreaContext *context; - GtkIconViewPrivate *priv = icon_view->priv; - GtkWidget *widget = GTK_WIDGET (icon_view); - gint x, current_width; - GList *items, *last_item; - gint col; - gint max_height = 0; - gboolean rtl; - - rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL; - - x = 0; - col = 0; - items = first_item; - current_width = 0; - - x += priv->margin; - current_width += 2 * priv->margin; - - gtk_widget_get_allocation (widget, &allocation); - - context = gtk_cell_area_copy_context (priv->cell_area, priv->cell_area_context); - g_ptr_array_add (priv->row_contexts, context); - - /* In the first loop we iterate horizontally until we hit allocation width - * and collect the aligned height-for-width */ - items = first_item; - while (items) - { - GtkIconViewItem *item = items->data; - GdkRectangle *item_area = &item->cell_area; - - item_area->width = item_width; - - current_width += item_area->width + icon_view->priv->item_padding * 2; - - if (items != first_item) - { - if ((icon_view->priv->columns <= 0 && current_width > allocation.width) || - (icon_view->priv->columns > 0 && col >= icon_view->priv->columns)) - break; - } - - /* Get this item's particular width & height (all alignments are cached by now) */ - _gtk_icon_view_set_cell_data (icon_view, item); - gtk_cell_area_get_preferred_height_for_width (priv->cell_area, - context, - widget, item_width, - NULL, NULL); - - current_width += icon_view->priv->column_spacing; - - item_area->y = *y + icon_view->priv->item_padding; - item_area->x = x + icon_view->priv->item_padding; - - x = current_width - icon_view->priv->margin; - - if (current_width > *maximum_width) - *maximum_width = current_width; - - item->row = row; - item->col = col; - - col ++; - items = items->next; - } - - last_item = items; - - gtk_cell_area_context_get_preferred_height_for_width (context, item_width, &max_height, NULL); - gtk_cell_area_context_allocate (context, item_width, max_height); - - /* In the second loop the item height has been aligned and derived and - * we just set the height and handle rtl layout */ - for (items = first_item; items != last_item; items = items->next) - { - GtkIconViewItem *item = items->data; - GdkRectangle *item_area = &item->cell_area; - - if (rtl) - { - item_area->x = *maximum_width - item_area->width - item_area->x; - item->col = col - 1 - item->col; - } - - /* All items in the same row get the same height */ - item_area->height = max_height; - } - - /* Adjust the new y coordinate. */ - *y += max_height + icon_view->priv->row_spacing + icon_view->priv->item_padding * 2; - - return last_item; -} - static void adjust_wrap_width (GtkIconView *icon_view) { @@ -2897,124 +2762,132 @@ adjust_wrap_width (GtkIconView *icon_view) } } +static gint +compare_sizes (gconstpointer p1, + gconstpointer p2, + gpointer unused) +{ + return GPOINTER_TO_INT (((const GtkRequestedSize *) p1)->data) + - GPOINTER_TO_INT (((const GtkRequestedSize *) p2)->data); +} + static void gtk_icon_view_layout (GtkIconView *icon_view) { - GtkAllocation allocation; - GtkWidget *widget; - GList *icons; - gint y = 0, maximum_width = 0; - gint row; + GtkIconViewPrivate *priv = icon_view->priv; + GtkWidget *widget = GTK_WIDGET (icon_view); + GList *items; gint item_width; - gboolean size_changed = FALSE; + gint n_columns, n_rows, n_items; + gint col, row; + GtkRequestedSize *sizes; - if (icon_view->priv->model == NULL) - return; - - widget = GTK_WIDGET (icon_view); - - item_width = icon_view->priv->item_width; + n_items = gtk_icon_view_get_n_items (icon_view); /* Update the wrap width for the text cell before going and requesting sizes */ - adjust_wrap_width (icon_view); + if (n_items) + adjust_wrap_width (icon_view); - /* Update the context widths for any invalidated items */ - gtk_icon_view_cache_widths (icon_view); + gtk_icon_view_compute_n_items_for_size (icon_view, + GTK_ORIENTATION_HORIZONTAL, + gtk_widget_get_allocated_width (widget), + NULL, + &n_columns); + n_rows = (n_items + n_columns - 1) / n_columns; - /* Fetch the new item width if needed */ - if (item_width < 0) - gtk_cell_area_context_get_preferred_width (icon_view->priv->cell_area_context, - &item_width, NULL); - - gtk_cell_area_context_allocate (icon_view->priv->cell_area_context, item_width, -1); - - icons = icon_view->priv->items; - y += icon_view->priv->margin; - row = 0; - - /* Clear the per row contexts */ - g_ptr_array_set_size (icon_view->priv->row_contexts, 0); - - do + if (n_columns <= 1) { - icons = gtk_icon_view_layout_single_row (icon_view, icons, - item_width, row, - &y, &maximum_width); - row++; + /* We might need vertical scrolling here */ + gtk_icon_view_get_preferred_item_size (icon_view, GTK_ORIENTATION_HORIZONTAL, -1, &item_width, NULL); + item_width += 2 * priv->item_padding + 2 * priv->margin; + priv->width = MAX (item_width, gtk_widget_get_allocated_width (widget)); } - while (icons != NULL); - - if (maximum_width != icon_view->priv->width) + else { - icon_view->priv->width = maximum_width; - size_changed = TRUE; + priv->width = gtk_widget_get_allocated_width (widget); } - y += icon_view->priv->margin; - - if (y != icon_view->priv->height) + item_width = (priv->width - 2 * priv->margin + priv->column_spacing) / n_columns; + item_width -= priv->column_spacing; + item_width -= 2 * priv->item_padding; + + gtk_cell_area_context_reset (priv->cell_area_context); + gtk_cell_area_context_allocate (priv->cell_area_context, item_width, -1); + /* because layouting is complicated. We designed an API + * that is O(N²) and nonsensical. + * And we're proud of it. */ + for (items = priv->items; items; items = items->next) { - icon_view->priv->height = y; - size_changed = TRUE; + _gtk_icon_view_set_cell_data (icon_view, items->data); + gtk_cell_area_get_preferred_width (priv->cell_area, + priv->cell_area_context, + widget, + NULL, NULL); } - gtk_icon_view_set_hadjustment_values (icon_view); - gtk_icon_view_set_vadjustment_values (icon_view); + sizes = g_newa (GtkRequestedSize, n_rows); + items = priv->items; + priv->height = priv->margin; - if (size_changed) - gtk_widget_queue_resize_no_redraw (widget); - - gtk_widget_get_allocation (widget, &allocation); - if (gtk_widget_get_realized (GTK_WIDGET (icon_view))) - gdk_window_resize (icon_view->priv->bin_window, - MAX (icon_view->priv->width, allocation.width), - MAX (icon_view->priv->height, allocation.height)); - - if (icon_view->priv->scroll_to_path) + /* Collect the heights for all rows */ + for (row = 0; row < n_rows; row++) { - GtkTreePath *path; + for (col = 0; col < n_columns && items; col++, items = items->next) + { + GtkIconViewItem *item = items->data; - path = gtk_tree_row_reference_get_path (icon_view->priv->scroll_to_path); - gtk_tree_row_reference_free (icon_view->priv->scroll_to_path); - icon_view->priv->scroll_to_path = NULL; + _gtk_icon_view_set_cell_data (icon_view, item); + gtk_cell_area_get_preferred_height_for_width (priv->cell_area, + priv->cell_area_context, + widget, + item_width, + NULL, NULL); + } - gtk_icon_view_scroll_to_path (icon_view, path, - icon_view->priv->scroll_to_use_align, - icon_view->priv->scroll_to_row_align, - icon_view->priv->scroll_to_col_align); - gtk_tree_path_free (path); + sizes[row].data = GINT_TO_POINTER (row); + gtk_cell_area_context_get_preferred_height_for_width (priv->cell_area_context, + item_width, + &sizes[row].minimum_size, + &sizes[row].natural_size); + priv->height += sizes[row].minimum_size + 2 * priv->item_padding + priv->row_spacing; } + + priv->height -= priv->row_spacing; + priv->height += priv->margin; + priv->height = MIN (priv->height, gtk_widget_get_allocated_height (widget)); + + gtk_distribute_natural_allocation (gtk_widget_get_allocated_height (widget) - priv->height, + n_rows, + sizes); + + /* Actually allocate the rows */ + g_qsort_with_data (sizes, n_rows, sizeof (GtkRequestedSize), compare_sizes, NULL); - gtk_widget_queue_draw (widget); -} + items = priv->items; + priv->height = priv->margin; -/* This ensures that all widths have been cached in the - * context and we have proper alignments to go on. - */ -static void -gtk_icon_view_cache_widths (GtkIconView *icon_view) -{ - GList *items; - - g_signal_handler_block (icon_view->priv->cell_area_context, - icon_view->priv->context_changed_id); - - for (items = icon_view->priv->items; items; items = items->next) + for (row = 0; row < n_rows; row++) { - GtkIconViewItem *item = items->data; + priv->height += priv->item_padding; - /* Only fetch the width of items with invalidated sizes */ - if (item->cell_area.width < 0) - { - _gtk_icon_view_set_cell_data (icon_view, item); - gtk_cell_area_get_preferred_width (icon_view->priv->cell_area, - icon_view->priv->cell_area_context, - GTK_WIDGET (icon_view), NULL, NULL); - } + for (col = 0; col < n_columns && items; col++, items = items->next) + { + GtkIconViewItem *item = items->data; + + item->cell_area.x = priv->margin + (col * 2 + 1) * priv->item_padding + col * (priv->column_spacing + item_width); + item->cell_area.width = item_width; + item->cell_area.y = priv->height; + item->cell_area.height = sizes[row].minimum_size; + item->row = row; + item->col = col; + } + + priv->height += sizes[row].minimum_size + priv->item_padding + priv->row_spacing; } - g_signal_handler_unblock (icon_view->priv->cell_area_context, - icon_view->priv->context_changed_id); + priv->height -= priv->row_spacing; + priv->height += priv->margin; + priv->height = MAX (priv->height, gtk_widget_get_allocated_height (widget)); } static void @@ -3024,16 +2897,6 @@ gtk_icon_view_invalidate_sizes (GtkIconView *icon_view) g_list_foreach (icon_view->priv->items, (GFunc)gtk_icon_view_item_invalidate_size, NULL); - /* Reset the context */ - if (icon_view->priv->cell_area_context) - { - g_signal_handler_block (icon_view->priv->cell_area_context, - icon_view->priv->context_changed_id); - gtk_cell_area_context_reset (icon_view->priv->cell_area_context); - g_signal_handler_unblock (icon_view->priv->cell_area_context, - icon_view->priv->context_changed_id); - } - /* Re-layout the items */ gtk_widget_queue_resize (GTK_WIDGET (icon_view)); } @@ -3059,7 +2922,6 @@ gtk_icon_view_paint_item (GtkIconView *icon_view, GtkStyleContext *style_context; GtkWidget *widget = GTK_WIDGET (icon_view); GtkIconViewPrivate *priv = icon_view->priv; - GtkCellAreaContext *context; if (priv->model == NULL) return; @@ -3114,8 +2976,8 @@ gtk_icon_view_paint_item (GtkIconView *icon_view, cell_area.width = item->cell_area.width; cell_area.height = item->cell_area.height; - context = g_ptr_array_index (priv->row_contexts, item->row); - gtk_cell_area_render (priv->cell_area, context, + gtk_cell_area_context_allocate (priv->cell_area_context, item->cell_area.width, item->cell_area.height); + gtk_cell_area_render (priv->cell_area, priv->cell_area_context, widget, cr, &cell_area, &cell_area, flags, draw_focus); @@ -3292,17 +3154,19 @@ _gtk_icon_view_get_item_at_coords (GtkIconView *icon_view, if (only_in_cell || cell_at_pos) { GtkCellRenderer *cell = NULL; - GtkCellAreaContext *context; - context = g_ptr_array_index (icon_view->priv->row_contexts, item->row); _gtk_icon_view_set_cell_data (icon_view, item); if (x >= item_area->x && x <= item_area->x + item_area->width && y >= item_area->y && y <= item_area->y + item_area->height) - cell = gtk_cell_area_get_cell_at_position (icon_view->priv->cell_area, context, - GTK_WIDGET (icon_view), - item_area, - x, y, NULL); + { + gtk_cell_area_context_allocate (icon_view->priv->cell_area_context, item->cell_area.width, item->cell_area.height); + cell = gtk_cell_area_get_cell_at_position (icon_view->priv->cell_area, + icon_view->priv->cell_area_context, + GTK_WIDGET (icon_view), + item_area, + x, y, NULL); + } if (cell_at_pos) *cell_at_pos = cell; @@ -4276,9 +4140,6 @@ gtk_icon_view_ensure_cell_area (GtkIconView *icon_view, priv->remove_editable_id = g_signal_connect (priv->cell_area, "remove-editable", G_CALLBACK (gtk_icon_view_remove_editable), icon_view); - priv->context_changed_id = - g_signal_connect (priv->cell_area_context, "notify", - G_CALLBACK (gtk_icon_view_context_changed), icon_view); update_text_cell (icon_view); update_pixbuf_cell (icon_view); @@ -4546,11 +4407,10 @@ gtk_icon_view_set_tooltip_cell (GtkIconView *icon_view, if (cell) { - GtkCellAreaContext *context; - - context = g_ptr_array_index (icon_view->priv->row_contexts, item->row); _gtk_icon_view_set_cell_data (icon_view, item); - gtk_cell_area_get_cell_allocation (icon_view->priv->cell_area, context, + gtk_cell_area_context_allocate (icon_view->priv->cell_area_context, item->cell_area.width, item->cell_area.height); + gtk_cell_area_get_cell_allocation (icon_view->priv->cell_area, + icon_view->priv->cell_area_context, GTK_WIDGET (icon_view), cell, &item->cell_area, &rect); } diff --git a/gtk/gtkiconviewprivate.h b/gtk/gtkiconviewprivate.h index 093a19c4dc..d51ef6f640 100644 --- a/gtk/gtkiconviewprivate.h +++ b/gtk/gtkiconviewprivate.h @@ -42,9 +42,6 @@ struct _GtkIconViewPrivate gulong add_editable_id; gulong remove_editable_id; - gulong context_changed_id; - - GPtrArray *row_contexts; gint width, height;