[libdazzle] shortcuts: add global activation phases



commit 07155066a259b97d2ee3bf4b97fc03112b77b45a
Author: Christian Hergert <chergert redhat com>
Date:   Wed Jul 19 02:37:01 2017 -0700

    shortcuts: add global activation phases
    
    This adds an activation phase for global keybindings. You can have them as
    either CAPTURE or BUBBLE phases. If CAPTURE, they run before the normal
    capture phase of the hierarchy. If BUBBLE, then run after the BUBBLE phase
    of the hierarchy.
    
    There are still some things I wish we could do here, but getting all these
    pieces to work well together has been quite tricky. Especially as we have
    contexts, chords, and integration with the previous GtkBindingSets.
    
    DzlShortcutPhase has been made a flags type, so we can bitwise-OR the
    GLOBAL bit with the capture/bubble phases.
    
    This breaks the ABI of the DzlShortcutEntry while we can still do that
    without too much disruption. (Given that nothing is using this except
    our branch of Builder).
    
    I think we should add a property/attribute to the XML theme files that
    allow us to have a phase specified for global keybindings. That is
    currently a missing piece.
    
    We might, someday, want to get really tricky with things and allow global
    activation based on the current context. That is going to make things
    much more tricky and I'm just not up to it right now.

 examples/app/example-window.c           |    6 +-
 src/shortcuts/dzl-shortcut-controller.c |  202 ++++++++++++++++++++++---------
 src/shortcuts/dzl-shortcut-manager.c    |   51 +++++++--
 src/shortcuts/dzl-shortcut-manager.h    |   15 ++-
 src/shortcuts/dzl-shortcut-model.c      |    4 +-
 src/shortcuts/dzl-shortcut-phase.c      |    7 +-
 src/shortcuts/dzl-shortcut-phase.h      |    7 +-
 src/shortcuts/dzl-shortcut-private.h    |    5 +-
 src/shortcuts/dzl-shortcut-theme-load.c |    4 +-
 src/shortcuts/dzl-shortcut-theme.c      |   66 ++++++----
 src/shortcuts/dzl-shortcut-theme.h      |   17 ++-
 tests/test-shortcuts.c                  |   28 ++--
 12 files changed, 279 insertions(+), 133 deletions(-)
