[gtk/wip/matthiasc/shortcut-4: 78/85] popover: Implement auto mnemonics



commit 642503afb4e9ad726237fb2241c8df3288a2da8e
Author: Matthias Clasen <mclasen redhat com>
Date:   Tue Mar 24 12:45:43 2020 -0400

    popover: Implement auto mnemonics
    
    Unfortunately, this involves copying a bunch of
    code from gtkwindow.c. The only difference here
    is that we add a private method to turn this off,
    which will be used by GtkPopoverMenu to implement
    its own auto mnemonics.

 gtk/gtkpopover.c        | 154 +++++++++++++++++++++++++++++++++++++++++++++++-
 gtk/gtkpopoverprivate.h |   2 +
 2 files changed, 155 insertions(+), 1 deletion(-)
---
diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c
index 88426dd7a7..1c90dc2af9 100644
--- a/gtk/gtkpopover.c
+++ b/gtk/gtkpopover.c
@@ -102,6 +102,7 @@
 #include "gtknative.h"
 #include "gtkwidgetprivate.h"
 #include "gtkeventcontrollerkey.h"
+#include "gtkeventcontrollerfocus.h"
 #include "gtkcssnodeprivate.h"
 #include "gtkbinlayout.h"
 #include "gtkenums.h"
@@ -130,6 +131,8 @@
 #include "wayland/gdkwayland.h"
 #endif
 
+#define MNEMONICS_DELAY 300 /* ms */
+
 #define TAIL_GAP_WIDTH  24
 #define TAIL_HEIGHT     12
 
@@ -147,6 +150,9 @@ typedef struct {
   gboolean autohide;
   gboolean has_arrow;
   gboolean mnemonics_visible;
+  gboolean disable_auto_mnemonics;
+
+  guint mnemonics_display_timeout_id;
 
   GtkWidget *contents_widget;
   GtkCssNode *arrow_node;
@@ -579,18 +585,137 @@ close_menu (GtkPopover *popover)
     }
 }
 
