From: Adrien Plazas Date: Mon, 31 Aug 2020 10:40:01 +0200 Subject: Add GtkHdyViewSwitcherButton This is imported from HdyViewSwitcherButton from libhandy 1.0.0. --- gtk/hdy-view-switcher-button-private.h | 51 ++++ gtk/hdy-view-switcher-button.c | 544 +++++++++++++++++++++++++++++++++ gtk/meson.build | 2 + gtk/ui/hdy-view-switcher-button.ui | 105 +++++++ 4 files changed, 702 insertions(+) create mode 100644 gtk/hdy-view-switcher-button-private.h create mode 100644 gtk/hdy-view-switcher-button.c create mode 100644 gtk/ui/hdy-view-switcher-button.ui diff --git a/gtk/hdy-view-switcher-button-private.h b/gtk/hdy-view-switcher-button-private.h new file mode 100644 index 0000000..a37add6 --- /dev/null +++ b/gtk/hdy-view-switcher-button-private.h @@ -0,0 +1,51 @@ +/* + * 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 "gtkradiobutton.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_HDY_VIEW_SWITCHER_BUTTON (gtk_hdy_view_switcher_button_get_type()) + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkRadioButton, g_object_unref) + +G_DECLARE_FINAL_TYPE (GtkHdyViewSwitcherButton, gtk_hdy_view_switcher_button, GTK, HDY_VIEW_SWITCHER_BUTTON, GtkRadioButton) + +GtkWidget *gtk_hdy_view_switcher_button_new (void); + +const gchar *gtk_hdy_view_switcher_button_get_icon_name (GtkHdyViewSwitcherButton *self); +void gtk_hdy_view_switcher_button_set_icon_name (GtkHdyViewSwitcherButton *self, + const gchar *icon_name); + +GtkIconSize gtk_hdy_view_switcher_button_get_icon_size (GtkHdyViewSwitcherButton *self); +void gtk_hdy_view_switcher_button_set_icon_size (GtkHdyViewSwitcherButton *self, + GtkIconSize icon_size); + +gboolean gtk_hdy_view_switcher_button_get_needs_attention (GtkHdyViewSwitcherButton *self); +void gtk_hdy_view_switcher_button_set_needs_attention (GtkHdyViewSwitcherButton *self, + gboolean needs_attention); + +const gchar *gtk_hdy_view_switcher_button_get_label (GtkHdyViewSwitcherButton *self); +void gtk_hdy_view_switcher_button_set_label (GtkHdyViewSwitcherButton *self, + const gchar *label); + +void gtk_hdy_view_switcher_button_set_narrow_ellipsize (GtkHdyViewSwitcherButton *self, + PangoEllipsizeMode mode); + +void gtk_hdy_view_switcher_button_get_size (GtkHdyViewSwitcherButton *self, + gint *h_min_width, + gint *h_nat_width, + gint *v_min_width, + gint *v_nat_width); + +G_END_DECLS diff --git a/gtk/hdy-view-switcher-button.c b/gtk/hdy-view-switcher-button.c new file mode 100644 index 0000000..875ca7d --- /dev/null +++ b/gtk/hdy-view-switcher-button.c @@ -0,0 +1,544 @@ +/* + * Copyright (C) 2019 Zander Brown + * Copyright (C) 2019 Purism SPC + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" +#include + +#include "gtkbox.h" +#include "gtkimage.h" +#include "gtklabel.h" +#include "gtkorientable.h" +#include "gtkstack.h" +#include "gtkstylecontext.h" +#include "gtkstyleprovider.h" +#include "hdy-view-switcher-button-private.h" + + +/** + * PRIVATE:gtk-hdy-view-switcher-button + * @short_description: Button used in #GtkHdyViewSwitcher. + * @title: GtkHdyViewSwitcherButton + * @See_also: #GtkHdyViewSwitcher + * @stability: Private + * + * #GtkHdyViewSwitcherButton represents an application's view. It is designed to be + * used exclusively internally by #GtkHdyViewSwitcher. + * + * Since: 0.0.10 + */ + +enum { + PROP_0, + PROP_ICON_SIZE, + PROP_ICON_NAME, + PROP_NEEDS_ATTENTION, + + /* Overridden properties */ + PROP_LABEL, + PROP_ORIENTATION, + + LAST_PROP = PROP_NEEDS_ATTENTION + 1, +}; + +struct _GtkHdyViewSwitcherButton +{ + GtkRadioButton parent_instance; + + GtkBox *horizontal_box; + GtkImage *horizontal_image; + GtkLabel *horizontal_label_active; + GtkLabel *horizontal_label_inactive; + GtkStack *horizontal_label_stack; + GtkStack *stack; + GtkBox *vertical_box; + GtkImage *vertical_image; + GtkLabel *vertical_label_active; + GtkLabel *vertical_label_inactive; + GtkStack *vertical_label_stack; + + gchar *icon_name; + GtkIconSize icon_size; + gchar *label; + GtkOrientation orientation; +}; + +static GParamSpec *props[LAST_PROP]; + +G_DEFINE_TYPE_WITH_CODE (GtkHdyViewSwitcherButton, gtk_hdy_view_switcher_button, GTK_TYPE_RADIO_BUTTON, + G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)) + +static void +on_active_changed (GtkHdyViewSwitcherButton *self) +{ + g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER_BUTTON (self)); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self))) { + gtk_stack_set_visible_child (self->horizontal_label_stack, GTK_WIDGET (self->horizontal_label_active)); + gtk_stack_set_visible_child (self->vertical_label_stack, GTK_WIDGET (self->vertical_label_active)); + } else { + gtk_stack_set_visible_child (self->horizontal_label_stack, GTK_WIDGET (self->horizontal_label_inactive)); + gtk_stack_set_visible_child (self->vertical_label_stack, GTK_WIDGET (self->vertical_label_inactive)); + } +} + +static GtkOrientation +get_orientation (GtkHdyViewSwitcherButton *self) +{ + g_return_val_if_fail (GTK_IS_HDY_VIEW_SWITCHER_BUTTON (self), GTK_ORIENTATION_HORIZONTAL); + + return self->orientation; +} + +static void +set_orientation (GtkHdyViewSwitcherButton *self, + GtkOrientation orientation) +{ + g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER_BUTTON (self)); + + if (self->orientation == orientation) + return; + + self->orientation = orientation; + + gtk_stack_set_visible_child (self->stack, + GTK_WIDGET (self->orientation == GTK_ORIENTATION_VERTICAL ? + self->vertical_box : + self->horizontal_box)); +} + +static void +gtk_hdy_view_switcher_button_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkHdyViewSwitcherButton *self = GTK_HDY_VIEW_SWITCHER_BUTTON (object); + + switch (prop_id) { + case PROP_ICON_NAME: + g_value_set_string (value, gtk_hdy_view_switcher_button_get_icon_name (self)); + break; + case PROP_ICON_SIZE: + g_value_set_int (value, gtk_hdy_view_switcher_button_get_icon_size (self)); + break; + case PROP_NEEDS_ATTENTION: + g_value_set_boolean (value, gtk_hdy_view_switcher_button_get_needs_attention (self)); + break; + case PROP_LABEL: + g_value_set_string (value, gtk_hdy_view_switcher_button_get_label (self)); + break; + case PROP_ORIENTATION: + g_value_set_enum (value, get_orientation (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_hdy_view_switcher_button_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkHdyViewSwitcherButton *self = GTK_HDY_VIEW_SWITCHER_BUTTON (object); + + switch (prop_id) { + case PROP_ICON_NAME: + gtk_hdy_view_switcher_button_set_icon_name (self, g_value_get_string (value)); + break; + case PROP_ICON_SIZE: + gtk_hdy_view_switcher_button_set_icon_size (self, g_value_get_int (value)); + break; + case PROP_NEEDS_ATTENTION: + gtk_hdy_view_switcher_button_set_needs_attention (self, g_value_get_boolean (value)); + break; + case PROP_LABEL: + gtk_hdy_view_switcher_button_set_label (self, g_value_get_string (value)); + break; + case PROP_ORIENTATION: + set_orientation (self, g_value_get_enum (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_hdy_view_switcher_button_finalize (GObject *object) +{ + GtkHdyViewSwitcherButton *self = GTK_HDY_VIEW_SWITCHER_BUTTON (object); + + g_free (self->icon_name); + g_free (self->label); + + G_OBJECT_CLASS (gtk_hdy_view_switcher_button_parent_class)->finalize (object); +} + +static void +gtk_hdy_view_switcher_button_class_init (GtkHdyViewSwitcherButtonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = gtk_hdy_view_switcher_button_get_property; + object_class->set_property = gtk_hdy_view_switcher_button_set_property; + object_class->finalize = gtk_hdy_view_switcher_button_finalize; + + g_object_class_override_property (object_class, + PROP_LABEL, + "label"); + + g_object_class_override_property (object_class, + PROP_ORIENTATION, + "orientation"); + + /** + * GtkHdyViewSwitcherButton:icon-name: + * + * The icon name representing the view, or %NULL for no icon. + * + * Since: 0.0.10 + */ + props[PROP_ICON_NAME] = + g_param_spec_string ("icon-name", + _("Icon Name"), + _("Icon name for image"), + "text-x-generic-symbolic", + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE); + + /** + * GtkHdyViewSwitcherButton:icon-size: + * + * The icon size. + * + * Since: 0.0.10 + */ + props[PROP_ICON_SIZE] = + g_param_spec_int ("icon-size", + _("Icon Size"), + _("Symbolic size to use for named icon"), + 0, G_MAXINT, GTK_ICON_SIZE_BUTTON, + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE); + + /** + * GtkHdyViewSwitcherButton:needs-attention: + * + * Sets a flag specifying whether the view requires the user attention. This + * is used by the GtkHdyViewSwitcher to change the appearance of the + * corresponding button when a view needs attention and it is not the current + * one. + * + * Since: 0.0.10 + */ + props[PROP_NEEDS_ATTENTION] = + g_param_spec_boolean ("needs-attention", + _("Needs attention"), + _("Hint the view needs attention"), + FALSE, + G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, LAST_PROP, props); + + /* We probably should set the class's CSS name to "viewswitcherbutton" + * here, but it doesn't work because GtkCheckButton hardcodes it to "button" + * on instantiation, and the functions required to override it are private. + * In the meantime, we can use the "viewswitcher > button" CSS selector as + * a fairly safe fallback. + */ + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gtk/libgtk/ui/hdy-view-switcher-button.ui"); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherButton, horizontal_box); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherButton, horizontal_image); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherButton, horizontal_label_active); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherButton, horizontal_label_inactive); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherButton, horizontal_label_stack); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherButton, stack); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherButton, vertical_box); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherButton, vertical_image); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherButton, vertical_label_active); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherButton, vertical_label_inactive); + gtk_widget_class_bind_template_child (widget_class, GtkHdyViewSwitcherButton, vertical_label_stack); + gtk_widget_class_bind_template_callback (widget_class, on_active_changed); +} + +static void +gtk_hdy_view_switcher_button_init (GtkHdyViewSwitcherButton *self) +{ + self->icon_size = GTK_ICON_SIZE_BUTTON; + + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_stack_set_visible_child (GTK_STACK (self->stack), GTK_WIDGET (self->horizontal_box)); + + gtk_widget_set_focus_on_click (GTK_WIDGET (self), FALSE); + /* Make the button look like a regular button and not a radio button. */ + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (self), FALSE); + + on_active_changed (self); +} + +/** + * gtk_hdy_view_switcher_button_new: + * + * Creates a new #GtkHdyViewSwitcherButton widget. + * + * Returns: a new #GtkHdyViewSwitcherButton + * + * Since: 0.0.10 + */ +GtkWidget * +gtk_hdy_view_switcher_button_new (void) +{ + return g_object_new (GTK_TYPE_HDY_VIEW_SWITCHER_BUTTON, NULL); +} + +/** + * gtk_hdy_view_switcher_button_get_icon_name: + * @self: a #GtkHdyViewSwitcherButton + * + * Gets the icon name representing the view, or %NULL is no icon is set. + * + * Returns: (transfer none) (nullable): the icon name, or %NULL + * + * Since: 0.0.10 + **/ +const gchar * +gtk_hdy_view_switcher_button_get_icon_name (GtkHdyViewSwitcherButton *self) +{ + g_return_val_if_fail (GTK_IS_HDY_VIEW_SWITCHER_BUTTON (self), NULL); + + return self->icon_name; +} + +/** + * gtk_hdy_view_switcher_button_set_icon_name: + * @self: a #GtkHdyViewSwitcherButton + * @icon_name: (nullable): an icon name or %NULL + * + * Sets the icon name representing the view, or %NULL to disable the icon. + * + * Since: 0.0.10 + **/ +void +gtk_hdy_view_switcher_button_set_icon_name (GtkHdyViewSwitcherButton *self, + const gchar *icon_name) +{ + g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER_BUTTON (self)); + + if (!g_strcmp0 (self->icon_name, icon_name)) + return; + + g_free (self->icon_name); + self->icon_name = g_strdup (icon_name); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_NAME]); +} + +/** + * gtk_hdy_view_switcher_button_get_icon_size: + * @self: a #GtkHdyViewSwitcherButton + * + * Gets the icon size used by @self. + * + * Returns: the icon size used by @self + * + * Since: 0.0.10 + **/ +GtkIconSize +gtk_hdy_view_switcher_button_get_icon_size (GtkHdyViewSwitcherButton *self) +{ + g_return_val_if_fail (GTK_IS_HDY_VIEW_SWITCHER_BUTTON (self), GTK_ICON_SIZE_INVALID); + + return self->icon_size; +} + +/** + * gtk_hdy_view_switcher_button_set_icon_size: + * @self: a #GtkHdyViewSwitcherButton + * @icon_size: the new icon size + * + * Sets the icon size used by @self. + * + * Since: 0.0.10 + */ +void +gtk_hdy_view_switcher_button_set_icon_size (GtkHdyViewSwitcherButton *self, + GtkIconSize icon_size) +{ + g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER_BUTTON (self)); + + if (self->icon_size == icon_size) + return; + + self->icon_size = icon_size; + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_SIZE]); +} + +/** + * gtk_hdy_view_switcher_button_get_needs_attention: + * @self: a #GtkHdyViewSwitcherButton + * + * Gets whether the view represented by @self requires the user attention. + * + * Returns: %TRUE if the view represented by @self requires the user attention, %FALSE otherwise + * + * Since: 0.0.10 + **/ +gboolean +gtk_hdy_view_switcher_button_get_needs_attention (GtkHdyViewSwitcherButton *self) +{ + GtkStyleContext *context; + + g_return_val_if_fail (GTK_IS_HDY_VIEW_SWITCHER_BUTTON (self), FALSE); + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + + return gtk_style_context_has_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION); +} + +/** + * gtk_hdy_view_switcher_button_set_needs_attention: + * @self: a #GtkHdyViewSwitcherButton + * @needs_attention: the new icon size + * + * Sets whether the view represented by @self requires the user attention. + * + * Since: 0.0.10 + */ +void +gtk_hdy_view_switcher_button_set_needs_attention (GtkHdyViewSwitcherButton *self, + gboolean needs_attention) +{ + GtkStyleContext *context; + + g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER_BUTTON (self)); + + needs_attention = !!needs_attention; + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + if (gtk_style_context_has_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION) == needs_attention) + return; + + if (needs_attention) + gtk_style_context_add_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION); + else + gtk_style_context_remove_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NEEDS_ATTENTION]); +} + +/** + * gtk_hdy_view_switcher_button_get_label: + * @self: a #GtkHdyViewSwitcherButton + * + * Gets the label representing the view. + * + * Returns: (transfer none) (nullable): the label, or %NULL + * + * Since: 0.0.10 + **/ +const gchar * +gtk_hdy_view_switcher_button_get_label (GtkHdyViewSwitcherButton *self) +{ + g_return_val_if_fail (GTK_IS_HDY_VIEW_SWITCHER_BUTTON (self), NULL); + + return self->label; +} + +/** + * gtk_hdy_view_switcher_button_set_label: + * @self: a #GtkHdyViewSwitcherButton + * @label: (nullable): a label or %NULL + * + * Sets the label representing the view. + * + * Since: 0.0.10 + **/ +void +gtk_hdy_view_switcher_button_set_label (GtkHdyViewSwitcherButton *self, + const gchar *label) +{ + g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER_BUTTON (self)); + + if (!g_strcmp0 (self->label, label)) + return; + + g_free (self->label); + self->label = g_strdup (label); + + g_object_notify (G_OBJECT (self), "label"); +} + +/** + * gtk_hdy_view_switcher_button_set_narrow_ellipsize: + * @self: a #GtkHdyViewSwitcherButton + * @mode: a #PangoEllipsizeMode + * + * Set the mode used to ellipsize the text in narrow mode if there is not + * enough space to render the entire string. + * + * Since: 0.0.10 + **/ +void +gtk_hdy_view_switcher_button_set_narrow_ellipsize (GtkHdyViewSwitcherButton *self, + PangoEllipsizeMode mode) +{ + g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER_BUTTON (self)); + g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE && mode <= PANGO_ELLIPSIZE_END); + + gtk_label_set_ellipsize (self->vertical_label_active, mode); + gtk_label_set_ellipsize (self->vertical_label_inactive, mode); +} + +/** + * gtk_hdy_view_switcher_button_get_size: + * @self: a #GtkHdyViewSwitcherButton + * @h_min_width: (out) (nullable): the minimum width when horizontal + * @h_nat_width: (out) (nullable): the natural width when horizontal + * @v_min_width: (out) (nullable): the minimum width when vertical + * @v_nat_width: (out) (nullable): the natural width when vertical + * + * Measure the size requests in both horizontal and vertical modes. + * + * Since: 0.0.10 + */ +void +gtk_hdy_view_switcher_button_get_size (GtkHdyViewSwitcherButton *self, + gint *h_min_width, + gint *h_nat_width, + gint *v_min_width, + gint *v_nat_width) +{ + GtkStyleContext *context; + GtkStateFlags state; + GtkBorder border; + + /* gtk_widget_get_preferred_width() doesn't accept both its out parameters to + * be NULL, so we must have guards. + */ + if (h_min_width != NULL || h_nat_width != NULL) + gtk_widget_get_preferred_width (GTK_WIDGET (self->horizontal_box), h_min_width, h_nat_width); + if (v_min_width != NULL || v_nat_width != NULL) + gtk_widget_get_preferred_width (GTK_WIDGET (self->vertical_box), v_min_width, v_nat_width); + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + state = gtk_style_context_get_state (context); + gtk_style_context_get_border (context, state, &border); + if (h_min_width != NULL) + *h_min_width += border.left + border.right; + if (h_nat_width != NULL) + *h_nat_width += border.left + border.right; + if (v_min_width != NULL) + *v_min_width += border.left + border.right; + if (v_nat_width != NULL) + *v_nat_width += border.left + border.right; +} diff --git a/gtk/meson.build b/gtk/meson.build index 4b7a453..38b810c 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -383,6 +383,7 @@ gtk_sources = files( 'gtkwin32draw.c', 'gtkwin32theme.c', 'gdkpixbufutils.c', + 'hdy-view-switcher-button.c', 'language-names.c', 'script-names.c', ) @@ -390,6 +391,7 @@ gtk_sources = files( gtk_private_type_headers = files( 'gtkcsstypesprivate.h', 'gtktexthandleprivate.h', + 'hdy-view-switcher-button-private.h', ) gtk_gir_public_headers = files( diff --git a/gtk/ui/hdy-view-switcher-button.ui b/gtk/ui/hdy-view-switcher-button.ui new file mode 100644 index 0000000..6e437dc --- /dev/null +++ b/gtk/ui/hdy-view-switcher-button.ui @@ -0,0 +1,105 @@ + + + + +