[gimp/wip/Jehan/issue-498-quick-brush-edit: 519/523] app: add a GimpModifiersManager object to GimpDisplayConfig.




commit cbd7b6bd951cf5e233853806dc47a709b67b24e6
Author: Jehan <jehan girinstud io>
Date:   Tue Jul 26 19:21:56 2022 +0200

    app: add a GimpModifiersManager object to GimpDisplayConfig.
    
    This object's goal will be to manage customized modifiers per input
    device button, which is why I add it to GimpDisplayConfig. It is in its
    own new config file (`modifiersrc` in config dir) because it requires
    GDK types access (well I could have done without, but it would have been
    less semantic, hence not as good of an API). Anyway it is only useful
    when running GIMP as GUI.
    
    The GUI widget and the usage code to make this actually useful will come
    in upcoming commits.

 app/config/gimpdisplayconfig.c     |  19 ++
 app/config/gimpdisplayconfig.h     |   2 +
 app/display/Makefile.am            |   2 +
 app/display/display-enums.c        |  43 +++
 app/display/display-enums.h        |  19 ++
 app/display/display-types.h        |   2 +
 app/display/gimpmodifiersmanager.c | 585 +++++++++++++++++++++++++++++++++++++
 app/display/gimpmodifiersmanager.h |  80 +++++
 app/display/meson.build            |   1 +
 app/gui/Makefile.am                |   2 +
 app/gui/gui.c                      |   6 +
 app/gui/meson.build                |   1 +
 app/gui/modifiers.c                | 218 ++++++++++++++
 app/gui/modifiers.h                |  36 +++
 14 files changed, 1016 insertions(+)
---
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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#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__  */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]