From 66347fa3bfe952f4008f864576c1fe8060c0421e Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 16 Apr 2020 17:22:54 -0400 Subject: [PATCH] viewport: Add GtkViewport:scroll-to-focus And implement this property by listening for focus changes, and updating the adjustments. This is a replacement for setting focus adjustments on containers. --- gtk/gtkviewport.c | 176 +++++++++++++++++++++++++++++++++++++++++++++- gtk/gtkviewport.h | 6 ++ 2 files changed, 180 insertions(+), 2 deletions(-) diff --git a/gtk/gtkviewport.c b/gtk/gtkviewport.c index c257bc741b..64011d1f3f 100644 --- a/gtk/gtkviewport.c +++ b/gtk/gtkviewport.c @@ -26,7 +26,7 @@ #include "gtkviewport.h" -#include "gtkadjustment.h" +#include "gtkadjustmentprivate.h" #include "gtkintl.h" #include "gtkmarshalers.h" #include "gtkprivate.h" @@ -34,6 +34,7 @@ #include "gtkstylecontext.h" #include "gtktypebuiltins.h" #include "gtkwidgetprivate.h" +#include "gtktext.h" /** @@ -80,6 +81,9 @@ struct _GtkViewportPrivate * driving the scrollable adjustment values */ guint hscroll_policy : 1; guint vscroll_policy : 1; + guint scroll_to_focus : 1; + + gulong focus_handler; }; struct _GtkViewportClass @@ -92,7 +96,8 @@ enum { PROP_HADJUSTMENT, PROP_VADJUSTMENT, PROP_HSCROLL_POLICY, - PROP_VSCROLL_POLICY + PROP_VSCROLL_POLICY, + PROP_SCROLL_TO_FOCUS }; @@ -115,6 +120,9 @@ static void viewport_set_adjustment (GtkViewport *viewport, GtkOrientation orientation, GtkAdjustment *adjustment); +static void setup_focus_change_handler (GtkViewport *viewport); +static void clear_focus_change_handler (GtkViewport *viewport); + G_DEFINE_TYPE_WITH_CODE (GtkViewport, gtk_viewport, GTK_TYPE_BIN, G_ADD_PRIVATE (GtkViewport) G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL)) @@ -228,6 +236,39 @@ gtk_viewport_measure (GtkWidget *widget, NULL, NULL); } +static void +gtk_viewport_dispose (GObject *object) +{ + clear_focus_change_handler (GTK_VIEWPORT (object)); + + G_OBJECT_CLASS (gtk_viewport_parent_class)->dispose (object); + +} + +static void +gtk_viewport_root (GtkWidget *widget) +{ + GtkViewport *viewport = GTK_VIEWPORT (widget); + GtkViewportPrivate *priv = gtk_viewport_get_instance_private (viewport); + + GTK_WIDGET_CLASS (gtk_viewport_parent_class)->root (widget); + + if (priv->scroll_to_focus) + setup_focus_change_handler (viewport); +} + +static void +gtk_viewport_unroot (GtkWidget *widget) +{ + GtkViewport *viewport = GTK_VIEWPORT (widget); + GtkViewportPrivate *priv = gtk_viewport_get_instance_private (viewport); + + if (priv->scroll_to_focus) + clear_focus_change_handler (viewport); + + GTK_WIDGET_CLASS (gtk_viewport_parent_class)->unroot (widget); +} + static void gtk_viewport_class_init (GtkViewportClass *class) { @@ -237,12 +278,15 @@ gtk_viewport_class_init (GtkViewportClass *class) gobject_class = G_OBJECT_CLASS (class); widget_class = (GtkWidgetClass*) class; + gobject_class->dispose = gtk_viewport_dispose; gobject_class->set_property = gtk_viewport_set_property; gobject_class->get_property = gtk_viewport_get_property; widget_class->destroy = gtk_viewport_destroy; widget_class->size_allocate = gtk_viewport_size_allocate; widget_class->measure = gtk_viewport_measure; + widget_class->root = gtk_viewport_root; + widget_class->unroot = gtk_viewport_unroot; gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_VIEWPORT); @@ -252,6 +296,14 @@ gtk_viewport_class_init (GtkViewportClass *class) g_object_class_override_property (gobject_class, PROP_HSCROLL_POLICY, "hscroll-policy"); g_object_class_override_property (gobject_class, PROP_VSCROLL_POLICY, "vscroll-policy"); + g_object_class_install_property (gobject_class, + PROP_SCROLL_TO_FOCUS, + g_param_spec_boolean ("scroll-to-focus", + P_("Scroll to focus"), + P_("Whether to scroll when the focus changes"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); + gtk_widget_class_set_css_name (widget_class, I_("viewport")); } @@ -288,6 +340,9 @@ gtk_viewport_set_property (GObject *object, g_object_notify_by_pspec (object, pspec); } break; + case PROP_SCROLL_TO_FOCUS: + gtk_viewport_set_scroll_to_focus (viewport, g_value_get_boolean (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -317,6 +372,9 @@ gtk_viewport_get_property (GObject *object, case PROP_VSCROLL_POLICY: g_value_set_enum (value, priv->vscroll_policy); break; + case PROP_SCROLL_TO_FOCUS: + g_value_set_boolean (value, priv->scroll_to_focus); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -464,3 +522,117 @@ gtk_viewport_adjustment_value_changed (GtkAdjustment *adjustment, { gtk_widget_queue_allocate (GTK_WIDGET (data)); } + +gboolean +gtk_viewport_get_scroll_to_focus (GtkViewport *viewport) +{ + GtkViewportPrivate *priv = gtk_viewport_get_instance_private (viewport); + + g_return_val_if_fail (GTK_IS_VIEWPORT (viewport), FALSE); + + return priv->scroll_to_focus; +} + +void +gtk_viewport_set_scroll_to_focus (GtkViewport *viewport, + gboolean scroll_to_focus) +{ + GtkViewportPrivate *priv = gtk_viewport_get_instance_private (viewport); + + g_return_if_fail (GTK_IS_VIEWPORT (viewport)); + + if (priv->scroll_to_focus == scroll_to_focus) + return; + + priv->scroll_to_focus = scroll_to_focus; + + if (gtk_widget_get_root (GTK_WIDGET (viewport))) + { + if (scroll_to_focus) + setup_focus_change_handler (viewport); + else + clear_focus_change_handler (viewport); + } + + g_object_notify (G_OBJECT (viewport), "scroll-to-focus"); +} + +static void +scroll_to_view (GtkAdjustment *adj, + double pos, + double size) +{ + double value, page_size; + + value = gtk_adjustment_get_value (adj); + page_size = gtk_adjustment_get_page_size (adj); + + if (pos < 0) + gtk_adjustment_animate_to_value (adj, value + pos); + else if (pos + size >= page_size) + gtk_adjustment_animate_to_value (adj, value + pos + size - page_size); +} + +static void +focus_change_handler (GtkWidget *widget) +{ + GtkViewport *viewport = GTK_VIEWPORT (widget); + GtkViewportPrivate *priv = gtk_viewport_get_instance_private (viewport); + GtkRoot *root; + GtkWidget *focus_widget; + GtkWidget *child; + graphene_rect_t rect; + int x, y; + + if ((gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_FOCUS_WITHIN) == 0) + return; + + root = gtk_widget_get_root (widget); + focus_widget = gtk_root_get_focus (root); + + if (!focus_widget) + return; + + if (GTK_IS_TEXT (focus_widget)) + focus_widget = gtk_widget_get_parent (focus_widget); + + child = gtk_bin_get_child (GTK_BIN (viewport)); + + if (!gtk_widget_compute_bounds (focus_widget, child, &rect)) + return; + + gtk_widget_translate_coordinates (child, widget, + (int)rect.origin.x, + (int)rect.origin.y, + &x, &y); + + scroll_to_view (priv->hadjustment, x, rect.size.width); + scroll_to_view (priv->vadjustment, y, rect.size.height); +} + +static void +setup_focus_change_handler (GtkViewport *viewport) +{ + GtkViewportPrivate *priv = gtk_viewport_get_instance_private (viewport); + GtkRoot *root; + + root = gtk_widget_get_root (GTK_WIDGET (viewport)); + + priv->focus_handler = g_signal_connect_swapped (root, "notify::focus-widget", + G_CALLBACK (focus_change_handler), viewport); +} + +static void +clear_focus_change_handler (GtkViewport *viewport) +{ + GtkViewportPrivate *priv = gtk_viewport_get_instance_private (viewport); + GtkRoot *root; + + root = gtk_widget_get_root (GTK_WIDGET (viewport)); + + if (priv->focus_handler) + { + g_signal_handler_disconnect (root, priv->focus_handler); + priv->focus_handler = 0; + } +} diff --git a/gtk/gtkviewport.h b/gtk/gtkviewport.h index 45188923b0..b1f430e169 100644 --- a/gtk/gtkviewport.h +++ b/gtk/gtkviewport.h @@ -50,6 +50,12 @@ GDK_AVAILABLE_IN_ALL GtkWidget* gtk_viewport_new (GtkAdjustment *hadjustment, GtkAdjustment *vadjustment); +GDK_AVAILABLE_IN_ALL +gboolean gtk_viewport_get_scroll_to_focus (GtkViewport *viewport); +GDK_AVAILABLE_IN_ALL +void gtk_viewport_set_scroll_to_focus (GtkViewport *viewport, + gboolean scroll_to_focus); + G_END_DECLS