shortcutcontroller: Add GtkShortcutScope
authorBenjamin Otte <otte@redhat.com>
Mon, 13 Aug 2018 14:02:27 +0000 (16:02 +0200)
committerMatthias Clasen <mclasen@redhat.com>
Thu, 26 Mar 2020 03:14:27 +0000 (23:14 -0400)
Allow setting the scope for a controller. The scope determines at what
point in event propagation the shortcuts will be activated.

Local scope is the usual activation, global scope means that the root
widget activates the shortcuts - ie they are activated at the very
start of event propagation (for global capture events) or the very end
(for global bubble events).
Managed scope so far is unimplemented.

This is supposed to be used to replace accelerators and mnemonics.

docs/reference/gtk/gtk4-sections.txt
gtk/gtkenums.h
gtk/gtkshortcutcontroller.c
gtk/gtkshortcutcontroller.h
gtk/gtkshortcutcontrollerprivate.h
gtk/gtkwidget.c

index b11bb4e2462e8fd497cbddf1508fa58a68b074bb..3a5b41d2575ed4e0571aaf5aa7df6e9e36b05702 100644 (file)
@@ -6101,6 +6101,9 @@ gtk_shortcut_get_type
 <TITLE>GtkShortcutController</TITLE>
 GtkShortcutController
 gtk_shortcut_controller_new
+GtkShortcutScope
+gtk_shortcut_controller_set_scope
+gtk_shortcut_controller_get_scope
 gtk_shortcut_controller_add_shortcut
 gtk_shortcut_controller_remove_shortcut
 
index 8cb42cff20c3b6255deff14c64a3ddafd17ee838..658be5f338b2823b53114534b99130cee4df1a62 100644 (file)
@@ -972,6 +972,25 @@ typedef enum
   GTK_PAN_DIRECTION_DOWN
 } GtkPanDirection;
 
