From 7e1a4800fa134365724d3310fc34ec5ccbc776e9 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 21 Dec 2013 21:58:24 -0500 Subject: [PATCH] Redo header bar decorations once more Applications need a way to fix or adapt the decoration layout, for situations like split header bars. Setting the layout from the theme with a style property did not offer a good way to do this, and the ::show-close-button property does not provide fine-grained control. To improve the situation, move the layout string to a property of GtkHeaderBar which is backed by a setting. This allows platforms to set a default button layout independent of the theme, while applications can override the default. The style GtkWindow style property is now deprecated and ignored. --- docs/reference/gtk/gtk3-sections.txt | 4 +- gdk/x11/gdksettings.c | 1 + gtk/gtkheaderbar.c | 188 ++++++++++++++++++++++++--- gtk/gtkheaderbar.h | 6 + gtk/gtksettings.c | 35 +++++ tests/testtitlebar.c | 22 +--- 6 files changed, 218 insertions(+), 38 deletions(-) diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt index 1be171dd6f..ae68281376 100644 --- a/docs/reference/gtk/gtk3-sections.txt +++ b/docs/reference/gtk/gtk3-sections.txt @@ -7696,8 +7696,8 @@ gtk_header_bar_pack_start gtk_header_bar_pack_end gtk_header_bar_set_show_close_button gtk_header_bar_get_show_close_button -gtk_header_bar_set_show_fallback_app_menu -gtk_header_bar_get_show_fallback_app_menu +gtk_header_bar_set_decoration_layout +gtk_header_bar_get_decoration_layout GTK_TYPE_HEADER_BAR diff --git a/gdk/x11/gdksettings.c b/gdk/x11/gdksettings.c index f9bc28e206..ac92cca833 100644 --- a/gdk/x11/gdksettings.c +++ b/gdk/x11/gdksettings.c @@ -58,6 +58,7 @@ static const struct { {"Gtk/ShellShowsAppMenu", "gtk-shell-shows-app-menu"}, {"Gtk/ShellShowsMenubar", "gtk-shell-shows-menubar"}, {"Gtk/ShellShowsDesktop", "gtk-shell-shows-desktop"}, + {"Gtk/DecorationLayout", "gtk-decoration-layout"}, {"Gtk/EnablePrimaryPaste", "gtk-enable-primary-paste"}, {"Gtk/RecentFilesMaxAge", "gtk-recent-files-max-age"}, {"Gtk/RecentFilesEnabled", "gtk-recent-files-enabled"}, diff --git a/gtk/gtkheaderbar.c b/gtk/gtkheaderbar.c index 1d8dac68b3..d1f3fa4188 100644 --- a/gtk/gtkheaderbar.c +++ b/gtk/gtkheaderbar.c @@ -37,11 +37,17 @@ * @Title: GtkHeaderBar * @See_also: #GtkBox * - * GtkHeaderBar is similar to a horizontal #GtkBox, it allows - * to place children at the start or the end. In addition, - * it allows a title to be displayed. The title will be - * centered with respect to the width of the box, even if the children - * at either side take up different amounts of space. + * GtkHeaderBar is similar to a horizontal #GtkBox, it allows to place + * children at the start or the end. In addition, it allows a title and + * subtitle to be displayed. The title will be centered with respect to + * the width of the box, even if the children at either side take up + * different amounts of space. The height of the titlebar will be + * set to provide sufficient space for the subtitle, even if none is + * currently set. If a subtitle is not needed, the space reservation + * can be turned off with gtk_header_bar_set_has_subtitle(). + * + * GtkHeaderBar can add typical window frame controls, such as minimize, + * maximize and close buttons, or the window icon. */ #define DEFAULT_SPACING 6 @@ -62,6 +68,8 @@ struct _GtkHeaderBarPrivate GList *children; gboolean shows_wm_decorations; + gchar *decoration_layout; + gboolean decoration_layout_set; GtkWidget *titlebar_start_box; GtkWidget *titlebar_end_box; @@ -88,6 +96,8 @@ enum { PROP_CUSTOM_TITLE, PROP_SPACING, PROP_SHOW_CLOSE_BUTTON, + PROP_DECORATION_LAYOUT, + PROP_DECORATION_LAYOUT_SET }; enum { @@ -265,6 +275,7 @@ void _gtk_header_bar_update_window_buttons (GtkHeaderBar *bar) { GtkHeaderBarPrivate *priv = gtk_header_bar_get_instance_private (bar); + GtkWidget *widget = GTK_WIDGET (bar); GtkWindow *window; GtkTextDirection direction; gchar *layout_desc; @@ -272,14 +283,11 @@ _gtk_header_bar_update_window_buttons (GtkHeaderBar *bar) gint i, j; GMenuModel *menu; gboolean shown_by_shell; + GdkWindowTypeHint type_hint; - if (!gtk_widget_get_realized (GTK_WIDGET (bar))) + if (!gtk_widget_get_realized (widget)) return; - window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (bar))); - - direction = gtk_widget_get_direction (GTK_WIDGET (window)); - if (priv->titlebar_icon) { gtk_widget_destroy (priv->titlebar_icon); @@ -319,18 +327,28 @@ _gtk_header_bar_update_window_buttons (GtkHeaderBar *bar) if (!priv->shows_wm_decorations) return; - gtk_widget_style_get (GTK_WIDGET (window), - "decoration-button-layout", &layout_desc, - NULL); + direction = gtk_widget_get_direction (widget); - g_object_get (gtk_widget_get_settings (GTK_WIDGET (window)), - "gtk-shell-shows-app-menu", &shown_by_shell, NULL); + g_object_get (gtk_widget_get_settings (widget), + "gtk-shell-shows-app-menu", &shown_by_shell, + "gtk-decoration-layout", &layout_desc, + NULL); + + if (priv->decoration_layout_set) + { + g_free (layout_desc); + layout_desc = g_strdup (priv->decoration_layout); + } + + window = GTK_WINDOW (gtk_widget_get_toplevel (widget)); if (!shown_by_shell && gtk_window_get_application (window)) menu = gtk_application_get_app_menu (gtk_window_get_application (window)); else menu = NULL; + type_hint = gtk_window_get_type_hint (window); + tokens = g_strsplit (layout_desc, ":", 2); if (tokens) { @@ -357,7 +375,7 @@ _gtk_header_bar_update_window_buttons (GtkHeaderBar *bar) AtkObject *accessible; if (strcmp (t[j], "icon") == 0 && - gtk_window_get_type_hint (window) == GDK_WINDOW_TYPE_HINT_NORMAL) + type_hint == GDK_WINDOW_TYPE_HINT_NORMAL) { button = gtk_image_new (); gtk_widget_set_valign (button, GTK_ALIGN_CENTER); @@ -374,7 +392,7 @@ _gtk_header_bar_update_window_buttons (GtkHeaderBar *bar) } else if (strcmp (t[j], "menu") == 0 && menu != NULL && - gtk_window_get_type_hint (window) == GDK_WINDOW_TYPE_HINT_NORMAL) + type_hint == GDK_WINDOW_TYPE_HINT_NORMAL) { button = gtk_menu_button_new (); gtk_widget_set_valign (button, GTK_ALIGN_CENTER); @@ -393,7 +411,7 @@ _gtk_header_bar_update_window_buttons (GtkHeaderBar *bar) gtk_image_set_from_icon_name (GTK_IMAGE (priv->titlebar_icon), "process-stop-symbolic", GTK_ICON_SIZE_MENU); } else if (strcmp (t[j], "minimize") == 0 && - gtk_window_get_type_hint (window) == GDK_WINDOW_TYPE_HINT_NORMAL) + type_hint == GDK_WINDOW_TYPE_HINT_NORMAL) { button = gtk_button_new (); gtk_widget_set_valign (button, GTK_ALIGN_CENTER); @@ -412,7 +430,7 @@ _gtk_header_bar_update_window_buttons (GtkHeaderBar *bar) } else if (strcmp (t[j], "maximize") == 0 && gtk_window_get_resizable (window) && - gtk_window_get_type_hint (window) == GDK_WINDOW_TYPE_HINT_NORMAL) + type_hint == GDK_WINDOW_TYPE_HINT_NORMAL) { const gchar *icon_name; gboolean maximized = _gtk_window_get_maximized (window); @@ -541,6 +559,8 @@ gtk_header_bar_init (GtkHeaderBar *bar) priv->children = NULL; priv->spacing = DEFAULT_SPACING; priv->has_subtitle = TRUE; + priv->decoration_layout = NULL; + priv->decoration_layout_set = FALSE; init_sizing_box (bar); construct_label_box (bar); @@ -1341,6 +1361,7 @@ gtk_header_bar_finalize (GObject *object) g_free (priv->title); g_free (priv->subtitle); + g_free (priv->decoration_layout); G_OBJECT_CLASS (gtk_header_bar_parent_class)->finalize (object); } @@ -1380,6 +1401,14 @@ gtk_header_bar_get_property (GObject *object, g_value_set_boolean (value, gtk_header_bar_get_has_subtitle (bar)); break; + case PROP_DECORATION_LAYOUT: + g_value_set_string (value, gtk_header_bar_get_decoration_layout (bar)); + break; + + case PROP_DECORATION_LAYOUT_SET: + g_value_set_boolean (value, priv->decoration_layout_set); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1422,6 +1451,14 @@ gtk_header_bar_set_property (GObject *object, gtk_header_bar_set_has_subtitle (bar, g_value_get_boolean (value)); break; + case PROP_DECORATION_LAYOUT: + gtk_header_bar_set_decoration_layout (bar, g_value_get_string (value)); + break; + + case PROP_DECORATION_LAYOUT_SET: + priv->decoration_layout_set = g_value_get_boolean (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1779,6 +1816,16 @@ gtk_header_bar_class_init (GtkHeaderBarClass *class) DEFAULT_SPACING, GTK_PARAM_READWRITE)); + /** + * GtkHeaderBar:show-close-button: + * + * Whether to show window decorations. + * + * Which buttons are actually shown and where is determined + * by the #GtkHeaderBar:decoration-layout property, and by + * the state of the window (e.g. a close button will not be + * shown if the window can't be closed). + */ g_object_class_install_property (object_class, PROP_SHOW_CLOSE_BUTTON, g_param_spec_boolean ("show-close-button", @@ -1787,6 +1834,41 @@ gtk_header_bar_class_init (GtkHeaderBarClass *class) FALSE, GTK_PARAM_READWRITE)); + /** + * GtkHeaderBar:decoration-layout: + * + * The decoration layout for buttons. If this property is + * not set, the #GtkSettings:gtk-decoration-layout setting + * is used. + * + * See gtk_header_bar_set_decoration_layout() for information + * about the format of this string. + * + * Since: 3.12 + */ + g_object_class_install_property (object_class, + PROP_DECORATION_LAYOUT, + g_param_spec_string ("decoration-layout", + P_("Decoration Layout"), + P_("The layout for window decorations"), + NULL, + GTK_PARAM_READWRITE)); + + /** + * GtkHeaderBar:decoration-layout-set: + * + * Set to %TRUE if #GtkHeaderBar:decoration-layout is set. + * + * Since: 3.12 + */ + g_object_class_install_property (object_class, + PROP_DECORATION_LAYOUT_SET, + g_param_spec_boolean ("decoration-layout-set", + P_("Decoration Layout Set"), + P_("Whether the decoration-layout property has been set"), + FALSE, + GTK_PARAM_READWRITE)); + /** * GtkHeaderBar:has-subtitle: * @@ -1981,3 +2063,71 @@ gtk_header_bar_get_has_subtitle (GtkHeaderBar *bar) return priv->has_subtitle; } + +/** + * gtk_header_bar_set_decoration_layout: + * @bar: a #GtkHeaderBar + * @layout: (allow-none): a decoration layout, or %NULL to + * unset the layout + * + * Sets the decoration layout for this header bar, overriding + * the #GtkSettings:gtk-decoration-layout setting. + * + * There can be valid reasons for overriding the setting, such + * as a header bar design that does not allow for buttons to take + * room on the right, or only offers room for a single close button. + * Split header bars are another example for overriding the + * setting. + * + * The format of the string is button names, separated by commas. + * A colon separates the buttons that should appear on the left + * from those on the right. Recognized button names are minimize, + * maximize, close, icon (the window icon) and menu (a menu button + * for the fallback app menu). + * + * For example, "menu:minimize,maximize,close" specifies a menu + * on the left, and minimize, maximize and close buttons on the right. + * + * Since: 3.12 + */ +void +gtk_header_bar_set_decoration_layout (GtkHeaderBar *bar, + const gchar *layout) +{ + GtkHeaderBarPrivate *priv; + + g_return_if_fail (GTK_IS_HEADER_BAR (bar)); + + priv = gtk_header_bar_get_instance_private (bar); + + priv->decoration_layout = g_strdup (layout); + priv->decoration_layout_set = (layout != NULL); + + _gtk_header_bar_update_window_buttons (bar); + + g_object_notify (G_OBJECT (bar), "decoration-layout"); + g_object_notify (G_OBJECT (bar), "decoration-layout-set"); +} + +/** + * gtk_header_bar_get_decoration_layout: + * @bar: a #GtkHeaderBar + * + * Gets the decoration layout set with + * gtk_header_bar_set_decoration_layout(). + * + * Returns: the decoration layout + * + * Since: 3.12 + */ +const gchar * +gtk_header_bar_get_decoration_layout (GtkHeaderBar *bar) +{ + GtkHeaderBarPrivate *priv; + + g_return_val_if_fail (GTK_IS_HEADER_BAR (bar), NULL); + + priv = gtk_header_bar_get_instance_private (bar); + + return priv->decoration_layout; +} diff --git a/gtk/gtkheaderbar.h b/gtk/gtkheaderbar.h index 1df62b7773..73346e4d29 100644 --- a/gtk/gtkheaderbar.h +++ b/gtk/gtkheaderbar.h @@ -96,6 +96,12 @@ void gtk_header_bar_set_has_subtitle (GtkHeaderBar *bar, GDK_AVAILABLE_IN_3_12 gboolean gtk_header_bar_get_has_subtitle (GtkHeaderBar *bar); +GDK_AVAILABLE_IN_3_12 +void gtk_header_bar_set_decoration_layout (GtkHeaderBar *bar, + const gchar *layout); +GDK_AVAILABLE_IN_3_12 +const gchar *gtk_header_bar_get_decoration_layout (GtkHeaderBar *bar); + G_END_DECLS #endif /* __GTK_HEADER_BAR_H__ */ diff --git a/gtk/gtksettings.c b/gtk/gtksettings.c index 01f2ef81dd..3c74274286 100644 --- a/gtk/gtksettings.c +++ b/gtk/gtksettings.c @@ -214,6 +214,7 @@ enum { PROP_SHELL_SHOWS_APP_MENU, PROP_SHELL_SHOWS_MENUBAR, PROP_SHELL_SHOWS_DESKTOP, + PROP_DECORATION_LAYOUT, PROP_ENABLE_PRIMARY_PASTE, PROP_RECENT_FILES_ENABLED }; @@ -1540,6 +1541,40 @@ gtk_settings_class_init (GtkSettingsClass *class) NULL); g_assert (result == PROP_SHELL_SHOWS_DESKTOP); + /** + * GtkSettings:gtk-decoration-layout: + * + * This setting determines which buttons should be put in the + * titlebar of client-side decorated windows, and whether they + * should be placed at the left of right. + * + * The format of the string is button names, separated by commas. + * A colon separates the buttons that should appear on the left + * from those on the right. Recognized button names are minimize, + * maximize, close, icon (the window icon) and menu (a menu button + * for the fallback app menu). + * + * For example, "menu:minimize,maximize,close" specifies a menu + * on the left, and minimize, maximize and close buttons on the right. + * + * Note that buttons will only be shown when they are meaningful. + * E.g. a menu button only appears when the desktop shell does not + * show the app menu, and a close button only appears on a window + * that can be closed. + * + * Also note that the setting can be overridden with the + * #GtkHeaderBar:decoration-layout property. + * + * Since: 3.12 + */ + result = settings_install_property_parser (class, + g_param_spec_string ("gtk-decoration-layout", + P_("Decoration Layout"), + P_("The layout for window decorations"), + "menu:close", GTK_PARAM_READWRITE), + NULL); + g_assert (result == PROP_DECORATION_LAYOUT); + /** * GtkSettings:gtk-enable-primary-paste: * diff --git a/tests/testtitlebar.c b/tests/testtitlebar.c index f5d032d74c..d11bb4b903 100644 --- a/tests/testtitlebar.c +++ b/tests/testtitlebar.c @@ -3,19 +3,13 @@ static void on_text_changed (GtkEntry *entry, GParamSpec *pspec, - GtkCssProvider *provider) + GtkHeaderBar *bar) { const gchar *layout; - gchar *css; layout = gtk_entry_get_text (entry); - css = g_strdup_printf ("GtkWindow {\n" - " -GtkWindow-decoration-button-layout: '%s';\n" - "}", layout); - - gtk_css_provider_load_from_data (provider, css, -1, NULL); - g_free (css); + gtk_header_bar_set_decoration_layout (bar, layout); } static void @@ -30,7 +24,6 @@ activate (GApplication *gapp) GtkWidget *check; GtkBuilder *builder; GMenuModel *menu; - GtkCssProvider *provider; gchar *layout; g_action_map_add_action (G_ACTION_MAP (gapp), G_ACTION (g_simple_action_new ("test", NULL))); @@ -58,11 +51,6 @@ activate (GApplication *gapp) gtk_header_bar_pack_end (GTK_HEADER_BAR (header), gtk_button_new_with_label ("End")); gtk_window_set_titlebar (GTK_WINDOW (window), header); - provider = gtk_css_provider_new (); - - gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), - GTK_STYLE_PROVIDER (provider), 600); - grid = gtk_grid_new (); g_object_set (grid, "halign", GTK_ALIGN_CENTER, @@ -93,16 +81,16 @@ activate (GApplication *gapp) gtk_widget_set_halign (label, GTK_ALIGN_END); entry = gtk_entry_new (); - gtk_widget_style_get (window, "decoration-button-layout", &layout, NULL); + g_object_get (gtk_widget_get_settings (window), "gtk-decoration-layout", &layout, NULL); gtk_entry_set_text (GTK_ENTRY (entry), layout); g_free (layout); g_signal_connect (entry, "notify::text", - G_CALLBACK (on_text_changed), provider); + G_CALLBACK (on_text_changed), header); gtk_grid_attach (GTK_GRID (grid), label, 0, 2, 1, 1); gtk_grid_attach (GTK_GRID (grid), entry, 1, 2, 1, 1); - label = gtk_label_new ("Close Button"); + label = gtk_label_new ("Decorations"); gtk_widget_set_halign (label, GTK_ALIGN_END); check = gtk_check_button_new (); g_object_bind_property (header, "show-close-button",