/* 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 . */ #include "config.h" #include #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); } } }