PIKApp/app/display/pikamodifiersmanager.c

627 lines
23 KiB
C

/* PIKA - Photo and Image Kooker Application
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
*
* Original copyright, applying to most contents (license remains unchanged):
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* pikamodifiersmanager.c
* Copyright (C) 2022 Jehan
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gtk/gtk.h>
#include "libpikabase/pikabase.h"
#include "libpikaconfig/pikaconfig.h"
#include "display-types.h"
#include "widgets/pikawidgets-utils.h"
#include "pikamodifiersmanager.h"
#include "pika-intl.h"
enum
{
MODIFIERS_MANAGER_MAPPING,
MODIFIERS_MANAGER_MODIFIERS,
MODIFIERS_MANAGER_MOD_ACTION,
};
typedef struct
{
GdkModifierType modifiers;
PikaModifierAction mod_action;
gchar *action_desc;
} PikaModifierMapping;
struct _PikaModifiersManagerPrivate
{
GHashTable *actions;
GList *buttons;
};
static void pika_modifiers_manager_config_iface_init (PikaConfigInterface *iface);
static void pika_modifiers_manager_finalize (GObject *object);
static gboolean pika_modifiers_manager_serialize (PikaConfig *config,
PikaConfigWriter *writer,
gpointer data);
static gboolean pika_modifiers_manager_deserialize (PikaConfig *config,
GScanner *scanner,
gint nest_level,
gpointer data);
static void pika_modifiers_manager_free_mapping (PikaModifierMapping *mapping);
static void pika_modifiers_manager_get_keys (GdkDevice *device,
guint button,
GdkModifierType modifiers,
gchar **actions_key,
gchar **buttons_key);
static void pika_modifiers_manager_initialize (PikaModifiersManager *manager,
GdkDevice *device,
guint button);
G_DEFINE_TYPE_WITH_CODE (PikaModifiersManager, pika_modifiers_manager, G_TYPE_OBJECT,
G_ADD_PRIVATE (PikaModifiersManager)
G_IMPLEMENT_INTERFACE (PIKA_TYPE_CONFIG,
pika_modifiers_manager_config_iface_init))
#define parent_class pika_modifiers_manager_parent_class
static void
pika_modifiers_manager_class_init (PikaModifiersManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = pika_modifiers_manager_finalize;
}
static void
pika_modifiers_manager_init (PikaModifiersManager *manager)
{
manager->p = pika_modifiers_manager_get_instance_private (manager);
manager->p->actions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
(GDestroyNotify) pika_modifiers_manager_free_mapping);
}
static void
pika_modifiers_manager_config_iface_init (PikaConfigInterface *iface)
{
iface->serialize = pika_modifiers_manager_serialize;
iface->deserialize = pika_modifiers_manager_deserialize;
}
static void
pika_modifiers_manager_finalize (GObject *object)
{
PikaModifiersManager *manager = PIKA_MODIFIERS_MANAGER (object);
G_OBJECT_CLASS (parent_class)->finalize (object);
g_hash_table_unref (manager->p->actions);
g_list_free_full (manager->p->buttons, g_free);
}
static gboolean
pika_modifiers_manager_serialize (PikaConfig *config,
PikaConfigWriter *writer,
gpointer data)
{
PikaModifiersManager *manager = PIKA_MODIFIERS_MANAGER (config);
GEnumClass *enum_class;
GList *keys;
GList *iter;
enum_class = g_type_class_ref (PIKA_TYPE_MODIFIER_ACTION);
keys = g_hash_table_get_keys (manager->p->actions);
for (iter = keys; iter; iter = iter->next)
{
const gchar *button = iter->data;
PikaModifierMapping *mapping;
GEnumValue *enum_value;
pika_config_writer_open (writer, "mapping");
pika_config_writer_string (writer, button);
mapping = g_hash_table_lookup (manager->p->actions, button);
pika_config_writer_open (writer, "modifiers");
pika_config_writer_printf (writer, "%d", mapping->modifiers);
pika_config_writer_close (writer);
enum_value = g_enum_get_value (enum_class, GPOINTER_TO_INT (mapping->mod_action));
pika_config_writer_open (writer, "mod-action");
pika_config_writer_identifier (writer, enum_value->value_nick);
if (mapping->mod_action == PIKA_MODIFIER_ACTION_ACTION)
pika_config_writer_string (writer, mapping->action_desc);
pika_config_writer_close (writer);
pika_config_writer_close (writer);
}
g_list_free (keys);
g_type_class_unref (enum_class);
return TRUE;
}
static gboolean
pika_modifiers_manager_deserialize (PikaConfig *config,
GScanner *scanner,
gint nest_level,
gpointer data)
{
PikaModifiersManager *manager = PIKA_MODIFIERS_MANAGER (config);
GEnumClass *enum_class;
GTokenType token;
guint scope_id;
guint old_scope_id;
gchar *actions_key = NULL;
GdkModifierType modifiers = 0;
scope_id = g_type_qname (G_TYPE_FROM_INSTANCE (config));
old_scope_id = g_scanner_set_scope (scanner, scope_id);
enum_class = g_type_class_ref (PIKA_TYPE_MODIFIER_ACTION);
g_scanner_scope_add_symbol (scanner, scope_id, "mapping",
GINT_TO_POINTER (MODIFIERS_MANAGER_MAPPING));
g_scanner_scope_add_symbol (scanner, scope_id, "modifiers",
GINT_TO_POINTER (MODIFIERS_MANAGER_MODIFIERS));
g_scanner_scope_add_symbol (scanner, scope_id, "mod-action",
GINT_TO_POINTER (MODIFIERS_MANAGER_MOD_ACTION));
token = G_TOKEN_LEFT_PAREN;
while (g_scanner_peek_next_token (scanner) == token)
{
token = g_scanner_get_next_token (scanner);
switch (token)
{
case G_TOKEN_LEFT_PAREN:
token = G_TOKEN_SYMBOL;
break;
case G_TOKEN_SYMBOL:
switch (GPOINTER_TO_INT (scanner->value.v_symbol))
{
case MODIFIERS_MANAGER_MAPPING:
token = G_TOKEN_LEFT_PAREN;
if (! pika_scanner_parse_string (scanner, &actions_key))
goto error;
break;
case MODIFIERS_MANAGER_MOD_ACTION:
{
PikaModifierMapping *mapping;
GEnumValue *enum_value;
token = G_TOKEN_IDENTIFIER;
if (g_scanner_peek_next_token (scanner) != token)
goto error;
g_scanner_get_next_token (scanner);
enum_value = g_enum_get_value_by_nick (enum_class,
scanner->value.v_identifier);
if (! enum_value)
enum_value = g_enum_get_value_by_name (enum_class,
scanner->value.v_identifier);
if (! enum_value)
{
g_scanner_error (scanner,
_("invalid value '%s' for contextual action"),
scanner->value.v_identifier);
return G_TOKEN_NONE;
}
if (g_hash_table_lookup (manager->p->actions, actions_key))
{
/* This should not happen. But let's avoid breaking
* the whole parsing for a duplicate. Just output to
* stderr to track any problematic modifiersrc
* creation.
*/
g_printerr ("%s: ignoring duplicate action %s for mapping %s\n",
G_STRFUNC, scanner->value.v_identifier, actions_key);
g_clear_pointer (&actions_key, g_free);
}
else
{
gchar *suffix;
gchar *action_desc = NULL;
if (enum_value->value == PIKA_MODIFIER_ACTION_ACTION)
{
if (! pika_scanner_parse_string (scanner, &action_desc))
{
g_printerr ("%s: missing action description for mapping %s\n",
G_STRFUNC, actions_key);
goto error;
}
}
suffix = g_strdup_printf ("-%d", modifiers);
if (g_str_has_suffix (actions_key, suffix))
{
gchar *buttons_key = g_strndup (actions_key,
strlen (actions_key) - strlen (suffix));
mapping = g_slice_new (PikaModifierMapping);
mapping->modifiers = modifiers;
mapping->mod_action = enum_value->value;
mapping->action_desc = action_desc;
g_hash_table_insert (manager->p->actions, actions_key, mapping);
if (g_list_find_custom (manager->p->buttons, buttons_key, (GCompareFunc) g_strcmp0))
g_free (buttons_key);
else
manager->p->buttons = g_list_prepend (manager->p->buttons, buttons_key);
}
else
{
g_printerr ("%s: ignoring mapping %s with invalid modifiers %d\n",
G_STRFUNC, actions_key, modifiers);
g_clear_pointer (&actions_key, g_free);
}
g_free (suffix);
}
/* Closing parentheses twice. */
token = G_TOKEN_RIGHT_PAREN;
if (g_scanner_peek_next_token (scanner) != token)
goto error;
g_scanner_get_next_token (scanner);
}
break;
case MODIFIERS_MANAGER_MODIFIERS:
token = G_TOKEN_RIGHT_PAREN;
if (! pika_scanner_parse_int (scanner, (int *) &modifiers))
goto error;
break;
default:
break;
}
break;
case G_TOKEN_RIGHT_PAREN:
token = G_TOKEN_LEFT_PAREN;
break;
default:
break;
}
}
error:
g_scanner_scope_remove_symbol (scanner, scope_id, "mapping");
g_scanner_scope_remove_symbol (scanner, scope_id, "modifiers");
g_scanner_scope_remove_symbol (scanner, scope_id, "mod-action");
g_scanner_set_scope (scanner, old_scope_id);
g_type_class_unref (enum_class);
return G_TOKEN_LEFT_PAREN;
}
/* public functions */
PikaModifiersManager *
pika_modifiers_manager_new (void)
{
return g_object_new (PIKA_TYPE_MODIFIERS_MANAGER, NULL);
}
PikaModifierAction
pika_modifiers_manager_get_action (PikaModifiersManager *manager,
GdkDevice *device,
guint button,
GdkModifierType state,
const gchar **action_desc)
{
gchar *actions_key = NULL;
gchar *buttons_key = NULL;
GdkModifierType mod_state;
PikaModifierAction retval = PIKA_MODIFIER_ACTION_NONE;
g_return_val_if_fail (PIKA_IS_MODIFIERS_MANAGER (manager), PIKA_MODIFIER_ACTION_NONE);
g_return_val_if_fail (GDK_IS_DEVICE (device), PIKA_MODIFIER_ACTION_NONE);
g_return_val_if_fail (action_desc != NULL && *action_desc == NULL, PIKA_MODIFIER_ACTION_NONE);
mod_state = state & pika_get_all_modifiers_mask ();
pika_modifiers_manager_get_keys (device, button, mod_state,
&actions_key, &buttons_key);
if (g_list_find_custom (manager->p->buttons, buttons_key, (GCompareFunc) g_strcmp0))
{
PikaModifierMapping *mapping;
mapping = g_hash_table_lookup (manager->p->actions, actions_key);
if (mapping == NULL)
retval = PIKA_MODIFIER_ACTION_NONE;
else
retval = mapping->mod_action;
if (retval == PIKA_MODIFIER_ACTION_ACTION)
*action_desc = mapping->action_desc;
}
else if (button == 2)
{
if (mod_state == pika_get_extend_selection_mask ())
retval = PIKA_MODIFIER_ACTION_ROTATING;
else if (mod_state == (pika_get_extend_selection_mask () | GDK_CONTROL_MASK))
retval = PIKA_MODIFIER_ACTION_STEP_ROTATING;
else if (mod_state == pika_get_toggle_behavior_mask ())
retval = PIKA_MODIFIER_ACTION_ZOOMING;
else if (mod_state == GDK_MOD1_MASK)
retval = PIKA_MODIFIER_ACTION_LAYER_PICKING;
else if (mod_state == 0)
retval = PIKA_MODIFIER_ACTION_PANNING;
}
else if (button == 3)
{
if (mod_state == GDK_MOD1_MASK)
retval = PIKA_MODIFIER_ACTION_BRUSH_PIXEL_SIZE;
else if (mod_state == 0)
retval = PIKA_MODIFIER_ACTION_MENU;
}
g_free (actions_key);
g_free (buttons_key);
return retval;
}
GList *
pika_modifiers_manager_get_modifiers (PikaModifiersManager *manager,
GdkDevice *device,
guint button)
{
gchar *buttons_key = NULL;
GList *modifiers = NULL;
GList *action_keys;
GList *iter;
gchar *action_prefix;
pika_modifiers_manager_initialize (manager, device, button);
pika_modifiers_manager_get_keys (device, button, 0, NULL,
&buttons_key);
action_prefix = g_strdup_printf ("%s-", buttons_key);
g_free (buttons_key);
action_keys = g_hash_table_get_keys (manager->p->actions);
for (iter = action_keys; iter; iter = iter->next)
{
if (g_str_has_prefix (iter->data, action_prefix))
{
PikaModifierMapping *mapping;
mapping = g_hash_table_lookup (manager->p->actions, iter->data);
/* TODO: the modifiers list should be sorted to ensure
* consistency.
*/
modifiers = g_list_prepend (modifiers, GINT_TO_POINTER (mapping->modifiers));
}
}
g_free (action_prefix);
g_list_free (action_keys);
return modifiers;
}
void
pika_modifiers_manager_set (PikaModifiersManager *manager,
GdkDevice *device,
guint button,
GdkModifierType modifiers,
PikaModifierAction action,
const gchar *action_desc)
{
gchar *actions_key = NULL;
gchar *buttons_key = NULL;
g_return_if_fail (PIKA_IS_MODIFIERS_MANAGER (manager));
g_return_if_fail (GDK_IS_DEVICE (device));
pika_modifiers_manager_get_keys (device, button, modifiers,
&actions_key, &buttons_key);
g_free (buttons_key);
pika_modifiers_manager_initialize (manager, device, button);
if (action == PIKA_MODIFIER_ACTION_NONE ||
(action == PIKA_MODIFIER_ACTION_ACTION && action_desc == NULL))
{
g_hash_table_remove (manager->p->actions, actions_key);
g_free (actions_key);
}
else
{
PikaModifierMapping *mapping;
mapping = g_slice_new (PikaModifierMapping);
mapping->modifiers = modifiers;
mapping->mod_action = action;
mapping->action_desc = action_desc ? g_strdup (action_desc) : NULL;
g_hash_table_insert (manager->p->actions, actions_key,
mapping);
}
}
void
pika_modifiers_manager_remove (PikaModifiersManager *manager,
GdkDevice *device,
guint button,
GdkModifierType modifiers)
{
pika_modifiers_manager_set (manager, device, button, modifiers,
PIKA_MODIFIER_ACTION_NONE, NULL);
}
void
pika_modifiers_manager_clear (PikaModifiersManager *manager)
{
g_hash_table_remove_all (manager->p->actions);
g_list_free_full (manager->p->buttons, g_free);
manager->p->buttons = NULL;
}
/* Private functions */
static void
pika_modifiers_manager_free_mapping (PikaModifierMapping *mapping)
{
g_free (mapping->action_desc);
g_slice_free (PikaModifierMapping, mapping);
}
static void
pika_modifiers_manager_get_keys (GdkDevice *device,
guint button,
GdkModifierType modifiers,
gchar **actions_key,
gchar **buttons_key)
{
const gchar *vendor_id;
const gchar *product_id;
g_return_if_fail (GDK_IS_DEVICE (device) || device == NULL);
vendor_id = device ? gdk_device_get_vendor_id (device) : NULL;
product_id = device ? gdk_device_get_product_id (device) : NULL;
modifiers = modifiers & pika_get_all_modifiers_mask ();
if (actions_key)
*actions_key = g_strdup_printf ("%s:%s-%d-%d",
vendor_id ? vendor_id : "(no-vendor-id)",
product_id ? product_id : "(no-product-id)",
button, modifiers);
if (buttons_key)
*buttons_key = g_strdup_printf ("%s:%s-%d",
vendor_id ? vendor_id : "(no-vendor-id)",
product_id ? product_id : "(no-product-id)",
button);
}
static void
pika_modifiers_manager_initialize (PikaModifiersManager *manager,
GdkDevice *device,
guint button)
{
gchar *buttons_key = NULL;
g_return_if_fail (PIKA_IS_MODIFIERS_MANAGER (manager));
g_return_if_fail (GDK_IS_DEVICE (device));
pika_modifiers_manager_get_keys (device, button, 0,
NULL, &buttons_key);
/* Add the button to buttons whether or not we insert or remove an
* action. It mostly mean that we "touched" the settings for a given
* device/button. So it's a per-button initialized flag.
*/
if (g_list_find_custom (manager->p->buttons, buttons_key, (GCompareFunc) g_strcmp0))
{
g_free (buttons_key);
}
else
{
gchar *actions_key = NULL;
PikaModifierMapping *mapping;
manager->p->buttons = g_list_prepend (manager->p->buttons, buttons_key);
if (button == 2)
{
/* The default mapping for second (middle) button which had no explicit configuration. */
mapping = g_slice_new (PikaModifierMapping);
mapping->modifiers = GDK_MOD1_MASK;
mapping->mod_action = PIKA_MODIFIER_ACTION_LAYER_PICKING;
pika_modifiers_manager_get_keys (device, 2, mapping->modifiers,
&actions_key, NULL);
g_hash_table_insert (manager->p->actions, actions_key, mapping);
mapping = g_slice_new (PikaModifierMapping);
mapping->modifiers = pika_get_extend_selection_mask () | GDK_CONTROL_MASK;
mapping->mod_action = PIKA_MODIFIER_ACTION_STEP_ROTATING;
pika_modifiers_manager_get_keys (device, 2, mapping->modifiers,
&actions_key, NULL);
g_hash_table_insert (manager->p->actions, actions_key, mapping);
mapping = g_slice_new (PikaModifierMapping);
mapping->modifiers = pika_get_extend_selection_mask ();
mapping->mod_action = PIKA_MODIFIER_ACTION_ROTATING;
pika_modifiers_manager_get_keys (device, 2, mapping->modifiers,
&actions_key, NULL);
g_hash_table_insert (manager->p->actions, actions_key, mapping);
mapping = g_slice_new (PikaModifierMapping);
mapping->modifiers = pika_get_toggle_behavior_mask ();
mapping->mod_action = PIKA_MODIFIER_ACTION_ZOOMING;
pika_modifiers_manager_get_keys (device, 2, mapping->modifiers,
&actions_key, NULL);
g_hash_table_insert (manager->p->actions, actions_key, mapping);
mapping = g_slice_new (PikaModifierMapping);
mapping->modifiers = 0;
mapping->mod_action = PIKA_MODIFIER_ACTION_PANNING;
pika_modifiers_manager_get_keys (device, 2, mapping->modifiers,
&actions_key, NULL);
g_hash_table_insert (manager->p->actions, actions_key, mapping);
}
else if (button == 3)
{
/* The default mapping for third button which had no explicit configuration. */
mapping = g_slice_new (PikaModifierMapping);
mapping->modifiers = GDK_MOD1_MASK;
mapping->mod_action = PIKA_MODIFIER_ACTION_BRUSH_PIXEL_SIZE;
pika_modifiers_manager_get_keys (device, 3, mapping->modifiers,
&actions_key, NULL);
g_hash_table_insert (manager->p->actions, actions_key, mapping);
mapping = g_slice_new (PikaModifierMapping);
mapping->modifiers = 0;
mapping->mod_action = PIKA_MODIFIER_ACTION_MENU;
pika_modifiers_manager_get_keys (device, 3, mapping->modifiers,
&actions_key, NULL);
g_hash_table_insert (manager->p->actions, actions_key, mapping);
}
}
}