+/**
+ * GtkShortcutScope:
+ * @GTK_SHORTCUT_SCOPE_LOCAL: Shortcuts are handled inside
+ *     the widget the controller belongs to.
+ * @GTK_SHORTCUT_SCOPE_MANAGED: Shortcuts are handled by
+ *     the first ancestor that is a #GtkShortcutManager
+ * @GTK_SHORTCUT_SCOPE_GLOBAL: Shortcuts are handled by
+ *     the root widget.
+ *
+ * Describes where #GtkShortcuts added to a
+ * #GtkShortcutController get handled.
+ */
+typedef enum
+{
+  GTK_SHORTCUT_SCOPE_LOCAL,
+  GTK_SHORTCUT_SCOPE_MANAGED,
+  GTK_SHORTCUT_SCOPE_GLOBAL
+} GtkShortcutScope;
+
 /**
  * GtkPopoverConstraint:
  * @GTK_POPOVER_CONSTRAINT_NONE: Don't constrain the popover position
index b4538d07291890f13e88c0e86e7ec9c0c81fdfdd..ed56a98dc67d030bef4991424c1badc130f2afff 100644 (file)
@@ -32,7 +32,9 @@
 #include "gtkshortcutcontrollerprivate.h"
 
 #include "gtkeventcontrollerprivate.h"
+#include "gtkintl.h"
 #include "gtkshortcut.h"
+#include "gtktypebuiltins.h"
 #include "gtkwidgetprivate.h"
 
 #include <gdk/gdk.h>
@@ -42,8 +44,10 @@ struct _GtkShortcutController
   GtkEventController parent_instance;
 
   GSList *shortcuts;
+  GtkShortcutScope scope;
 
   guint run_class : 1;
+  guint run_managed : 1;
 };
 
 struct _GtkShortcutControllerClass
@@ -51,9 +55,67 @@ struct _GtkShortcutControllerClass
   GtkEventControllerClass parent_class;
 };
 
+enum {
+  PROP_0,
+  PROP_SCOPE,
+
+  N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
 G_DEFINE_TYPE (GtkShortcutController, gtk_shortcut_controller,
                GTK_TYPE_EVENT_CONTROLLER)
 
+static gboolean
+gtk_shortcut_controller_is_rooted (GtkShortcutController *self)
+{
+  GtkWidget *widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self));
+
+  if (widget == NULL)
+    return FALSE;
+
+  return gtk_widget_get_root (widget) != NULL;
+}
+
+static void
+gtk_shortcut_controller_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+  GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (object);
+
+  switch (prop_id)
+    {
+    case PROP_SCOPE:
+      gtk_shortcut_controller_set_scope (self, g_value_get_enum (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_shortcut_controller_get_property (GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+  GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (object);
+
+  switch (prop_id)
+    {
+    case PROP_SCOPE:
+      g_value_set_enum (value, self->scope);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
 static void
 gtk_shortcut_controller_dispose (GObject *object)
 {
@@ -77,10 +139,10 @@ gtk_shortcut_controller_trigger_shortcut (GtkShortcutController *self,
 }
 
 static gboolean
-gtk_shortcut_controller_handle_event (GtkEventController *controller,
-                                      GdkEvent           *event,
-                                      double              x,
-                                      double              y)
+gtk_shortcut_controller_run_controllers (GtkEventController *controller,
+                                         GdkEvent           *event,
+                                         double              x,
+                                         double              y)
 {
   GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (controller);
   GtkWidget *widget;
@@ -103,17 +165,91 @@ gtk_shortcut_controller_handle_event (GtkEventController *controller,
         }
     }
 
+  if (self->run_managed)
+    {
+      GtkPropagationPhase current_phase = gtk_event_controller_get_propagation_phase (controller);
+      widget = gtk_event_controller_get_widget (controller); 
+      
+      for (l = g_object_get_data (G_OBJECT (widget), "gtk-shortcut-controllers"); l; l = l->next)
+        {
+          if (gtk_event_controller_get_propagation_phase (l->data) != current_phase)
+            continue;
+
+          if (gtk_shortcut_controller_run_controllers (l->data, event, x, y))
+            return TRUE;
+        }
+    }
+
   return FALSE;
 }
 
+static gboolean
+gtk_shortcut_controller_handle_event (GtkEventController *controller,
+                                      GdkEvent           *event,
+                                      double              x,
+                                      double              y)
+{
+  GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (controller);
+
+  if (self->scope != GTK_SHORTCUT_SCOPE_LOCAL)
+    return FALSE;
+
+  return gtk_shortcut_controller_run_controllers (controller, event, x, y);
+}
+
+static void
+gtk_shortcut_controller_set_widget (GtkEventController *controller,
+                                    GtkWidget          *widget)
+{
+  GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (controller);
+
+  GTK_EVENT_CONTROLLER_CLASS (gtk_shortcut_controller_parent_class)->set_widget (controller, widget);
+
+  if (_gtk_widget_get_root (widget))
+    gtk_shortcut_controller_root (self);
+}
+
+static void
+gtk_shortcut_controller_unset_widget (GtkEventController *controller)
+{
+  GtkShortcutController *self = GTK_SHORTCUT_CONTROLLER (controller);
+  GtkWidget *widget = gtk_event_controller_get_widget (controller);
+
+  if (_gtk_widget_get_root (widget))
+    gtk_shortcut_controller_unroot (self);
+
+  GTK_EVENT_CONTROLLER_CLASS (gtk_shortcut_controller_parent_class)->unset_widget (controller);
+}
+
 static void
 gtk_shortcut_controller_class_init (GtkShortcutControllerClass *klass)
 {
-  GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (klass);
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (klass);
 
   object_class->dispose = gtk_shortcut_controller_dispose;
+  object_class->set_property = gtk_shortcut_controller_set_property;
+  object_class->get_property = gtk_shortcut_controller_get_property;
+
   controller_class->handle_event = gtk_shortcut_controller_handle_event;
+  controller_class->set_widget = gtk_shortcut_controller_set_widget;
+  controller_class->unset_widget = gtk_shortcut_controller_unset_widget;
+
+  /**
+   * GtkShortcutController:scope:
+   *
+   * What scope the shortcuts will be handled in.
+   */
+  properties[PROP_SCOPE] =
+      g_param_spec_enum ("scope",
+                         P_("Scope"),
+                         P_("What scope the shortcuts will be handled in"),
+                         GTK_TYPE_SHORTCUT_SCOPE,
+                         GTK_SHORTCUT_SCOPE_LOCAL,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
 }
 
 static void
@@ -121,6 +257,66 @@ gtk_shortcut_controller_init (GtkShortcutController *controller)
 {
 }
 
