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.
This commit is contained in:
Benjamin Otte 2018-08-13 16:02:27 +02:00 committed by Matthias Clasen
parent 2309e1dd38
commit 730154df7d
6 changed files with 327 additions and 8 deletions

View 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

View 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

View 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;
}

View 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

View File

@ -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__ */

View 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)