From 730154df7d144a7b14b4c74c3a47592a2bf62bed Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 13 Aug 2018 16:02:27 +0200 Subject: [PATCH] shortcutcontroller: Add GtkShortcutScope 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 | 3 + gtk/gtkenums.h | 19 ++ gtk/gtkshortcutcontroller.c | 267 ++++++++++++++++++++++++++- gtk/gtkshortcutcontroller.h | 10 +- gtk/gtkshortcutcontrollerprivate.h | 5 + gtk/gtkwidget.c | 31 +++- 6 files changed, 327 insertions(+), 8 deletions(-) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index b11bb4e246..3a5b41d257 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -6101,6 +6101,9 @@ gtk_shortcut_get_type GtkShortcutController 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 diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h index 8cb42cff20..658be5f338 100644 --- a/gtk/gtkenums.h +++ b/gtk/gtkenums.h @@ -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 diff --git a/gtk/gtkshortcutcontroller.c b/gtk/gtkshortcutcontroller.c index b4538d0729..ed56a98dc6 100644 --- a/gtk/gtkshortcutcontroller.c +++ b/gtk/gtkshortcutcontroller.c @@ -32,7 +32,9 @@ #include "gtkshortcutcontrollerprivate.h" #include "gtkeventcontrollerprivate.h" +#include "gtkintl.h" #include "gtkshortcut.h" +#include "gtktypebuiltins.h" #include "gtkwidgetprivate.h" #include @@ -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; +} + diff --git a/gtk/gtkshortcutcontroller.h b/gtk/gtkshortcutcontroller.h index e078e3241a..fea66f3455 100644 --- a/gtk/gtkshortcutcontroller.h +++ b/gtk/gtkshortcutcontroller.h @@ -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 diff --git a/gtk/gtkshortcutcontrollerprivate.h b/gtk/gtkshortcutcontrollerprivate.h index 945e526e66..42594ea6eb 100644 --- a/gtk/gtkshortcutcontrollerprivate.h +++ b/gtk/gtkshortcutcontrollerprivate.h @@ -24,5 +24,10 @@ 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__ */ diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index 8b3f4b30fb..8d04910922 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -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)