diff --git a/ChangeLog b/ChangeLog index 6d16cd96ac..950650ab69 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +2008-07-11 Kristian Rietveld + + Bug 316087 - Resizing columns is chaotic + + * gtk/gtktreeprivate.h: add new member fields. + + * gtk/gtktreeview.c (gtk_tree_view_init), (validate_row): set post + validation flag, + (gtk_tree_view_size_allocate_columns): rework the size allocation + mechanism to only recalculate the expand values if the width of the + widget, content or the column configuration has changed, + (gtk_tree_view_size_allocate): move call to size_allocate_columns() + to before the adjustment updates so the proper width is used after + we updated it, + (gtk_tree_view_button_press), (gtk_tree_view_motion_resize_column): + use the column width minus the expand value for the resized width, + (gtk_tree_view_move_column_after): update call to + gtk_tree_view_size_allocate_columns(). + + * gtk/gtktreeviewcolumn.c (gtk_tree_view_column_set_expand): set use + resized width to FALSE. + + * tests/Makefile.am: + * tests/testtreecolumnsizing.c: new interactive test program + for testing column resizing with different column configurations. + 2008-07-11 Simos Xenitellis * gtk/compose-parse.py: diff --git a/gtk/gtktreeprivate.h b/gtk/gtktreeprivate.h index 51bb9511f8..384c176982 100644 --- a/gtk/gtktreeprivate.h +++ b/gtk/gtktreeprivate.h @@ -236,6 +236,8 @@ struct _GtkTreeViewPrivate guint in_grab : 1; + guint post_validation_flag : 1; + /* Auto expand/collapse timeout in hover mode */ guint auto_expand_timeout; @@ -268,6 +270,10 @@ struct _GtkTreeViewPrivate GdkGC *tree_line_gc; gint tooltip_column; + + gint last_extra_space; + gint last_extra_space_per_column; + gint last_number_of_expand_columns; }; #ifdef __GNUC__ diff --git a/gtk/gtktreeview.c b/gtk/gtktreeview.c index 5da659d6a7..afa0629704 100644 --- a/gtk/gtktreeview.c +++ b/gtk/gtktreeview.c @@ -1370,6 +1370,8 @@ gtk_tree_view_init (GtkTreeView *tree_view) tree_view->priv->tree_lines_enabled = FALSE; tree_view->priv->tooltip_column = -1; + + tree_view->priv->post_validation_flag = FALSE; } @@ -1988,6 +1990,7 @@ gtk_tree_view_update_size (GtkTreeView *tree_view) tree_view->priv->prev_width = tree_view->priv->width; tree_view->priv->width = 0; + /* keep this in sync with size_allocate below */ for (list = tree_view->priv->columns, i = 0; list; list = list->next, i++) { @@ -2150,18 +2153,20 @@ gtk_tree_view_get_real_requested_width_from_column (GtkTreeView *tree_view /* GtkWidget::size_allocate helper */ static void -gtk_tree_view_size_allocate_columns (GtkWidget *widget) +gtk_tree_view_size_allocate_columns (GtkWidget *widget, + gboolean *width_changed) { GtkTreeView *tree_view; GList *list, *first_column, *last_column; GtkTreeViewColumn *column; GtkAllocation allocation; gint width = 0; - gint extra, extra_per_column; + gint extra, extra_per_column, extra_for_last; gint full_requested_width = 0; gint number_of_expand_columns = 0; gboolean column_changed = FALSE; gboolean rtl; + gboolean update_expand; tree_view = GTK_TREE_VIEW (widget); @@ -2196,12 +2201,42 @@ gtk_tree_view_size_allocate_columns (GtkWidget *widget) number_of_expand_columns++; } - extra = MAX (widget->allocation.width - full_requested_width, 0); + /* Only update the expand value if the width of the widget has changed, + * or the number of expand columns has changed, or if there are no expand + * columns, or if we didn't have an size-allocation yet after the + * last validated node. + */ + update_expand = (width_changed && *width_changed == TRUE) + || number_of_expand_columns != tree_view->priv->last_number_of_expand_columns + || number_of_expand_columns == 0 + || tree_view->priv->post_validation_flag == TRUE; + + tree_view->priv->post_validation_flag = FALSE; + + if (!update_expand) + { + extra = tree_view->priv->last_extra_space; + extra_for_last = MAX (widget->allocation.width - full_requested_width - extra, 0); + } + else + { + extra = MAX (widget->allocation.width - full_requested_width, 0); + extra_for_last = 0; + + tree_view->priv->last_extra_space = extra; + } + if (number_of_expand_columns > 0) extra_per_column = extra/number_of_expand_columns; else extra_per_column = 0; + if (update_expand) + { + tree_view->priv->last_extra_space_per_column = extra_per_column; + tree_view->priv->last_number_of_expand_columns = number_of_expand_columns; + } + for (list = (rtl ? last_column : first_column); list != (rtl ? first_column->prev : last_column->next); list = (rtl ? list->prev : list->next)) @@ -2257,6 +2292,12 @@ gtk_tree_view_size_allocate_columns (GtkWidget *widget) column->width += extra; } + /* In addition to expand, the last column can get even more + * extra space so all available space is filled up. + */ + if (extra_for_last > 0 && list == last_column) + column->width += extra_for_last; + g_object_notify (G_OBJECT (column), "width"); allocation.width = column->width; @@ -2274,6 +2315,12 @@ gtk_tree_view_size_allocate_columns (GtkWidget *widget) TREE_VIEW_DRAG_WIDTH, allocation.height); } + /* We change the width here. The user might have been resizing columns, + * so the total width of the tree view changes. + */ + tree_view->priv->width = width; + *width_changed = TRUE; + if (column_changed) gtk_widget_queue_draw (GTK_WIDGET (tree_view)); } @@ -2310,6 +2357,10 @@ gtk_tree_view_size_allocate (GtkWidget *widget, gtk_widget_size_allocate (child->widget, &allocation); } + /* We size-allocate the columns first because the width of the + * tree view (used in updating the adjustments below) might change. + */ + gtk_tree_view_size_allocate_columns (widget, &width_changed); tree_view->priv->hadjustment->page_size = allocation->width; tree_view->priv->hadjustment->page_increment = allocation->width * 0.9; @@ -2318,28 +2369,30 @@ gtk_tree_view_size_allocate (GtkWidget *widget, tree_view->priv->hadjustment->upper = MAX (tree_view->priv->hadjustment->page_size, tree_view->priv->width); if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL) - { + { if (allocation->width < tree_view->priv->width) - { - if (tree_view->priv->init_hadjust_value) - { - tree_view->priv->hadjustment->value = MAX (tree_view->priv->width - allocation->width, 0); - tree_view->priv->init_hadjust_value = FALSE; - } - else if(allocation->width != old_width) - tree_view->priv->hadjustment->value = CLAMP(tree_view->priv->hadjustment->value - allocation->width + old_width, 0, tree_view->priv->width - allocation->width); - else - tree_view->priv->hadjustment->value = CLAMP(tree_view->priv->width - (tree_view->priv->prev_width - tree_view->priv->hadjustment->value), 0, tree_view->priv->width - allocation->width); - } + { + if (tree_view->priv->init_hadjust_value) + { + tree_view->priv->hadjustment->value = MAX (tree_view->priv->width - allocation->width, 0); + tree_view->priv->init_hadjust_value = FALSE; + } + else if (allocation->width != old_width) + { + tree_view->priv->hadjustment->value = CLAMP (tree_view->priv->hadjustment->value - allocation->width + old_width, 0, tree_view->priv->width - allocation->width); + } + else + tree_view->priv->hadjustment->value = CLAMP (tree_view->priv->width - (tree_view->priv->prev_width - tree_view->priv->hadjustment->value), 0, tree_view->priv->width - allocation->width); + } else - { - tree_view->priv->hadjustment->value = 0; - tree_view->priv->init_hadjust_value = TRUE; - } - } + { + tree_view->priv->hadjustment->value = 0; + tree_view->priv->init_hadjust_value = TRUE; + } + } else - if (tree_view->priv->hadjustment->value + allocation->width > tree_view->priv->width) - tree_view->priv->hadjustment->value = MAX (tree_view->priv->width - allocation->width, 0); + if (tree_view->priv->hadjustment->value + allocation->width > tree_view->priv->width) + tree_view->priv->hadjustment->value = MAX (tree_view->priv->width - allocation->width, 0); gtk_adjustment_changed (tree_view->priv->hadjustment); @@ -2379,8 +2432,6 @@ gtk_tree_view_size_allocate (GtkWidget *widget, allocation->height - TREE_VIEW_HEADER_HEIGHT (tree_view)); } - gtk_tree_view_size_allocate_columns (widget); - if (tree_view->priv->tree == NULL) invalidate_empty_focus (tree_view); @@ -2820,7 +2871,7 @@ gtk_tree_view_button_press (GtkWidget *widget, gtk_grab_add (widget); GTK_TREE_VIEW_SET_FLAG (tree_view, GTK_TREE_VIEW_IN_COLUMN_RESIZE); - column->resized_width = column->width; + column->resized_width = column->width - tree_view->priv->last_extra_space_per_column; /* block attached dnd signal handler */ drag_data = g_object_get_data (G_OBJECT (widget), "gtk-site-data"); @@ -3524,6 +3575,8 @@ gtk_tree_view_motion_resize_column (GtkWidget *widget, { column->use_resized_width = TRUE; column->resized_width = new_width; + if (column->expand) + column->resized_width -= tree_view->priv->last_extra_space_per_column; gtk_widget_queue_resize (widget); } @@ -5619,6 +5672,7 @@ validate_row (GtkTreeView *tree_view, _gtk_rbtree_node_set_height (tree, node, height); } _gtk_rbtree_node_mark_valid (tree, node); + tree_view->priv->post_validation_flag = TRUE; return retval; } @@ -11408,7 +11462,7 @@ gtk_tree_view_move_column_after (GtkTreeView *tree_view, if (GTK_WIDGET_REALIZED (tree_view)) { gtk_widget_queue_resize (GTK_WIDGET (tree_view)); - gtk_tree_view_size_allocate_columns (GTK_WIDGET (tree_view)); + gtk_tree_view_size_allocate_columns (GTK_WIDGET (tree_view), NULL); } g_signal_emit (tree_view, tree_view_signals[COLUMNS_CHANGED], 0); diff --git a/gtk/gtktreeviewcolumn.c b/gtk/gtktreeviewcolumn.c index 2abd45bed5..a0450bf3d4 100644 --- a/gtk/gtktreeviewcolumn.c +++ b/gtk/gtktreeviewcolumn.c @@ -2141,6 +2141,13 @@ gtk_tree_view_column_set_expand (GtkTreeViewColumn *tree_column, tree_column->tree_view != NULL && GTK_WIDGET_REALIZED (tree_column->tree_view)) { + /* We want to continue using the original width of the + * column that includes additional space added by the user + * resizing the columns and possibly extra (expanded) space, which + * are not included in the resized width. + */ + tree_column->use_resized_width = FALSE; + gtk_widget_queue_resize (tree_column->tree_view); } diff --git a/tests/Makefile.am b/tests/Makefile.am index edd18b01da..fa5ce14117 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -72,6 +72,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \ testtreefocus \ testtreeflow \ testtreecolumns \ + testtreecolumnsizing \ testtreesort \ treestoretest \ testxinerama \ @@ -145,6 +146,7 @@ testtreeview_DEPENDENCIES = $(DEPS) testtreefocus_DEPENDENCIES = $(DEPS) testtreeflow_DEPENDENCIES = $(DEPS) testtreecolumns_DEPENDENCIES = $(DEPS) +testtreecolumnsizing_DEPENDENCIES = $(DEPS) testtreesort_DEPENDENCIES = $(DEPS) treestoretest_DEPENDENCIES = $(TEST_DEPS) testxinerama_DEPENDENCIES = $(TEST_DEPS) @@ -200,6 +202,7 @@ testtreeview_LDADD = $(LDADDS) testtreefocus_LDADD = $(LDADDS) testtreeflow_LDADD = $(LDADDS) testtreecolumns_LDADD = $(LDADDS) +testtreecolumnsizing_LDADD = $(LDADDS) testtreesort_LDADD = $(LDADDS) testtext_LDADD = $(LDADDS) treestoretest_LDADD = $(LDADDS) diff --git a/tests/testtreecolumnsizing.c b/tests/testtreecolumnsizing.c new file mode 100644 index 0000000000..13c0f47df0 --- /dev/null +++ b/tests/testtreecolumnsizing.c @@ -0,0 +1,237 @@ +/* testtreecolumnsizing.c: Test case for tree view column resizing. + * + * Copyright (C) 2008 Kristian Rietveld + * + * This work is provided "as is"; redistribution and modification + * in whole or in part, in any medium, physical or electronic is + * permitted without restriction. + * + * This work 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. + * + * In no event shall the authors or contributors be liable for any + * direct, indirect, incidental, special, exemplary, or consequential + * damages (including, but not limited to, procurement of substitute + * goods or services; loss of use, data, or profits; or business + * interruption) however caused and on any theory of liability, whether + * in contract, strict liability, or tort (including negligence or + * otherwise) arising in any way out of the use of this software, even + * if advised of the possibility of such damage. + */ + +#include +#include + +#define NO_EXPAND "No expandable columns" +#define SINGLE_EXPAND "One expandable column" +#define MULTI_EXPAND "Multiple expandable columns" +#define LAST_EXPAND "Last column is expandable" +#define BORDER_EXPAND "First and last columns are expandable" +#define ALL_EXPAND "All columns are expandable" + +#define N_ROWS 10 + + +static GtkTreeModel * +create_model (void) +{ + int i; + GtkListStore *store; + + store = gtk_list_store_new (5, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING); + + for (i = 0; i < N_ROWS; i++) + { + gchar *str; + + str = g_strdup_printf ("Row %d", i); + gtk_list_store_insert_with_values (store, NULL, i, + 0, str, + 1, "Blah blah blah blah blah", + 2, "Less blah", + 3, "Medium length", + 4, "Eek", + -1); + g_free (str); + } + + return GTK_TREE_MODEL (store); +} + +static void +toggle_long_content_row (GtkToggleButton *button, + gpointer user_data) +{ + GtkTreeModel *model; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (user_data)); + if (gtk_tree_model_iter_n_children (model, NULL) == N_ROWS) + { + gtk_list_store_insert_with_values (GTK_LIST_STORE (model), NULL, N_ROWS, + 0, "Very very very very longggggg", + 1, "Blah blah blah blah blah", + 2, "Less blah", + 3, "Medium length", + 4, "Eek we make the scrollbar appear", + -1); + } + else + { + GtkTreeIter iter; + + gtk_tree_model_iter_nth_child (model, &iter, NULL, N_ROWS); + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + } +} + +static void +combo_box_changed (GtkComboBox *combo_box, + gpointer user_data) +{ + gchar *str; + GList *list; + GList *columns; + + str = gtk_combo_box_get_active_text (GTK_COMBO_BOX (combo_box)); + if (!str) + return; + + columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (user_data)); + + if (!strcmp (str, NO_EXPAND)) + { + for (list = columns; list; list = list->next) + gtk_tree_view_column_set_expand (list->data, FALSE); + } + else if (!strcmp (str, SINGLE_EXPAND)) + { + for (list = columns; list; list = list->next) + { + if (list->prev && !list->prev->prev) + /* This is the second column */ + gtk_tree_view_column_set_expand (list->data, TRUE); + else + gtk_tree_view_column_set_expand (list->data, FALSE); + } + } + else if (!strcmp (str, MULTI_EXPAND)) + { + for (list = columns; list; list = list->next) + { + if (list->prev && !list->prev->prev) + /* This is the second column */ + gtk_tree_view_column_set_expand (list->data, TRUE); + else if (list->prev && !list->prev->prev->prev) + /* This is the third column */ + gtk_tree_view_column_set_expand (list->data, TRUE); + else + gtk_tree_view_column_set_expand (list->data, FALSE); + } + } + else if (!strcmp (str, LAST_EXPAND)) + { + for (list = columns; list->next; list = list->next) + gtk_tree_view_column_set_expand (list->data, FALSE); + /* This is the last column */ + gtk_tree_view_column_set_expand (list->data, TRUE); + } + else if (!strcmp (str, BORDER_EXPAND)) + { + gtk_tree_view_column_set_expand (columns->data, TRUE); + for (list = columns->next; list->next; list = list->next) + gtk_tree_view_column_set_expand (list->data, FALSE); + /* This is the last column */ + gtk_tree_view_column_set_expand (list->data, TRUE); + } + else if (!strcmp (str, ALL_EXPAND)) + { + for (list = columns; list; list = list->next) + gtk_tree_view_column_set_expand (list->data, TRUE); + } + + g_free (str); + g_list_free (columns); +} + +int +main (int argc, char **argv) +{ + int i; + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *combo_box; + GtkWidget *sw; + GtkWidget *tree_view; + GtkWidget *button; + + gtk_init (&argc, &argv); + + /* Window and box */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size (GTK_WINDOW (window), 640, 480); + g_signal_connect (window, "delete-event", G_CALLBACK (gtk_main_quit), NULL); + gtk_container_set_border_width (GTK_CONTAINER (window), 5); + + vbox = gtk_vbox_new (FALSE, 5); + gtk_container_add (GTK_CONTAINER (window), vbox); + + /* Option menu contents */ + combo_box = gtk_combo_box_new_text (); + + gtk_combo_box_append_text (GTK_COMBO_BOX (combo_box), NO_EXPAND); + gtk_combo_box_append_text (GTK_COMBO_BOX (combo_box), SINGLE_EXPAND); + gtk_combo_box_append_text (GTK_COMBO_BOX (combo_box), MULTI_EXPAND); + gtk_combo_box_append_text (GTK_COMBO_BOX (combo_box), LAST_EXPAND); + gtk_combo_box_append_text (GTK_COMBO_BOX (combo_box), BORDER_EXPAND); + gtk_combo_box_append_text (GTK_COMBO_BOX (combo_box), ALL_EXPAND); + + gtk_box_pack_start (GTK_BOX (vbox), combo_box, FALSE, FALSE, 0); + + /* Scrolled window and tree view */ + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); + + tree_view = gtk_tree_view_new_with_model (create_model ()); + gtk_container_add (GTK_CONTAINER (sw), tree_view); + + for (i = 0; i < 5; i++) + { + GtkTreeViewColumn *column; + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree_view), + i, "Header", + gtk_cell_renderer_text_new (), + "text", i, + NULL); + + column = gtk_tree_view_get_column (GTK_TREE_VIEW (tree_view), i); + gtk_tree_view_column_set_resizable (column, TRUE); + } + + /* Toggle button for long content row */ + button = gtk_toggle_button_new_with_label ("Toggle long content row"); + g_signal_connect (button, "toggled", + G_CALLBACK (toggle_long_content_row), tree_view); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + + /* Set up option menu callback and default item */ + g_signal_connect (combo_box, "changed", + G_CALLBACK (combo_box_changed), tree_view); + gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), 0); + + /* Done */ + gtk_widget_show_all (window); + + gtk_main (); + + return 0; +}