From 31abdf723aa5af06bd8ea730e9575d2f38ff0a98 Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Sat, 16 Oct 2010 18:01:33 +0900 Subject: [PATCH] Allow GtkComboBox popup to be wider than the combo itself. This patch adds a GtkComboBox:popup-fixed-width to decide if the popup's width should be a fixed width matching the combo's allocated width. The patch includes a kindof hack to work around treeviews currently not supporting height-for-width geometry (for list-mode only), this hack can be safely removed once treeviews start reporting natural widths properly. --- gtk/gtkcombobox.c | 162 ++++++++++++++++++++++++++++++++++++++++------ gtk/gtkcombobox.h | 4 ++ tests/testcombo.c | 54 ++++++++++++++++ 3 files changed, 199 insertions(+), 21 deletions(-) diff --git a/gtk/gtkcombobox.c b/gtk/gtkcombobox.c index 1a86277fe8..ea98ef15e9 100644 --- a/gtk/gtkcombobox.c +++ b/gtk/gtkcombobox.c @@ -155,6 +155,7 @@ struct _GtkComboBoxPrivate guint focus_on_click : 1; guint button_sensitivity : 2; guint has_entry : 1; + guint popup_fixed_width : 1; GtkTreeViewRowSeparatorFunc row_separator_func; gpointer row_separator_data; @@ -243,7 +244,8 @@ enum { PROP_BUTTON_SENSITIVITY, PROP_EDITING_CANCELED, PROP_HAS_ENTRY, - PROP_ENTRY_TEXT_COLUMN + PROP_ENTRY_TEXT_COLUMN, + PROP_POPUP_FIXED_WIDTH }; static guint combo_box_signals[LAST_SIGNAL] = {0,}; @@ -949,6 +951,24 @@ gtk_combo_box_class_init (GtkComboBoxClass *klass) -1, G_MAXINT, -1, GTK_PARAM_READWRITE)); + /** + * GtkComboBox:popup-fixed-width: + * + * Whether the popup's width should be a fixed width matching the + * allocated width of the combo box. + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_POPUP_FIXED_WIDTH, + g_param_spec_boolean ("popup-fixed-width", + P_("Popup Fixed Width"), + P_("Whether the popup's width should be a " + "fixed width matching the allocated width " + "of the combo box"), + TRUE, + GTK_PARAM_READWRITE)); + gtk_widget_class_install_style_property (widget_class, g_param_spec_boolean ("appears-as-list", P_("Appears as list"), @@ -1056,6 +1076,7 @@ gtk_combo_box_init (GtkComboBox *combo_box) priv->focus_on_click = TRUE; priv->button_sensitivity = GTK_SENSITIVITY_AUTO; priv->has_entry = FALSE; + priv->popup_fixed_width = TRUE; priv->text_column = -1; priv->text_renderer = NULL; @@ -1133,6 +1154,11 @@ gtk_combo_box_set_property (GObject *object, g_value_get_enum (value)); break; + case PROP_POPUP_FIXED_WIDTH: + gtk_combo_box_set_popup_fixed_width (combo_box, + g_value_get_boolean (value)); + break; + case PROP_EDITING_CANCELED: combo_box->priv->editing_canceled = g_value_get_boolean (value); break; @@ -1206,6 +1232,10 @@ gtk_combo_box_get_property (GObject *object, g_value_set_enum (value, combo_box->priv->button_sensitivity); break; + case PROP_POPUP_FIXED_WIDTH: + g_value_set_boolean (value, combo_box->priv->popup_fixed_width); + break; + case PROP_EDITING_CANCELED: g_value_set_boolean (value, priv->editing_canceled); break; @@ -1677,8 +1707,10 @@ gtk_combo_box_menu_position_below (GtkMenu *menu, if (GTK_SHADOW_NONE != combo_box->priv->shadow_type) sx -= gtk_widget_get_style (GTK_WIDGET (combo_box))->xthickness; - gtk_widget_get_preferred_size (GTK_WIDGET (menu), - &req, NULL); + if (combo_box->priv->popup_fixed_width) + gtk_widget_get_preferred_size (GTK_WIDGET (menu), &req, NULL); + else + gtk_widget_get_preferred_size (GTK_WIDGET (menu), NULL, &req); if (gtk_widget_get_direction (GTK_WIDGET (combo_box)) == GTK_TEXT_DIR_LTR) *x = sx; @@ -1737,7 +1769,10 @@ gtk_combo_box_menu_position_over (GtkMenu *menu, menu_xpos = allocation.x; menu_ypos = allocation.y + allocation.height / 2 - 2; - gtk_widget_get_preferred_width (GTK_WIDGET (menu), &menu_width, NULL); + if (combo_box->priv->popup_fixed_width) + gtk_widget_get_preferred_width (GTK_WIDGET (menu), &menu_width, NULL); + else + gtk_widget_get_preferred_width (GTK_WIDGET (menu), NULL, &menu_width); if (active != NULL) { @@ -1795,7 +1830,6 @@ gtk_combo_box_menu_position (GtkMenu *menu, GtkComboBoxPrivate *priv = combo_box->priv; GtkWidget *menu_item; - if (priv->wrap_width > 0 || priv->cell_view == NULL) gtk_combo_box_menu_position_below (menu, x, y, push_in, user_data); else @@ -1854,16 +1888,47 @@ gtk_combo_box_list_position (GtkComboBox *combo_box, hpolicy = vpolicy = GTK_POLICY_NEVER; gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), hpolicy, vpolicy); - gtk_widget_get_preferred_size (priv->scrolled_window, - &popup_req, NULL); - if (popup_req.width > *width) + /* XXX This set_size_request call is part of the hack outlined below and can + * go away once height-for-width is implemented on treeviews. */ + gtk_widget_set_size_request (priv->tree_view, -1, -1); + + if (combo_box->priv->popup_fixed_width) { - hpolicy = GTK_POLICY_ALWAYS; - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), - hpolicy, vpolicy); - gtk_widget_get_preferred_size (priv->scrolled_window, - &popup_req, NULL); + gtk_widget_get_preferred_size (priv->scrolled_window, &popup_req, NULL); + + if (popup_req.width > *width) + { + hpolicy = GTK_POLICY_ALWAYS; + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), + hpolicy, vpolicy); + } + } + else + { + gtk_combo_box_remeasure (combo_box); + + if (priv->natural_width > *width) + { + hpolicy = GTK_POLICY_NEVER; + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), + hpolicy, vpolicy); + + + /* XXX Currently we set the size-request on the internal treeview to be + * the natural width of the cells, this hack can go away once our + * treeview does height-for-width properly (i.e. just adjust *width + * here to be the natural width request of the scrolled-window). + * + * I can't tell why the magic number 5 is needed here (i.e. without it + * treeviews are left ellipsizing) , however it this all should be + * removed with height-for-width treeviews. + */ + gtk_widget_set_size_request (priv->tree_view, priv->natural_width + 5, -1); + gtk_widget_get_preferred_size (priv->scrolled_window, NULL, &popup_req); + + *width = popup_req.width; + } } *height = popup_req.height; @@ -1872,6 +1937,9 @@ gtk_combo_box_list_position (GtkComboBox *combo_box, monitor_num = gdk_screen_get_monitor_at_window (screen, window); gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); + if (gtk_widget_get_direction (GTK_WIDGET (combo_box)) == GTK_TEXT_DIR_RTL) + *x = *x + allocation.width - *width; + if (*x < monitor.x) *x = monitor.x; else if (*x + *width > monitor.x + monitor.width) @@ -2014,7 +2082,7 @@ gtk_combo_box_menu_popup (GtkComboBox *combo_box, GtkComboBoxPrivate *priv = combo_box->priv; GtkTreePath *path; gint active_item; - gint width, min_width; + gint width, min_width, nat_width; update_menu_sensitivity (combo_box, priv->popup_widget); @@ -2039,10 +2107,14 @@ gtk_combo_box_menu_popup (GtkComboBox *combo_box, gtk_widget_get_allocation (GTK_WIDGET (combo_box), &allocation); width = allocation.width; gtk_widget_set_size_request (priv->popup_widget, -1, -1); - gtk_widget_get_preferred_width (priv->popup_widget, &min_width, NULL); - - gtk_widget_set_size_request (priv->popup_widget, - MAX (width, min_width), -1); + gtk_widget_get_preferred_width (priv->popup_widget, &min_width, &nat_width); + + if (combo_box->priv->popup_fixed_width) + width = MAX (width, min_width); + else + width = MAX (width, nat_width); + + gtk_widget_set_size_request (priv->popup_widget, width, -1); } gtk_menu_popup (GTK_MENU (priv->popup_widget), @@ -2440,7 +2512,7 @@ gtk_combo_box_size_allocate (GtkWidget *widget, if (gtk_widget_get_visible (priv->popup_widget)) { - gint width, min_width; + gint width, menu_width; if (priv->wrap_width == 0) { @@ -2449,9 +2521,14 @@ gtk_combo_box_size_allocate (GtkWidget *widget, gtk_widget_get_allocation (GTK_WIDGET (combo_box), &combo_box_allocation); width = combo_box_allocation.width; gtk_widget_set_size_request (priv->popup_widget, -1, -1); - gtk_widget_get_preferred_width (priv->popup_widget, &min_width, NULL); + + if (combo_box->priv->popup_fixed_width) + gtk_widget_get_preferred_width (priv->popup_widget, &menu_width, NULL); + else + gtk_widget_get_preferred_width (priv->popup_widget, NULL, &menu_width); + gtk_widget_set_size_request (priv->popup_widget, - MAX (width, min_width), -1); + MAX (width, menu_width), -1); } /* reposition the menu after giving it a new width */ @@ -6021,6 +6098,49 @@ gtk_combo_box_set_title (GtkComboBox *combo_box, } } + +/** + * gtk_combo_box_set_popup_fixed_width: + * @combo_box: a #GtkComboBox + * @fixed: whether to use a fixed popup width + * + * Specifies whether the popup's width should be a fixed width matching + * the allocated width of the combo box. + * + * Since: 3.0 + **/ +void +gtk_combo_box_set_popup_fixed_width (GtkComboBox *combo_box, + gboolean fixed) +{ + g_return_if_fail (GTK_IS_COMBO_BOX (combo_box)); + + if (combo_box->priv->popup_fixed_width != fixed) + { + combo_box->priv->popup_fixed_width = fixed; + + g_object_notify (G_OBJECT (combo_box), "popup-fixed-width"); + } +} + +/** + * gtk_combo_box_get_popup_fixed_width: + * @combo_box: a #GtkComboBox + * + * Gets whether the popup uses a fixed width matching + * the allocated width of the combo box. + * + * Since: 3.0 + **/ +gboolean +gtk_combo_box_get_popup_fixed_width (GtkComboBox *combo_box) +{ + g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE); + + return combo_box->priv->popup_fixed_width; +} + + /** * gtk_combo_box_get_popup_accessible: * @combo_box: a #GtkComboBox diff --git a/gtk/gtkcombobox.h b/gtk/gtkcombobox.h index 42476b0572..df59ed6476 100644 --- a/gtk/gtkcombobox.h +++ b/gtk/gtkcombobox.h @@ -124,6 +124,10 @@ void gtk_combo_box_set_entry_text_column (GtkComboBox *com gint text_column); gint gtk_combo_box_get_entry_text_column (GtkComboBox *combo_box); +void gtk_combo_box_set_popup_fixed_width (GtkComboBox *combo_box, + gboolean fixed); +gboolean gtk_combo_box_get_popup_fixed_width (GtkComboBox *combo_box); + #ifndef GTK_DISABLE_DEPRECATED /* convenience -- text */ diff --git a/tests/testcombo.c b/tests/testcombo.c index 9e0fc40548..65e5b5ec91 100644 --- a/tests/testcombo.c +++ b/tests/testcombo.c @@ -372,6 +372,39 @@ create_list_blaat (void) return GTK_TREE_MODEL (store); } + +static GtkTreeModel * +create_list_long (void) +{ + GtkTreeIter iter; + GtkListStore *store; + + store = gtk_list_store_new (1, G_TYPE_STRING); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, "here is some long long text that grows out of the combo's allocation", + -1); + + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, "with at least a few of these rows", + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, "so that we can get some ellipsized text here", + -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, "and see the combo box menu being allocated without any constraints", + -1); + + return GTK_TREE_MODEL (store); +} + /* blaat */ static GtkTreeModel * create_phylogenetic_tree (void) @@ -1343,6 +1376,27 @@ main (int argc, char **argv) gdk_threads_add_timeout (1000, (GSourceFunc) capital_animation, model); #endif + /* Ellipsizing growing combos */ + tmp = gtk_frame_new ("Unconstrained Menu"); + gtk_box_pack_start (GTK_BOX (mainbox), tmp, FALSE, FALSE, 0); + + boom = gtk_vbox_new (FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (boom), 5); + gtk_container_add (GTK_CONTAINER (tmp), boom); + + model = create_list_long (); + combobox = gtk_combo_box_new_with_model (model); + g_object_unref (model); + gtk_container_add (GTK_CONTAINER (boom), combobox); + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL); + + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combobox), renderer, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combobox), renderer, + "text", 0, NULL); + gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), 0); + gtk_combo_box_set_popup_fixed_width (GTK_COMBO_BOX (combobox), FALSE); + gtk_widget_show_all (window); gtk_main ();