/*
* Copyright © 2013 Canonical Limited
* Copyright © 2016 Sébastien Wilmet
*
* 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 of the licence, 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 .
*
* Authors: Ryan Lortie
* Sébastien Wilmet
*/
#include "config.h"
#include "gtkapplicationaccelsprivate.h"
#include "gtkactionmuxerprivate.h"
#include "gtkshortcut.h"
#include "gtkshortcutaction.h"
#include "gtkshortcuttrigger.h"
struct _GtkApplicationAccels
{
GObject parent;
GListModel *shortcuts;
};
G_DEFINE_TYPE (GtkApplicationAccels, gtk_application_accels, G_TYPE_OBJECT)
static void
gtk_application_accels_finalize (GObject *object)
{
GtkApplicationAccels *accels = GTK_APPLICATION_ACCELS (object);
g_list_store_remove_all (G_LIST_STORE (accels->shortcuts));
g_object_unref (accels->shortcuts);
G_OBJECT_CLASS (gtk_application_accels_parent_class)->finalize (object);
}
static void
gtk_application_accels_class_init (GtkApplicationAccelsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gtk_application_accels_finalize;
}
static void
gtk_application_accels_init (GtkApplicationAccels *accels)
{
accels->shortcuts = G_LIST_MODEL (g_list_store_new (GTK_TYPE_SHORTCUT));
}
GtkApplicationAccels *
gtk_application_accels_new (void)
{
return g_object_new (GTK_TYPE_APPLICATION_ACCELS, NULL);
}
void
gtk_application_accels_set_accels_for_action (GtkApplicationAccels *accels,
const gchar *detailed_action_name,
const gchar * const *accelerators)
{
gchar *action_name;
GVariant *target;
GtkShortcut *shortcut;
GtkShortcutTrigger *trigger = NULL;
GError *error = NULL;
guint i;
if (!g_action_parse_detailed_name (detailed_action_name, &action_name, &target, &error))
{
g_critical ("Error parsing action name: %s", error->message);
g_error_free (error);
return;
}
/* remove the accelerator if it already exists */
for (i = 0; i < g_list_model_get_n_items (accels->shortcuts); i++)
{
GtkShortcut *shortcut_i = g_list_model_get_item (accels->shortcuts, i);
GtkShortcutAction *action = gtk_shortcut_get_action (shortcut_i);
GVariant *args = gtk_shortcut_get_arguments (shortcut_i);
if (!GTK_IS_NAMED_ACTION (action) ||
!g_str_equal (gtk_named_action_get_action_name (GTK_NAMED_ACTION (action)), action_name))
{
g_object_unref (shortcut_i);
continue;
}
if ((target == NULL && args != NULL) ||
(target != NULL && (args == NULL || !g_variant_equal (target, args))))
{
g_object_unref (shortcut_i);
continue;
}
g_list_store_remove (G_LIST_STORE (accels->shortcuts), i);
break;
}
if (accelerators == NULL)
goto out;
for (i = 0; accelerators[i]; i++)
{
GtkShortcutTrigger *new_trigger;
guint key, modifier;
if (!gtk_accelerator_parse (accelerators[i], &key, &modifier))
{
g_critical ("Unable to parse accelerator '%s': ignored request to install accelerators",
accelerators[i]);
g_clear_object (&trigger);
goto out;
}
new_trigger = gtk_keyval_trigger_new (key, modifier);
if (trigger)
trigger = gtk_alternative_trigger_new (trigger, new_trigger);
else
trigger = new_trigger;
}
if (trigger == NULL)
goto out;
shortcut = gtk_shortcut_new (trigger, gtk_named_action_new (action_name));
gtk_shortcut_set_arguments (shortcut, target);
g_list_store_append (G_LIST_STORE (accels->shortcuts), shortcut);
g_object_unref (shortcut);
out:
g_free (action_name);
if (target)
g_variant_unref (target);
}
static void
append_accelerators (GPtrArray *accels,
GtkShortcutTrigger *trigger)
{
if (GTK_IS_KEYVAL_TRIGGER (trigger))
{
GtkKeyvalTrigger *kt = GTK_KEYVAL_TRIGGER (trigger);
guint keyval = gtk_keyval_trigger_get_keyval (kt);
GdkModifierType mods = gtk_keyval_trigger_get_modifiers (kt);
g_ptr_array_add (accels, gtk_accelerator_name (keyval, mods));
return;
}
else if (GTK_IS_ALTERNATIVE_TRIGGER (trigger))
{
GtkAlternativeTrigger *at = GTK_ALTERNATIVE_TRIGGER (trigger);
GtkShortcutTrigger *first = gtk_alternative_trigger_get_first (at);
GtkShortcutTrigger *second = gtk_alternative_trigger_get_second (at);
append_accelerators (accels, first);
append_accelerators (accels, second);
return;
}
}
gchar **
gtk_application_accels_get_accels_for_action (GtkApplicationAccels *accels,
const gchar *detailed_action_name)
{
GPtrArray *result;
char *action_name;
GVariant *target;
GError *error = NULL;
guint i;
result = g_ptr_array_new ();
if (!g_action_parse_detailed_name (detailed_action_name, &action_name, &target, &error))
{
g_critical ("Error parsing action name: %s", error->message);
g_error_free (error);
g_ptr_array_add (result, NULL);
return (gchar **) g_ptr_array_free (result, FALSE);
}
for (i = 0; i < g_list_model_get_n_items (accels->shortcuts); i++)
{
GtkShortcut *shortcut = g_list_model_get_item (accels->shortcuts, i);
GtkShortcutAction *action = gtk_shortcut_get_action (shortcut);
GVariant *args = gtk_shortcut_get_arguments (shortcut);
if (!GTK_IS_NAMED_ACTION (action) ||
!g_str_equal (gtk_named_action_get_action_name (GTK_NAMED_ACTION (action)), action_name))
{
g_object_unref (shortcut);
continue;
}
if ((target == NULL && args != NULL) ||
(target != NULL && (args == NULL || !g_variant_equal (target, args))))
{
g_object_unref (shortcut);
continue;
}
append_accelerators (result, gtk_shortcut_get_trigger (shortcut));
g_object_unref (shortcut);
break;
}
g_free (action_name);
if (target)
g_variant_unref (target);
g_ptr_array_add (result, NULL);
return (gchar **) g_ptr_array_free (result, FALSE);
}
static gboolean
trigger_matches_accel (GtkShortcutTrigger *trigger,
guint keyval,
GdkModifierType modifiers)
{
if (GTK_IS_KEYVAL_TRIGGER (trigger))
{
GtkKeyvalTrigger *kt = GTK_KEYVAL_TRIGGER (trigger);
return gtk_keyval_trigger_get_keyval (kt) == keyval
&& gtk_keyval_trigger_get_modifiers (kt) == modifiers;
}
else if (GTK_IS_ALTERNATIVE_TRIGGER (trigger))
{
GtkAlternativeTrigger *at = GTK_ALTERNATIVE_TRIGGER (trigger);
return trigger_matches_accel (gtk_alternative_trigger_get_first (at), keyval, modifiers)
|| trigger_matches_accel (gtk_alternative_trigger_get_second (at), keyval, modifiers);
}
else
{
return FALSE;
}
}
static char *
get_detailed_name_for_shortcut (GtkShortcut *shortcut)
{
GtkShortcutAction *action = gtk_shortcut_get_action (shortcut);
if (!GTK_IS_NAMED_ACTION (action))
return NULL;
return g_action_print_detailed_name (gtk_named_action_get_action_name (GTK_NAMED_ACTION (action)),
gtk_shortcut_get_arguments (shortcut));
}
gchar **
gtk_application_accels_get_actions_for_accel (GtkApplicationAccels *accels,
const gchar *accel)
{
GPtrArray *result;
guint key, modifiers;
guint i;
if (!gtk_accelerator_parse (accel, &key, &modifiers))
{
g_critical ("invalid accelerator string '%s'", accel);
return NULL;
}
result = g_ptr_array_new ();
for (i = 0; i < g_list_model_get_n_items (accels->shortcuts); i++)
{
GtkShortcut *shortcut = g_list_model_get_item (accels->shortcuts, i);
char *detailed_name;
if (!trigger_matches_accel (gtk_shortcut_get_trigger (shortcut), key, modifiers))
{
g_object_unref (shortcut);
continue;
}
detailed_name = get_detailed_name_for_shortcut (shortcut);
if (detailed_name)
g_ptr_array_add (result, detailed_name);
g_object_unref (shortcut);
}
g_ptr_array_add (result, NULL);
return (gchar **) g_ptr_array_free (result, FALSE);
}
gchar **
gtk_application_accels_list_action_descriptions (GtkApplicationAccels *accels)
{
GPtrArray *result;
guint i;
result = g_ptr_array_new ();
for (i = 0; i < g_list_model_get_n_items (accels->shortcuts); i++)
{
GtkShortcut *shortcut = g_list_model_get_item (accels->shortcuts, i);
char *detailed_name;
detailed_name = get_detailed_name_for_shortcut (shortcut);
if (detailed_name)
g_ptr_array_add (result, detailed_name);
g_object_unref (shortcut);
}
g_ptr_array_add (result, NULL);
return (gchar **) g_ptr_array_free (result, FALSE);
}
GListModel *
gtk_application_accels_get_shortcuts (GtkApplicationAccels *accels)
{
return accels->shortcuts;
}