+static void
+complain_if_reached (gpointer should_be_gone)
+{
+  g_critical ("Shortcut controllers failed to clean up.");
+}
+
+void
+gtk_shortcut_controller_root (GtkShortcutController *self)
+{
+  GtkWidget *attach;
+  GSList *controllers;
+
+  switch (self->scope)
+    {
+    case GTK_SHORTCUT_SCOPE_LOCAL:
+      return;
+
+    case GTK_SHORTCUT_SCOPE_MANAGED:
+    case GTK_SHORTCUT_SCOPE_GLOBAL:
+      attach = GTK_WIDGET (gtk_widget_get_root (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self))));
+      break;
+
+    default:
+      g_assert_not_reached ();
+      return;
+    }
+
+  controllers = g_object_steal_data (G_OBJECT (attach), "gtk-shortcut-controllers");
+  controllers = g_slist_prepend (controllers, g_object_ref (self));
+  g_object_set_data_full (G_OBJECT (attach), "gtk-shortcut-controllers", controllers, complain_if_reached);
+}
+
+void
+gtk_shortcut_controller_unroot (GtkShortcutController *self)
+{
+  GtkWidget *attach;
+  GSList *controllers;
+
+  switch (self->scope)
+    {
+    case GTK_SHORTCUT_SCOPE_LOCAL:
+      return;
+
+    case GTK_SHORTCUT_SCOPE_MANAGED:
+    case GTK_SHORTCUT_SCOPE_GLOBAL:
+      attach = GTK_WIDGET (gtk_widget_get_root (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (self))));
+      break;
+
+    default:
+      g_assert_not_reached ();
+      return;
+    }
+
+  controllers = g_object_steal_data (G_OBJECT (attach), "gtk-shortcut-controllers");
+  controllers = g_slist_remove (controllers, self);
+  if (controllers)
+    g_object_set_data_full (G_OBJECT (attach), "gtk-shortcut-controllers", controllers, complain_if_reached);
+  g_object_unref (self);
+}
+
 GtkEventController *
 gtk_shortcut_controller_new (void)
 {
@@ -135,6 +331,13 @@ gtk_shortcut_controller_set_run_class (GtkShortcutController  *controller,
   controller->run_class = run_class;
 }
 
+void
+gtk_shortcut_controller_set_run_managed (GtkShortcutController  *controller,
+                                         gboolean                run_managed)
+{
+  controller->run_managed = run_managed;
+}
+
 /**
  * gtk_shortcut_controller_add_shortcut:
  * @self: the controller
@@ -184,3 +387,57 @@ gtk_shortcut_controller_remove_shortcut (GtkShortcutController  *self,
   self->shortcuts = g_slist_delete_link (self->shortcuts, l);
   g_object_unref (shortcut);
 }
+
+/**
+ * gtk_shortcut_controller_set_scope:
+ * @self: a #GtkShortcutController
+ * @scope: the new scope to use
+ *
+ * Sets the controller to have the given @scope.
+ *
+ * The scope allows shortcuts to be activated outside of the normal
+ * event propagation. In particular, it allows installing global
+ * keyboard shortcuts that can be activated even when a widget does
+ * not have focus.
+ **/
+void
+gtk_shortcut_controller_set_scope (GtkShortcutController *self,
+                                   GtkShortcutScope       scope)
+{
+  gboolean rooted;
+
+  g_return_if_fail (GTK_IS_SHORTCUT_CONTROLLER (self));
+
+  if (self->scope == scope)
+    return;
+
+  rooted = gtk_shortcut_controller_is_rooted (self);
+
+  if (rooted)
+    gtk_shortcut_controller_unroot (self);
+
+  self->scope = scope;
+
+  if (rooted)
+    gtk_shortcut_controller_root (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SCOPE]);
+}
+
+/**
+ * gtk_shortcut_controller_get_scope:
+ * @self: a #GtkShortcutController
+ *
+ * Gets the scope for when this controller activates its shortcuts. See
+ * gtk_shortcut_controller_set_scope() for details.
+ *
+ * Returns: the controller's scope
+ **/
+GtkShortcutScope
+gtk_shortcut_controller_get_scope (GtkShortcutController *self)
+{
+  g_return_val_if_fail (GTK_IS_SHORTCUT_CONTROLLER (self), GTK_SHORTCUT_SCOPE_LOCAL);
+
+  return self->scope;
+}
+
index e078e3241a1084c32df8bea080dee44df16ef73b..fea66f34555b9549dc09d1a30b6a8184865a7f89 100644 (file)
@@ -46,10 +46,16 @@ GDK_AVAILABLE_IN_ALL
 GtkEventController *    gtk_shortcut_controller_new                     (void);
 
 GDK_AVAILABLE_IN_ALL
