diff --git a/app/config/gimpdisplayconfig.c b/app/config/gimpdisplayconfig.c index 89ecaa3a23..1037b81924 100644 --- a/app/config/gimpdisplayconfig.c +++ b/app/config/gimpdisplayconfig.c @@ -65,6 +65,7 @@ enum PROP_SHOW_PAINT_TOOL_CURSOR, PROP_IMAGE_TITLE_FORMAT, PROP_IMAGE_STATUS_FORMAT, + PROP_MODIFIERS_MANAGER, PROP_MONITOR_XRESOLUTION, PROP_MONITOR_YRESOLUTION, PROP_MONITOR_RES_FROM_GDK, @@ -388,6 +389,17 @@ gimp_display_config_class_init (GimpDisplayConfigClass *klass) TRUE, GIMP_PARAM_STATIC_STRINGS | GIMP_CONFIG_PARAM_IGNORE); + + /* Stored as a property because we want to copy the object when we + * copy the config (for Preferences, etc.). But we don't want it to be + * stored as a config property into the rc files. + * Modifiers have their own rc file. + */ + g_object_class_install_property (object_class, PROP_MODIFIERS_MANAGER, + g_param_spec_object ("modifiers-manager", + NULL, NULL, + G_TYPE_OBJECT, + GIMP_PARAM_READWRITE)); } static void @@ -418,6 +430,7 @@ gimp_display_config_finalize (GObject *object) g_clear_object (&display_config->default_view); g_clear_object (&display_config->default_fullscreen_view); + g_clear_object (&display_config->modifiers_manager); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -523,6 +536,9 @@ gimp_display_config_set_property (GObject *object, case PROP_SPACE_BAR_ACTION: display_config->space_bar_action = g_value_get_enum (value); break; + case PROP_MODIFIERS_MANAGER: + display_config->modifiers_manager = g_value_dup_object (value); + break; case PROP_ZOOM_QUALITY: display_config->zoom_quality = g_value_get_enum (value); break; @@ -640,6 +656,9 @@ gimp_display_config_get_property (GObject *object, case PROP_SPACE_BAR_ACTION: g_value_set_enum (value, display_config->space_bar_action); break; + case PROP_MODIFIERS_MANAGER: + g_value_set_object (value, display_config->modifiers_manager); + break; case PROP_ZOOM_QUALITY: g_value_set_enum (value, display_config->zoom_quality); break; diff --git a/app/config/gimpdisplayconfig.h b/app/config/gimpdisplayconfig.h index 008b009a1a..5fb3d15bb5 100644 --- a/app/config/gimpdisplayconfig.h +++ b/app/config/gimpdisplayconfig.h @@ -71,6 +71,8 @@ struct _GimpDisplayConfig GimpSpaceBarAction space_bar_action; GimpZoomQuality zoom_quality; gboolean use_event_history; + + GObject *modifiers_manager; }; struct _GimpDisplayConfigClass diff --git a/app/display/Makefile.am b/app/display/Makefile.am index cb30eea9b7..f64d98d9d3 100644 --- a/app/display/Makefile.am +++ b/app/display/Makefile.am @@ -143,6 +143,8 @@ libappdisplay_a_sources = \ gimpdisplayshell-transform.h \ gimpdisplayshell-utils.c \ gimpdisplayshell-utils.h \ + gimpmodifiersmanager.c \ + gimpmodifiersmanager.h \ gimpimagewindow.c \ gimpimagewindow.h \ gimpmotionbuffer.c \ diff --git a/app/display/display-enums.c b/app/display/display-enums.c index b1cd881407..db317dddbc 100644 --- a/app/display/display-enums.c +++ b/app/display/display-enums.c @@ -592,6 +592,49 @@ gimp_zoom_focus_get_type (void) return type; } +GType +gimp_modifier_action_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_MODIFIER_ACTION_NONE, "GIMP_MODIFIER_ACTION_NONE", "none" }, + { GIMP_MODIFIER_ACTION_PANNING, "GIMP_MODIFIER_ACTION_PANNING", "panning" }, + { GIMP_MODIFIER_ACTION_ZOOMING, "GIMP_MODIFIER_ACTION_ZOOMING", "zooming" }, + { GIMP_MODIFIER_ACTION_ROTATING, "GIMP_MODIFIER_ACTION_ROTATING", "rotating" }, + { GIMP_MODIFIER_ACTION_STEP_ROTATING, "GIMP_MODIFIER_ACTION_STEP_ROTATING", "step-rotating" }, + { GIMP_MODIFIER_ACTION_LAYER_PICKING, "GIMP_MODIFIER_ACTION_LAYER_PICKING", "layer-picking" }, + { GIMP_MODIFIER_ACTION_MENU, "GIMP_MODIFIER_ACTION_MENU", "menu" }, + { GIMP_MODIFIER_ACTION_BRUSH_PIXEL_SIZE, "GIMP_MODIFIER_ACTION_BRUSH_PIXEL_SIZE", "brush-pixel-size" }, + { GIMP_MODIFIER_ACTION_BRUSH_SIZE, "GIMP_MODIFIER_ACTION_BRUSH_SIZE", "brush-size" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_MODIFIER_ACTION_NONE, NC_("modifier-action", "No action"), NULL }, + { GIMP_MODIFIER_ACTION_PANNING, NC_("modifier-action", "Pan"), NULL }, + { GIMP_MODIFIER_ACTION_ZOOMING, NC_("modifier-action", "Zoom"), NULL }, + { GIMP_MODIFIER_ACTION_ROTATING, NC_("modifier-action", "Rotate"), NULL }, + { GIMP_MODIFIER_ACTION_STEP_ROTATING, NC_("modifier-action", "Rotate by 15 degree steps"), NULL }, + { GIMP_MODIFIER_ACTION_LAYER_PICKING, NC_("modifier-action", "Pick a layer"), NULL }, + { GIMP_MODIFIER_ACTION_MENU, NC_("modifier-action", "Display the menu"), NULL }, + { GIMP_MODIFIER_ACTION_BRUSH_PIXEL_SIZE, NC_("modifier-action", "Change brush size in canvas pixels"), NULL }, + { GIMP_MODIFIER_ACTION_BRUSH_SIZE, NC_("modifier-action", "Change brush size relatively"), NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpModifierAction", values); + gimp_type_set_translation_context (type, "modifier-action"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + /* Generated data ends here */ diff --git a/app/display/display-enums.h b/app/display/display-enums.h index 71374f2856..82d2dd7dd2 100644 --- a/app/display/display-enums.h +++ b/app/display/display-enums.h @@ -261,6 +261,25 @@ typedef enum } GimpZoomFocus; +#define GIMP_TYPE_MODIFIER_ACTION (gimp_modifier_action_get_type ()) + +GType gimp_modifier_action_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_MODIFIER_ACTION_NONE, /*< desc="No action" >*/ + GIMP_MODIFIER_ACTION_PANNING, /*< desc="Pan" >*/ + GIMP_MODIFIER_ACTION_ZOOMING, /*< desc="Zoom" >*/ + GIMP_MODIFIER_ACTION_ROTATING, /*< desc="Rotate" >*/ + GIMP_MODIFIER_ACTION_STEP_ROTATING, /*< desc="Rotate by 15 degree steps" >*/ + GIMP_MODIFIER_ACTION_LAYER_PICKING, /*< desc="Pick a layer" >*/ + + GIMP_MODIFIER_ACTION_MENU, /*< desc="Display the menu" >*/ + GIMP_MODIFIER_ACTION_BRUSH_PIXEL_SIZE, /*< desc="Change brush size in canvas pixels" >*/ + GIMP_MODIFIER_ACTION_BRUSH_SIZE /*< desc="Change brush size relatively" >*/ +} GimpModifierAction; + + /* * non-registered enums; register them if needed */ diff --git a/app/display/display-types.h b/app/display/display-types.h index 302f465d19..04f2e8d237 100644 --- a/app/display/display-types.h +++ b/app/display/display-types.h @@ -48,5 +48,7 @@ typedef struct _GimpToolWidgetGroup GimpToolWidgetGroup; typedef struct _GimpDisplayXfer GimpDisplayXfer; typedef struct _Selection Selection; +typedef struct _GimpModifiersManager GimpModifiersManager; + #endif /* __DISPLAY_TYPES_H__ */ diff --git a/app/display/gimpmodifiersmanager.c b/app/display/gimpmodifiersmanager.c new file mode 100644 index 0000000000..9b31560cb4 --- /dev/null +++ b/app/display/gimpmodifiersmanager.c @@ -0,0 +1,585 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmodifiersmanager.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 "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" + +#include "display-types.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "gimpmodifiersmanager.h" + +#include "gimp-intl.h" + +enum +{ + MODIFIERS_MANAGER_MAPPING, + MODIFIERS_MANAGER_MODIFIERS, + MODIFIERS_MANAGER_MOD_ACTION, +}; + +typedef struct +{ + GdkModifierType modifiers; + GimpModifierAction mod_action; +} GimpModifierMapping; + +struct _GimpModifiersManagerPrivate +{ + GHashTable *actions; + GList *buttons; +}; + +static void gimp_modifiers_manager_config_iface_init (GimpConfigInterface *iface); +static void gimp_modifiers_manager_finalize (GObject *object); +static gboolean gimp_modifiers_manager_serialize (GimpConfig *config, + GimpConfigWriter *writer, + gpointer data); +static gboolean gimp_modifiers_manager_deserialize (GimpConfig *config, + GScanner *scanner, + gint nest_level, + gpointer data); + +static void gimp_modifiers_manager_free_mapping (GimpModifierMapping *mapping); + +static void gimp_modifiers_manager_get_keys (GdkDevice *device, + guint button, + GdkModifierType modifiers, + gchar **actions_key, + gchar **buttons_key); +static void gimp_modifiers_manager_initialize (GimpModifiersManager *manager, + GdkDevice *device, + guint button); + + +G_DEFINE_TYPE_WITH_CODE (GimpModifiersManager, gimp_modifiers_manager, G_TYPE_OBJECT, + G_ADD_PRIVATE (GimpModifiersManager) + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, + gimp_modifiers_manager_config_iface_init)) + +#define parent_class gimp_modifiers_manager_parent_class + + +static void +gimp_modifiers_manager_class_init (GimpModifiersManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gimp_modifiers_manager_finalize; +} + +static void +gimp_modifiers_manager_init (GimpModifiersManager *manager) +{ + manager->p = gimp_modifiers_manager_get_instance_private (manager); + + manager->p->actions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) gimp_modifiers_manager_free_mapping); +} + +static void +gimp_modifiers_manager_config_iface_init (GimpConfigInterface *iface) +{ + iface->serialize = gimp_modifiers_manager_serialize; + iface->deserialize = gimp_modifiers_manager_deserialize; +} + +static void +gimp_modifiers_manager_finalize (GObject *object) +{ + GimpModifiersManager *manager = GIMP_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 +gimp_modifiers_manager_serialize (GimpConfig *config, + GimpConfigWriter *writer, + gpointer data) +{ + GimpModifiersManager *manager = GIMP_MODIFIERS_MANAGER (config); + GEnumClass *enum_class; + GList *keys; + GList *iter; + + enum_class = g_type_class_ref (GIMP_TYPE_MODIFIER_ACTION); + keys = g_hash_table_get_keys (manager->p->actions); + + for (iter = keys; iter; iter = iter->next) + { + const gchar *button = iter->data; + GimpModifierMapping *mapping; + GEnumValue *enum_value; + + gimp_config_writer_open (writer, "mapping"); + gimp_config_writer_string (writer, button); + + mapping = g_hash_table_lookup (manager->p->actions, button); + + gimp_config_writer_open (writer, "modifiers"); + gimp_config_writer_printf (writer, "%d", mapping->modifiers); + gimp_config_writer_close (writer); + + enum_value = g_enum_get_value (enum_class, GPOINTER_TO_INT (mapping->mod_action)); + gimp_config_writer_open (writer, "mod-action"); + gimp_config_writer_identifier (writer, enum_value->value_nick); + gimp_config_writer_close (writer); + + gimp_config_writer_close (writer); + } + + g_list_free (keys); + g_type_class_unref (enum_class); + + return TRUE; +} + +static gboolean +gimp_modifiers_manager_deserialize (GimpConfig *config, + GScanner *scanner, + gint nest_level, + gpointer data) +{ + GimpModifiersManager *manager = GIMP_MODIFIERS_MANAGER (config); + GEnumClass *enum_class; + GTokenType token; + guint scope_id; + guint old_scope_id; + gchar *actions_key = NULL; + GdkModifierType modifiers; + + 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 (GIMP_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 (! gimp_scanner_parse_string (scanner, &actions_key)) + goto error; + break; + + case MODIFIERS_MANAGER_MOD_ACTION: + { + GimpModifierMapping *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; + 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 (GimpModifierMapping); + mapping->modifiers = modifiers; + mapping->mod_action = enum_value->value; + 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 (! gimp_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 */ + +GimpModifiersManager * +gimp_modifiers_manager_new (void) +{ + return g_object_new (GIMP_TYPE_MODIFIERS_MANAGER, NULL); +} + +GimpModifierAction +gimp_modifiers_manager_get_action (GimpModifiersManager *manager, + GdkDevice *device, + guint button, + GdkModifierType state) +{ + gchar *actions_key = NULL; + gchar *buttons_key = NULL; + GdkModifierType mod_state; + GimpModifierAction retval = GIMP_MODIFIER_ACTION_NONE; + + mod_state = state & gimp_get_all_modifiers_mask (); + + gimp_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)) + { + GimpModifierMapping *mapping; + + mapping = g_hash_table_lookup (manager->p->actions, actions_key); + + if (mapping == NULL) + retval = GIMP_MODIFIER_ACTION_NONE; + else + retval = mapping->mod_action; + } + else if (button == 2) + { + if (mod_state == gimp_get_extend_selection_mask ()) + retval = GIMP_MODIFIER_ACTION_ROTATING; + else if (mod_state == (gimp_get_extend_selection_mask () | GDK_CONTROL_MASK)) + retval = GIMP_MODIFIER_ACTION_STEP_ROTATING; + else if (mod_state == gimp_get_toggle_behavior_mask ()) + retval = GIMP_MODIFIER_ACTION_ZOOMING; + else if (mod_state == GDK_MOD1_MASK) + retval = GIMP_MODIFIER_ACTION_LAYER_PICKING; + else if (mod_state == 0) + retval = GIMP_MODIFIER_ACTION_PANNING; + } + else if (button == 3) + { + if (mod_state == GDK_MOD1_MASK) + retval = GIMP_MODIFIER_ACTION_BRUSH_SIZE; + else if (mod_state == 0) + retval = GIMP_MODIFIER_ACTION_MENU; + } + + g_free (actions_key); + g_free (buttons_key); + + return retval; +} + +GList * +gimp_modifiers_manager_get_modifiers (GimpModifiersManager *manager, + GdkDevice *device, + guint button) +{ + gchar *buttons_key = NULL; + GList *modifiers = NULL; + GList *action_keys; + GList *iter; + gchar *action_prefix; + + gimp_modifiers_manager_initialize (manager, device, button); + + gimp_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)) + { + GimpModifierMapping *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 +gimp_modifiers_manager_set (GimpModifiersManager *manager, + GdkDevice *device, + guint button, + GdkModifierType modifiers, + GimpModifierAction action) +{ + gchar *actions_key = NULL; + gchar *buttons_key = NULL; + + g_return_if_fail (GIMP_IS_MODIFIERS_MANAGER (manager)); + g_return_if_fail (GDK_IS_DEVICE (device)); + + gimp_modifiers_manager_get_keys (device, button, modifiers, + &actions_key, &buttons_key); + + gimp_modifiers_manager_initialize (manager, device, button); + + if (action == GIMP_MODIFIER_ACTION_NONE) + { + g_hash_table_remove (manager->p->actions, actions_key); + g_free (actions_key); + } + else + { + GimpModifierMapping *mapping; + + mapping = g_slice_new (GimpModifierMapping); + mapping->modifiers = modifiers; + mapping->mod_action = action; + g_hash_table_insert (manager->p->actions, actions_key, + mapping); + } +} + +void +gimp_modifiers_manager_remove (GimpModifiersManager *manager, + GdkDevice *device, + guint button, + GdkModifierType modifiers) +{ + gimp_modifiers_manager_set (manager, device, button, modifiers, + GIMP_MODIFIER_ACTION_NONE); +} + +/* Private functions */ + +static void +gimp_modifiers_manager_free_mapping (GimpModifierMapping *mapping) +{ + g_slice_free (GimpModifierMapping, mapping); +} + +static void +gimp_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 & gimp_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 +gimp_modifiers_manager_initialize (GimpModifiersManager *manager, + GdkDevice *device, + guint button) +{ + gchar *buttons_key = NULL; + + g_return_if_fail (GIMP_IS_MODIFIERS_MANAGER (manager)); + g_return_if_fail (GDK_IS_DEVICE (device)); + + gimp_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; + GimpModifierMapping *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 (GimpModifierMapping); + mapping->modifiers = GDK_MOD1_MASK; + mapping->mod_action = GIMP_MODIFIER_ACTION_LAYER_PICKING; + gimp_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 (GimpModifierMapping); + mapping->modifiers = gimp_get_extend_selection_mask () | GDK_CONTROL_MASK; + mapping->mod_action = GIMP_MODIFIER_ACTION_STEP_ROTATING; + gimp_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 (GimpModifierMapping); + mapping->modifiers = gimp_get_extend_selection_mask (); + mapping->mod_action = GIMP_MODIFIER_ACTION_ROTATING; + gimp_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 (GimpModifierMapping); + mapping->modifiers = gimp_get_toggle_behavior_mask (); + mapping->mod_action = GIMP_MODIFIER_ACTION_ZOOMING; + gimp_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 (GimpModifierMapping); + mapping->modifiers = 0; + mapping->mod_action = GIMP_MODIFIER_ACTION_PANNING; + gimp_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 (GimpModifierMapping); + mapping->modifiers = GDK_MOD1_MASK; + mapping->mod_action = GIMP_MODIFIER_ACTION_BRUSH_SIZE; + gimp_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 (GimpModifierMapping); + mapping->modifiers = 0; + mapping->mod_action = GIMP_MODIFIER_ACTION_MENU; + gimp_modifiers_manager_get_keys (device, 3, mapping->modifiers, + &actions_key, NULL); + g_hash_table_insert (manager->p->actions, actions_key, mapping); + } + } +} diff --git a/app/display/gimpmodifiersmanager.h b/app/display/gimpmodifiersmanager.h new file mode 100644 index 0000000000..420efd66f9 --- /dev/null +++ b/app/display/gimpmodifiersmanager.h @@ -0,0 +1,80 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmodifiersmanager.h + * 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 . + */ + +#ifndef __GIMP_MODIFIERS_MANAGER_H__ +#define __GIMP_MODIFIERS_MANAGER_H__ + + +#define GIMP_TYPE_MODIFIERS_MANAGER (gimp_modifiers_manager_get_type ()) +#define GIMP_MODIFIERS_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MODIFIERS_MANAGER, GimpModifiersManager)) +#define GIMP_MODIFIERS_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MODIFIERS_MANAGER, GimpModifiersManagerClass)) +#define GIMP_IS_MODIFIERS_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MODIFIERS_MANAGER)) +#define GIMP_IS_MODIFIERS_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MODIFIERS_MANAGER)) +#define GIMP_MODIFIERS_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MODIFIERS_MANAGER, GimpModifiersManagerClass)) + + +typedef struct _GimpModifiersManagerPrivate GimpModifiersManagerPrivate; +typedef struct _GimpModifiersManagerClass GimpModifiersManagerClass; + +/** + * GimpModifiersManager: + * + * Contains modifiers configuration for canvas interaction. + */ +struct _GimpModifiersManager +{ + GObject parent_instance; + + GimpModifiersManagerPrivate *p; +}; + +struct _GimpModifiersManagerClass +{ + GObjectClass parent_class; +}; + + +GType gimp_modifiers_manager_get_type (void) G_GNUC_CONST; + +GimpModifiersManager * gimp_modifiers_manager_new (void); + +GimpModifierAction gimp_modifiers_manager_get_action (GimpModifiersManager *manager, + GdkDevice *device, + guint button, + GdkModifierType modifiers); + +/* Protected functions: only use them from GimpModifiersEditor */ + +GList * gimp_modifiers_manager_get_modifiers (GimpModifiersManager *manager, + GdkDevice *device, + guint button); + +void gimp_modifiers_manager_set (GimpModifiersManager *manager, + GdkDevice *device, + guint button, + GdkModifierType modifiers, + GimpModifierAction action); +void gimp_modifiers_manager_remove (GimpModifiersManager *manager, + GdkDevice *device, + guint button, + GdkModifierType modifiers); + + +#endif /* __GIMP_MODIFIERS_MANAGER_H__ */ diff --git a/app/display/meson.build b/app/display/meson.build index d15953289f..43db1fd13e 100644 --- a/app/display/meson.build +++ b/app/display/meson.build @@ -82,6 +82,7 @@ libappdisplay_sources = [ 'gimpdisplayshell-transform.c', 'gimpdisplayshell-utils.c', 'gimpdisplayshell.c', + 'gimpmodifiersmanager.c', 'gimpimagewindow.c', 'gimpmotionbuffer.c', 'gimpmultiwindowstrategy.c', diff --git a/app/gui/Makefile.am b/app/gui/Makefile.am index 579cd63bc0..c43208d2aa 100644 --- a/app/gui/Makefile.am +++ b/app/gui/Makefile.am @@ -46,6 +46,8 @@ libappgui_a_sources = \ gui-types.h \ icon-themes.c \ icon-themes.h \ + modifiers.c \ + modifiers.h \ session.c \ session.h \ splash.c \ diff --git a/app/gui/gui.c b/app/gui/gui.c index 530504b8ef..647ea73e3f 100644 --- a/app/gui/gui.c +++ b/app/gui/gui.c @@ -79,6 +79,7 @@ #include "gui-unique.h" #include "gui-vtable.h" #include "icon-themes.h" +#include "modifiers.h" #include "session.h" #include "splash.h" #include "themes.h" @@ -530,6 +531,7 @@ gui_restore_callback (Gimp *gimp, gimp_devices_init (gimp); gimp_controllers_init (gimp); + modifiers_init (gimp); session_init (gimp); g_type_class_unref (g_type_class_ref (GIMP_TYPE_COLOR_SELECTOR_PALETTE)); @@ -622,6 +624,7 @@ gui_restore_after_callback (Gimp *gimp, gimp_devices_restore (gimp); gimp_controllers_restore (gimp, image_ui_manager); + modifiers_restore (gimp); if (status_callback == splash_update) splash_destroy (); @@ -766,6 +769,8 @@ gui_exit_callback (Gimp *gimp, if (TRUE /* gui_config->save_controllers */) gimp_controllers_save (gimp); + modifiers_save (gimp, FALSE); + g_signal_handlers_disconnect_by_func (gimp_get_user_context (gimp), gui_display_changed, gimp); @@ -819,6 +824,7 @@ gui_exit_after_callback (Gimp *gimp, gimp_render_exit (gimp); gimp_controllers_exit (gimp); + modifiers_exit (gimp); gimp_devices_exit (gimp); dialogs_exit (gimp); themes_exit (gimp); diff --git a/app/gui/meson.build b/app/gui/meson.build index d128d3101f..2883a1f350 100644 --- a/app/gui/meson.build +++ b/app/gui/meson.build @@ -14,6 +14,7 @@ libappgui_sources = [ 'gui-vtable.c', 'gui.c', 'icon-themes.c', + 'modifiers.c', 'session.c', 'splash.c', 'themes.c', diff --git a/app/gui/modifiers.c b/app/gui/modifiers.c new file mode 100644 index 0000000000..29e8a54937 --- /dev/null +++ b/app/gui/modifiers.c @@ -0,0 +1,218 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * modifiers.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 + +#include "libgimpbase/gimpbase.h" +#include "libgimpconfig/gimpconfig.h" + +#include "gui-types.h" + +#include "config/gimpconfig-file.h" +#include "config/gimpguiconfig.h" + +#include "core/gimp.h" +#include "core/gimperror.h" + +#include "display/gimpmodifiersmanager.h" + +#include "widgets/gimpwidgets-utils.h" + +#include "dialogs/dialogs.h" + +#include "modifiers.h" +#include "gimp-log.h" + +#include "gimp-intl.h" + + +enum +{ + MODIFIERS_INFO = 1, + HIDE_DOCKS, + SINGLE_WINDOW_MODE, + SHOW_TABS, + TABS_POSITION, + LAST_TIP_SHOWN +}; + + +static GFile * modifiers_file (Gimp *gimp); + + +/* private variables */ + +static gboolean modifiersrc_deleted = FALSE; + + +/* public functions */ + +void +modifiers_init (Gimp *gimp) +{ + GimpDisplayConfig *display_config; + GFile *file; + GimpModifiersManager *manager = NULL; + GError *error = NULL; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + display_config = GIMP_DISPLAY_CONFIG (gimp->config); + if (display_config->modifiers_manager != NULL) + return; + + manager = gimp_modifiers_manager_new (); + g_object_set (display_config, "modifiers-manager", manager, NULL); + g_object_unref (manager); + + file = modifiers_file (gimp); + + if (gimp->be_verbose) + g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file)); + + gimp_config_deserialize_file (GIMP_CONFIG (manager), file, NULL, &error); + + if (error) + { + /* File not existing is considered a normal event, not an error. + * It can happen for instance the first time you run GIMP. When + * this happens, we ignore the error. The GimpModifiersManager + * object will simply use default modifiers. + */ + if (error->domain != GIMP_CONFIG_ERROR || + error->code != GIMP_CONFIG_ERROR_OPEN_ENOENT) + { + gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message); + gimp_config_file_backup_on_error (file, "modifiersrc", NULL); + } + + g_clear_error (&error); + } + + g_object_unref (file); +} + +void +modifiers_exit (Gimp *gimp) +{ + g_return_if_fail (GIMP_IS_GIMP (gimp)); +} + +void +modifiers_restore (Gimp *gimp) +{ + g_return_if_fail (GIMP_IS_GIMP (gimp)); +} + +void +modifiers_save (Gimp *gimp, + gboolean always_save) +{ + GimpDisplayConfig *display_config; + GFile *file; + GimpModifiersManager *manager = NULL; + GError *error = NULL; + + g_return_if_fail (GIMP_IS_GIMP (gimp)); + + if (modifiersrc_deleted && ! always_save) + return; + + display_config = GIMP_DISPLAY_CONFIG (gimp->config); + g_return_if_fail (GIMP_IS_DISPLAY_CONFIG (display_config)); + + manager = GIMP_MODIFIERS_MANAGER (display_config->modifiers_manager); + g_return_if_fail (manager != NULL); + g_return_if_fail (GIMP_IS_MODIFIERS_MANAGER (manager)); + file = modifiers_file (gimp); + + if (gimp->be_verbose) + g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file)); + + gimp_config_serialize_to_file (GIMP_CONFIG (manager), file, + "GIMP modifiersrc\n\n" + "This file stores modifiers configuration. " + "You are not supposed to edit it manually, " + "but of course you can do. The modifiersrc " + "will be entirely rewritten every time you " + "quit GIMP. If this file isn't found, " + "defaults are used.", + NULL, NULL, &error); + if (error != NULL) + { + gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message); + g_clear_error (&error); + } + + g_object_unref (file); + + modifiersrc_deleted = FALSE; +} + +gboolean +modifiers_clear (Gimp *gimp, + GError **error) +{ + GFile *file; + GError *my_error = NULL; + gboolean success = TRUE; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + file = modifiers_file (gimp); + + if (! g_file_delete (file, NULL, &my_error) && + my_error->code != G_IO_ERROR_NOT_FOUND) + { + success = FALSE; + + g_set_error (error, GIMP_ERROR, GIMP_FAILED, + _("Deleting \"%s\" failed: %s"), + gimp_file_get_utf8_name (file), my_error->message); + } + else + { + modifiersrc_deleted = TRUE; + } + + g_clear_error (&my_error); + g_object_unref (file); + + return success; +} + +static GFile * +modifiers_file (Gimp *gimp) +{ + const gchar *basename; + GFile *file; + + basename = g_getenv ("GIMP_TESTING_MODIFIERSRC_NAME"); + if (! basename) + basename = "modifiersrc"; + + file = gimp_directory_file (basename, NULL); + + return file; +} diff --git a/app/gui/modifiers.h b/app/gui/modifiers.h new file mode 100644 index 0000000000..72a1d9c2e0 --- /dev/null +++ b/app/gui/modifiers.h @@ -0,0 +1,36 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * modifiers.h + * 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 . + */ + +#ifndef __MODIFIERS_H__ +#define __MODIFIERS_H__ + + +void modifiers_init (Gimp *gimp); +void modifiers_exit (Gimp *gimp); + +void modifiers_restore (Gimp *gimp); +void modifiers_save (Gimp *gimp, + gboolean always_save); + +gboolean modifiers_clear (Gimp *gimp, + GError **error); + + +#endif /* __MODIFIERS_H__ */