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__ */