gtk2/gtk/gtkshortcuttrigger.c
Emmanuele Bassi 457b6657bb Turn GtkShortcutTrigger into an object
The lightweight inheritance mechanism used for GtkShortcutTrigger is not
going to be usable by bindings, because boxed types cannot have derived
types.

We could use GTypeInstance and derive everything from that, like
GParamSpec, but in the end shortcuts are not really a performance
critical paths, unlike CSS values or render nodes.
2020-03-25 23:14:45 -04:00

1297 lines
37 KiB
C

/*
* Copyright © 2018 Benjamin Otte
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
/**
* SECTION:gtkshortcuttrigger
* @Title: GtkShortcutTrigger
* @Short_description: Triggers to track if shortcuts should be activated
* @See_also: #GtkShortcut
*
* #GtkShortcutTrigger is the object used to track if a #GtkShortcut should be
* activated. For this purpose, gtk_shortcut_trigger_trigger() can be called
* on a #GdkEvent.
*
* #GtkShortcutTriggers contain functions that allow easy presentation to end
* users as well as being printed for debugging.
*
* All #GtkShortcutTriggers are immutable, you can only specify their properties
* during construction. If you want to change a trigger, you have to replace it
* with a new one.
*/
#include "config.h"
#include "gtkshortcuttrigger.h"
#include "gtkaccelgroupprivate.h"
#include "gtkprivate.h"
#include "gtkintl.h"
#define GTK_SHORTCUT_TRIGGER_HASH_NEVER 0u
#define GTK_SHORTCUT_TRIGGER_HASH_KEYVAL 1u
#define GTK_SHORTCUT_TRIGGER_HASH_MNEMONIC 2u
#define GTK_SHORTCUT_TRIGGER_HASH_ALTERNATIVE 3u
struct _GtkShortcutTrigger
{
GObject parent_instance;
};
struct _GtkShortcutTriggerClass
{
GObjectClass parent_class;
GtkShortcutTriggerMatch
(* trigger) (GtkShortcutTrigger *trigger,
GdkEvent *event,
gboolean enable_mnemonics);
guint (* hash) (GtkShortcutTrigger *trigger);
int (* compare) (GtkShortcutTrigger *trigger1,
GtkShortcutTrigger *trigger2);
void (* print) (GtkShortcutTrigger *trigger,
GString *string);
gboolean (* print_label) (GtkShortcutTrigger *trigger,
GdkDisplay *display,
GString *string);
};
G_DEFINE_ABSTRACT_TYPE (GtkShortcutTrigger, gtk_shortcut_trigger, G_TYPE_OBJECT)
static void
gtk_shortcut_trigger_class_init (GtkShortcutTriggerClass *klass)
{
}
static void
gtk_shortcut_trigger_init (GtkShortcutTrigger *self)
{
}
/**
* gtk_shortcut_trigger_trigger:
* @self: a #GtkShortcutTrigger
* @event: the event to check
* @enable_mnemonics: %TRUE if mnemonics should trigger. Usually the
* value of this property is determined by checking that the passed
* in @event is a Key event and has the right modifiers set.
*
* Checks if the given @event triggers @self.
*
* Returns: Whether the event triggered the shortcut
**/
GtkShortcutTriggerMatch
gtk_shortcut_trigger_trigger (GtkShortcutTrigger *self,
GdkEvent *event,
gboolean enable_mnemonics)
{
g_return_val_if_fail (GTK_IS_SHORTCUT_TRIGGER (self), GTK_SHORTCUT_TRIGGER_MATCH_NONE);
return GTK_SHORTCUT_TRIGGER_GET_CLASS (self)->trigger (self, event, enable_mnemonics);
}
/**
* gtk_shortcut_trigger_parse_string: (constructor)
* @string: the string to parse
*
* Tries to parse the given string into a trigger. On success,
* the parsed trigger is returned. When parsing failed, %NULL is
* returned.
*
* The accepted strings are:
*
* - `never`, for #GtkNeverTrigger
* - a string parsed by gtk_accelerator_parse(), for a #GtkKeyvalTrigger
* - underscore, followed by a single character, for #GtkMnemonicTrigger
*
* Returns: (nullable): a new #GtkShortcutTrigger or %NULL on error
*/
GtkShortcutTrigger *
gtk_shortcut_trigger_parse_string (const char *string)
{
GdkModifierType modifiers;
guint keyval;
g_return_val_if_fail (string != NULL, NULL);
if (g_str_equal (string, "never"))
return gtk_never_trigger_get ();
if (string[0] == '_')
{
if (gtk_accelerator_parse (string + 1, &keyval, &modifiers))
return gtk_mnemonic_trigger_new (keyval);
}
if (gtk_accelerator_parse (string, &keyval, &modifiers))
return gtk_keyval_trigger_new (keyval, modifiers);
return NULL;
}
/**
* gtk_shortcut_trigger_to_string:
* @self: a #GtkShortcutTrigger
*
* Prints the given trigger into a human-readable string.
* This is a small wrapper around gdk_content_formats_print()
* to help when debugging.
*
* Returns: (transfer full): a new string
*/
char *
gtk_shortcut_trigger_to_string (GtkShortcutTrigger *self)
{
GString *string;
g_return_val_if_fail (self != NULL, NULL);
string = g_string_new (NULL);
gtk_shortcut_trigger_print (self, string);
return g_string_free (string, FALSE);
}
/**
* gtk_shortcut_trigger_print:
* @self: a #GtkShortcutTrigger
* @string: a #GString to print into
*
* Prints the given trigger into a string for the developer.
* This is meant for debugging and logging.
*
* The form of the representation may change at any time
* and is not guaranteed to stay identical.
*/
void
gtk_shortcut_trigger_print (GtkShortcutTrigger *self,
GString *string)
{
g_return_if_fail (GTK_IS_SHORTCUT_TRIGGER (self));
g_return_if_fail (string != NULL);
GTK_SHORTCUT_TRIGGER_GET_CLASS (self)->print (self, string);
}
/**
* gtk_shortcut_trigger_to_label:
* @self: a #GtkShortcutTrigger
* @display: #GdkDisplay to print for
*
* Gets textual representation for the given trigger. This
* function is returning a translated string for presentation
* to end users for example in menu items or in help texts.
*
* The @display in use may influence the resulting string in
* various forms, such as resolving hardware keycodes or by
* causing display-specific modifier names.
*
* The form of the representation may change at any time and is
* not guaranteed to stay identical.
*
* Returns: (transfer full): a new string
**/
char *
gtk_shortcut_trigger_to_label (GtkShortcutTrigger *self,
GdkDisplay *display)
{
GString *string;
g_return_val_if_fail (self != NULL, NULL);
string = g_string_new (NULL);
gtk_shortcut_trigger_print_label (self, display, string);
return g_string_free (string, FALSE);
}
/**
* gtk_shortcut_trigger_print_label:
* @self: a #GtkShortcutTrigger
* @display: #GdkDisplay to print for
* @string: a #GString to print into
*
* Prints the given trigger into a string. This function is
* returning a translated string for presentation to end users
* for example in menu items or in help texts.
*
* The @display in use may influence the resulting string in
* various forms, such as resolving hardware keycodes or by
* causing display-specific modifier names.
*
* The form of the representation may change at any time and is
* not guaranteed to stay identical.
*
* Returns: %TRUE if something was printed or %FALSE if the
* trigger did not have a textual representation suitable
* for end users.
**/
gboolean
gtk_shortcut_trigger_print_label (GtkShortcutTrigger *self,
GdkDisplay *display,
GString *string)
{
g_return_val_if_fail (GTK_IS_SHORTCUT_TRIGGER (self), FALSE);
g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);
g_return_val_if_fail (string != NULL, FALSE);
return GTK_SHORTCUT_TRIGGER_GET_CLASS (self)->print_label (self, display, string);
}
/**
* gtk_shortcut_trigger_hash:
* @trigger: (type GtkShortcutTrigger): a #GtkShortcutTrigger
*
* Generates a hash value for a #GtkShortcutTrigger.
*
* The output of this function is guaranteed to be the same for a given
* value only per-process. It may change between different processor
* architectures or even different versions of GTK. Do not use this
* function as a basis for building protocols or file formats.
*
* The types of @trigger is #gconstpointer only to allow use of this
* function with #GHashTable. They must each be a #GtkShortcutTrigger.
*
* Returns: a hash value corresponding to @trigger
**/
guint
gtk_shortcut_trigger_hash (gconstpointer trigger)
{
GtkShortcutTrigger *t = (GtkShortcutTrigger *) trigger;
g_return_val_if_fail (GTK_IS_SHORTCUT_TRIGGER (t), 0);
return GTK_SHORTCUT_TRIGGER_GET_CLASS (t)->hash (t);
}
/**
* gtk_shortcut_trigger_equal:
* @trigger1: (type GtkShortcutTrigger): a #GtkShortcutTrigger
* @trigger2: (type GtkShortcutTrigger): a #GtkShortcutTrigger
*
* Checks if @trigger1 and @trigger2 trigger under the same conditions.
*
* The types of @one and @two are #gconstpointer only to allow use of this
* function with #GHashTable. They must each be a #GtkShortcutTrigger.
*
* Returns: %TRUE if @trigger1 and @trigger2 are equal
**/
gboolean
gtk_shortcut_trigger_equal (gconstpointer trigger1,
gconstpointer trigger2)
{
return gtk_shortcut_trigger_compare (trigger1, trigger2) == 0;
}
/**
* gtk_shortcut_trigger_compare:
* @trigger1: (type GtkShortcutTrigger): a #GtkShortcutTrigger
* @trigger2: (type GtkShortcutTrigger): a #GtkShortcutTrigger
*
*
* The types of @one and @two are #gconstpointer only to allow use of this
* function as a #GCompareFunc. They must each be a #GtkShortcutTrigger.
*
* Returns: An integer less than, equal to, or greater than zero if
* @trigger1 is found, respectively, to be less than, to match,
* or be greater than @trigger2.
**/
gint
gtk_shortcut_trigger_compare (gconstpointer trigger1,
gconstpointer trigger2)
{
GtkShortcutTrigger *t1 = (GtkShortcutTrigger *) trigger1;
GtkShortcutTrigger *t2 = (GtkShortcutTrigger *) trigger2;
GType type1, type2;
g_return_val_if_fail (GTK_IS_SHORTCUT_TRIGGER (t1), -1);
g_return_val_if_fail (GTK_IS_SHORTCUT_TRIGGER (t2), 1);
type1 = G_OBJECT_TYPE (t1);
type2 = G_OBJECT_TYPE (t2);
if (type1 == type2)
{
return GTK_SHORTCUT_TRIGGER_GET_CLASS (t1)->compare (t1, t2);
}
else
{ /* never < keyval < mnemonic < alternative */
if (type1 == GTK_TYPE_NEVER_TRIGGER ||
type2 == GTK_TYPE_ALTERNATIVE_TRIGGER)
return -1;
if (type2 == GTK_TYPE_NEVER_TRIGGER ||
type1 == GTK_TYPE_ALTERNATIVE_TRIGGER)
return 1;
if (type1 == GTK_TYPE_KEYVAL_TRIGGER)
return -1;
else
return 1;
}
}
struct _GtkNeverTrigger
{
GtkShortcutTrigger parent_instance;
guint never;
GdkModifierType modifiers;
};
struct _GtkNeverTriggerClass
{
GtkShortcutTriggerClass parent_class;
};
G_DEFINE_TYPE (GtkNeverTrigger, gtk_never_trigger, GTK_TYPE_SHORTCUT_TRIGGER)
static void
gtk_never_trigger_finalize (GObject *gobject)
{
g_assert_not_reached ();
G_OBJECT_CLASS (gtk_never_trigger_parent_class)->finalize (gobject);
}
static GtkShortcutTriggerMatch
gtk_never_trigger_trigger (GtkShortcutTrigger *trigger,
GdkEvent *event,
gboolean enable_mnemonics)
{
return GTK_SHORTCUT_TRIGGER_MATCH_NONE;
}
static guint
gtk_never_trigger_hash (GtkShortcutTrigger *trigger)
{
return GTK_SHORTCUT_TRIGGER_HASH_NEVER;
}
static int
gtk_never_trigger_compare (GtkShortcutTrigger *trigger1,
GtkShortcutTrigger *trigger2)
{
return 0;
}
static void
gtk_never_trigger_print (GtkShortcutTrigger *trigger,
GString *string)
{
g_string_append (string, "<never>");
}
static gboolean
gtk_never_trigger_print_label (GtkShortcutTrigger *trigger,
GdkDisplay *display,
GString *string)
{
return FALSE;
}
static void
gtk_never_trigger_class_init (GtkNeverTriggerClass *klass)
{
GtkShortcutTriggerClass *trigger_class = GTK_SHORTCUT_TRIGGER_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = gtk_never_trigger_finalize;
trigger_class->trigger = gtk_never_trigger_trigger;
trigger_class->hash = gtk_never_trigger_hash;
trigger_class->compare = gtk_never_trigger_compare;
trigger_class->print = gtk_never_trigger_print;
trigger_class->print_label = gtk_never_trigger_print_label;
}
static void
gtk_never_trigger_init (GtkNeverTrigger *self)
{
}
/**
* gtk_never_trigger_get:
*
* Gets the never trigger. This is a singleton for a trigger
* that never triggers. Use this trigger instead of %NULL
* because it implements all virtual functions.
*
* Returns: (type GtkNeverTrigger) (transfer none): The never trigger
*/
GtkShortcutTrigger *
gtk_never_trigger_get (void)
{
static GtkShortcutTrigger *never = NULL;
if (G_UNLIKELY (never == NULL))
never = g_object_new (GTK_TYPE_NEVER_TRIGGER, NULL);
return never;
}
struct _GtkKeyvalTrigger
{
GtkShortcutTrigger parent_instance;
guint keyval;
GdkModifierType modifiers;
};
struct _GtkKeyvalTriggerClass
{
GtkShortcutTriggerClass parent_class;
};
G_DEFINE_TYPE (GtkKeyvalTrigger, gtk_keyval_trigger, GTK_TYPE_SHORTCUT_TRIGGER)
enum
{
KEYVAL_PROP_KEYVAL = 1,
KEYVAL_PROP_MODIFIERS,
KEYVAL_N_PROPS
};
static GParamSpec *keyval_props[KEYVAL_N_PROPS];
static GtkShortcutTriggerMatch
gtk_keyval_trigger_trigger (GtkShortcutTrigger *trigger,
GdkEvent *event,
gboolean enable_mnemonics)
{
GtkKeyvalTrigger *self = GTK_KEYVAL_TRIGGER (trigger);
guint keycode;
GdkModifierType state;
GdkModifierType mask;
int group;
GdkKeymap *keymap;
guint keyval;
int effective_group;
int level;
GdkModifierType consumed_modifiers;
GdkModifierType shift_group_mask;
gboolean group_mod_is_accel_mod = FALSE;
const GdkModifierType xmods = GDK_MOD2_MASK|GDK_MOD3_MASK|GDK_MOD4_MASK|GDK_MOD5_MASK;
const GdkModifierType vmods = GDK_SUPER_MASK|GDK_HYPER_MASK|GDK_META_MASK;
GdkModifierType modifiers;
if (gdk_event_get_event_type (event) != GDK_KEY_PRESS)
return GTK_SHORTCUT_TRIGGER_MATCH_NONE;
mask = gtk_accelerator_get_default_mod_mask ();
keycode = gdk_key_event_get_keycode (event);
state = gdk_event_get_modifier_state (event);
group = gdk_key_event_get_group (event);
keymap = gdk_display_get_keymap (gdk_event_get_display (event));
/* We don't want Caps_Lock to affect keybinding lookups.
*/
state &= ~GDK_LOCK_MASK;
_gtk_translate_keyboard_accel_state (keymap,
keycode, state, mask, group,
&keyval,
&effective_group, &level,
&consumed_modifiers);
/* if the group-toggling modifier is part of the default accel mod
* mask, and it is active, disable it for matching
*/
shift_group_mask = gdk_keymap_get_modifier_mask (keymap,
GDK_MODIFIER_INTENT_SHIFT_GROUP);
if (mask & shift_group_mask)
group_mod_is_accel_mod = TRUE;
gdk_keymap_map_virtual_modifiers (keymap, &mask);
gdk_keymap_add_virtual_modifiers (keymap, &state);
modifiers = self->modifiers;
if (gdk_keymap_map_virtual_modifiers (keymap, &modifiers) &&
((modifiers & ~consumed_modifiers & mask & ~vmods) == (state & ~consumed_modifiers & mask & ~vmods) ||
(modifiers & ~consumed_modifiers & mask & ~xmods) == (state & ~consumed_modifiers & mask & ~xmods)))
{
/* modifier match */
GdkKeymapKey *keys;
int n_keys;
int i;
guint key;
/* Shift gets consumed and applied for the event,
* so apply it to our keyval to match
*/
key = self->keyval;
if (self->modifiers & GDK_SHIFT_MASK)
key = gdk_keyval_to_upper (key);
if (keyval == key && /* exact match */
(!group_mod_is_accel_mod ||
(state & shift_group_mask) == (self->modifiers & shift_group_mask)))
return GTK_SHORTCUT_TRIGGER_MATCH_EXACT;
gdk_keymap_get_entries_for_keyval (keymap,
self->keyval,
&keys, &n_keys);
for (i = 0; i < n_keys; i++)
{
if (keys[i].keycode == keycode &&
keys[i].level == level &&
/* Only match for group if it's an accel mod */
(!group_mod_is_accel_mod ||
keys[i].group == effective_group))
{
/* partial match */
g_free (keys);
return GTK_SHORTCUT_TRIGGER_MATCH_PARTIAL;
}
}
g_free (keys);
}
return GTK_SHORTCUT_TRIGGER_MATCH_NONE;
}
static guint
gtk_keyval_trigger_hash (GtkShortcutTrigger *trigger)
{
GtkKeyvalTrigger *self = GTK_KEYVAL_TRIGGER (trigger);
return (self->modifiers << 24)
| (self->modifiers >> 8)
| (self->keyval << 16)
| GTK_SHORTCUT_TRIGGER_HASH_KEYVAL;
}
static int
gtk_keyval_trigger_compare (GtkShortcutTrigger *trigger1,
GtkShortcutTrigger *trigger2)
{
GtkKeyvalTrigger *self1 = GTK_KEYVAL_TRIGGER (trigger1);
GtkKeyvalTrigger *self2 = GTK_KEYVAL_TRIGGER (trigger2);
if (self1->modifiers != self2->modifiers)
return self2->modifiers - self1->modifiers;
return self1->keyval - self2->keyval;
}
static void
gtk_keyval_trigger_print (GtkShortcutTrigger *trigger,
GString *string)
{
GtkKeyvalTrigger *self = GTK_KEYVAL_TRIGGER (trigger);
char *accelerator_name;
accelerator_name = gtk_accelerator_name (self->keyval, self->modifiers);
g_string_append (string, accelerator_name);
g_free (accelerator_name);
}
static gboolean
gtk_keyval_trigger_print_label (GtkShortcutTrigger *trigger,
GdkDisplay *display,
GString *string)
{
GtkKeyvalTrigger *self = GTK_KEYVAL_TRIGGER (trigger);
gtk_accelerator_print_label (string, self->keyval, self->modifiers);
return TRUE;
}
static void
gtk_keyval_trigger_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkKeyvalTrigger *self = GTK_KEYVAL_TRIGGER (gobject);
switch (prop_id)
{
case KEYVAL_PROP_KEYVAL:
{
guint keyval = g_value_get_uint (value);
/* We store keyvals as lower key */
if (keyval == GDK_KEY_ISO_Left_Tab)
self->keyval = GDK_KEY_Tab;
else
self->keyval = gdk_keyval_to_lower (keyval);
}
break;
case KEYVAL_PROP_MODIFIERS:
self->modifiers = g_value_get_flags (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
gtk_keyval_trigger_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkKeyvalTrigger *self = GTK_KEYVAL_TRIGGER (gobject);
switch (prop_id)
{
case KEYVAL_PROP_KEYVAL:
g_value_set_uint (value, self->keyval);
break;
case KEYVAL_PROP_MODIFIERS:
g_value_set_flags (value, self->modifiers);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
gtk_keyval_trigger_class_init (GtkKeyvalTriggerClass *klass)
{
GtkShortcutTriggerClass *trigger_class = GTK_SHORTCUT_TRIGGER_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->set_property = gtk_keyval_trigger_set_property;
gobject_class->get_property = gtk_keyval_trigger_get_property;
trigger_class->trigger = gtk_keyval_trigger_trigger;
trigger_class->hash = gtk_keyval_trigger_hash;
trigger_class->compare = gtk_keyval_trigger_compare;
trigger_class->print = gtk_keyval_trigger_print;
trigger_class->print_label = gtk_keyval_trigger_print_label;
/**
* GtkKeyvalTrigger:keyval:
*
* The key value for the trigger.
*/
keyval_props[KEYVAL_PROP_KEYVAL] =
g_param_spec_uint (I_("keyval"),
P_("Key value"),
P_("The key value for the trigger"),
0, G_MAXINT,
0,
G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE);
/**
* GtkKeyvalTrigger:modifiers:
*
* The key modifiers for the trigger.
*/
keyval_props[KEYVAL_PROP_MODIFIERS] =
g_param_spec_flags (I_("modifiers"),
P_("Modifiers"),
P_("The key modifiers for the trigger"),
GDK_TYPE_MODIFIER_TYPE,
0,
G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE);
g_object_class_install_properties (gobject_class, KEYVAL_N_PROPS, keyval_props);
}
static void
gtk_keyval_trigger_init (GtkKeyvalTrigger *self)
{
}
/**
* gtk_keyval_trigger_new:
* @keyval: The keyval to trigger for
* @modifiers: the modifiers that need to be present
*
* Creates a #GtkShortcutTrigger that will trigger whenever
* the key with the given @keyval and @modifiers is pressed.
*
* Returns: A new #GtkShortcutTrigger
*/
GtkShortcutTrigger *
gtk_keyval_trigger_new (guint keyval,
GdkModifierType modifiers)
{
return g_object_new (GTK_TYPE_KEYVAL_TRIGGER,
"keyval", keyval,
"modifiers", modifiers,
NULL);
}
/**
* gtk_keyval_trigger_get_modifiers:
* @self: a keyval #GtkShortcutTrigger
*
* Gets the modifiers that must be present to succeed
* triggering @self.
*
* Returns: the modifiers
**/
GdkModifierType
gtk_keyval_trigger_get_modifiers (GtkKeyvalTrigger *self)
{
g_return_val_if_fail (GTK_IS_KEYVAL_TRIGGER (self), 0);
return self->modifiers;
}
/**
* gtk_keyval_trigger_get_keyval:
* @self: a keyval #GtkShortcutTrigger
*
* Gets the keyval that must be pressed to succeed
* triggering @self.
*
* Returns: the keyval
**/
guint
gtk_keyval_trigger_get_keyval (GtkKeyvalTrigger *self)
{
g_return_val_if_fail (GTK_IS_KEYVAL_TRIGGER (self), 0);
return self->keyval;
}
/*** GTK_MNEMONIC_TRIGGER ***/
struct _GtkMnemonicTrigger
{
GtkShortcutTrigger parent_instance;
guint keyval;
};
struct _GtkMnemonicTriggerClass
{
GtkShortcutTriggerClass parent_class;
};
G_DEFINE_TYPE (GtkMnemonicTrigger, gtk_mnemonic_trigger, GTK_TYPE_SHORTCUT_TRIGGER)
enum
{
MNEMONIC_PROP_KEYVAL = 1,
MNEMONIC_N_PROPS
};
static GParamSpec *mnemonic_props[MNEMONIC_N_PROPS];
static GtkShortcutTriggerMatch
gtk_mnemonic_trigger_trigger (GtkShortcutTrigger *trigger,
GdkEvent *event,
gboolean enable_mnemonics)
{
GtkMnemonicTrigger *self = GTK_MNEMONIC_TRIGGER (trigger);
guint keyval;
if (!enable_mnemonics)
return GTK_SHORTCUT_TRIGGER_MATCH_NONE;
if (gdk_event_get_event_type (event) != GDK_KEY_PRESS)
return GTK_SHORTCUT_TRIGGER_MATCH_NONE;
/* XXX: This needs to deal with groups */
keyval = gdk_key_event_get_keyval (event);
if (keyval == GDK_KEY_ISO_Left_Tab)
keyval = GDK_KEY_Tab;
else
keyval = gdk_keyval_to_lower (keyval);
if (keyval != self->keyval)
return GTK_SHORTCUT_TRIGGER_MATCH_NONE;
return GTK_SHORTCUT_TRIGGER_MATCH_EXACT;
}
static guint
gtk_mnemonic_trigger_hash (GtkShortcutTrigger *trigger)
{
GtkMnemonicTrigger *self = GTK_MNEMONIC_TRIGGER (trigger);
return (self->keyval << 8)
| GTK_SHORTCUT_TRIGGER_HASH_MNEMONIC;
}
static int
gtk_mnemonic_trigger_compare (GtkShortcutTrigger *trigger1,
GtkShortcutTrigger *trigger2)
{
GtkMnemonicTrigger *self1 = GTK_MNEMONIC_TRIGGER (trigger1);
GtkMnemonicTrigger *self2 = GTK_MNEMONIC_TRIGGER (trigger2);
return self1->keyval - self2->keyval;
}
static void
gtk_mnemonic_trigger_print (GtkShortcutTrigger *trigger,
GString *string)
{
GtkMnemonicTrigger *self = GTK_MNEMONIC_TRIGGER (trigger);
const char *keyval_str;
keyval_str = gdk_keyval_name (self->keyval);
if (keyval_str == NULL)
keyval_str = "???";
g_string_append (string, "<Mnemonic>");
g_string_append (string, keyval_str);
}
static gboolean
gtk_mnemonic_trigger_print_label (GtkShortcutTrigger *trigger,
GdkDisplay *display,
GString *string)
{
GtkMnemonicTrigger *self = GTK_MNEMONIC_TRIGGER (trigger);
const char *keyval_str;
keyval_str = gdk_keyval_name (self->keyval);
if (keyval_str == NULL)
return FALSE;
g_string_append (string, keyval_str);
return TRUE;
}
static void
gtk_mnemonic_trigger_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkMnemonicTrigger *self = GTK_MNEMONIC_TRIGGER (gobject);
switch (prop_id)
{
case MNEMONIC_PROP_KEYVAL:
{
guint keyval = g_value_get_uint (value);
/* We store keyvals as lower key */
if (keyval == GDK_KEY_ISO_Left_Tab)
self->keyval = GDK_KEY_Tab;
else
self->keyval = gdk_keyval_to_lower (keyval);
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
gtk_mnemonic_trigger_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkMnemonicTrigger *self = GTK_MNEMONIC_TRIGGER (gobject);
switch (prop_id)
{
case MNEMONIC_PROP_KEYVAL:
g_value_set_uint (value, self->keyval);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
gtk_mnemonic_trigger_class_init (GtkMnemonicTriggerClass *klass)
{
GtkShortcutTriggerClass *trigger_class = GTK_SHORTCUT_TRIGGER_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->set_property = gtk_mnemonic_trigger_set_property;
gobject_class->get_property = gtk_mnemonic_trigger_get_property;
trigger_class->trigger = gtk_mnemonic_trigger_trigger;
trigger_class->hash = gtk_mnemonic_trigger_hash;
trigger_class->compare = gtk_mnemonic_trigger_compare;
trigger_class->print = gtk_mnemonic_trigger_print;
trigger_class->print_label = gtk_mnemonic_trigger_print_label;
/**
* GtkMnemonicTrigger:keyval:
*
* The key value for the trigger.
*/
mnemonic_props[KEYVAL_PROP_KEYVAL] =
g_param_spec_uint (I_("keyval"),
P_("Key value"),
P_("The key value for the trigger"),
0, G_MAXINT,
0,
G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE);
g_object_class_install_properties (gobject_class, MNEMONIC_N_PROPS, mnemonic_props);
}
static void
gtk_mnemonic_trigger_init (GtkMnemonicTrigger *self)
{
}
/**
* gtk_mnemonic_trigger_new:
* @keyval: The keyval to trigger for
*
* Creates a #GtkShortcutTrigger that will trigger whenever the key with
* the given @keyval is pressed and mnemonics have been activated.
*
* Mnemonics are activated by calling code when a key event with the right
* modifiers is detected.
*
* Returns: (transfer full) (type GtkMnemonicTrigger): A new #GtkShortcutTrigger
*/
GtkShortcutTrigger *
gtk_mnemonic_trigger_new (guint keyval)
{
return g_object_new (GTK_TYPE_MNEMONIC_TRIGGER,
"keyval", keyval,
NULL);
}
/**
* gtk_mnemonic_trigger_get_keyval:
* @self: a mnemonic #GtkShortcutTrigger
*
* Gets the keyval that must be pressed to succeed triggering @self.
*
* Returns: the keyval
**/
guint
gtk_mnemonic_trigger_get_keyval (GtkMnemonicTrigger *self)
{
g_return_val_if_fail (GTK_IS_MNEMONIC_TRIGGER (self), 0);
return self->keyval;
}
/*** GTK_ALTERNATIVE_TRIGGER ***/
struct _GtkAlternativeTrigger
{
GtkShortcutTrigger parent_instance;
GtkShortcutTrigger *first;
GtkShortcutTrigger *second;
};
struct _GtkAlternativeTriggerClass
{
GtkShortcutTriggerClass parent_class;
};
G_DEFINE_TYPE (GtkAlternativeTrigger, gtk_alternative_trigger, GTK_TYPE_SHORTCUT_TRIGGER)
enum
{
ALTERNATIVE_PROP_FIRST = 1,
ALTERNATIVE_PROP_SECOND,
ALTERNATIVE_N_PROPS
};
static GParamSpec *alternative_props[ALTERNATIVE_N_PROPS];
static void
gtk_alternative_trigger_dispose (GObject *gobject)
{
GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (gobject);
g_clear_object (&self->first);
g_clear_object (&self->second);
G_OBJECT_CLASS (gtk_alternative_trigger_parent_class)->dispose (gobject);
}
static GtkShortcutTriggerMatch
gtk_alternative_trigger_trigger (GtkShortcutTrigger *trigger,
GdkEvent *event,
gboolean enable_mnemonics)
{
GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (trigger);
return MAX (gtk_shortcut_trigger_trigger (self->first, event, enable_mnemonics),
gtk_shortcut_trigger_trigger (self->second, event, enable_mnemonics));
}
static guint
gtk_alternative_trigger_hash (GtkShortcutTrigger *trigger)
{
GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (trigger);
guint result;
result = gtk_shortcut_trigger_hash (self->first);
result <<= 5;
result |= gtk_shortcut_trigger_hash (self->second);
result <<= 5;
return result | GTK_SHORTCUT_TRIGGER_HASH_ALTERNATIVE;
}
static int
gtk_alternative_trigger_compare (GtkShortcutTrigger *trigger1,
GtkShortcutTrigger *trigger2)
{
GtkAlternativeTrigger *self1 = GTK_ALTERNATIVE_TRIGGER (trigger1);
GtkAlternativeTrigger *self2 = GTK_ALTERNATIVE_TRIGGER (trigger2);
int cmp;
cmp = gtk_shortcut_trigger_compare (self1->first, self2->first);
if (cmp != 0)
return cmp;
return gtk_shortcut_trigger_compare (self1->second, self2->second);
}
static void
gtk_alternative_trigger_print (GtkShortcutTrigger *trigger,
GString *string)
{
GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (trigger);
gtk_shortcut_trigger_print (self->first, string);
g_string_append (string, ", ");
gtk_shortcut_trigger_print (self->second, string);
}
static gboolean
gtk_alternative_trigger_print_label (GtkShortcutTrigger *trigger,
GdkDisplay *display,
GString *string)
{
GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (trigger);
if (gtk_shortcut_trigger_print_label (self->first, display, string))
{
g_string_append (string, ", ");
if (!gtk_shortcut_trigger_print_label (self->second, display, string))
g_string_truncate (string, string->len - 2);
return TRUE;
}
else
{
return gtk_shortcut_trigger_print_label (self->second, display, string);
}
}
static void
gtk_alternative_trigger_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (gobject);
switch (prop_id)
{
case ALTERNATIVE_PROP_FIRST:
self->first = g_value_dup_object (value);
break;
case ALTERNATIVE_PROP_SECOND:
self->second = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
gtk_alternative_trigger_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (gobject);
switch (prop_id)
{
case ALTERNATIVE_PROP_FIRST:
g_value_set_object (value, self->first);
break;
case ALTERNATIVE_PROP_SECOND:
g_value_set_object (value, self->second);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
gtk_alternative_trigger_constructed (GObject *gobject)
{
GtkAlternativeTrigger *self = GTK_ALTERNATIVE_TRIGGER (gobject);
if (self->first == NULL || self->second == NULL)
{
g_critical ("Invalid alternative trigger, disabling");
self->first = g_object_ref (gtk_never_trigger_get ());
self->second = g_object_ref (gtk_never_trigger_get ());
}
G_OBJECT_CLASS (gtk_alternative_trigger_parent_class)->constructed (gobject);
}
static void
gtk_alternative_trigger_class_init (GtkAlternativeTriggerClass *klass)
{
GtkShortcutTriggerClass *trigger_class = GTK_SHORTCUT_TRIGGER_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->constructed = gtk_alternative_trigger_constructed;
gobject_class->set_property = gtk_alternative_trigger_set_property;
gobject_class->get_property = gtk_alternative_trigger_get_property;
gobject_class->dispose = gtk_alternative_trigger_dispose;
trigger_class->trigger = gtk_alternative_trigger_trigger;
trigger_class->hash = gtk_alternative_trigger_hash;
trigger_class->compare = gtk_alternative_trigger_compare;
trigger_class->print = gtk_alternative_trigger_print;
trigger_class->print_label = gtk_alternative_trigger_print_label;
/**
* GtkAlternativeTrigger:first:
*
* The first #GtkShortcutTrigger to check.
*/
alternative_props[ALTERNATIVE_PROP_FIRST] =
g_param_spec_object (I_("first"),
P_("First"),
P_("The first trigger to check"),
GTK_TYPE_SHORTCUT_TRIGGER,
G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE);
/**
* GtkAlternativeTrigger:second:
*
* The second #GtkShortcutTrigger to check.
*/
alternative_props[ALTERNATIVE_PROP_SECOND] =
g_param_spec_object (I_("second"),
P_("Second"),
P_("The second trigger to check"),
GTK_TYPE_SHORTCUT_TRIGGER,
G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE);
g_object_class_install_properties (gobject_class, ALTERNATIVE_N_PROPS, alternative_props);
}
static void
gtk_alternative_trigger_init (GtkAlternativeTrigger *self)
{
}
/**
* gtk_alternative_trigger_new:
* @first: (transfer full): The first trigger that may trigger
* @second: (transfer full): The second trigger that may trigger
*
* Creates a #GtkShortcutTrigger that will trigger whenever
* either of the two given triggers gets triggered.
*
* Note that nesting is allowed, so if you want more than two
* alternative, create a new alternative trigger for each option.
*
* Returns: a new #GtkShortcutTrigger
*/
GtkShortcutTrigger *
gtk_alternative_trigger_new (GtkShortcutTrigger *first,
GtkShortcutTrigger *second)
{
GtkShortcutTrigger *res;
g_return_val_if_fail (GTK_IS_SHORTCUT_TRIGGER (first), NULL);
g_return_val_if_fail (GTK_IS_SHORTCUT_TRIGGER (second), NULL);
res = g_object_new (GTK_TYPE_ALTERNATIVE_TRIGGER,
"first", first,
"second", second,
NULL);
g_object_unref (first);
g_object_unref (second);
return res;
}
/**
* gtk_alternative_trigger_get_first:
* @self: an alternative #GtkShortcutTrigger
*
* Gets the first of the two alternative triggers that may
* trigger @self. gtk_alternative_trigger_get_second() will
* return the other one.
*
* Returns: (transfer none): the first alternative trigger
**/
GtkShortcutTrigger *
gtk_alternative_trigger_get_first (GtkAlternativeTrigger *self)
{
g_return_val_if_fail (GTK_IS_ALTERNATIVE_TRIGGER (self), NULL);
return self->first;
}
/**
* gtk_alternative_trigger_get_second:
* @self: an alternative #GtkShortcutTrigger
*
* Gets the second of the two alternative triggers that may
* trigger @self. gtk_alternative_trigger_get_first() will
* return the other one.
*
* Returns: (transfer none): the second alternative trigger
**/
GtkShortcutTrigger *
gtk_alternative_trigger_get_second (GtkAlternativeTrigger *self)
{
g_return_val_if_fail (GTK_IS_ALTERNATIVE_TRIGGER (self), NULL);
return self->second;
}