[libdazzle] suggestions: add rudimentary grab support



commit 8429bfb69c4c60c0cad131d5a2f343eed07f43a0
Author: Christian Hergert <chergert redhat com>
Date:   Mon Jan 28 18:38:20 2019 -0800

    suggestions: add rudimentary grab support
    
    This moves the grab to the popover and then dispatches events back to
    the entry. Doing so is a bit more like entry completion which could improve
    our chances of avoiding xdg-popup focus weirdness.
    
    Also, it ensures you can't press on a button from the main window while
    the popover is visible, which is more inline with what the user would
    expect given other gtk widgets. Clicking outside the drop down area will
    cause the grab to break, and the popover to be dismissed.

 src/suggestions/dzl-suggestion-entry.c   |   7 ++
 src/suggestions/dzl-suggestion-popover.c | 153 ++++++++++++++++++++++++++++++-
 src/suggestions/dzl-suggestion-private.h |   2 +
 3 files changed, 161 insertions(+), 1 deletion(-)
---
diff --git a/src/suggestions/dzl-suggestion-entry.c b/src/suggestions/dzl-suggestion-entry.c
index 61c01fc..546e425 100644
--- a/src/suggestions/dzl-suggestion-entry.c
+++ b/src/suggestions/dzl-suggestion-entry.c
@@ -197,11 +197,18 @@ dzl_suggestion_entry_key_press_event (GtkWidget   *widget,
 {
   DzlSuggestionEntry *self = (DzlSuggestionEntry *)widget;
   DzlSuggestionEntryPrivate *priv = dzl_suggestion_entry_get_instance_private (self);
+  GdkDevice *device;
   gboolean ret;
 
   g_assert (DZL_IS_SUGGESTION_ENTRY (self));
   g_assert (priv->in_key_press >= 0);
 
+  if ((device = gdk_event_get_device ((GdkEvent *)key)) &&
+      gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
+    device = gdk_device_get_associated_device (device);
+
+  _dzl_suggestion_popover_set_device (priv->popover, device);
+
   /*
    * If Tab was pressed, and there is uncommitted suggested text,
    * commit it and stop propagation of the key press.
diff --git a/src/suggestions/dzl-suggestion-popover.c b/src/suggestions/dzl-suggestion-popover.c
index fba5945..b8988c8 100644
--- a/src/suggestions/dzl-suggestion-popover.c
+++ b/src/suggestions/dzl-suggestion-popover.c
@@ -25,6 +25,8 @@
 #include "dzl-debug.h"
 
 #include "animation/dzl-animation.h"
+#include "shortcuts/dzl-shortcut-controller.h"
+#include "shortcuts/dzl-shortcut-private.h"
 #include "suggestions/dzl-suggestion.h"
 #include "suggestions/dzl-suggestion-entry.h"
 #include "suggestions/dzl-suggestion-popover.h"
@@ -55,6 +57,8 @@ struct _DzlSuggestionPopover
 
   GListModel         *model;
 
+  GdkDevice          *grab_device;
+
   GType               row_type;
 
   gulong              delete_event_handler;
@@ -70,6 +74,7 @@ struct _DzlSuggestionPopover
 
   guint               popup_requested : 1;
   guint               entry_focused : 1;
+  guint               has_grab : 1;
 };
 
 enum {
@@ -497,12 +502,102 @@ attach_cb (DzlListBox    *list_box,
                                      self->subtitle_ellipsize);
 }
 
+static gboolean
+dzl_suggestion_popover_key_press_event (GtkWidget   *widget,
+                                        GdkEventKey *event)
+{
+  DzlSuggestionPopover *self = (DzlSuggestionPopover *)widget;
+
+  g_assert (DZL_IS_SUGGESTION_POPOVER (self));
+  g_assert (event != NULL);
+
+  if (self->relative_to != NULL)
+    {
+      DzlShortcutController *controller = dzl_shortcut_controller_try_find (self->relative_to);
+      g_autoptr(DzlShortcutChord) chord = NULL;
+
+      /* NOTE: This only allows for shortcuts on the target entry, not any
+       *       global shortcut. That is similar to how GtkEntryCompletion works
+       *       and ensures that we don't have state taken during global dispatch.
+       */
+
+      if (controller != NULL &&
+          (chord = dzl_shortcut_chord_new_from_event (event)))
+        {
+          DzlShortcutMatch match;
+
+          match = _dzl_shortcut_controller_handle (controller,
+                                                   event,
+                                                   chord,
+                                                   DZL_SHORTCUT_PHASE_DISPATCH,
+                                                   self->relative_to);
+
+          if (match == DZL_SHORTCUT_MATCH_EQUAL)
+            return TRUE;
+        }
+
+      return gtk_widget_event (self->relative_to, (GdkEvent *)event);
+    }
+
+  return GTK_WIDGET_CLASS (dzl_suggestion_popover_parent_class)->key_press_event (widget, event);
+}
+
+static gboolean
+dzl_suggestion_popover_key_release_event (GtkWidget   *widget,
+                                          GdkEventKey *event)
+{
+  DzlSuggestionPopover *self = (DzlSuggestionPopover *)widget;
+
+  g_assert (DZL_IS_SUGGESTION_POPOVER (self));
+  g_assert (event != NULL);
+
+  if (self->relative_to != NULL)
+    return gtk_widget_event (self->relative_to, (GdkEvent *)event);
+
+  return GTK_WIDGET_CLASS (dzl_suggestion_popover_parent_class)->key_release_event (widget, event);
+}
+
+static gboolean
+dzl_suggestion_popover_grab_broken_event (GtkWidget          *widget,
+                                          GdkEventGrabBroken *event)
+{
+  DzlSuggestionPopover *self = (DzlSuggestionPopover *)widget;
+
+  g_assert (DZL_IS_SUGGESTION_POPOVER (self));
+  g_assert (event != NULL);
+
+  dzl_suggestion_popover_popdown (self);
+
+  return GTK_WIDGET_CLASS (dzl_suggestion_popover_parent_class)->grab_broken_event (widget, event);
+}
+
+static gboolean
+dzl_suggestion_popover_button_release_event (GtkWidget      *widget,
+                                             GdkEventButton *event)
+{
+  DzlSuggestionPopover *self = (DzlSuggestionPopover *)widget;
+  gboolean ret;
+
+  g_assert (DZL_IS_SUGGESTION_POPOVER (self));
+  g_assert (event != NULL);
+
+  ret = GTK_WIDGET_CLASS (dzl_suggestion_popover_parent_class)->button_release_event (widget, event);
+
+  dzl_suggestion_popover_popdown (self);
+
+  /* Force immediate hide */
+  gtk_widget_hide (GTK_WIDGET (self));
+
+  return ret;
+}
+
 static void
 dzl_suggestion_popover_destroy (GtkWidget *widget)
 {
   DzlSuggestionPopover *self = (DzlSuggestionPopover *)widget;
 
   g_clear_handle_id (&self->queued_popdown, g_source_remove);
+  g_clear_object (&self->grab_device);
 
   dzl_suggestion_popover_set_transient_for (self, NULL);
 
@@ -603,6 +698,10 @@ dzl_suggestion_popover_class_init (DzlSuggestionPopoverClass *klass)
   widget_class->screen_changed = dzl_suggestion_popover_screen_changed;
   widget_class->realize = dzl_suggestion_popover_realize;
   widget_class->show = dzl_suggestion_popover_show;
+  widget_class->button_release_event = dzl_suggestion_popover_button_release_event;
+  widget_class->key_press_event = dzl_suggestion_popover_key_press_event;
+  widget_class->key_release_event = dzl_suggestion_popover_key_release_event;
+  widget_class->grab_broken_event = dzl_suggestion_popover_grab_broken_event;
 
   properties [PROP_MODEL] =
     g_param_spec_object ("model",
@@ -710,6 +809,7 @@ dzl_suggestion_popover_new (void)
 void
 dzl_suggestion_popover_popup (DzlSuggestionPopover *self)
 {
+  GdkScreen *screen = NULL;
   guint duration = 250;
   guint n_items;
 
@@ -721,6 +821,9 @@ dzl_suggestion_popover_popup (DzlSuggestionPopover *self)
       return;
     }
 
+  if (gtk_widget_get_mapped (GTK_WIDGET (self)))
+    return;
+
   if (self->relative_to != NULL)
     {
       GdkDisplay *display;
@@ -733,6 +836,9 @@ dzl_suggestion_popover_popup (DzlSuggestionPopover *self)
       display = gtk_widget_get_display (GTK_WIDGET (self->relative_to));
       window = gtk_widget_get_window (GTK_WIDGET (self->relative_to));
       monitor = gdk_display_get_monitor_at_window (display, window);
+      screen = gtk_widget_get_screen (GTK_WIDGET (self->relative_to));
+
+      gtk_window_set_screen (GTK_WINDOW (self), screen);
 
       gtk_widget_get_preferred_height (GTK_WIDGET (self), &min_height, &nat_height);
       gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
@@ -740,8 +846,19 @@ dzl_suggestion_popover_popup (DzlSuggestionPopover *self)
       duration = dzl_animation_calculate_duration (monitor, alloc.height, nat_height);
     }
 
+  gtk_widget_grab_focus (GTK_WIDGET (self));
   gtk_widget_show (GTK_WIDGET (self));
 
+  if (!self->has_grab && self->grab_device != NULL)
+    {
+      self->has_grab = TRUE;
+      gtk_grab_add (GTK_WIDGET (self));
+      gdk_seat_grab (gdk_device_get_seat (self->grab_device),
+                     gtk_widget_get_window (GTK_WIDGET (self)),
+                     GDK_SEAT_CAPABILITY_POINTER | GDK_SEAT_CAPABILITY_TOUCH,
+                     TRUE, NULL, NULL, NULL, NULL);
+    }
+
   gtk_revealer_set_transition_duration (self->revealer, duration);
   gtk_revealer_set_reveal_child (self->revealer, TRUE);
 }
@@ -759,7 +876,20 @@ dzl_suggestion_popover_popdown (DzlSuggestionPopover *self)
 
   self->popup_requested = FALSE;
 
-  if (!gtk_widget_get_realized (GTK_WIDGET (self)))
+  if (self->has_grab)
+    {
+      self->has_grab = FALSE;
+      gtk_grab_remove (GTK_WIDGET (self));
+
+      if (self->grab_device != NULL)
+        {
+          gdk_seat_ungrab (gdk_device_get_seat (self->grab_device));
+          g_clear_object (&self->grab_device);
+        }
+    }
+
+
+  if (!gtk_widget_get_mapped (GTK_WIDGET (self)))
     return;
 
   display = gtk_widget_get_display (GTK_WIDGET (self->relative_to));
@@ -823,6 +953,8 @@ dzl_suggestion_popover_items_changed (DzlSuggestionPopover *self,
       DZL_EXIT;
     }
 
+  g_clear_handle_id (&self->queued_popdown, g_source_remove);
+
   if (self->popup_requested)
     {
       dzl_suggestion_popover_popup (self);
@@ -830,6 +962,9 @@ dzl_suggestion_popover_items_changed (DzlSuggestionPopover *self,
       DZL_EXIT;
     }
 
+  if (gtk_widget_get_mapped (GTK_WIDGET (self)))
+    return;
+
   /*
    * If we are currently animating in the initial view of the popover,
    * then we might need to cancel that animation and rely on the elastic
@@ -853,6 +988,7 @@ dzl_suggestion_popover_items_changed (DzlSuggestionPopover *self,
     {
       dzl_suggestion_popover_popup (self);
       self->popup_requested = FALSE;
+      DZL_EXIT;
     }
 
   DZL_EXIT;
@@ -1189,3 +1325,18 @@ _dzl_suggestion_popover_set_focused (DzlSuggestionPopover *self,
   if (!entry_focused)
     self->popup_requested = FALSE;
 }
+
+void
+_dzl_suggestion_popover_set_device (DzlSuggestionPopover *self,
+                                    GdkDevice            *device)
+{
+  g_return_if_fail (DZL_IS_SUGGESTION_POPOVER (self));
+  g_return_if_fail (!device || GDK_IS_DEVICE (device));
+
+  if (device != self->grab_device)
+    {
+      if (self->has_grab && self->grab_device != NULL)
+        gdk_seat_ungrab (gdk_device_get_seat (self->grab_device));
+      g_set_object (&self->grab_device, device);
+    }
+}
diff --git a/src/suggestions/dzl-suggestion-private.h b/src/suggestions/dzl-suggestion-private.h
index 2f076c9..d507195 100644
--- a/src/suggestions/dzl-suggestion-private.h
+++ b/src/suggestions/dzl-suggestion-private.h
@@ -26,6 +26,8 @@
 
 void _dzl_suggestion_entry_reposition       (DzlSuggestionEntry   *entry,
                                              DzlSuggestionPopover *popover);
+void _dzl_suggestion_popover_set_device     (DzlSuggestionPopover *self,
+                                             GdkDevice            *device);
 void _dzl_suggestion_popover_set_focused    (DzlSuggestionPopover *self,
                                              gboolean              entry_focused);
 void _dzl_suggestion_popover_set_max_height (DzlSuggestionPopover *popover,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]