+static gboolean
+gtk_popover_has_mnemonic_modifier_pressed (GtkPopover *popover)
+{
+  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
+  GList *seats, *s;
+  gboolean retval = FALSE;
+
+  seats = gdk_display_list_seats (gtk_widget_get_display (GTK_WIDGET (popover)));
+
+  for (s = seats; s; s = s->next)
+    {
+      GdkDevice *dev = gdk_seat_get_pointer (s->data);
+      GdkModifierType mask;
+
+      gdk_device_get_state (dev, priv->surface, NULL, &mask);
+      if ((mask & gtk_accelerator_get_default_mod_mask ()) == GDK_MOD1_MASK)
+        {
+          retval = TRUE;
+          break;
+        }
+    }
+
+  g_list_free (seats);
+
+  return retval;
+}
+
+static gboolean
+schedule_mnemonics_visible_cb (gpointer data)
+{
+  GtkPopover *popover = data;
+  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
+
+  priv->mnemonics_display_timeout_id = 0;
+
+  gtk_popover_set_mnemonics_visible (popover, TRUE);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+gtk_popover_schedule_mnemonics_visible (GtkPopover *popover)
+{
+  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
+
+  if (priv->mnemonics_display_timeout_id)
+    return;
+
+  priv->mnemonics_display_timeout_id =
+    g_timeout_add (MNEMONICS_DELAY, schedule_mnemonics_visible_cb, popover);
+  g_source_set_name_by_id (priv->mnemonics_display_timeout_id, "[gtk] 
popover_schedule_mnemonics_visible_cb");
+}
+
+static void
+gtk_popover_focus_in (GtkWidget *widget)
+{
+  GtkPopover *popover = GTK_POPOVER (widget);
+  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
+
+  if (priv->disable_auto_mnemonics)
+    return;
+
+  if (gtk_widget_get_visible (widget))
+    {
+      if (gtk_popover_has_mnemonic_modifier_pressed (popover))
+        gtk_popover_schedule_mnemonics_visible (popover);
+    }
+}
+
+static void
+gtk_popover_focus_out (GtkWidget *widget)
+{
+  GtkPopover *popover = GTK_POPOVER (widget);
+  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
+
+  if (priv->disable_auto_mnemonics)
+    return;
+
+  gtk_popover_set_mnemonics_visible (popover, FALSE);
+}
+
+static void
+update_mnemonics_visible (GtkPopover      *popover,
+                          guint            keyval,
+                          GdkModifierType  state,
+                          gboolean         visible)
+{
+  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
+
+  if (priv->disable_auto_mnemonics)
+    return;
+
+  if ((keyval == GDK_KEY_Alt_L || keyval == GDK_KEY_Alt_R) &&
+      ((state & (gtk_accelerator_get_default_mod_mask ()) & ~(GDK_MOD1_MASK)) == 0))
+    {
+      if (visible)
+        gtk_popover_schedule_mnemonics_visible (popover);
+      else
+        gtk_popover_set_mnemonics_visible (popover, FALSE);
+    }
+}
+
 static gboolean
 gtk_popover_key_pressed (GtkWidget       *widget,
                          guint            keyval,
                          guint            keycode,
                          GdkModifierType  state)
 {
+  GtkPopover *popover = GTK_POPOVER (widget);
+
   if (keyval == GDK_KEY_Escape)
     {
-      close_menu (GTK_POPOVER (widget));
+      close_menu (popover);
       return TRUE;
     }
 
+  update_mnemonics_visible (popover, keyval, state, TRUE);
+
+  return FALSE;
+}
+
+static gboolean
+gtk_popover_key_released (GtkWidget       *widget,
+                          guint            keyval,
+                          guint            keycode,
+                          GdkModifierType  state)
+{
+  GtkPopover *popover = GTK_POPOVER (widget);
+
+  update_mnemonics_visible (popover, keyval, state, FALSE);
+
   return FALSE;
 }
 
@@ -709,8 +834,14 @@ gtk_popover_init (GtkPopover *popover)
 
   controller = gtk_event_controller_key_new ();
   g_signal_connect_swapped (controller, "key-pressed", G_CALLBACK (gtk_popover_key_pressed), popover);
+  g_signal_connect_swapped (controller, "key-released", G_CALLBACK (gtk_popover_key_released), popover);
   gtk_widget_add_controller (GTK_WIDGET (popover), controller);
 
+  controller = gtk_event_controller_focus_new ();
+  g_signal_connect_swapped (controller, "enter", G_CALLBACK (gtk_popover_focus_in), popover);
+  g_signal_connect_swapped (controller, "leave", G_CALLBACK (gtk_popover_focus_out), popover);
+  gtk_widget_add_controller (widget, controller);
+
   priv->arrow_node = gtk_css_node_new ();
   gtk_css_node_set_name (priv->arrow_node, g_quark_from_static_string ("arrow"));
   gtk_css_node_set_parent (priv->arrow_node, gtk_widget_get_css_node (widget));
@@ -797,6 +928,7 @@ gtk_popover_show (GtkWidget *widget)
 static void
 gtk_popover_hide (GtkWidget *widget)
 {
+  gtk_popover_set_mnemonics_visible (GTK_POPOVER (widget), FALSE);
   _gtk_widget_set_visible_flag (widget, FALSE);
   gtk_widget_unmap (widget);
   g_signal_emit (widget, signals[CLOSED], 0);
@@ -899,6 +1031,12 @@ gtk_popover_finalize (GObject *object)
 
   g_clear_pointer (&priv->layout, gdk_popup_layout_unref);
 
+  if (priv->mnemonics_display_timeout_id)
+    {
+      g_source_remove (priv->mnemonics_display_timeout_id);
+      priv->mnemonics_display_timeout_id = 0;
+    }
+
   G_OBJECT_CLASS (gtk_popover_parent_class)->finalize (object);
 }
 
@@ -1938,6 +2076,12 @@ gtk_popover_set_mnemonics_visible (GtkPopover *popover,
 
   g_object_notify_by_pspec (G_OBJECT (popover), properties[PROP_MNEMONICS_VISIBLE]);
   gtk_widget_queue_resize (GTK_WIDGET (popover));
+
+  if (priv->mnemonics_display_timeout_id)
+    {
+      g_source_remove (priv->mnemonics_display_timeout_id);
+      priv->mnemonics_display_timeout_id = 0;
+    }
 }
 
 /**
@@ -1957,3 +2101,11 @@ gtk_popover_get_mnemonics_visible (GtkPopover *popover)
 
   return priv->mnemonics_visible;
 }
+
+void
+gtk_popover_disable_auto_mnemonics (GtkPopover *popover)
+{
+  GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover);
+
+  priv->disable_auto_mnemonics = TRUE;
+}
diff --git a/gtk/gtkpopoverprivate.h b/gtk/gtkpopoverprivate.h
index db5fa6e57a..879562332b 100644
--- a/gtk/gtkpopoverprivate.h
+++ b/gtk/gtkpopoverprivate.h
@@ -24,6 +24,8 @@ G_BEGIN_DECLS
 
 GtkWidget *gtk_popover_get_contents_widget (GtkPopover *popover);
 
+void       gtk_popover_disable_auto_mnemonics (GtkPopover *popover);
+
 G_END_DECLS
 
 #endif /* __GTK_POPOVER_PRIVATE_H__ */


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