[gtksourceview] hoverassistant: implement dismissal without grabs
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtksourceview] hoverassistant: implement dismissal without grabs
- Date: Thu, 1 Sep 2022 01:00:02 +0000 (UTC)
commit a0f168f886283181ede6c517596b913b0799b928
Author: Christian Hergert <chergert redhat com>
Date: Wed Aug 31 12:21:34 2022 -0700
hoverassistant: implement dismissal without grabs
When using gtk_popover_set_autohide(..., TRUE), we incur device grabs
implicitely by GDK via GdkPopup:autohide. Those grabs can be quite
difficult to break simply using motion/crossing events.
Instead, this manages a few motion controllers (including a shared motion
controller on the GtkRoot) to track when pointer motion has left in such
a way that we want to dismiss the popover.
Fixes https://gitlab.gnome.org/GNOME/gnome-builder/-/issues/1749
gtksourceview/gtksourcehoverassistant.c | 349 ++++++++++++++++++++++----------
1 file changed, 247 insertions(+), 102 deletions(-)
---
diff --git a/gtksourceview/gtksourcehoverassistant.c b/gtksourceview/gtksourcehoverassistant.c
index 150b8455..7de6cb81 100644
--- a/gtksourceview/gtksourcehoverassistant.c
+++ b/gtksourceview/gtksourcehoverassistant.c
@@ -21,104 +21,267 @@
#include "config.h"
+#include <string.h>
+
#include "gtksourceassistant-private.h"
#include "gtksourcehoverassistant-private.h"
#include "gtksourcehovercontext-private.h"
#include "gtksourcehoverdisplay-private.h"
+#include "gtksourcehover-private.h"
#include "gtksourceview.h"
-#define GRACE_X 20
-#define GRACE_Y 20
+#define GTK_SOURCE_HOVER_ASSISTANT_MOTION "GTK_SOURCE_HOVER_ASSISTANT_MOTION"
struct _GtkSourceHoverAssistant
{
GtkSourceAssistant parent_instance;
+
+ GtkEventController *popover_motion;
+ GtkEventController *root_motion;
+
GtkSourceHoverDisplay *display;
+
GCancellable *cancellable;
+
GdkRectangle hovered_at;
+
+ double root_x;
+ double root_y;
+
+ gulong root_motion_handler;
+ gulong root_leave_handler;
+
+ GSource *dismiss_source;
};
G_DEFINE_TYPE (GtkSourceHoverAssistant, gtk_source_hover_assistant, GTK_SOURCE_TYPE_ASSISTANT)
+static gboolean
+gtk_source_hover_assistant_should_dismiss (GtkSourceHoverAssistant *self)
+{
+ GdkSurface *surface;
+
+ g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self));
+
+ if (gtk_event_controller_motion_contains_pointer (GTK_EVENT_CONTROLLER_MOTION (self->popover_motion)))
+ {
+ return FALSE;
+ }
+
+ if ((surface = gtk_native_get_surface (GTK_NATIVE (self))))
+ {
+ GdkRectangle popup_area, root_area;
+ double popup_x, popup_y;
+ double transform_x, transform_y;
+ GtkRoot *root;
+
+ popup_x = gdk_popup_get_position_x (GDK_POPUP (surface));
+ popup_y = gdk_popup_get_position_y (GDK_POPUP (surface));
+ gtk_native_get_surface_transform (GTK_NATIVE (self), &transform_x, &transform_y);
+
+ popup_area.x = popup_x - transform_x;
+ popup_area.y = popup_y - transform_y;
+ popup_area.width = gdk_surface_get_width (surface);
+ popup_area.height = gdk_surface_get_height (surface);
+
+ root = gtk_widget_get_root (GTK_WIDGET (self));
+ root_area.x = 0;
+ root_area.y = 0;
+ root_area.width = gtk_widget_get_width (GTK_WIDGET (root));
+ root_area.height = gtk_widget_get_height (GTK_WIDGET (root));
+
+ if (gdk_rectangle_intersect (&root_area, &popup_area, &popup_area) &&
+ gdk_rectangle_contains_point (&popup_area, self->root_x, self->root_y))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gtk_source_hover_assistant_dismiss_cb (GtkSourceHoverAssistant *self)
+{
+ g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self));
+
+ g_clear_pointer (&self->dismiss_source, g_source_destroy);
+
+ if (gtk_source_hover_assistant_should_dismiss (self))
+ {
+ _gtk_source_hover_assistant_dismiss (self);
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
static void
-gtk_source_hover_assistant_get_target_location (GtkSourceAssistant *assistant,
- GdkRectangle *rect)
+gtk_source_hover_assistant_queue_dismiss (GtkSourceHoverAssistant *self)
{
- *rect = GTK_SOURCE_HOVER_ASSISTANT (assistant)->hovered_at;
+ g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self));
+
+ if (self->dismiss_source != NULL)
+ {
+ return;
+ }
+
+ self->dismiss_source = g_idle_source_new ();
+ g_source_set_name (self->dismiss_source, "gtk-source-hover-assistant-timeout");
+ g_source_set_callback (self->dismiss_source, (GSourceFunc)gtk_source_hover_assistant_dismiss_cb,
self, NULL);
+ g_source_attach (self->dismiss_source, NULL);
+ g_source_unref (self->dismiss_source);
}
static void
-gtk_source_hover_assistant_motion_cb (GtkSourceHoverAssistant *self,
- double x,
- double y,
- GtkEventControllerMotion *controller)
+gtk_source_hover_assistant_popover_leave_cb (GtkSourceHoverAssistant *self,
+ GtkEventControllerMotion *controller)
{
- GdkSurface *assistant_surface;
- GtkWidget *parent;
- GtkRoot *root;
- double tx, ty;
- int width, height;
+ g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self));
+ g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (controller));
+
+ gtk_source_hover_assistant_queue_dismiss (self);
+}
+static void
+gtk_source_hover_assistant_root_leave_cb (GtkSourceHoverAssistant *self,
+ GtkEventControllerMotion *controller)
+{
+ g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self));
+ g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (controller));
+
+ gtk_source_hover_assistant_queue_dismiss (self);
+}
+
+static void
+gtk_source_hover_assistant_root_motion_cb (GtkSourceHoverAssistant *self,
+ double x,
+ double y,
+ GtkEventControllerMotion *controller)
+{
g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self));
g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (controller));
- if (gtk_event_controller_motion_contains_pointer (controller))
+ self->root_x = x;
+ self->root_y = y;
+
+ gtk_source_hover_assistant_queue_dismiss (self);
+}
+
+static void
+gtk_source_hover_assistant_root (GtkWidget *widget)
+{
+ GtkSourceHoverAssistant *self = (GtkSourceHoverAssistant *)widget;
+ GtkRoot *root;
+
+ GTK_WIDGET_CLASS (gtk_source_hover_assistant_parent_class)->root (widget);
+
+ if ((root = gtk_widget_get_root (widget)))
{
- return;
+ GtkEventController *motion;
+
+ if (!(motion = g_object_get_data (G_OBJECT (root), GTK_SOURCE_HOVER_ASSISTANT_MOTION)))
+ {
+ motion = gtk_event_controller_motion_new ();
+ gtk_event_controller_set_name (motion, "gtk-source-hover-assistant-motion");
+ g_object_set_data (G_OBJECT (root), GTK_SOURCE_HOVER_ASSISTANT_MOTION, motion);
+ gtk_widget_add_controller (GTK_WIDGET (root), motion);
+ }
+
+ self->root_motion = g_object_ref (motion);
+ self->root_motion_handler =
+ g_signal_connect_object (self->root_motion,
+ "motion",
+ G_CALLBACK (gtk_source_hover_assistant_root_motion_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ self->root_leave_handler =
+ g_signal_connect_object (self->root_motion,
+ "leave",
+ G_CALLBACK (gtk_source_hover_assistant_root_leave_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ if (!gtk_widget_get_visible (GTK_WIDGET (self)))
+ {
+ g_signal_handler_block (self->root_motion, self->root_motion_handler);
+ g_signal_handler_block (self->root_motion, self->root_leave_handler);
+ }
}
+}
- if (!(parent = gtk_widget_get_parent (GTK_WIDGET (self))) ||
- !(root = gtk_widget_get_root (parent)) ||
- !(assistant_surface = gtk_native_get_surface (GTK_NATIVE (self))))
+static void
+gtk_source_hover_assistant_unroot (GtkWidget *widget)
+{
+ GtkSourceHoverAssistant *self = (GtkSourceHoverAssistant *)widget;
+ GtkRoot *root;
+
+ if ((root = gtk_widget_get_root (widget)))
{
- return;
+ GtkEventController *motion;
+
+ if ((motion = g_object_get_data (G_OBJECT (root), GTK_SOURCE_HOVER_ASSISTANT_MOTION)))
+ {
+ g_clear_signal_handler (&self->root_motion_handler, motion);
+ g_clear_signal_handler (&self->root_leave_handler, motion);
+ }
+ else
+ {
+ self->root_motion_handler = 0;
+ self->root_leave_handler = 0;
+ }
}
- gtk_widget_translate_coordinates (parent, GTK_WIDGET (root), x, y, &x, &y);
- x -= gdk_popup_get_position_x (GDK_POPUP (assistant_surface));
- y -= gdk_popup_get_position_y (GDK_POPUP (assistant_surface));
+ GTK_WIDGET_CLASS (gtk_source_hover_assistant_parent_class)->unroot (widget);
+}
- gtk_native_get_surface_transform (GTK_NATIVE (root), &tx, &ty);
- x += tx;
- y += ty;
+static void
+gtk_source_hover_assistant_show (GtkWidget *widget)
+{
+ GtkSourceHoverAssistant *self = GTK_SOURCE_HOVER_ASSISTANT (widget);
+ GtkRoot *root;
- width = gdk_surface_get_width (assistant_surface);
- height = gdk_surface_get_height (assistant_surface);
+ GTK_WIDGET_CLASS (gtk_source_hover_assistant_parent_class)->show (widget);
- if (x < -GRACE_X ||
- x > width + GRACE_Y ||
- y < -GRACE_Y ||
- y > height + GRACE_Y)
+ if ((root = gtk_widget_get_root (widget)))
{
- GtkWidget *focus = gtk_root_get_focus (root);
- GtkWidget *view = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_SOURCE_TYPE_VIEW);
- gboolean return_focus;
-
- return_focus = focus != NULL &&
- (focus == GTK_WIDGET (self) ||
- gtk_widget_is_ancestor (focus, GTK_WIDGET (self)));
+ GtkEventController *motion;
- gtk_widget_hide (GTK_WIDGET (self));
-
- if (return_focus && view != NULL)
+ if ((motion = g_object_get_data (G_OBJECT (root), GTK_SOURCE_HOVER_ASSISTANT_MOTION)))
{
- gtk_widget_grab_focus (GTK_WIDGET (view));
+ g_signal_handler_unblock (motion, self->root_motion_handler);
+ g_signal_handler_unblock (motion, self->root_leave_handler);
}
}
}
-static gboolean
-gtk_source_hover_assistant_scroll_cb (GtkSourceHoverAssistant *self,
- double dx,
- double dy,
- GtkEventControllerScroll *controller)
+static void
+gtk_source_hover_assistant_hide (GtkWidget *widget)
{
- g_assert (GTK_SOURCE_IS_HOVER_ASSISTANT (self));
- g_assert (GTK_IS_EVENT_CONTROLLER_SCROLL (controller));
+ GtkSourceHoverAssistant *self = GTK_SOURCE_HOVER_ASSISTANT (widget);
+ GtkRoot *root;
- gtk_widget_hide (GTK_WIDGET (self));
+ GTK_WIDGET_CLASS (gtk_source_hover_assistant_parent_class)->hide (widget);
+
+ gtk_popover_set_pointing_to (GTK_POPOVER (widget), NULL);
+ gtk_popover_set_offset (GTK_POPOVER (widget), 0, 0);
+
+ if ((root = gtk_widget_get_root (widget)))
+ {
+ GtkEventController *motion;
+
+ if ((motion = g_object_get_data (G_OBJECT (root), GTK_SOURCE_HOVER_ASSISTANT_MOTION)))
+ {
+ g_signal_handler_block (motion, self->root_motion_handler);
+ g_signal_handler_block (motion, self->root_leave_handler);
+ }
+ }
+}
- return GDK_EVENT_PROPAGATE;
+static void
+gtk_source_hover_assistant_get_target_location (GtkSourceAssistant *assistant,
+ GdkRectangle *rect)
+{
+ *rect = GTK_SOURCE_HOVER_ASSISTANT (assistant)->hovered_at;
}
static void
@@ -128,6 +291,11 @@ gtk_source_hover_assistant_dispose (GObject *object)
self->display = NULL;
+ g_clear_pointer (&self->dismiss_source, g_source_destroy);
+
+ g_clear_object (&self->root_motion);
+ g_clear_object (&self->popover_motion);
+
g_clear_object (&self->cancellable);
G_OBJECT_CLASS (gtk_source_hover_assistant_parent_class)->dispose (object);
@@ -136,43 +304,39 @@ gtk_source_hover_assistant_dispose (GObject *object)
static void
gtk_source_hover_assistant_class_init (GtkSourceHoverAssistantClass *klass)
{
- GtkSourceAssistantClass *assistant_class = GTK_SOURCE_ASSISTANT_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkSourceAssistantClass *assistant_class = GTK_SOURCE_ASSISTANT_CLASS (klass);
object_class->dispose = gtk_source_hover_assistant_dispose;
+ widget_class->root = gtk_source_hover_assistant_root;
+ widget_class->unroot = gtk_source_hover_assistant_unroot;
+ widget_class->show = gtk_source_hover_assistant_show;
+ widget_class->hide = gtk_source_hover_assistant_hide;
+
assistant_class->get_target_location = gtk_source_hover_assistant_get_target_location;
}
static void
gtk_source_hover_assistant_init (GtkSourceHoverAssistant *self)
{
- GtkEventController *motion;
- GtkEventController *scroll;
-
gtk_widget_add_css_class (GTK_WIDGET (self), "hover-assistant");
- gtk_popover_set_autohide (GTK_POPOVER (self), TRUE);
+ gtk_popover_set_autohide (GTK_POPOVER (self), FALSE);
+ gtk_popover_set_position (GTK_POPOVER (self), GTK_POS_TOP);
self->display = g_object_new (GTK_SOURCE_TYPE_HOVER_DISPLAY, NULL);
_gtk_source_assistant_set_child (GTK_SOURCE_ASSISTANT (self), GTK_WIDGET (self->display));
- motion = gtk_event_controller_motion_new ();
- gtk_event_controller_set_propagation_phase (motion, GTK_PHASE_CAPTURE);
- g_signal_connect_object (motion,
- "motion",
- G_CALLBACK (gtk_source_hover_assistant_motion_cb),
+ self->popover_motion = gtk_event_controller_motion_new ();
+ gtk_event_controller_set_name (self->popover_motion, "gkt-source-hover-assistant-motion");
+ g_signal_connect_object (self->popover_motion,
+ "leave",
+ G_CALLBACK (gtk_source_hover_assistant_popover_leave_cb),
self,
G_CONNECT_SWAPPED);
- gtk_widget_add_controller (GTK_WIDGET (self), motion);
-
- scroll = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
- g_signal_connect_object (scroll,
- "scroll",
- G_CALLBACK (gtk_source_hover_assistant_scroll_cb),
- self,
- G_CONNECT_SWAPPED);
- gtk_widget_add_controller (GTK_WIDGET (self), scroll);
+ gtk_widget_add_controller (GTK_WIDGET (self), g_object_ref (self->popover_motion));
}
GtkSourceAssistant *
@@ -225,20 +389,15 @@ _gtk_source_hover_assistant_display (GtkSourceHoverAssistant *self,
g_return_if_fail (end != NULL);
g_return_if_fail (location != NULL);
- if (n_providers == 0)
- {
- if (gtk_widget_get_visible (GTK_WIDGET (self)))
- {
- gtk_widget_hide (GTK_WIDGET (self));
- }
+ memset (&self->hovered_at, 0, sizeof self->hovered_at);
- return;
- }
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
- if (self->cancellable != NULL)
+ if (n_providers == 0)
{
- g_cancellable_cancel (self->cancellable);
- g_clear_object (&self->cancellable);
+ gtk_widget_hide (GTK_WIDGET (self));
+ return;
}
view = GTK_SOURCE_VIEW (gtk_widget_get_parent (GTK_WIDGET (self)));
@@ -247,31 +406,14 @@ _gtk_source_hover_assistant_display (GtkSourceHoverAssistant *self,
gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), begin, &begin_rect);
gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), end, &end_rect);
gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), location, &location_rect);
-
gdk_rectangle_union (&begin_rect, &end_rect, &location_rect);
if (!gdk_rectangle_intersect (&location_rect, &visible_rect, &location_rect))
{
- if (gtk_widget_get_visible (GTK_WIDGET (self)))
- {
- gtk_widget_hide (GTK_WIDGET (self));
- }
-
- memset (&self->hovered_at, 0, sizeof self->hovered_at);
-
+ gtk_widget_hide (GTK_WIDGET (self));
return;
}
- if (gtk_text_iter_equal (begin, end) && gtk_text_iter_starts_line (begin))
- {
- location_rect.width = 1;
- gtk_popover_set_position (GTK_POPOVER (self), GTK_POS_RIGHT);
- }
- else
- {
- gtk_popover_set_position (GTK_POPOVER (self), GTK_POS_TOP);
- }
-
self->hovered_at = location_rect;
context = _gtk_source_hover_context_new (view, begin, end, location);
@@ -281,10 +423,10 @@ _gtk_source_hover_assistant_display (GtkSourceHoverAssistant *self,
_gtk_source_hover_context_add_provider (context, providers[i]);
}
- self->cancellable = g_cancellable_new ();
-
_gtk_source_hover_display_clear (self->display);
+ self->cancellable = g_cancellable_new ();
+
_gtk_source_hover_context_populate_async (context,
self->display,
self->cancellable,
@@ -300,6 +442,9 @@ _gtk_source_hover_assistant_dismiss (GtkSourceHoverAssistant *self)
g_return_if_fail (GTK_SOURCE_IS_HOVER_ASSISTANT (self));
g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+
gtk_widget_hide (GTK_WIDGET (self));
+
_gtk_source_hover_display_clear (self->display);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]