953 lines
30 KiB
Diff
953 lines
30 KiB
Diff
From: Adrien Plazas <kekun.plazas@laposte.net>
|
|
Date: Mon, 31 Aug 2020 10:40:08 +0200
|
|
Subject: Add GtkHdyViewSwitcher
|
|
|
|
This is imported from HdyViewSwitcher from libhandy 1.0.1.
|
|
---
|
|
gtk/gtkprivate.h | 1 +
|
|
gtk/hdy-css-private.h | 25 ++
|
|
gtk/hdy-css.c | 74 ++++
|
|
gtk/hdy-view-switcher-private.h | 43 +++
|
|
gtk/hdy-view-switcher.c | 733 ++++++++++++++++++++++++++++++++++++++++
|
|
gtk/meson.build | 4 +
|
|
6 files changed, 880 insertions(+)
|
|
create mode 100644 gtk/hdy-css-private.h
|
|
create mode 100644 gtk/hdy-css.c
|
|
create mode 100644 gtk/hdy-view-switcher-private.h
|
|
create mode 100644 gtk/hdy-view-switcher.c
|
|
|
|
diff --git a/gtk/gtkprivate.h b/gtk/gtkprivate.h
|
|
index dcab28d..473bb99 100644
|
|
--- a/gtk/gtkprivate.h
|
|
+++ b/gtk/gtkprivate.h
|
|
@@ -30,6 +30,7 @@
|
|
|
|
#include "gtkcsstypesprivate.h"
|
|
#include "gtktexthandleprivate.h"
|
|
+#include "hdy-view-switcher-private.h"
|
|
|
|
G_BEGIN_DECLS
|
|
|
|
diff --git a/gtk/hdy-css-private.h b/gtk/hdy-css-private.h
|
|
new file mode 100644
|
|
index 0000000..3f5fca8
|
|
--- /dev/null
|
|
+++ b/gtk/hdy-css-private.h
|
|
@@ -0,0 +1,25 @@
|
|
+/*
|
|
+ * Copyright (C) 2020 Purism SPC
|
|
+ *
|
|
+ * SPDX-License-Identifier: LGPL-2.1+
|
|
+ */
|
|
+
|
|
+#pragma once
|
|
+
|
|
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
|
+#error "Only <gtk/gtk.h> can be included directly."
|
|
+#endif
|
|
+
|
|
+#include "gtkwidget.h"
|
|
+
|
|
+G_BEGIN_DECLS
|
|
+
|
|
+void gtk_hdy_css_measure (GtkWidget *widget,
|
|
+ GtkOrientation orientation,
|
|
+ gint *minimum,
|
|
+ gint *natural);
|
|
+
|
|
+void gtk_hdy_css_size_allocate (GtkWidget *widget,
|
|
+ GtkAllocation *allocation);
|
|
+
|
|
+G_END_DECLS
|
|
diff --git a/gtk/hdy-css.c b/gtk/hdy-css.c
|
|
new file mode 100644
|
|
index 0000000..16cbc01
|
|
--- /dev/null
|
|
+++ b/gtk/hdy-css.c
|
|
@@ -0,0 +1,74 @@
|
|
+/*
|
|
+ * Copyright (C) 2020 Purism SPC
|
|
+ *
|
|
+ * SPDX-License-Identifier: LGPL-2.1+
|
|
+ */
|
|
+
|
|
+#include "config.h"
|
|
+
|
|
+#include "hdy-css-private.h"
|
|
+#include "gtkstylecontext.h"
|
|
+
|
|
+void
|
|
+gtk_hdy_css_measure (GtkWidget *widget,
|
|
+ GtkOrientation orientation,
|
|
+ gint *minimum,
|
|
+ gint *natural)
|
|
+{
|
|
+ GtkStyleContext *style_context = gtk_widget_get_style_context (widget);
|
|
+ GtkStateFlags state_flags = gtk_widget_get_state_flags (widget);
|
|
+ GtkBorder border, margin, padding;
|
|
+ gint css_width, css_height;
|
|
+
|
|
+ /* Manually apply minimum sizes, the border, the padding and the margin as we
|
|
+ * can't use the private GtkGagdet.
|
|
+ */
|
|
+ gtk_style_context_get (style_context, state_flags,
|
|
+ "min-width", &css_width,
|
|
+ "min-height", &css_height,
|
|
+ NULL);
|
|
+ gtk_style_context_get_border (style_context, state_flags, &border);
|
|
+ gtk_style_context_get_margin (style_context, state_flags, &margin);
|
|
+ gtk_style_context_get_padding (style_context, state_flags, &padding);
|
|
+ if (orientation == GTK_ORIENTATION_VERTICAL) {
|
|
+ *minimum = MAX (*minimum, css_height) +
|
|
+ border.top + margin.top + padding.top +
|
|
+ border.bottom + margin.bottom + padding.bottom;
|
|
+ *natural = MAX (*natural, css_height) +
|
|
+ border.top + margin.top + padding.top +
|
|
+ border.bottom + margin.bottom + padding.bottom;
|
|
+ } else {
|
|
+ *minimum = MAX (*minimum, css_width) +
|
|
+ border.left + margin.left + padding.left +
|
|
+ border.right + margin.right + padding.right;
|
|
+ *natural = MAX (*natural, css_width) +
|
|
+ border.left + margin.left + padding.left +
|
|
+ border.right + margin.right + padding.right;
|
|
+ }
|
|
+}
|
|
+
|
|
+void
|
|
+gtk_hdy_css_size_allocate (GtkWidget *widget,
|
|
+ GtkAllocation *allocation)
|
|
+{
|
|
+ GtkStyleContext *style_context;
|
|
+ GtkStateFlags state_flags;
|
|
+ GtkBorder border, margin, padding;
|
|
+
|
|
+ /* Manually apply the border, the padding and the margin as we can't use the
|
|
+ * private GtkGagdet.
|
|
+ */
|
|
+ style_context = gtk_widget_get_style_context (widget);
|
|
+ state_flags = gtk_widget_get_state_flags (widget);
|
|
+ gtk_style_context_get_border (style_context, state_flags, &border);
|
|
+ gtk_style_context_get_margin (style_context, state_flags, &margin);
|
|
+ gtk_style_context_get_padding (style_context, state_flags, &padding);
|
|
+ allocation->width -= border.left + border.right +
|
|
+ margin.left + margin.right +
|
|
+ padding.left + padding.right;
|
|
+ allocation->height -= border.top + border.bottom +
|
|
+ margin.top + margin.bottom +
|
|
+ padding.top + padding.bottom;
|
|
+ allocation->x += border.left + margin.left + padding.left;
|
|
+ allocation->y += border.top + margin.top + padding.top;
|
|
+}
|
|
diff --git a/gtk/hdy-view-switcher-private.h b/gtk/hdy-view-switcher-private.h
|
|
new file mode 100644
|
|
index 0000000..6267afd
|
|
--- /dev/null
|
|
+++ b/gtk/hdy-view-switcher-private.h
|
|
@@ -0,0 +1,43 @@
|
|
+/*
|
|
+ * Copyright (C) 2019 Zander Brown <zbrown@gnome.org>
|
|
+ * Copyright (C) 2019 Purism SPC
|
|
+ *
|
|
+ * SPDX-License-Identifier: LGPL-2.1+
|
|
+ */
|
|
+
|
|
+#pragma once
|
|
+
|
|
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
|
+#error "Only <gtk/gtk.h> can be included directly."
|
|
+#endif
|
|
+
|
|
+#include "gtkbin.h"
|
|
+#include "gtkstack.h"
|
|
+
|
|
+G_BEGIN_DECLS
|
|
+
|
|
+#define GTK_TYPE_HDY_VIEW_SWITCHER (gtk_hdy_view_switcher_get_type())
|
|
+
|
|
+typedef enum {
|
|
+ GTK_HDY_VIEW_SWITCHER_POLICY_AUTO,
|
|
+ GTK_HDY_VIEW_SWITCHER_POLICY_NARROW,
|
|
+ GTK_HDY_VIEW_SWITCHER_POLICY_WIDE,
|
|
+} GtkHdyViewSwitcherPolicy;
|
|
+
|
|
+G_DECLARE_FINAL_TYPE (GtkHdyViewSwitcher, gtk_hdy_view_switcher, GTK, HDY_VIEW_SWITCHER, GtkBin)
|
|
+
|
|
+GtkWidget *hdy_view_switcher_new (void);
|
|
+
|
|
+GtkHdyViewSwitcherPolicy gtk_hdy_view_switcher_get_policy (GtkHdyViewSwitcher *self);
|
|
+void gtk_hdy_view_switcher_set_policy (GtkHdyViewSwitcher *self,
|
|
+ GtkHdyViewSwitcherPolicy policy);
|
|
+
|
|
+PangoEllipsizeMode gtk_hdy_view_switcher_get_narrow_ellipsize (GtkHdyViewSwitcher *self);
|
|
+void gtk_hdy_view_switcher_set_narrow_ellipsize (GtkHdyViewSwitcher *self,
|
|
+ PangoEllipsizeMode mode);
|
|
+
|
|
+GtkStack *gtk_hdy_view_switcher_get_stack (GtkHdyViewSwitcher *self);
|
|
+void gtk_hdy_view_switcher_set_stack (GtkHdyViewSwitcher *self,
|
|
+ GtkStack *stack);
|
|
+
|
|
+G_END_DECLS
|
|
diff --git a/gtk/hdy-view-switcher.c b/gtk/hdy-view-switcher.c
|
|
new file mode 100644
|
|
index 0000000..2664b3e
|
|
--- /dev/null
|
|
+++ b/gtk/hdy-view-switcher.c
|
|
@@ -0,0 +1,733 @@
|
|
+/*
|
|
+ * Copyright (C) 2019 Zander Brown <zbrown@gnome.org>
|
|
+ * Copyright (C) 2019 Purism SPC
|
|
+ *
|
|
+ * Based on gtkstackswitcher.c, Copyright (c) 2013 Red Hat, Inc.
|
|
+ * https://gitlab.gnome.org/GNOME/gtk/blob/a0129f556b1fd655215165739d0277d7f7a2c1a8/gtk/gtkstackswitcher.c
|
|
+ *
|
|
+ * SPDX-License-Identifier: LGPL-2.1+
|
|
+ */
|
|
+
|
|
+#include "config.h"
|
|
+#include <glib/gi18n-lib.h>
|
|
+
|
|
+#include "gtkbox.h"
|
|
+#include "gtkdragdest.h"
|
|
+#include "gtkorientable.h"
|
|
+#include "gtkprivatetypebuiltins.h"
|
|
+#include "hdy-css-private.h"
|
|
+#include "hdy-view-switcher-button-private.h"
|
|
+#include "hdy-view-switcher-private.h"
|
|
+
|
|
+/**
|
|
+ * SECTION:gtk-hdy-view-switcher
|
|
+ * @short_description: An adaptive view switcher.
|
|
+ * @title: GtkHdyViewSwitcher
|
|
+ *
|
|
+ * An adaptive view switcher, designed to switch between multiple views in a
|
|
+ * similar fashion than a #GtkStackSwitcher.
|
|
+ *
|
|
+ * Depending on the available width, the view switcher can adapt from a wide
|
|
+ * mode showing the view's icon and title side by side, to a narrow mode showing
|
|
+ * the view's icon and title one on top of the other, in a more compact way.
|
|
+ * This can be controlled via the policy property.
|
|
+ *
|
|
+ * To look good in a header bar, an #GtkHdyViewSwitcher requires to fill its full
|
|
+ * height. Contrary to #GtkHeaderBar, #GtkHdyHeaderBar doesn't force a vertical
|
|
+ * alignment on its title widget, so we recommend it over #GtkHeaderBar.
|
|
+ *
|
|
+ * # CSS nodes
|
|
+ *
|
|
+ * #GtkHdyViewSwitcher has a single CSS node with name viewswitcher.
|
|
+ *
|
|
+ * Since: 0.0.10
|
|
+ */
|
|
+
|
|
+#define MIN_NAT_BUTTON_WIDTH 100
|
|
+#define TIMEOUT_EXPAND 500
|
|
+
|
|
+enum {
|
|
+ PROP_0,
|
|
+ PROP_POLICY,
|
|
+ PROP_NARROW_ELLIPSIZE,
|
|
+ PROP_STACK,
|
|
+ LAST_PROP,
|
|
+};
|
|
+
|
|
+struct _GtkHdyViewSwitcher
|
|
+{
|
|
+ GtkBin parent_instance;
|
|
+
|
|
+ GtkWidget *box;
|
|
+ GHashTable *buttons;
|
|
+ gboolean in_child_changed;
|
|
+ GtkWidget *switch_button;
|
|
+ guint switch_timer;
|
|
+
|
|
+ GtkHdyViewSwitcherPolicy policy;
|
|
+ PangoEllipsizeMode narrow_ellipsize;
|
|
+ GtkStack *stack;
|
|
+};
|
|
+
|
|
+static GParamSpec *props[LAST_PROP];
|
|
+
|
|
+G_DEFINE_TYPE (GtkHdyViewSwitcher, gtk_hdy_view_switcher, GTK_TYPE_BIN)
|
|
+
|
|
+static void
|
|
+set_visible_stack_child_for_button (GtkHdyViewSwitcher *self,
|
|
+ GtkHdyViewSwitcherButton *button)
|
|
+{
|
|
+ if (self->in_child_changed)
|
|
+ return;
|
|
+
|
|
+ gtk_stack_set_visible_child (self->stack, GTK_WIDGET (g_object_get_data (G_OBJECT (button), "stack-child")));
|
|
+}
|
|
+
|
|
+static void
|
|
+update_button (GtkHdyViewSwitcher *self,
|
|
+ GtkWidget *widget,
|
|
+ GtkHdyViewSwitcherButton *button)
|
|
+{
|
|
+ g_autofree gchar *title = NULL;
|
|
+ g_autofree gchar *icon_name = NULL;
|
|
+ gboolean needs_attention;
|
|
+
|
|
+ gtk_container_child_get (GTK_CONTAINER (self->stack), widget,
|
|
+ "title", &title,
|
|
+ "icon-name", &icon_name,
|
|
+ "needs-attention", &needs_attention,
|
|
+ NULL);
|
|
+
|
|
+ g_object_set (G_OBJECT (button),
|
|
+ "icon-name", icon_name,
|
|
+ "icon-size", GTK_ICON_SIZE_BUTTON,
|
|
+ "label", title,
|
|
+ "needs-attention", needs_attention,
|
|
+ NULL);
|
|
+
|
|
+ gtk_widget_set_visible (GTK_WIDGET (button),
|
|
+ gtk_widget_get_visible (widget) && (title != NULL || icon_name != NULL));
|
|
+}
|
|
+
|
|
+static void
|
|
+on_stack_child_updated (GtkWidget *widget,
|
|
+ GParamSpec *pspec,
|
|
+ GtkHdyViewSwitcher *self)
|
|
+{
|
|
+ update_button (self, widget, g_hash_table_lookup (self->buttons, widget));
|
|
+}
|
|
+
|
|
+static void
|
|
+on_position_updated (GtkWidget *widget,
|
|
+ GParamSpec *pspec,
|
|
+ GtkHdyViewSwitcher *self)
|
|
+{
|
|
+ GtkWidget *button = g_hash_table_lookup (self->buttons, widget);
|
|
+ gint position;
|
|
+
|
|
+ gtk_container_child_get (GTK_CONTAINER (self->stack), widget,
|
|
+ "position", &position,
|
|
+ NULL);
|
|
+ gtk_box_reorder_child (GTK_BOX (self->box), button, position);
|
|
+}
|
|
+
|
|
+static void
|
|
+remove_switch_timer (GtkHdyViewSwitcher *self)
|
|
+{
|
|
+ if (!self->switch_timer)
|
|
+ return;
|
|
+
|
|
+ g_source_remove (self->switch_timer);
|
|
+ self->switch_timer = 0;
|
|
+}
|
|
+
|
|
+static gboolean
|
|
+gtk_hdy_view_switcher_switch_timeout (gpointer data)
|
|
+{
|
|
+ GtkHdyViewSwitcher *self = GTK_HDY_VIEW_SWITCHER (data);
|
|
+ GtkWidget *button = self->switch_button;
|
|
+
|
|
+ self->switch_timer = 0;
|
|
+ self->switch_button = NULL;
|
|
+
|
|
+ if (button)
|
|
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
|
|
+
|
|
+ return G_SOURCE_REMOVE;
|
|
+}
|
|
+
|
|
+static gboolean
|
|
+gtk_hdy_view_switcher_drag_motion (GtkWidget *widget,
|
|
+ GdkDragContext *context,
|
|
+ gint x,
|
|
+ gint y,
|
|
+ guint time)
|
|
+{
|
|
+ GtkHdyViewSwitcher *self = GTK_HDY_VIEW_SWITCHER (widget);
|
|
+ GtkAllocation allocation;
|
|
+ GtkWidget *button;
|
|
+ GHashTableIter iter;
|
|
+ gpointer value;
|
|
+ gboolean retval = FALSE;
|
|
+
|
|
+ gtk_widget_get_allocation (widget, &allocation);
|
|
+
|
|
+ x += allocation.x;
|
|
+ y += allocation.y;
|
|
+
|
|
+ button = NULL;
|
|
+ g_hash_table_iter_init (&iter, self->buttons);
|
|
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
|
|
+ gtk_widget_get_allocation (GTK_WIDGET (value), &allocation);
|
|
+ if (x >= allocation.x && x <= allocation.x + allocation.width &&
|
|
+ y >= allocation.y && y <= allocation.y + allocation.height) {
|
|
+ button = GTK_WIDGET (value);
|
|
+ retval = TRUE;
|
|
+
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (button != self->switch_button)
|
|
+ remove_switch_timer (self);
|
|
+
|
|
+ self->switch_button = button;
|
|
+
|
|
+ if (button && !self->switch_timer) {
|
|
+ self->switch_timer = gdk_threads_add_timeout (TIMEOUT_EXPAND,
|
|
+ gtk_hdy_view_switcher_switch_timeout,
|
|
+ self);
|
|
+ g_source_set_name_by_id (self->switch_timer, "[gtk+] gtk_hdy_view_switcher_switch_timeout");
|
|
+ }
|
|
+
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+static void
|
|
+gtk_hdy_view_switcher_drag_leave (GtkWidget *widget,
|
|
+ GdkDragContext *context,
|
|
+ guint time)
|
|
+{
|
|
+ GtkHdyViewSwitcher *self = GTK_HDY_VIEW_SWITCHER (widget);
|
|
+
|
|
+ remove_switch_timer (self);
|
|
+}
|
|
+
|
|
+static void
|
|
+add_button_for_stack_child (GtkHdyViewSwitcher *self,
|
|
+ GtkWidget *stack_child)
|
|
+{
|
|
+ g_autoptr (GList) children = gtk_container_get_children (GTK_CONTAINER (self->box));
|
|
+ GtkHdyViewSwitcherButton *button = GTK_HDY_VIEW_SWITCHER_BUTTON (gtk_hdy_view_switcher_button_new ());
|
|
+
|
|
+ g_object_set_data (G_OBJECT (button), "stack-child", stack_child);
|
|
+ gtk_hdy_view_switcher_button_set_narrow_ellipsize (button, self->narrow_ellipsize);
|
|
+
|
|
+ update_button (self, stack_child, button);
|
|
+
|
|
+ if (children != NULL)
|
|
+ gtk_radio_button_join_group (GTK_RADIO_BUTTON (button), GTK_RADIO_BUTTON (children->data));
|
|
+
|
|
+ gtk_container_add (GTK_CONTAINER (self->box), GTK_WIDGET (button));
|
|
+
|
|
+ g_signal_connect_swapped (button, "clicked", G_CALLBACK (set_visible_stack_child_for_button), self);
|
|
+ g_signal_connect (stack_child, "notify::visible", G_CALLBACK (on_stack_child_updated), self);
|
|
+ g_signal_connect (stack_child, "child-notify::title", G_CALLBACK (on_stack_child_updated), self);
|
|
+ g_signal_connect (stack_child, "child-notify::icon-name", G_CALLBACK (on_stack_child_updated), self);
|
|
+ g_signal_connect (stack_child, "child-notify::needs-attention", G_CALLBACK (on_stack_child_updated), self);
|
|
+ g_signal_connect (stack_child, "child-notify::position", G_CALLBACK (on_position_updated), self);
|
|
+
|
|
+ g_hash_table_insert (self->buttons, stack_child, button);
|
|
+}
|
|
+
|
|
+static void
|
|
+add_button_for_stack_child_cb (GtkWidget *stack_child,
|
|
+ GtkHdyViewSwitcher *self)
|
|
+{
|
|
+ g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER (self));
|
|
+ g_return_if_fail (GTK_IS_WIDGET (stack_child));
|
|
+
|
|
+ add_button_for_stack_child (self, stack_child);
|
|
+}
|
|
+
|
|
+static void
|
|
+remove_button_for_stack_child (GtkHdyViewSwitcher *self,
|
|
+ GtkWidget *stack_child)
|
|
+{
|
|
+ g_signal_handlers_disconnect_by_func (stack_child, on_stack_child_updated, self);
|
|
+ g_signal_handlers_disconnect_by_func (stack_child, on_position_updated, self);
|
|
+ gtk_container_remove (GTK_CONTAINER (self->box), g_hash_table_lookup (self->buttons, stack_child));
|
|
+ g_hash_table_remove (self->buttons, stack_child);
|
|
+}
|
|
+
|
|
+static void
|
|
+remove_button_for_stack_child_cb (GtkWidget *stack_child,
|
|
+ GtkHdyViewSwitcher *self)
|
|
+{
|
|
+ g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER (self));
|
|
+ g_return_if_fail (GTK_IS_WIDGET (stack_child));
|
|
+
|
|
+ remove_button_for_stack_child (self, stack_child);
|
|
+}
|
|
+
|
|
+static void
|
|
+update_active_button_for_visible_stack_child (GtkHdyViewSwitcher *self)
|
|
+{
|
|
+ GtkWidget *visible_stack_child = gtk_stack_get_visible_child (self->stack);
|
|
+ GtkWidget *button = g_hash_table_lookup (self->buttons, visible_stack_child);
|
|
+
|
|
+ if (button == NULL)
|
|
+ return;
|
|
+
|
|
+ self->in_child_changed = TRUE;
|
|
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
|
|
+ self->in_child_changed = FALSE;
|
|
+}
|
|
+
|
|
+static void
|
|
+disconnect_stack_signals (GtkHdyViewSwitcher *self)
|
|
+{
|
|
+ g_signal_handlers_disconnect_by_func (self->stack, add_button_for_stack_child, self);
|
|
+ g_signal_handlers_disconnect_by_func (self->stack, remove_button_for_stack_child, self);
|
|
+ g_signal_handlers_disconnect_by_func (self->stack, update_active_button_for_visible_stack_child, self);
|
|
+ g_signal_handlers_disconnect_by_func (self->stack, disconnect_stack_signals, self);
|
|
+}
|
|
+
|
|
+static void
|
|
+connect_stack_signals (GtkHdyViewSwitcher *self)
|
|
+{
|
|
+ g_signal_connect_object (self->stack, "add",
|
|
+ G_CALLBACK (add_button_for_stack_child), self,
|
|
+ G_CONNECT_AFTER | G_CONNECT_SWAPPED);
|
|
+ g_signal_connect_object (self->stack, "remove",
|
|
+ G_CALLBACK (remove_button_for_stack_child), self,
|
|
+ G_CONNECT_AFTER | G_CONNECT_SWAPPED);
|
|
+ g_signal_connect_object (self->stack, "notify::visible-child",
|
|
+ G_CALLBACK (update_active_button_for_visible_stack_child), self,
|
|
+ G_CONNECT_SWAPPED);
|
|
+ g_signal_connect_object (self->stack, "destroy",
|
|
+ G_CALLBACK (disconnect_stack_signals), self,
|
|
+ G_CONNECT_SWAPPED);
|
|
+}
|
|
+
|
|
+static void
|
|
+gtk_hdy_view_switcher_get_property (GObject *object,
|
|
+ guint prop_id,
|
|
+ GValue *value,
|
|
+ GParamSpec *pspec)
|
|
+{
|
|
+ GtkHdyViewSwitcher *self = GTK_HDY_VIEW_SWITCHER (object);
|
|
+
|
|
+ switch (prop_id) {
|
|
+ case PROP_POLICY:
|
|
+ g_value_set_enum (value, gtk_hdy_view_switcher_get_policy (self));
|
|
+ break;
|
|
+ case PROP_NARROW_ELLIPSIZE:
|
|
+ g_value_set_enum (value, gtk_hdy_view_switcher_get_narrow_ellipsize (self));
|
|
+ break;
|
|
+ case PROP_STACK:
|
|
+ g_value_set_object (value, gtk_hdy_view_switcher_get_stack (self));
|
|
+ break;
|
|
+ default:
|
|
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+gtk_hdy_view_switcher_set_property (GObject *object,
|
|
+ guint prop_id,
|
|
+ const GValue *value,
|
|
+ GParamSpec *pspec)
|
|
+{
|
|
+ GtkHdyViewSwitcher *self = GTK_HDY_VIEW_SWITCHER (object);
|
|
+
|
|
+ switch (prop_id) {
|
|
+ case PROP_POLICY:
|
|
+ gtk_hdy_view_switcher_set_policy (self, g_value_get_enum (value));
|
|
+ break;
|
|
+ case PROP_NARROW_ELLIPSIZE:
|
|
+ gtk_hdy_view_switcher_set_narrow_ellipsize (self, g_value_get_enum (value));
|
|
+ break;
|
|
+ case PROP_STACK:
|
|
+ gtk_hdy_view_switcher_set_stack (self, g_value_get_object (value));
|
|
+ break;
|
|
+ default:
|
|
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+gtk_hdy_view_switcher_dispose (GObject *object)
|
|
+{
|
|
+ GtkHdyViewSwitcher *self = GTK_HDY_VIEW_SWITCHER (object);
|
|
+
|
|
+ remove_switch_timer (self);
|
|
+ gtk_hdy_view_switcher_set_stack (self, NULL);
|
|
+
|
|
+ G_OBJECT_CLASS (gtk_hdy_view_switcher_parent_class)->dispose (object);
|
|
+}
|
|
+
|
|
+static void
|
|
+gtk_hdy_view_switcher_finalize (GObject *object)
|
|
+{
|
|
+ GtkHdyViewSwitcher *self = GTK_HDY_VIEW_SWITCHER (object);
|
|
+
|
|
+ g_hash_table_destroy (self->buttons);
|
|
+
|
|
+ G_OBJECT_CLASS (gtk_hdy_view_switcher_parent_class)->finalize (object);
|
|
+}
|
|
+
|
|
+static void
|
|
+gtk_hdy_view_switcher_get_preferred_width (GtkWidget *widget,
|
|
+ gint *min,
|
|
+ gint *nat)
|
|
+{
|
|
+ GtkHdyViewSwitcher *self = GTK_HDY_VIEW_SWITCHER (widget);
|
|
+ g_autoptr (GList) children = gtk_container_get_children (GTK_CONTAINER (self->box));
|
|
+ gint max_h_min = 0, max_h_nat = 0, max_v_min = 0, max_v_nat = 0;
|
|
+ gint n_children = 0;
|
|
+
|
|
+ for (GList *l = children; l != NULL; l = g_list_next (l)) {
|
|
+ gint h_min = 0, h_nat = 0, v_min = 0, v_nat = 0;
|
|
+
|
|
+ if (!gtk_widget_get_visible (l->data))
|
|
+ continue;
|
|
+
|
|
+ gtk_hdy_view_switcher_button_get_size (GTK_HDY_VIEW_SWITCHER_BUTTON (l->data), &h_min, &h_nat, &v_min, &v_nat);
|
|
+ max_h_min = MAX (h_min, max_h_min);
|
|
+ max_h_nat = MAX (h_nat, max_h_nat);
|
|
+ max_v_min = MAX (v_min, max_v_min);
|
|
+ max_v_nat = MAX (v_nat, max_v_nat);
|
|
+
|
|
+ n_children++;
|
|
+ }
|
|
+
|
|
+ /* Make the buttons ask at least a minimum arbitrary size for their natural
|
|
+ * width. This prevents them from looking terribly narrow in a very wide bar.
|
|
+ */
|
|
+ max_h_nat = MAX (max_h_nat, MIN_NAT_BUTTON_WIDTH);
|
|
+ max_v_nat = MAX (max_v_nat, MIN_NAT_BUTTON_WIDTH);
|
|
+
|
|
+ switch (self->policy) {
|
|
+ case GTK_HDY_VIEW_SWITCHER_POLICY_NARROW:
|
|
+ *min = max_v_min * n_children;
|
|
+ *nat = max_v_nat * n_children;
|
|
+ break;
|
|
+ case GTK_HDY_VIEW_SWITCHER_POLICY_WIDE:
|
|
+ *min = max_h_min * n_children;
|
|
+ *nat = max_h_nat * n_children;
|
|
+ break;
|
|
+ case GTK_HDY_VIEW_SWITCHER_POLICY_AUTO:
|
|
+ default:
|
|
+ *min = max_v_min * n_children;
|
|
+ *nat = max_h_nat * n_children;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ gtk_hdy_css_measure (widget, GTK_ORIENTATION_HORIZONTAL, min, nat);
|
|
+}
|
|
+
|
|
+static gint
|
|
+is_narrow (GtkHdyViewSwitcher *self,
|
|
+ gint width)
|
|
+{
|
|
+ g_autoptr (GList) children = gtk_container_get_children (GTK_CONTAINER (self->box));
|
|
+ gint max_h_min = 0;
|
|
+ gint n_children = 0;
|
|
+
|
|
+ if (self->policy == GTK_HDY_VIEW_SWITCHER_POLICY_NARROW)
|
|
+ return TRUE;
|
|
+
|
|
+ if (self->policy == GTK_HDY_VIEW_SWITCHER_POLICY_WIDE)
|
|
+ return FALSE;
|
|
+
|
|
+ for (GList *l = children; l != NULL; l = g_list_next (l)) {
|
|
+ gint h_min = 0;
|
|
+
|
|
+ if (!gtk_widget_get_visible (l->data))
|
|
+ continue;
|
|
+
|
|
+ gtk_hdy_view_switcher_button_get_size (GTK_HDY_VIEW_SWITCHER_BUTTON (l->data), &h_min, NULL, NULL, NULL);
|
|
+ max_h_min = MAX (max_h_min, h_min);
|
|
+
|
|
+ n_children++;
|
|
+ }
|
|
+
|
|
+ return (max_h_min * n_children) > width;
|
|
+}
|
|
+
|
|
+static void
|
|
+gtk_hdy_view_switcher_size_allocate (GtkWidget *widget,
|
|
+ GtkAllocation *allocation)
|
|
+{
|
|
+ GtkHdyViewSwitcher *self = GTK_HDY_VIEW_SWITCHER (widget);
|
|
+
|
|
+ g_autoptr (GList) children = gtk_container_get_children (GTK_CONTAINER (self->box));
|
|
+ GtkOrientation orientation;
|
|
+
|
|
+ gtk_hdy_css_size_allocate (widget, allocation);
|
|
+
|
|
+ orientation = is_narrow (GTK_HDY_VIEW_SWITCHER (widget), allocation->width) ?
|
|
+ GTK_ORIENTATION_VERTICAL :
|
|
+ GTK_ORIENTATION_HORIZONTAL;
|
|
+
|
|
+ for (GList *l = children; l != NULL; l = g_list_next (l))
|
|
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (l->data), orientation);
|
|
+
|
|
+ GTK_WIDGET_CLASS (gtk_hdy_view_switcher_parent_class)->size_allocate (widget, allocation);
|
|
+}
|
|
+
|
|
+static void
|
|
+gtk_hdy_view_switcher_class_init (GtkHdyViewSwitcherClass *klass)
|
|
+{
|
|
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
+
|
|
+ object_class->get_property = gtk_hdy_view_switcher_get_property;
|
|
+ object_class->set_property = gtk_hdy_view_switcher_set_property;
|
|
+ object_class->dispose = gtk_hdy_view_switcher_dispose;
|
|
+ object_class->finalize = gtk_hdy_view_switcher_finalize;
|
|
+
|
|
+ widget_class->size_allocate = gtk_hdy_view_switcher_size_allocate;
|
|
+ widget_class->get_preferred_width = gtk_hdy_view_switcher_get_preferred_width;
|
|
+ widget_class->drag_motion = gtk_hdy_view_switcher_drag_motion;
|
|
+ widget_class->drag_leave = gtk_hdy_view_switcher_drag_leave;
|
|
+
|
|
+ /**
|
|
+ * GtkHdyViewSwitcher:policy:
|
|
+ *
|
|
+ * The #GtkHdyViewSwitcherPolicy the view switcher should use to determine which
|
|
+ * mode to use.
|
|
+ *
|
|
+ * Since: 0.0.10
|
|
+ */
|
|
+ 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);
|
|
+
|
|
+ /**
|
|
+ * GtkHdyViewSwitcher:narrow-ellipsize:
|
|
+ *
|
|
+ * The preferred place to ellipsize the string, if the narrow mode label does
|
|
+ * not have enough room to display the entire string, specified as a
|
|
+ * #PangoEllipsizeMode.
|
|
+ *
|
|
+ * Note that setting this property to a value other than %PANGO_ELLIPSIZE_NONE
|
|
+ * has the side-effect that the label requests only enough space to display
|
|
+ * the ellipsis.
|
|
+ *
|
|
+ * Since: 0.0.10
|
|
+ */
|
|
+ props[PROP_NARROW_ELLIPSIZE] =
|
|
+ g_param_spec_enum ("narrow-ellipsize",
|
|
+ _("Narrow ellipsize"),
|
|
+ _("The preferred place to ellipsize the string, if the narrow mode label does not have enough room to display the entire string"),
|
|
+ PANGO_TYPE_ELLIPSIZE_MODE,
|
|
+ PANGO_ELLIPSIZE_NONE,
|
|
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
+
|
|
+ /**
|
|
+ * GtkHdyViewSwitcher:stack:
|
|
+ *
|
|
+ * The #GtkStack the view switcher controls.
|
|
+ *
|
|
+ * Since: 0.0.10
|
|
+ */
|
|
+ props[PROP_STACK] =
|
|
+ g_param_spec_object ("stack",
|
|
+ _("Stack"),
|
|
+ _("Stack"),
|
|
+ GTK_TYPE_STACK,
|
|
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
|
+
|
|
+ g_object_class_install_properties (object_class, LAST_PROP, props);
|
|
+
|
|
+ gtk_widget_class_set_css_name (widget_class, "viewswitcher");
|
|
+}
|
|
+
|
|
+static void
|
|
+gtk_hdy_view_switcher_init (GtkHdyViewSwitcher *self)
|
|
+{
|
|
+ self->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
|
+ gtk_widget_show (self->box);
|
|
+ gtk_box_set_homogeneous (GTK_BOX (self->box), TRUE);
|
|
+ gtk_container_add (GTK_CONTAINER (self), self->box);
|
|
+
|
|
+ self->buttons = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
+
|
|
+ gtk_widget_set_valign (GTK_WIDGET (self), GTK_ALIGN_FILL);
|
|
+
|
|
+ gtk_drag_dest_set (GTK_WIDGET (self), 0, NULL, 0, 0);
|
|
+ gtk_drag_dest_set_track_motion (GTK_WIDGET (self), TRUE);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * gtk_hdy_view_switcher_new:
|
|
+ *
|
|
+ * Creates a new #GtkHdyViewSwitcher widget.
|
|
+ *
|
|
+ * Returns: a new #GtkHdyViewSwitcher
|
|
+ *
|
|
+ * Since: 0.0.10
|
|
+ */
|
|
+GtkWidget *
|
|
+gtk_hdy_view_switcher_new (void)
|
|
+{
|
|
+ return g_object_new (GTK_TYPE_HDY_VIEW_SWITCHER, NULL);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * gtk_hdy_view_switcher_get_policy:
|
|
+ * @self: a #GtkHdyViewSwitcher
|
|
+ *
|
|
+ * Gets the policy of @self.
|
|
+ *
|
|
+ * Returns: the policy of @self
|
|
+ *
|
|
+ * Since: 0.0.10
|
|
+ */
|
|
+GtkHdyViewSwitcherPolicy
|
|
+gtk_hdy_view_switcher_get_policy (GtkHdyViewSwitcher *self)
|
|
+{
|
|
+ g_return_val_if_fail (GTK_IS_HDY_VIEW_SWITCHER (self), GTK_HDY_VIEW_SWITCHER_POLICY_AUTO);
|
|
+
|
|
+ return self->policy;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * gtk_hdy_view_switcher_set_policy:
|
|
+ * @self: a #GtkHdyViewSwitcher
|
|
+ * @policy: the new policy
|
|
+ *
|
|
+ * Sets the policy of @self.
|
|
+ *
|
|
+ * Since: 0.0.10
|
|
+ */
|
|
+void
|
|
+gtk_hdy_view_switcher_set_policy (GtkHdyViewSwitcher *self,
|
|
+ GtkHdyViewSwitcherPolicy policy)
|
|
+{
|
|
+ g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER (self));
|
|
+
|
|
+ if (self->policy == policy)
|
|
+ return;
|
|
+
|
|
+ self->policy = policy;
|
|
+
|
|
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_POLICY]);
|
|
+
|
|
+ gtk_widget_queue_resize (GTK_WIDGET (self));
|
|
+}
|
|
+
|
|
+/**
|
|
+ * gtk_hdy_view_switcher_get_narrow_ellipsize:
|
|
+ * @self: a #GtkHdyViewSwitcher
|
|
+ *
|
|
+ * Get the ellipsizing position of the narrow mode label. See
|
|
+ * gtk_hdy_view_switcher_set_narrow_ellipsize().
|
|
+ *
|
|
+ * Returns: #PangoEllipsizeMode
|
|
+ *
|
|
+ * Since: 0.0.10
|
|
+ **/
|
|
+PangoEllipsizeMode
|
|
+gtk_hdy_view_switcher_get_narrow_ellipsize (GtkHdyViewSwitcher *self)
|
|
+{
|
|
+ g_return_val_if_fail (GTK_IS_HDY_VIEW_SWITCHER (self), PANGO_ELLIPSIZE_NONE);
|
|
+
|
|
+ return self->narrow_ellipsize;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * gtk_hdy_view_switcher_set_narrow_ellipsize:
|
|
+ * @self: a #GtkHdyViewSwitcher
|
|
+ * @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_set_narrow_ellipsize (GtkHdyViewSwitcher *self,
|
|
+ PangoEllipsizeMode mode)
|
|
+{
|
|
+ GHashTableIter iter;
|
|
+ gpointer button;
|
|
+
|
|
+ g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER (self));
|
|
+ g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE && mode <= PANGO_ELLIPSIZE_END);
|
|
+
|
|
+ if ((PangoEllipsizeMode) self->narrow_ellipsize == mode)
|
|
+ return;
|
|
+
|
|
+ self->narrow_ellipsize = mode;
|
|
+
|
|
+ g_hash_table_iter_init (&iter, self->buttons);
|
|
+ while (g_hash_table_iter_next (&iter, NULL, &button))
|
|
+ gtk_hdy_view_switcher_button_set_narrow_ellipsize (GTK_HDY_VIEW_SWITCHER_BUTTON (button), mode);
|
|
+
|
|
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NARROW_ELLIPSIZE]);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * gtk_hdy_view_switcher_get_stack:
|
|
+ * @self: a #GtkHdyViewSwitcher
|
|
+ *
|
|
+ * Get the #GtkStack being controlled by the #GtkHdyViewSwitcher.
|
|
+ *
|
|
+ * See: gtk_hdy_view_switcher_set_stack()
|
|
+ *
|
|
+ * Returns: (nullable) (transfer none): the #GtkStack, or %NULL if none has been set
|
|
+ *
|
|
+ * Since: 0.0.10
|
|
+ */
|
|
+GtkStack *
|
|
+gtk_hdy_view_switcher_get_stack (GtkHdyViewSwitcher *self)
|
|
+{
|
|
+ g_return_val_if_fail (GTK_IS_HDY_VIEW_SWITCHER (self), NULL);
|
|
+
|
|
+ return self->stack;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * gtk_hdy_view_switcher_set_stack:
|
|
+ * @self: a #GtkHdyViewSwitcher
|
|
+ * @stack: (nullable): a #GtkStack
|
|
+ *
|
|
+ * Sets the #GtkStack to control.
|
|
+ *
|
|
+ * Since: 0.0.10
|
|
+ */
|
|
+void
|
|
+gtk_hdy_view_switcher_set_stack (GtkHdyViewSwitcher *self,
|
|
+ GtkStack *stack)
|
|
+{
|
|
+ g_return_if_fail (GTK_IS_HDY_VIEW_SWITCHER (self));
|
|
+ g_return_if_fail (stack == NULL || GTK_IS_STACK (stack));
|
|
+
|
|
+ if (self->stack == stack)
|
|
+ return;
|
|
+
|
|
+ if (self->stack) {
|
|
+ disconnect_stack_signals (self);
|
|
+ gtk_container_foreach (GTK_CONTAINER (self->stack), (GtkCallback) remove_button_for_stack_child_cb, self);
|
|
+ }
|
|
+
|
|
+ g_set_object (&self->stack, stack);
|
|
+
|
|
+ if (self->stack) {
|
|
+ gtk_container_foreach (GTK_CONTAINER (self->stack), (GtkCallback) add_button_for_stack_child_cb, self);
|
|
+ update_active_button_for_visible_stack_child (self);
|
|
+ connect_stack_signals (self);
|
|
+ }
|
|
+
|
|
+ gtk_widget_queue_resize (GTK_WIDGET (self));
|
|
+
|
|
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STACK]);
|
|
+}
|
|
diff --git a/gtk/meson.build b/gtk/meson.build
|
|
index 38b810c..1054e29 100644
|
|
--- a/gtk/meson.build
|
|
+++ b/gtk/meson.build
|
|
@@ -383,7 +383,9 @@ gtk_sources = files(
|
|
'gtkwin32draw.c',
|
|
'gtkwin32theme.c',
|
|
'gdkpixbufutils.c',
|
|
+ 'hdy-css.c',
|
|
'hdy-view-switcher-button.c',
|
|
+ 'hdy-view-switcher.c',
|
|
'language-names.c',
|
|
'script-names.c',
|
|
)
|
|
@@ -391,7 +393,9 @@ gtk_sources = files(
|
|
gtk_private_type_headers = files(
|
|
'gtkcsstypesprivate.h',
|
|
'gtktexthandleprivate.h',
|
|
+ 'hdy-css-private.h',
|
|
'hdy-view-switcher-button-private.h',
|
|
+ 'hdy-view-switcher-private.h',
|
|
)
|
|
|
|
gtk_gir_public_headers = files(
|