From: Adrien Plazas Date: Thu, 10 Sep 2020 14:26:18 +0200 Subject: Add GtkHdyViewSwitcherTitle This is imported from HdyViewSwitcherTitle from libhandy 1.0.0. --- gtk/hdy-view-switcher-title-private.h | 46 +++ gtk/hdy-view-switcher-title.c | 606 ++++++++++++++++++++++++++++++++++ gtk/meson.build | 2 + gtk/ui/hdy-view-switcher-title.ui | 52 +++ 4 files changed, 706 insertions(+) create mode 100644 gtk/hdy-view-switcher-title-private.h create mode 100644 gtk/hdy-view-switcher-title.c create mode 100644 gtk/ui/hdy-view-switcher-title.ui diff --git a/gtk/hdy-view-switcher-title-private.h b/gtk/hdy-view-switcher-title-private.h new file mode 100644 index 0000000..6bc8fdf --- /dev/null +++ b/gtk/hdy-view-switcher-title-private.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 Zander Brown + * Copyright (C) 2019 Purism SPC + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include "hdy-view-switcher-private.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_HDY_VIEW_SWITCHER_TITLE (gtk_hdy_view_switcher_title_get_type()) + +G_DECLARE_FINAL_TYPE (GtkHdyViewSwitcherTitle, gtk_hdy_view_switcher_title, GTK, HDY_VIEW_SWITCHER_TITLE, GtkBin) + +GtkHdyViewSwitcherTitle *gtk_hdy_view_switcher_title_new (void); + +GtkHdyViewSwitcherPolicy gtk_hdy_view_switcher_title_get_policy (GtkHdyViewSwitcherTitle *self); +void gtk_hdy_view_switcher_title_set_policy (GtkHdyViewSwitcherTitle *self, + GtkHdyViewSwitcherPolicy policy); + +GtkStack *gtk_hdy_view_switcher_title_get_stack (GtkHdyViewSwitcherTitle *self); +void gtk_hdy_view_switcher_title_set_stack (GtkHdyViewSwitcherTitle *self, + GtkStack *stack); + +const gchar *gtk_hdy_view_switcher_title_get_title (GtkHdyViewSwitcherTitle *self); +void gtk_hdy_view_switcher_title_set_title (GtkHdyViewSwitcherTitle *self, + const gchar *title); + +const gchar *gtk_hdy_view_switcher_title_get_subtitle (GtkHdyViewSwitcherTitle *self); +void gtk_hdy_view_switcher_title_set_subtitle (GtkHdyViewSwitcherTitle *self, + const gchar *subtitle); + +gboolean gtk_hdy_view_switcher_title_get_view_switcher_enabled (GtkHdyViewSwitcherTitle *self); +void gtk_hdy_view_switcher_title_set_view_switcher_enabled (GtkHdyViewSwitcherTitle *self, + gboolean enabled); + +gboolean gtk_hdy_view_switcher_title_get_title_visible (GtkHdyViewSwitcherTitle *self); + +G_END_DECLS diff --git a/gtk/hdy-view-switcher-title.c b/gtk/hdy-view-switcher-title.c new file mode 100644 index 0000000..4cb815b --- /dev/null +++ b/gtk/hdy-view-switcher-title.c @@ -0,0 +1,606 @@ +/* + * Copyright (C) 2019 Zander Brown + * Copyright (C) 2019 Purism SPC + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" +#include + +#include "gtkbox.h" +#include "gtklabel.h" +#include "gtkprivatetypebuiltins.h" +#include "hdy-view-switcher-title-private.h" +#include "hdy-squeezer-private.h" + +/** + * SECTION:gtk-hdy-view-switcher-title + * @short_description: A view switcher title. + * @title: GtkHdyViewSwitcherTitle + * @See_also: #GtkHdyHeaderBar, #GtkHdyViewSwitcher, #GtkHdyViewSwitcherBar + * + * A widget letting you switch between multiple views offered by a #GtkStack, + * via an #GtkHdyViewSwitcher. It is designed to be used as the title widget of a + * #GtkHdyHeaderBar, and will display the window's title when the window is too + * narrow to fit the view switcher e.g. on mobile phones, or if there are less + * than two views. + * + * You can conveniently bind the #GtkHdyViewSwitcherBar:reveal property to + * #GtkHdyViewSwitcherTitle:title-visible to automatically reveal the view switcher + * bar when the title label is displayed in place of the view switcher. + * + * An example of the UI definition for a common use case: + * |[ + * + * + * + * strict + * + * + * stack + * + * + * + * + * + * + * + * + * + * + * + * stack + * + * + * + * + * + * + * ]| + * + * # CSS nodes + * + * #GtkHdyViewSwitcherTitle has a single CSS node with name viewswitchertitle. + * + * Since: 1.0 + */ + +enum { + PROP_0, + PROP_POLICY, + PROP_STACK, + PROP_TITLE, + PROP_SUBTITLE, + PROP_VIEW_SWITCHER_ENABLED, + PROP_TITLE_VISIBLE, + LAST_PROP, +}; + +struct _GtkHdyViewSwitcherTitle +{ + GtkBin parent_instance; + + GtkHdySqueezer *squeezer; + GtkLabel *subtitle_label; + GtkBox *title_box; + GtkLabel *title_label; + GtkHdyViewSwitcher *view_switcher; + + gboolean view_switcher_enabled; +}; + +static GParamSpec *props[LAST_PROP]; + +G_DEFINE_TYPE (GtkHdyViewSwitcherTitle, gtk_hdy_view_switcher_title, GTK_TYPE_BIN) + +static void +update_subtitle_label (GtkHdyViewSwitcherTitle *self) +{ + const gchar *subtitle = gtk_label_get_label (self->subtitle_label); + + gtk_widget_set_visible (GTK_WIDGET (self->subtitle_label), subtitle && subtitle[0]); + + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +static void +count_children_cb (GtkWidget *widget, + gint *count) +{ + (*count)++; +} + +static void +update_view_switcher_visible (GtkHdyViewSwitcherTitle *self) +{ + GtkStack *stack = gtk_hdy_view_switcher_get_stack (self->view_switcher); + gint count = 0; + + if (self->view_switcher_enabled && stack) + gtk_container_foreach (GTK_CONTAINER (stack), (GtkCallback) count_children_cb, &count); + + gtk_hdy_squeezer_set_child_enabled (self->squeezer, GTK_WIDGET (self->view_switcher), count > 1); +} + +static void +notify_squeezer_visible_child_cb (GObject *self) +{ + g_object_notify_by_pspec (self, props[PROP_TITLE_VISIBLE]); +} + +static void +gtk_hdy_view_switcher_title_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkHdyViewSwitcherTitle *self = GTK_HDY_VIEW_SWITCHER_TITLE (object); + + switch (prop_id) { + case PROP_POLICY: + g_value_set_enum (value, gtk_hdy_view_switcher_title_get_policy (self)); + break; + case PROP_STACK: + g_value_set_object (value, gtk_hdy_view_switcher_title_get_stack (self)); + break; + case PROP_TITLE: + g_value_set_string (value, gtk_hdy_view_switcher_title_get_title (self)); + break; + case PROP_SUBTITLE: + g_value_set_string (value, gtk_hdy_view_switcher_title_get_subtitle (self)); + break; + case PROP_VIEW_SWITCHER_ENABLED: + g_value_set_boolean (value, gtk_hdy_view_switcher_title_get_view_switcher_enabled (self)); + break; + case PROP_TITLE_VISIBLE: + g_value_set_boolean (value, gtk_hdy_view_switcher_title_get_title_visible (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_hdy_view_switcher_title_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkHdyViewSwitcherTitle *self = GTK_HDY_VIEW_SWITCHER_TITLE (object); + + switch (prop_id) { + case PROP_POLICY: + gtk_hdy_view_switcher_title_set_policy (self, g_value_get_enum (value)); + break; + case PROP_STACK: + gtk_hdy_view_switcher_title_set_stack (self, g_value_get_object (value)); + break; + case PROP_TITLE: + gtk_hdy_view_switcher_title_set_title (self, g_value_get_string (value)); + break; + case PROP_SUBTITLE: + gtk_hdy_view_switcher_title_set_subtitle (self, g_value_get_string (value)); + break; + case PROP_VIEW_SWITCHER_ENABLED: + gtk_hdy_view_switcher_title_set_view_switcher_enabled (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_hdy_view_switcher_title_dispose (GObject *object) { + GtkHdyViewSwitcherTitle *self = (GtkHdyViewSwitcherTitle *)object; + + if (self->view_switcher) { + GtkStack *stack = gtk_hdy_view_switcher_get_stack (self->view_switcher); + + if (stack) + g_signal_handlers_disconnect_by_func (stack, G_CALLBACK (update_view_switcher_visible), self); + } + + G_OBJECT_CLASS (gtk_hdy_view_switcher_title_parent_class)->dispose (object); +} + +static void +gtk_hdy_view_switcher_title_class_init (GtkHdyViewSwitcherTitleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gtk_hdy_view_switcher_title_dispose; + object_class->get_property = gtk_hdy_view_switcher_title_get_property; + object_class->set_property = gtk_hdy_view_switcher_title_set_property; + + /** + * GtkHdyViewSwitcherTitle:policy: + * + * The #GtkHdyViewSwitcherPolicy the #GtkHdyViewSwitcher should use to determine + * which mode to use. + * + * Since: 1.0 + */ + props[PROP_POLICY] = + g_param_spec_enum ("policy", + _("Policy"), + _("The policy to determine the mode to use"), + GTK_TYPE_HDY_VIEW_SWITCHER_POLICY, GTK_HDY_VIEW_SWITCHER_POLICY_AUTO, + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GtkHdyViewSwitcherTitle:stack: + * + * The #GtkStack the #GtkHdyViewSwitcher controls. + * + * Since: 1.0 + */ + props[PROP_STACK] = + g_param_spec_object ("stack", + _("Stack"), + _("Stack"), + GTK_TYPE_STACK, + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GtkHdyViewSwitcherTitle:title: + * + * The title of the #GtkHdyViewSwitcher. + * + * Since: 1.0 + */ + props[PROP_TITLE] = + g_param_spec_string ("title", + _("Title"), + _("The title to display"), + NULL, + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GtkHdyViewSwitcherTitle:subtitle: + * + * The subtitle of the #GtkHdyViewSwitcher. + * + * Since: 1.0 + */ + props[PROP_SUBTITLE] = + g_param_spec_string ("subtitle", + _("Subtitle"), + _("The subtitle to display"), + NULL, + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GtkHdyViewSwitcherTitle:view-switcher-enabled: + * + * Whether the bar should be revealed or hidden. + * + * Since: 1.0 + */ + props[PROP_VIEW_SWITCHER_ENABLED] = + g_param_spec_boolean ("view-switcher-enabled", + _("View switcher enabled"), + _("Whether the view switcher is enabled"), + TRUE, + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GtkHdyViewSwitcherTitle:title-visible: + * + * Whether the bar should be revealed or hidden. + * + * Since: 1.0 + */ + props[PROP_TITLE_VISIBLE] = + g_param_spec_boolean ("title-visible", + _("Title visible"), + _("Whether the title label is visible"), + TRUE, + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, LAST_PROP, props); + + gtk_widget_class_set_css_name (widget_class, "viewswitchertitle"); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gtk/libgtk/ui/hdy-view-switcher-title.ui"); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherTitle, squeezer); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherTitle, subtitle_label); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherTitle, title_box); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherTitle, title_label); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherTitle, view_switcher); + gtk_widget_class_bind_template_callback (widget_class, notify_squeezer_visible_child_cb); +} + +static void +gtk_hdy_view_switcher_title_init (GtkHdyViewSwitcherTitle *self) +{ + /* This must be initialized before the template so the embedded view switcher + * can pick up the correct default value. + */ + self->view_switcher_enabled = TRUE; + + g_type_ensure (GTK_TYPE_HDY_SQUEEZER); + g_type_ensure (GTK_TYPE_HDY_VIEW_SWITCHER); + + gtk_widget_init_template (GTK_WIDGET (self)); + + update_subtitle_label (self); + update_view_switcher_visible (self); +} + +/** + * gtk_hdy_view_switcher_title_new: + * + * Creates a new #GtkHdyViewSwitcherTitle widget. + * + * Returns: a new #GtkHdyViewSwitcherTitle + * + * Since: 1.0 + */ +GtkHdyViewSwitcherTitle * +gtk_hdy_view_switcher_title_new (void) +{ + return g_object_new (GTK_TYPE_HDY_VIEW_SWITCHER_TITLE, NULL); +} + +/** + * gtk_hdy_view_switcher_title_get_policy: + * @self: a #GtkHdyViewSwitcherTitle + * + * Gets the policy of @self. + * + * Returns: the policy of @self + * + * Since: 1.0 + */ +GtkHdyViewSwitcherPolicy +gtk_hdy_view_switcher_title_get_policy (GtkHdyViewSwitcherTitle *self) +{ + g_return_val_if_fail (GTK_IS_HDY_VIEW_SWITCHER_TITLE (self), GTK_HDY_VIEW_SWITCHER_POLICY_NARROW); + + return gtk_hdy_view_switcher_get_policy (self->view_switcher); +} + +/** + * gtk_hdy_view_switcher_title_set_policy: + * @self: a #GtkHdyViewSwitcherTitle + * @policy: the new policy + * + * Sets the policy of @self. + * + * Since: 1.0 + */ +void +gtk_hdy_view_switcher_title_set_policy (GtkHdyViewSwitcherTitle *self, + GtkHdyViewSwitcherPolicy policy) +{ + g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER_TITLE (self)); + + if (gtk_hdy_view_switcher_get_policy (self->view_switcher) == policy) + return; + + gtk_hdy_view_switcher_set_policy (self->view_switcher, policy); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_POLICY]); + + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +/** + * gtk_hdy_view_switcher_title_get_stack: + * @self: a #GtkHdyViewSwitcherTitle + * + * Get the #GtkStack being controlled by the #GtkHdyViewSwitcher. + * + * Returns: (nullable) (transfer none): the #GtkStack, or %NULL if none has been set + * + * Since: 1.0 + */ +GtkStack * +gtk_hdy_view_switcher_title_get_stack (GtkHdyViewSwitcherTitle *self) +{ + g_return_val_if_fail (GTK_IS_HDY_VIEW_SWITCHER_TITLE (self), NULL); + + return gtk_hdy_view_switcher_get_stack (self->view_switcher); +} + +/** + * gtk_hdy_view_switcher_title_set_stack: + * @self: a #GtkHdyViewSwitcherTitle + * @stack: (nullable): a #GtkStack + * + * Sets the #GtkStack to control. + * + * Since: 1.0 + */ +void +gtk_hdy_view_switcher_title_set_stack (GtkHdyViewSwitcherTitle *self, + GtkStack *stack) +{ + GtkStack *previous_stack; + + g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER_TITLE (self)); + g_return_if_fail (stack == NULL || GTK_IS_STACK (stack)); + + previous_stack = gtk_hdy_view_switcher_get_stack (self->view_switcher); + + if (previous_stack == stack) + return; + + if (previous_stack) + g_signal_handlers_disconnect_by_func (previous_stack, G_CALLBACK (update_view_switcher_visible), self); + + gtk_hdy_view_switcher_set_stack (self->view_switcher, stack); + + if (stack) { + g_signal_connect_swapped (stack, "add", G_CALLBACK (update_view_switcher_visible), self); + g_signal_connect_swapped (stack, "remove", G_CALLBACK (update_view_switcher_visible), self); + } + + update_view_switcher_visible (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STACK]); +} + +/** + * gtk_hdy_view_switcher_title_get_title: + * @self: a #GtkHdyViewSwitcherTitle + * + * Gets the title of @self. See gtk_hdy_view_switcher_title_set_title(). + * + * Returns: (transfer none) (nullable): the title of @self, or %NULL. + * + * Since: 1.0 + */ +const gchar * +gtk_hdy_view_switcher_title_get_title (GtkHdyViewSwitcherTitle *self) +{ + g_return_val_if_fail (GTK_IS_HDY_VIEW_SWITCHER_TITLE (self), NULL); + + return gtk_label_get_label (self->title_label); +} + +/** + * gtk_hdy_view_switcher_title_set_title: + * @self: a #GtkHdyViewSwitcherTitle + * @title: (nullable): a title, or %NULL + * + * Sets the title of @self. The title should give a user additional details. A + * good title should not include the application name. + * + * Since: 1.0 + */ +void +gtk_hdy_view_switcher_title_set_title (GtkHdyViewSwitcherTitle *self, + const gchar *title) +{ + g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER_TITLE (self)); + + if (g_strcmp0 (gtk_label_get_label (self->title_label), title) == 0) + return; + + gtk_label_set_label (self->title_label, title); + gtk_widget_set_visible (GTK_WIDGET (self->title_label), title && title[0]); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]); +} + +/** + * gtk_hdy_view_switcher_title_get_subtitle: + * @self: a #GtkHdyViewSwitcherTitle + * + * Gets the subtitle of @self. See gtk_hdy_view_switcher_title_set_subtitle(). + * + * Returns: (transfer none) (nullable): the subtitle of @self, or %NULL. + * + * Since: 1.0 + */ +const gchar * +gtk_hdy_view_switcher_title_get_subtitle (GtkHdyViewSwitcherTitle *self) +{ + g_return_val_if_fail (GTK_IS_HDY_VIEW_SWITCHER_TITLE (self), NULL); + + return gtk_label_get_label (self->subtitle_label); +} + +/** + * gtk_hdy_view_switcher_title_set_subtitle: + * @self: a #GtkHdyViewSwitcherTitle + * @subtitle: (nullable): a subtitle, or %NULL + * + * Sets the subtitle of @self. The subtitle should give a user additional + * details. + * + * Since: 1.0 + */ +void +gtk_hdy_view_switcher_title_set_subtitle (GtkHdyViewSwitcherTitle *self, + const gchar *subtitle) +{ + g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER_TITLE (self)); + + if (g_strcmp0 (gtk_label_get_label (self->subtitle_label), subtitle) == 0) + return; + + gtk_label_set_label (self->subtitle_label, subtitle); + update_subtitle_label (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SUBTITLE]); +} + +/** + * gtk_hdy_view_switcher_title_get_view_switcher_enabled: + * @self: a #GtkHdyViewSwitcherTitle + * + * Gets whether @self's view switcher is enabled. + * + * See gtk_hdy_view_switcher_title_set_view_switcher_enabled(). + * + * Returns: %TRUE if the view switcher is enabled, %FALSE otherwise. + * + * Since: 1.0 + */ +gboolean +gtk_hdy_view_switcher_title_get_view_switcher_enabled (GtkHdyViewSwitcherTitle *self) +{ + g_return_val_if_fail (GTK_IS_HDY_VIEW_SWITCHER_TITLE (self), FALSE); + + return self->view_switcher_enabled; +} + +/** + * gtk_hdy_view_switcher_title_set_view_switcher_enabled: + * @self: a #GtkHdyViewSwitcherTitle + * @enabled: %TRUE to enable the view switcher, %FALSE to disable it + * + * Make @self enable or disable its view switcher. If it is disabled, the title + * will be displayed instead. This allows to programmatically and prematurely + * hide the view switcher of @self even if it fits in the available space. + * + * This can be used e.g. to ensure the view switcher is hidden below a certain + * window width, or any other constraint you find suitable. + * + * Since: 1.0 + */ +void +gtk_hdy_view_switcher_title_set_view_switcher_enabled (GtkHdyViewSwitcherTitle *self, + gboolean enabled) +{ + g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER_TITLE (self)); + + enabled = !!enabled; + + if (self->view_switcher_enabled == enabled) + return; + + self->view_switcher_enabled = enabled; + update_view_switcher_visible (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VIEW_SWITCHER_ENABLED]); +} + +/** + * gtk_hdy_view_switcher_title_get_title_visible: + * @self: a #GtkHdyViewSwitcherTitle + * + * Get whether the title label of @self is visible. + * + * Returns: %TRUE if the title label of @self is visible, %FALSE if not. + * + * Since: 1.0 + */ +gboolean +gtk_hdy_view_switcher_title_get_title_visible (GtkHdyViewSwitcherTitle *self) +{ + g_return_val_if_fail (GTK_IS_HDY_VIEW_SWITCHER_TITLE (self), FALSE); + + return gtk_hdy_squeezer_get_visible_child (self->squeezer) == (GtkWidget *) self->title_box; +} diff --git a/gtk/meson.build b/gtk/meson.build index 80ae1bf..4b3e871 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -389,6 +389,7 @@ gtk_sources = files( 'hdy-view-switcher-bar.c', 'hdy-view-switcher-button.c', 'hdy-view-switcher.c', + 'hdy-view-switcher-title.c', 'language-names.c', 'script-names.c', ) @@ -403,6 +404,7 @@ gtk_private_type_headers = files( 'hdy-view-switcher-bar-private.h', 'hdy-view-switcher-button-private.h', 'hdy-view-switcher-private.h', + 'hdy-view-switcher-title-private.h', ) gtk_gir_public_headers = files( diff --git a/gtk/ui/hdy-view-switcher-title.ui b/gtk/ui/hdy-view-switcher-title.ui new file mode 100644 index 0000000..3e16d1f --- /dev/null +++ b/gtk/ui/hdy-view-switcher-title.ui @@ -0,0 +1,52 @@ + + + + +