-void                    gtk_shortcut_controller_add_shortcut            (GtkShortcutController  *controller,
+void                    gtk_shortcut_controller_set_scope               (GtkShortcutController  *self,
+                                                                         GtkShortcutScope        scope);
+GDK_AVAILABLE_IN_ALL
+GtkShortcutScope        gtk_shortcut_controller_get_scope               (GtkShortcutController  *self);
+
+GDK_AVAILABLE_IN_ALL
+void                    gtk_shortcut_controller_add_shortcut            (GtkShortcutController  *self,
                                                                          GtkShortcut            *shortcut);
 GDK_AVAILABLE_IN_ALL
-void                    gtk_shortcut_controller_remove_shortcut         (GtkShortcutController  *controller,
+void                    gtk_shortcut_controller_remove_shortcut         (GtkShortcutController  *self,
                                                                          GtkShortcut            *shortcut);
 
 G_END_DECLS
index 945e526e667b552709a56dbe795398f0828aeff2..42594ea6eb738e26d095b1b25d8e63a4ee0fcf4f 100644 (file)
 
 void                    gtk_shortcut_controller_set_run_class           (GtkShortcutController  *controller,
                                                                          gboolean                run_class);
+void                    gtk_shortcut_controller_set_run_managed         (GtkShortcutController  *controller,
+                                                                         gboolean                run_managed);
+
+void                    gtk_shortcut_controller_root                    (GtkShortcutController  *controller);
+void                    gtk_shortcut_controller_unroot                  (GtkShortcutController  *controller);
 
 #endif /* __GTK_SHORTCUT_CONTROLLER_H__ */
index 8b3f4b30fb9e5c5977caaa23c1c79e70cf4af446..8d049109222bfe9403be13d05604b44e157ce2cf 100644 (file)
@@ -825,12 +825,30 @@ gtk_widget_real_grab_notify (GtkWidget *widget,
 static void
 gtk_widget_real_root (GtkWidget *widget)
 {
+  GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+  GList *l;
+
   gtk_widget_forall (widget, (GtkCallback) gtk_widget_root, NULL);
+
+  for (l = priv->event_controllers; l; l = l->next)
+    {
+      if (GTK_IS_SHORTCUT_CONTROLLER (l->data))
+        gtk_shortcut_controller_root (GTK_SHORTCUT_CONTROLLER (l->data));
+    }
 }
 
 static void
 gtk_widget_real_unroot (GtkWidget *widget)
 {
+  GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+  GList *l;
+
+  for (l = priv->event_controllers; l; l = l->next)
+    {
+      if (GTK_IS_SHORTCUT_CONTROLLER (l->data))
+        gtk_shortcut_controller_unroot (GTK_SHORTCUT_CONTROLLER (l->data));
+    }
+
   gtk_widget_forall (widget, (GtkCallback) gtk_widget_unroot, NULL);
 }
 
@@ -2404,7 +2422,18 @@ gtk_widget_init (GTypeInstance *instance, gpointer g_class)
   gtk_css_node_set_name (priv->cssnode, GTK_WIDGET_CLASS (g_class)->priv->css_name);
 
   if (g_type_is_a (G_TYPE_FROM_CLASS (g_class), GTK_TYPE_ROOT))
-    priv->root = (GtkRoot *) widget;
+    {
+      priv->root = (GtkRoot *) widget;
+
+      controller = gtk_shortcut_controller_new ();
+      gtk_shortcut_controller_set_run_managed (GTK_SHORTCUT_CONTROLLER (controller), TRUE);
+      gtk_widget_add_controller (widget, controller);
+
+      controller = gtk_shortcut_controller_new ();
+      gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
+      gtk_shortcut_controller_set_run_managed (GTK_SHORTCUT_CONTROLLER (controller), TRUE);
+      gtk_widget_add_controller (widget, controller);
+    }
 
   layout_manager_type = gtk_widget_class_get_layout_manager_type (g_class);
   if (layout_manager_type != G_TYPE_INVALID)