---
diff --git a/examples/app/example-window.c b/examples/app/example-window.c
index 253a9f8..267befd 100644
--- a/examples/app/example-window.c
+++ b/examples/app/example-window.c
@@ -17,9 +17,9 @@ struct _ExampleWindow
 G_DEFINE_TYPE (ExampleWindow, example_window, DZL_TYPE_APPLICATION_WINDOW)
 
 static const DzlShortcutEntry shortcuts[] = {
-  { "com.example.window.NewDoc", NULL, N_("Editing"), N_("Documents"), N_("New Document"), N_("Create a new 
document") },
-  { "com.example.window.CloseDoc", NULL, N_("Editing"), N_("Documents"), N_("Close Document"), N_("Close the 
current document") },
-  { "com.example.window.Fullscreen", "F11", N_("Editing"), N_("General"), N_("Fullscreen"), N_("Toggle 
window fullscreen") },
+  { "com.example.window.NewDoc", 0, NULL, N_("Editing"), N_("Documents"), N_("New Document"), N_("Create a 
new document") },
+  { "com.example.window.CloseDoc", 0, NULL, N_("Editing"), N_("Documents"), N_("Close Document"), N_("Close 
the current document") },
+  { "com.example.window.Fullscreen", 0, "F11", N_("Editing"), N_("General"), N_("Fullscreen"), N_("Toggle 
window fullscreen") },
 };
 
 ExampleDocumentView *
diff --git a/src/shortcuts/dzl-shortcut-controller.c b/src/shortcuts/dzl-shortcut-controller.c
index c2b4a1e..392efeb 100644
--- a/src/shortcuts/dzl-shortcut-controller.c
+++ b/src/shortcuts/dzl-shortcut-controller.c
@@ -92,12 +92,13 @@ typedef struct
    */
   GList descendants_link;
 
-  /*
-   * Signal handlers to react to various changes in the system.
-   */
+  /* Signal handlers to react to various changes in the system. */
   gulong hierarchy_changed_handler;
   gulong widget_destroy_handler;
   gulong manager_changed_handler;
+
+  /* If we have any global shortcuts registered */
+  guint have_global : 1;
 } DzlShortcutControllerPrivate;
 
 enum {
@@ -134,6 +135,13 @@ dzl_shortcut_controller_emit_reset (DzlShortcutController *self)
   g_signal_emit (self, signals[RESET], 0);
 }
 
+static inline gboolean
+dzl_shortcut_controller_is_root (DzlShortcutController *self)
+{
+  DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
+
+  return priv->root == NULL;
+}
 
 /**
  * dzl_shortcut_controller_get_manager:
@@ -260,31 +268,31 @@ dzl_shortcut_controller_widget_hierarchy_changed (DzlShortcutController *self,
   g_assert (!previous_toplevel || GTK_IS_WIDGET (previous_toplevel));
   g_assert (GTK_IS_WIDGET (widget));
 
-  g_object_ref (self);
-
   /*
-   * Here we register our controller with the toplevel controller. If that
-   * widget doesn't yet have a placeholder toplevel controller, then we
-   * create that and attach to it.
-   *
-   * The toplevel controller is used to dispatch events from the window
-   * to any controller that could be activating for the window.
+   * We attach our controller to the root controller if we have shortcuts in
+   * the global activation phase. That allows the bubble/capture phase to
+   * potentially dispatch to our controller.
    */
 
+  g_object_ref (self);
+
   if (priv->root != NULL)
     {
       dzl_shortcut_controller_remove (priv->root, self);
       g_clear_object (&priv->root);
     }
 
-  toplevel = gtk_widget_get_toplevel (widget);
-
-  if (toplevel != widget)
+  if (priv->have_global)
     {
-      priv->root = g_object_get_qdata (G_OBJECT (toplevel), root_quark);
-      if (priv->root == NULL)
-        priv->root = dzl_shortcut_controller_new (toplevel);
-      dzl_shortcut_controller_add (priv->root, self);
+      toplevel = gtk_widget_get_toplevel (widget);
+
+      if (toplevel != widget)
+        {
+          priv->root = g_object_get_qdata (G_OBJECT (toplevel), root_quark);
+          if (priv->root == NULL)
+            priv->root = dzl_shortcut_controller_new (toplevel);
+          dzl_shortcut_controller_add (priv->root, self);
+        }
     }
 
   g_object_unref (self);
@@ -782,12 +790,88 @@ dzl_shortcut_controller_process (DzlShortcutController  *self,
   return match;
 }
 
+static void
+dzl_shortcut_controller_do_global_chain (DzlShortcutController   *self,
+                                         DzlShortcutClosureChain *chain,
+                                         GtkWidget               *widget,
+                                         GList                   *next)
+{
+  DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
+
+  g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
+  g_assert (chain != NULL);
+  g_assert (GTK_IS_WIDGET (widget));
+
+  /*
+   * If this is an action chain, the best we're going to be able to do is
+   * activate the action from the current widget. For commands, we can try to
+   * resolve them by locating commands within our registered controllers.
+   */
+
+  if (chain->type != DZL_SHORTCUT_CLOSURE_COMMAND)
+    {
+      dzl_shortcut_closure_chain_execute (chain, widget);
+      return;
+    }
+
+  if (priv->commands != NULL &&
+      g_hash_table_contains (priv->commands, chain->command.name))
+    {
+      dzl_shortcut_closure_chain_execute (chain, priv->widget);
+      return;
+    }
+
+  if (next == NULL)
+    {
+      dzl_shortcut_closure_chain_execute (chain, widget);
+      return;
+    }
+
+  dzl_shortcut_controller_do_global_chain (next->data, chain, widget, next->next);
+}
+
+static DzlShortcutMatch
+dzl_shortcut_controller_do_global (DzlShortcutController  *self,
+                                   const DzlShortcutChord *chord,
+                                   DzlShortcutPhase        phase,
+                                   GtkWidget              *widget)
+{
+  DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
+  DzlShortcutClosureChain *chain = NULL;
+  DzlShortcutManager *manager;
+  DzlShortcutTheme *theme;
+  DzlShortcutMatch match;
+
+  g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
+  g_assert (chord != NULL);
+  g_assert ((phase & DZL_SHORTCUT_PHASE_GLOBAL) != 0);
+  g_assert (GTK_IS_WIDGET (widget));
+
+  manager = dzl_shortcut_controller_get_manager (self);
+  g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
+
+  theme = dzl_shortcut_manager_get_theme (manager);
+  g_assert (DZL_IS_SHORTCUT_THEME (theme));
+
+  /* See if we have a chain for this chord */
+  match = _dzl_shortcut_theme_match (theme, phase, chord, &chain);
+
+  /* If we matched, execute the chain, trying to locate the proper widget for
+   * the event delivery.
+   */
+  if (match == DZL_SHORTCUT_MATCH_EQUAL && chain->phase == phase)
+    dzl_shortcut_controller_do_global_chain (self, chain, widget, priv->descendants.head);
+
+  return match;
+}
+
 /**
  * _dzl_shortcut_controller_handle:
  * @self: An #DzlShortcutController
  * @event: A #GdkEventKey
  * @chord: the current chord for the toplevel
  * @phase: the dispatch phase
+ * @widget: the widget receiving @event
  *
  * This function uses @event to determine if the current context has a shortcut
  * registered matching the event. If so, the shortcut will be dispatched and
@@ -809,17 +893,18 @@ DzlShortcutMatch
 _dzl_shortcut_controller_handle (DzlShortcutController  *self,
                                  const GdkEventKey      *event,
                                  const DzlShortcutChord *chord,
-                                 DzlShortcutPhase        phase)
+                                 DzlShortcutPhase        phase,
+                                 GtkWidget              *widget)
 {
   DzlShortcutControllerPrivate *priv = dzl_shortcut_controller_get_instance_private (self);
-  DzlShortcutMatch match;
+  DzlShortcutMatch match = DZL_SHORTCUT_MATCH_NONE;
 
   DZL_ENTRY;
 
   g_return_val_if_fail (DZL_IS_SHORTCUT_CONTROLLER (self), FALSE);
   g_return_val_if_fail (event != NULL, FALSE);
   g_return_val_if_fail (chord != NULL, FALSE);
-  g_return_val_if_fail (phase <= DZL_SHORTCUT_PHASE_BUBBLE, FALSE);
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
 
   /* Nothing to do if the widget isn't visible/mapped/etc */
   if (priv->widget == NULL ||
@@ -828,6 +913,11 @@ _dzl_shortcut_controller_handle (DzlShortcutController  *self,
       !gtk_widget_is_sensitive (priv->widget))
     DZL_RETURN (DZL_SHORTCUT_MATCH_NONE);
 
+  /* Try to dispatch our capture global shortcuts first */
+  if (phase == (DZL_SHORTCUT_PHASE_CAPTURE | DZL_SHORTCUT_PHASE_GLOBAL) &&
+      dzl_shortcut_controller_is_root (self))
+    match = dzl_shortcut_controller_do_global (self, chord, phase, widget);
+
   /*
    * This function processes a particular phase for the event. If our phase
    * is DZL_SHORTCUT_PHASE_CAPTURE, that means we are in the process of working
@@ -842,57 +932,32 @@ _dzl_shortcut_controller_handle (DzlShortcutController  *self,
    * the default GtkEntry, the capture context name would be something like
    * "GtkEntry:capture". The bubble phase does not have a suffix.
    *
+   *   Toplevel Global Capture Accels
    *   Toplevel Capture
    *     - Child 1 Capture
    *       - Grandchild 1 Capture
    *       - Grandchild 1 Bubble
    *     - Child 1 Bubble
    *   Toplevel Bubble
+   *   Toplevel Global Bubble Accels
    *
    * If we come across a keybinding that is a partial match, we assume that
    * is the closest match in the dispatch chain and stop processing further.
    * Overlapping and conflicting keybindings are considered undefined behavior
    * and this falls under such a situation.
-   */
-
-  match = dzl_shortcut_controller_process (self, chord, phase);
-
-  /*
-   * If we still haven't located a match, we need to ask the theme if there
-   * is a keybinding registered for this chord to a command or action. If
-   * so, we will try to dispatch it now.
    *
-   * This is only performed by the root controller as it is for global actions.
-   * The theme may have specified a phase, so we must propagate that to the
-   * controller as well. Dispatch does not make sense, so we only perform this
-   * on capture/bubble.
+   * Note that we do not perform the bubble/capture phase here, that is handled
+   * by our caller in DzlShortcutManager.
    */
-  if (match == DZL_SHORTCUT_MATCH_NONE &&
-      phase != DZL_SHORTCUT_PHASE_DISPATCH &&
-      priv->root == NULL)
-    {
-      DzlShortcutClosureChain *chain = NULL;
-      DzlShortcutManager *manager;
-      DzlShortcutTheme *theme;
-      DzlShortcutMatch theme_match;
-
-      manager = dzl_shortcut_controller_get_manager (self);
-      theme = dzl_shortcut_manager_get_theme (manager);
-      theme_match = _dzl_shortcut_theme_match (theme, phase, chord, &chain);
-
-      /* If this chain doesn't match our phase, ignore it */
-      if (chain != NULL && chain->phase != phase)
-        {
-          theme_match = DZL_SHORTCUT_MATCH_NONE;
-          chain = NULL;
-        }
 
-      if (theme_match == DZL_SHORTCUT_MATCH_EQUAL)
-        dzl_shortcut_closure_chain_execute (chain, priv->widget);
+  if (match == DZL_SHORTCUT_MATCH_NONE)
+    match = dzl_shortcut_controller_process (self, chord, phase);
 
-      if (theme_match != DZL_SHORTCUT_MATCH_NONE)
-        match = theme_match;
-    }
+  /* Try to dispatch our capture global shortcuts first */
+  if (match == DZL_SHORTCUT_MATCH_NONE &&
+      dzl_shortcut_controller_is_root (self) &&
+      phase == (DZL_SHORTCUT_PHASE_BUBBLE | DZL_SHORTCUT_PHASE_GLOBAL))
+    match = dzl_shortcut_controller_do_global (self, chord, phase, widget);
 
   DZL_TRACE_MSG ("match = %s",
                  match == DZL_SHORTCUT_MATCH_NONE ? "none" :
@@ -980,15 +1045,17 @@ dzl_shortcut_controller_add_command (DzlShortcutController   *self,
   g_assert (DZL_IS_SHORTCUT_CONTROLLER (self));
   g_assert (command_id != NULL);
   g_assert (chain != NULL);
-  g_assert (phase <= DZL_SHORTCUT_PHASE_BUBBLE);
 
   /* Always use interned strings for command ids */
   command_id = g_intern_string (command_id);
 
   /*
    * Set the phase on the closure chain so we know what phase we are allowed
-   * to execute the chain within during capture/dispatch/bubble.
+   * to execute the chain within during capture/dispatch/bubble. There is no
+   * "global + dispatch" phase, so if global is set, default to bubble.
    */
+  if (phase == DZL_SHORTCUT_PHASE_GLOBAL)
+    phase |= DZL_SHORTCUT_PHASE_BUBBLE;
   chain->phase = phase;
 
   /* Add the closure chain to our set of commands. */
@@ -997,6 +1064,21 @@ dzl_shortcut_controller_add_command (DzlShortcutController   *self,
                                             (GDestroyNotify)dzl_shortcut_closure_chain_free);
   g_hash_table_insert (priv->commands, (gpointer)command_id, chain);
 
+  /*
+   * If this command can be executed in the global phase, we need to be
+   * sure that the root controller knows that we must be checked during
+   * global activation checks.
+   */
+  if ((phase & DZL_SHORTCUT_PHASE_GLOBAL) != 0)
+    {
+      if (priv->have_global != TRUE)
+        {
+          priv->have_global = TRUE;
+          if (priv->widget != NULL)
+            dzl_shortcut_controller_widget_hierarchy_changed (self, NULL, priv->widget);
+        }
+    }
+
   /* If an accel was provided, we need to register it in various places */
   if (default_accel != NULL)
     {
@@ -1013,7 +1095,7 @@ dzl_shortcut_controller_add_command (DzlShortcutController   *self,
           /* Set the value in the theme so it can have overrides by users */
           manager = dzl_shortcut_controller_get_manager (self);
           theme = _dzl_shortcut_manager_get_internal_theme (manager);
-          dzl_shortcut_theme_set_chord_for_command (theme, command_id, chord);
+          dzl_shortcut_theme_set_chord_for_command (theme, command_id, chord, phase);
         }
       else
         g_warning ("\"%s\" is not a valid accelerator chord", default_accel);
diff --git a/src/shortcuts/dzl-shortcut-manager.c b/src/shortcuts/dzl-shortcut-manager.c
index 31259be..259fc9b 100644
--- a/src/shortcuts/dzl-shortcut-manager.c
+++ b/src/shortcuts/dzl-shortcut-manager.c
@@ -676,17 +676,17 @@ dzl_shortcut_manager_run_phase (DzlShortcutManager     *self,
   g_assert (DZL_IS_SHORTCUT_MANAGER (self));
   g_assert (event != NULL);
   g_assert (chord != NULL);
-  g_assert (phase <= DZL_SHORTCUT_PHASE_BUBBLE);
+  g_assert ((phase & DZL_SHORTCUT_PHASE_GLOBAL) == 0);
   g_assert (GTK_IS_WIDGET (widget));
   g_assert (GTK_IS_WIDGET (focus));
 
   modifier = event->state & gtk_accelerator_get_default_mod_mask ();
 
   /*
-   * Collect all the widgets that might be needed for this phase and
-   * order them so that we can process from first-to-last. Capture
-   * phase is toplevel-to-widget, and bubble is widget-to-toplevel.
-   * Dispatch only has the the widget itself.
+   * Collect all the widgets that might be needed for this phase and order them
+   * so that we can process from first-to-last. Capture phase is
+   * toplevel-to-widget, and bubble is widget-to-toplevel.  Dispatch only has
+   * the the widget itself.
    */
   do
     {
@@ -698,6 +698,9 @@ dzl_shortcut_manager_run_phase (DzlShortcutManager     *self,
     }
   while (phase != DZL_SHORTCUT_PHASE_DISPATCH && ancestor != NULL);
 
+  /*
+   * Now look through our widget chain to find a match to activate.
+   */
   for (const GList *iter = queue.head; iter; iter = iter->next)
     {
       GtkWidget *current = iter->data;
@@ -734,7 +737,7 @@ dzl_shortcut_manager_run_phase (DzlShortcutManager     *self,
            * a partial match, it's undefined behavior to also have a shortcut
            * which would activate.
            */
-          ret = _dzl_shortcut_controller_handle (controller, event, chord, phase);
+          ret = _dzl_shortcut_controller_handle (controller, event, chord, phase, focus);
           if (ret)
             DZL_GOTO (cleanup);
         }
@@ -793,6 +796,33 @@ cleanup:
   DZL_RETURN (ret);
 }
 
+static DzlShortcutMatch
+dzl_shortcut_manager_run_global (DzlShortcutManager     *self,
+                                 const GdkEventKey      *event,
+                                 const DzlShortcutChord *chord,
+                                 DzlShortcutPhase        phase,
+                                 DzlShortcutController  *root,
+                                 GtkWidget              *widget)
+{
+  g_assert (DZL_IS_SHORTCUT_MANAGER (self));
+  g_assert (event != NULL);
+  g_assert (chord != NULL);
+  g_assert (phase == DZL_SHORTCUT_PHASE_CAPTURE ||
+            phase == DZL_SHORTCUT_PHASE_BUBBLE);
+  g_assert (DZL_IS_SHORTCUT_CONTROLLER (root));
+  g_assert (GTK_WIDGET (widget));
+
+  /*
+   * The goal of this function is to locate a shortcut within any
+   * controller registered with the root controller (or the root
+   * controller itself) that is registered as a "global shortcut".
+   */
+
+  phase |= DZL_SHORTCUT_PHASE_GLOBAL;
+
+  return _dzl_shortcut_controller_handle (root, event, chord, phase, widget);
+}
+
 /**
  * dzl_shortcut_manager_handle_event:
  * @self: (nullable): An #DzlShortcutManager
@@ -875,9 +905,11 @@ dzl_shortcut_manager_handle_event (DzlShortcutManager *self,
    * Now we have our chord/event to dispatch to the individual controllers
    * on widgets. We can run through the phases to capture/dispatch/bubble.
    */
-  if ((match = dzl_shortcut_manager_run_phase (self, event, chord, DZL_SHORTCUT_PHASE_CAPTURE, widget, 
focus)) ||
+  if ((match = dzl_shortcut_manager_run_global (self, event, chord, DZL_SHORTCUT_PHASE_CAPTURE, root, 
widget)) ||
+      (match = dzl_shortcut_manager_run_phase (self, event, chord, DZL_SHORTCUT_PHASE_CAPTURE, widget, 
focus)) ||
       (match = dzl_shortcut_manager_run_phase (self, event, chord, DZL_SHORTCUT_PHASE_DISPATCH, widget, 
focus)) ||
-      (match = dzl_shortcut_manager_run_phase (self, event, chord, DZL_SHORTCUT_PHASE_BUBBLE, widget, 
focus)))
+      (match = dzl_shortcut_manager_run_phase (self, event, chord, DZL_SHORTCUT_PHASE_BUBBLE, widget, 
focus)) ||
+      (match = dzl_shortcut_manager_run_global (self, event, chord, DZL_SHORTCUT_PHASE_BUBBLE, root, 
widget)))
     ret = GDK_EVENT_STOP;
 
   DZL_TRACE_MSG ("match = %d", match);
@@ -1400,7 +1432,8 @@ dzl_shortcut_manager_add_shortcut_entries (DzlShortcutManager     *self,
       if (entry->default_accel != NULL)
         dzl_shortcut_theme_set_accel_for_command (priv->internal_theme,
                                                   entry->command,
-                                                  entry->default_accel);
+                                                  entry->default_accel,
+                                                  entry->phase);
 
       dzl_shortcut_manager_add_command (self,
                                         entry->command,
diff --git a/src/shortcuts/dzl-shortcut-manager.h b/src/shortcuts/dzl-shortcut-manager.h
index 2676cbb..24d95d3 100644
--- a/src/shortcuts/dzl-shortcut-manager.h
+++ b/src/shortcuts/dzl-shortcut-manager.h
@@ -21,6 +21,7 @@
 
 #include <gtk/gtk.h>
 
+#include "dzl-shortcut-phase.h"
 #include "dzl-shortcut-theme.h"
 #include "dzl-shortcuts-window.h"
 
@@ -33,6 +34,7 @@ G_DECLARE_DERIVABLE_TYPE (DzlShortcutManager, dzl_shortcut_manager, DZL, SHORTCU
 /**
  * DzlShortcutEntry:
  * @command: the command identifier
+ * @phase: the phase for activation, or 0 for the default
  * @default_accel: the default accelerator for the command, if any
  * @section: the section for the shortcuts window
  * @group: the group for the shortcuts window
@@ -45,12 +47,13 @@ G_DECLARE_DERIVABLE_TYPE (DzlShortcutManager, dzl_shortcut_manager, DZL, SHORTCU
  */
 typedef struct
 {
-  const gchar *command;
-  const gchar *default_accel;
-  const gchar *section;
-  const gchar *group;
-  const gchar *title;
-  const gchar *subtitle;
+  const gchar      *command;
+  DzlShortcutPhase  phase;
+  const gchar      *default_accel;
+  const gchar      *section;
+  const gchar      *group;
+  const gchar      *title;
+  const gchar      *subtitle;
 } DzlShortcutEntry;
 
 struct _DzlShortcutManagerClass
diff --git a/src/shortcuts/dzl-shortcut-model.c b/src/shortcuts/dzl-shortcut-model.c
index 554173d..ce79395 100644
--- a/src/shortcuts/dzl-shortcut-model.c
+++ b/src/shortcuts/dzl-shortcut-model.c
@@ -302,9 +302,9 @@ dzl_shortcut_model_apply (DzlShortcutModel *self,
                       -1);
 
   if (type == DZL_SHORTCUT_NODE_ACTION)
-    dzl_shortcut_theme_set_chord_for_action (self->theme, id, chord);
+    dzl_shortcut_theme_set_chord_for_action (self->theme, id, chord, 0);
   else if (type == DZL_SHORTCUT_NODE_COMMAND)
-    dzl_shortcut_theme_set_chord_for_command (self->theme, id, chord);
+    dzl_shortcut_theme_set_chord_for_command (self->theme, id, chord, 0);
   else
     g_warning ("Unknown type: %d", type);
 }
diff --git a/src/shortcuts/dzl-shortcut-phase.c b/src/shortcuts/dzl-shortcut-phase.c
index c560e67..644249b 100644
--- a/src/shortcuts/dzl-shortcut-phase.c
+++ b/src/shortcuts/dzl-shortcut-phase.c
@@ -16,6 +16,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#define G_LOG_DOMAIN "dzl-shortcut-phase"
+
 #include "shortcuts/dzl-shortcut-phase.h"
 
 GType
@@ -25,13 +27,14 @@ dzl_shortcut_phase_get_type (void)
 
   if (g_once_init_enter (&type_id))
     {
-      static const GEnumValue values[] = {
+      static const GFlagsValue values[] = {
         { DZL_SHORTCUT_PHASE_DISPATCH, "DZL_SHORTCUT_PHASE_DISPATCH", "dispatch" },
         { DZL_SHORTCUT_PHASE_CAPTURE, "DZL_SHORTCUT_PHASE_CAPTURE", "capture" },
         { DZL_SHORTCUT_PHASE_BUBBLE, "DZL_SHORTCUT_PHASE_BUBBLE", "bubble" },
+        { DZL_SHORTCUT_PHASE_GLOBAL, "DZL_SHORTCUT_PHASE_GLOBAL", "global" },
         { 0 }
       };
-      GType _type_id = g_enum_register_static ("DzlShortcutPhase", values);
+      GType _type_id = g_flags_register_static ("DzlShortcutPhase", values);
       g_once_init_leave (&type_id, _type_id);
     }
 
diff --git a/src/shortcuts/dzl-shortcut-phase.h b/src/shortcuts/dzl-shortcut-phase.h
index 25206e7..67d9bac 100644
--- a/src/shortcuts/dzl-shortcut-phase.h
+++ b/src/shortcuts/dzl-shortcut-phase.h
@@ -35,12 +35,15 @@ G_BEGIN_DECLS
  * @DZL_SHORTCUT_BUBBLE: The final phase of event delivery. The event is
  *   delivered to each widget as it progresses from the target window widget
  *   up to the toplevel.
+ * @DZL_SHORTCUT_GLOBAL: The shortcut can be activated from anywhere in the
+ *   widget hierarchy, even outside the direct chain of focus.
  */
 typedef enum
 {
   DZL_SHORTCUT_PHASE_DISPATCH = 0,
-  DZL_SHORTCUT_PHASE_CAPTURE  = 1,
-  DZL_SHORTCUT_PHASE_BUBBLE   = 2,
+  DZL_SHORTCUT_PHASE_CAPTURE  = 1 << 0,
+  DZL_SHORTCUT_PHASE_BUBBLE   = 1 << 1,
+  DZL_SHORTCUT_PHASE_GLOBAL   = 1 << 2,
 } DzlShortcutPhase;
 
 GType dzl_shortcut_phase_get_type (void);
diff --git a/src/shortcuts/dzl-shortcut-private.h b/src/shortcuts/dzl-shortcut-private.h
index c1e1bf9..a599977 100644
--- a/src/shortcuts/dzl-shortcut-private.h
+++ b/src/shortcuts/dzl-shortcut-private.h
@@ -75,8 +75,8 @@ struct _DzlShortcutClosureChain
   GSList node;
 
   DzlShortcutClosureType type : 3;
+  DzlShortcutPhase phase : 3;
   guint executing : 1;
-  DzlShortcutPhase phase : 2;
 
   union {
     struct {
@@ -103,7 +103,8 @@ struct _DzlShortcutClosureChain
 DzlShortcutMatch       _dzl_shortcut_controller_handle              (DzlShortcutController      *self,
                                                                      const GdkEventKey          *event,
                                                                      const DzlShortcutChord     *chord,
-                                                                     DzlShortcutPhase            phase);
+                                                                     DzlShortcutPhase            phase,
+                                                                     GtkWidget                  *widget);
 DzlShortcutChord      *_dzl_shortcut_controller_push                (DzlShortcutController      *self,
                                                                      const GdkEventKey          *event);
 void                   _dzl_shortcut_controller_clear               (DzlShortcutController      *self);
diff --git a/src/shortcuts/dzl-shortcut-theme-load.c b/src/shortcuts/dzl-shortcut-theme-load.c
index cadb6a7..21fd8c3 100644
--- a/src/shortcuts/dzl-shortcut-theme-load.c
+++ b/src/shortcuts/dzl-shortcut-theme-load.c
@@ -161,7 +161,7 @@ load_state_add_action (LoadState   *state,
       if (context != NULL)
         dzl_shortcut_context_add_action (context, accel, action);
       else if (theme != NULL)
-        dzl_shortcut_theme_set_accel_for_action (theme, action, accel);
+        dzl_shortcut_theme_set_accel_for_action (theme, action, accel, 0);
     }
 }
 
@@ -196,7 +196,7 @@ load_state_add_command (LoadState   *state,
       if (context != NULL)
         dzl_shortcut_context_add_command (context, accel, command);
       else if (theme != NULL)
-        dzl_shortcut_theme_set_accel_for_command (theme, command, accel);
+        dzl_shortcut_theme_set_accel_for_command (theme, command, accel, 0);
     }
 }
 
diff --git a/src/shortcuts/dzl-shortcut-theme.c b/src/shortcuts/dzl-shortcut-theme.c
index 6c079a0..c962bcd 100644
--- a/src/shortcuts/dzl-shortcut-theme.c
+++ b/src/shortcuts/dzl-shortcut-theme.c
@@ -429,20 +429,21 @@ dzl_shortcut_theme_set_parent_name (DzlShortcutTheme *self,
 void
 dzl_shortcut_theme_set_chord_for_action (DzlShortcutTheme       *self,
                                          const gchar            *detailed_action_name,
-                                         const DzlShortcutChord *chord)
+                                         const DzlShortcutChord *chord,
+                                         DzlShortcutPhase        phase)
 {
   DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
 
   g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
 
-  detailed_action_name = g_intern_string (detailed_action_name);
-
   if (detailed_action_name == NULL)
     {
       dzl_shortcut_chord_table_remove (priv->actions_table, chord);
       return;
     }
 
+  detailed_action_name = g_intern_string (detailed_action_name);
+
   dzl_shortcut_chord_table_remove_data (priv->actions_table,
                                         (gpointer)detailed_action_name);
 
@@ -450,10 +451,17 @@ dzl_shortcut_theme_set_chord_for_action (DzlShortcutTheme       *self,
     dzl_shortcut_chord_table_add (priv->actions_table, chord,
                                   (gpointer)detailed_action_name);
 
+  if (phase == DZL_SHORTCUT_PHASE_DISPATCH)
+    phase = DZL_SHORTCUT_PHASE_BUBBLE | DZL_SHORTCUT_PHASE_GLOBAL;
+
   if (!g_hash_table_contains (priv->chains, detailed_action_name))
-    g_hash_table_insert (priv->chains,
-                         (gchar *)detailed_action_name,
-                         dzl_shortcut_closure_chain_append_action_string (NULL, detailed_action_name));
+  {
+    DzlShortcutClosureChain *chain;
+
+    chain = dzl_shortcut_closure_chain_append_action_string (NULL, detailed_action_name);
+    chain->phase = phase;
+    g_hash_table_insert (priv->chains, (gchar *)detailed_action_name, chain);
+  }
 }
 
 const DzlShortcutChord *
@@ -485,7 +493,8 @@ dzl_shortcut_theme_get_chord_for_action (DzlShortcutTheme *self,
 void
 dzl_shortcut_theme_set_accel_for_action (DzlShortcutTheme *self,
                                          const gchar      *detailed_action_name,
-                                         const gchar      *accel)
+                                         const gchar      *accel,
+                                         DzlShortcutPhase  phase)
 {
   g_autoptr(DzlShortcutChord) chord = NULL;
 
@@ -494,7 +503,7 @@ dzl_shortcut_theme_set_accel_for_action (DzlShortcutTheme *self,
   if (accel != NULL)
     chord = dzl_shortcut_chord_new_from_string (accel);
 
-  dzl_shortcut_theme_set_chord_for_action (self, detailed_action_name, chord);
+  dzl_shortcut_theme_set_chord_for_action (self, detailed_action_name, chord, phase);
 }
 
 /**
@@ -502,6 +511,7 @@ dzl_shortcut_theme_set_accel_for_action (DzlShortcutTheme *self,
  * @self: a #DzlShortcutTheme
  * @chord: (nullable): the chord for the command
  * @command: (nullable): the command to be executed
+ * @phase: the phase to activate within, or 0 for the default
  *
  * This will set the command to execute when @chord is pressed.  If command is
  * %NULL, the accelerator will be cleared.  If @chord is %NULL, all
@@ -510,29 +520,36 @@ dzl_shortcut_theme_set_accel_for_action (DzlShortcutTheme *self,
 void
 dzl_shortcut_theme_set_chord_for_command (DzlShortcutTheme       *self,
                                           const gchar            *command,
-                                          const DzlShortcutChord *chord)
+                                          const DzlShortcutChord *chord,
+                                          DzlShortcutPhase        phase)
 {
   DzlShortcutThemePrivate *priv = dzl_shortcut_theme_get_instance_private (self);
 
   g_return_if_fail (DZL_IS_SHORTCUT_THEME (self));
 
-  command = g_intern_string (command);
-
   if (command == NULL)
     {
       dzl_shortcut_chord_table_remove (priv->commands_table, chord);
       return;
     }
 
+  command = g_intern_string (command);
   dzl_shortcut_chord_table_remove_data (priv->commands_table, (gpointer)command);
 
   if (chord != NULL)
     dzl_shortcut_chord_table_add (priv->commands_table, chord, (gpointer)command);
 
+  if (phase == DZL_SHORTCUT_PHASE_DISPATCH)
+    phase = DZL_SHORTCUT_PHASE_BUBBLE | DZL_SHORTCUT_PHASE_GLOBAL;
+
   if (!g_hash_table_contains (priv->chains, command))
-    g_hash_table_insert (priv->chains,
-                         (gchar *)command,
-                         dzl_shortcut_closure_chain_append_command (NULL, command));
+    {
+      DzlShortcutClosureChain *chain;
+
+      chain = dzl_shortcut_closure_chain_append_command (NULL, command);
+      chain->phase = phase;
+      g_hash_table_insert (priv->chains, (gchar *)command, chain);
+    }
 }
 
 const DzlShortcutChord *
@@ -566,6 +583,7 @@ dzl_shortcut_theme_get_chord_for_command (DzlShortcutTheme *self,
  * @self: a #DzlShortcutTheme
  * @command: (nullable): the command to be executed
  * @accel: (nullable): the shortcut accelerator
+ * @phase: the phase to activate within, or 0 for the default
  *
  * This will set the command to execute when @accel is pressed.  If command is
  * %NULL, the accelerator will be cleared.  If accelerator is %NULL, all
@@ -574,7 +592,8 @@ dzl_shortcut_theme_get_chord_for_command (DzlShortcutTheme *self,
 void
 dzl_shortcut_theme_set_accel_for_command (DzlShortcutTheme *self,
                                           const gchar      *command,
-                                          const gchar      *accel)
+                                          const gchar      *accel,
+                                          DzlShortcutPhase  phase)
 {
   g_autoptr(DzlShortcutChord) chord = NULL;
 
@@ -583,7 +602,7 @@ dzl_shortcut_theme_set_accel_for_command (DzlShortcutTheme *self,
   if (accel != NULL)
     chord = dzl_shortcut_chord_new_from_string (accel);
 
-  dzl_shortcut_theme_set_chord_for_command (self, command, chord);
+  dzl_shortcut_theme_set_chord_for_command (self, command, chord, phase);
 }
 
 /**
@@ -632,19 +651,14 @@ _dzl_shortcut_theme_match (DzlShortcutTheme         *self,
   g_return_val_if_fail (chord != NULL, FALSE);
   g_return_val_if_fail (chain != NULL, FALSE);
 
-  if (phase != DZL_SHORTCUT_PHASE_BUBBLE)
-    return DZL_SHORTCUT_MATCH_NONE;
-
-  /* TODO: We probably need the ability to have a "block" or "unbind" type
-   *       disabling when we implement chaining up to the parent theme.
-   */
-
   match1 = dzl_shortcut_chord_table_lookup (priv->actions_table, chord, (gpointer *)&action_id);
 
   if (match1 == DZL_SHORTCUT_MATCH_EQUAL)
     {
       *chain = g_hash_table_lookup (priv->chains, action_id);
-      return match1;
+      if ((*chain)->phase == phase)
+        return match1;
+      match1 = DZL_SHORTCUT_MATCH_NONE;
     }
 
   match2 = dzl_shortcut_chord_table_lookup (priv->commands_table, chord, (gpointer *)&command_id);
@@ -652,7 +666,9 @@ _dzl_shortcut_theme_match (DzlShortcutTheme         *self,
   if (match2 == DZL_SHORTCUT_MATCH_EQUAL)
     {
       *chain = g_hash_table_lookup (priv->chains, command_id);
-      return match2;
+      if ((*chain)->phase == phase)
+        return match2;
+      match2 = DZL_SHORTCUT_MATCH_NONE;
     }
 
   /*
diff --git a/src/shortcuts/dzl-shortcut-theme.h b/src/shortcuts/dzl-shortcut-theme.h
index 3cd3b42..c71c068 100644
--- a/src/shortcuts/dzl-shortcut-theme.h
+++ b/src/shortcuts/dzl-shortcut-theme.h
@@ -21,8 +21,9 @@
 
 #include <gtk/gtk.h>
 
-#include "dzl-shortcut-chord.h"
-#include "dzl-shortcut-context.h"
+#include "shortcuts/dzl-shortcut-chord.h"
+#include "shortcuts/dzl-shortcut-context.h"
+#include "shortcuts/dzl-shortcut-phase.h"
 
 G_BEGIN_DECLS
 
@@ -63,20 +64,24 @@ void                    dzl_shortcut_theme_add_context           (DzlShortcutThe
                                                                   DzlShortcutContext      *context);
 void                    dzl_shortcut_theme_set_chord_for_action  (DzlShortcutTheme        *self,
                                                                   const gchar             
*detailed_action_name,
-                                                                  const DzlShortcutChord  *chord);
+                                                                  const DzlShortcutChord  *chord,
+                                                                  DzlShortcutPhase         phase);
 const DzlShortcutChord *dzl_shortcut_theme_get_chord_for_action  (DzlShortcutTheme        *self,
                                                                   const gchar             
*detailed_action_name);
 void                    dzl_shortcut_theme_set_accel_for_action  (DzlShortcutTheme        *self,
                                                                   const gchar             
*detailed_action_name,
-                                                                  const gchar             *accel);
+                                                                  const gchar             *accel,
+                                                                  DzlShortcutPhase         phase);
 void                    dzl_shortcut_theme_set_chord_for_command (DzlShortcutTheme        *self,
                                                                   const gchar             *command,
-                                                                  const DzlShortcutChord  *chord);
+                                                                  const DzlShortcutChord  *chord,
+                                                                  DzlShortcutPhase         phase);
 const DzlShortcutChord *dzl_shortcut_theme_get_chord_for_command (DzlShortcutTheme        *self,
                                                                   const gchar             *command);
 void                    dzl_shortcut_theme_set_accel_for_command (DzlShortcutTheme        *self,
                                                                   const gchar             *command,
-                                                                  const gchar             *accel);
+                                                                  const gchar             *accel,
+                                                                  DzlShortcutPhase         phase);
 gboolean                dzl_shortcut_theme_load_from_data        (DzlShortcutTheme        *self,
                                                                   const gchar             *data,
                                                                   gssize                   len,
diff --git a/tests/test-shortcuts.c b/tests/test-shortcuts.c
index 71ce8ba..9c4e387 100644
--- a/tests/test-shortcuts.c
+++ b/tests/test-shortcuts.c
@@ -138,13 +138,13 @@ main (gint argc,
   dzl_shortcut_controller_add_command_signal (app.search_controller,
                                               "com.example.foo.search",
                                               "<ctrl>y|<ctrl>y",
-                                              DZL_SHORTCUT_PHASE_CAPTURE,
+                                              DZL_SHORTCUT_PHASE_GLOBAL,
                                               "grab-focus",
                                               0);
   dzl_shortcut_controller_add_command_callback (app.search_controller,
                                                 "com.example.foo.test",
                                                 "<ctrl>x|<ctrl>r",
-                                                DZL_SHORTCUT_PHASE_CAPTURE,
+                                                DZL_SHORTCUT_PHASE_GLOBAL,
                                                 test_callback, NULL, NULL);
 
   app.shortcuts = g_object_new (GTK_TYPE_BUTTON,
@@ -222,22 +222,22 @@ main (gint argc,
   dzl_shortcut_manager_add_command (manager, "org.gnome.builder.paste", "Editor", "Editing", "Paste 
Selection", NULL);
   dzl_shortcut_manager_add_command (manager, "org.gnome.builder.delete", "Editor", "Editing", "Delete 
Selection", NULL);
 
-  dzl_shortcut_theme_set_accel_for_action (theme, "build-manager.build", "F7");
-  dzl_shortcut_theme_set_accel_for_action (theme, "build-manager.rebuild", "<Control>F7");
-  dzl_shortcut_theme_set_accel_for_action (theme, "build-manager.clean", "F8");
+  dzl_shortcut_theme_set_accel_for_action (theme, "build-manager.build", "F7", 0);
+  dzl_shortcut_theme_set_accel_for_action (theme, "build-manager.rebuild", "<Control>F7", 0);
+  dzl_shortcut_theme_set_accel_for_action (theme, "build-manager.clean", "F8", 0);
 
-  dzl_shortcut_theme_set_accel_for_action (theme, "win.new-terminal", "<primary><shift>t");
-  dzl_shortcut_theme_set_accel_for_action (theme, "win.new-terminal-in-runtime", "<primary><shift><alt>t");
+  dzl_shortcut_theme_set_accel_for_action (theme, "win.new-terminal", "<primary><shift>t", 0);
+  dzl_shortcut_theme_set_accel_for_action (theme, "win.new-terminal-in-runtime", "<primary><shift><alt>t", 
0);
 
-  dzl_shortcut_theme_set_accel_for_command (theme, "org.gnome.builder.cut", "<Primary>x");
-  dzl_shortcut_theme_set_accel_for_command (theme, "org.gnome.builder.copy", "<Primary>c");
-  dzl_shortcut_theme_set_accel_for_command (theme, "org.gnome.builder.paste", "<Primary>v");
-  dzl_shortcut_theme_set_accel_for_command (theme, "org.gnome.builder.delete", "Delete");
+  dzl_shortcut_theme_set_accel_for_command (theme, "org.gnome.builder.cut", "<Primary>x", 0);
+  dzl_shortcut_theme_set_accel_for_command (theme, "org.gnome.builder.copy", "<Primary>c", 0);
+  dzl_shortcut_theme_set_accel_for_command (theme, "org.gnome.builder.paste", "<Primary>v", 0);
+  dzl_shortcut_theme_set_accel_for_command (theme, "org.gnome.builder.delete", "Delete", 0);
 
-  dzl_shortcut_theme_set_accel_for_command (theme, "com.example.foo.test", "<Control>r|<Control>r");
+  dzl_shortcut_theme_set_accel_for_command (theme, "com.example.foo.test", "<Control>r|<Control>r", 0);
 
-  dzl_shortcut_theme_set_accel_for_action (theme, "run-manager.run", "F3");
-  dzl_shortcut_theme_set_accel_for_action (theme, "run-manager.run-with-handler::'debugger'", "<Shift>F3");
+  dzl_shortcut_theme_set_accel_for_action (theme, "run-manager.run", "F3", 0);
+  dzl_shortcut_theme_set_accel_for_action (theme, "run-manager.run-with-handler::'debugger'", "<Shift>F3", 
0);
 
   dzl_shortcut_manager_add_shortcut_entries (manager, entries, G_N_ELEMENTS (entries), NULL);
 



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