Gimp/app/menus/shortcuts-rc.c
Jehan a186220e58 app: be more forgiving in reading shortcutsrc.
- First do not write the protocol-version (and therefore don't read it either
  nor compare it to the current one). This file does not depend on the protocol
  version.
- Secondly when reading an unknown identifier, simply print a message on stderr
  for debugging/reference, but ignore it up to the next closing parenthese.
- Finally do not use the file-version as a terminal error as well. When we read
  a shortcutsrc with the wrong version, we still try to read it as best as we
  can. There might be issues, but it's better than fully dropping the whole list
  of shortcuts as it's a pretty important file (people might have spent a lot of
  time tweaking their shortcuts!).
2023-10-02 01:30:02 +02:00

342 lines
10 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* shortcuts-rc.c
* Copyright (C) 2023 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 "menus-types.h"
#include "widgets/gimpaction.h"
#include "widgets/gimpradioaction.h"
#include "shortcuts-rc.h"
#include "gimp-intl.h"
#define SHORTCUTS_RC_FILE_VERSION 1
/*
* All deserialize functions return G_TOKEN_LEFT_PAREN on success,
* or the GTokenType they would have expected but didn't get,
* or G_TOKEN_ERROR if the function already set an error itself.
*/
static GTokenType shortcuts_action_deserialize (GScanner *scanner,
GtkApplication *application);
enum
{
FILE_VERSION = 1,
ACTION,
};
gboolean
shortcuts_rc_parse (GtkApplication *application,
GFile *file,
GError **error)
{
GScanner *scanner;
gint file_version = SHORTCUTS_RC_FILE_VERSION;
GTokenType token;
g_return_val_if_fail (GTK_IS_APPLICATION (application), FALSE);
g_return_val_if_fail (G_IS_FILE (file), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
scanner = gimp_scanner_new_file (file, error);
if (! scanner)
return FALSE;
g_scanner_scope_add_symbol (scanner, 0,
"file-version",
GINT_TO_POINTER (FILE_VERSION));
g_scanner_scope_add_symbol (scanner, 0,
"action", GINT_TO_POINTER (ACTION));
token = G_TOKEN_LEFT_PAREN;
while (g_scanner_peek_next_token (scanner) == token ||
(token == G_TOKEN_SYMBOL &&
g_scanner_peek_next_token (scanner) == G_TOKEN_IDENTIFIER))
{
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 FILE_VERSION:
token = G_TOKEN_INT;
if (gimp_scanner_parse_int (scanner, &file_version))
token = G_TOKEN_RIGHT_PAREN;
break;
case ACTION:
g_scanner_set_scope (scanner, ACTION);
token = shortcuts_action_deserialize (scanner, application);
g_scanner_set_scope (scanner, 0);
break;
default:
break;
}
break;
case G_TOKEN_IDENTIFIER:
g_printerr ("%s: ignoring unknown symbol '%s'.\n", G_STRFUNC, scanner->value.v_string);
while ((token = g_scanner_get_next_token (scanner)) != G_TOKEN_EOF)
{
if (token == G_TOKEN_RIGHT_PAREN)
break;
}
token = G_TOKEN_LEFT_PAREN;
break;
case G_TOKEN_RIGHT_PAREN:
token = G_TOKEN_LEFT_PAREN;
break;
default: /* do nothing */
break;
}
}
if (file_version != SHORTCUTS_RC_FILE_VERSION)
{
g_printerr (_("Wrong shortcutsrc (%s) file format version: %d (expected: %d). "
"We tried to load shortcuts as well as possible.\n"),
gimp_file_get_utf8_name (file),
file_version, SHORTCUTS_RC_FILE_VERSION);
}
if (token != G_TOKEN_LEFT_PAREN)
{
if (token != G_TOKEN_ERROR)
{
g_scanner_get_next_token (scanner);
g_scanner_unexp_token (scanner, token, NULL, NULL, NULL,
_("fatal parse error"), TRUE);
}
return FALSE;
}
gimp_scanner_unref (scanner);
return TRUE;
}
gboolean
shortcuts_rc_write (GtkApplication *application,
GFile *file,
GError **error)
{
GimpConfigWriter *writer;
gchar **actions;
g_return_val_if_fail (GTK_IS_APPLICATION (application), FALSE);
g_return_val_if_fail (G_IS_FILE (file), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
writer = gimp_config_writer_new_from_file (file,
FALSE,
"GIMP shortcutsrc\n\n"
"If you delete this file, all shortcuts "
"will be reset to defaults.",
error);
if (! writer)
return FALSE;
actions = g_action_group_list_actions (G_ACTION_GROUP (application));
gimp_config_writer_open (writer, "file-version");
gimp_config_writer_printf (writer, "%d", SHORTCUTS_RC_FILE_VERSION);
gimp_config_writer_close (writer);
gimp_config_writer_linefeed (writer);
for (gint i = 0; actions[i] != NULL; i++)
{
GimpAction *action;
gchar **accels;
gchar *detailed_name;
gboolean commented = FALSE;
action = (GimpAction *) g_action_map_lookup_action (G_ACTION_MAP (application), actions[i]);
if (GIMP_IS_RADIO_ACTION (action))
{
gint value;
g_object_get ((GObject *) action,
"value",
&value,
NULL);
detailed_name = g_strdup_printf ("app.%s(%i)", actions[i],
value);
}
else
{
detailed_name = g_strdup_printf ("app.%s", actions[i]);
}
accels = gtk_application_get_accels_for_action (application, detailed_name);
if (gimp_action_use_default_accels (action))
commented = TRUE;
gimp_config_writer_comment_mode (writer, commented);
gimp_config_writer_open (writer, "action");
gimp_config_writer_string (writer, actions[i]);
for (gint j = 0; accels[j]; j++)
gimp_config_writer_string (writer, accels[j]);
gimp_config_writer_close (writer);
gimp_config_writer_comment_mode (writer, FALSE);
g_strfreev (accels);
g_free (detailed_name);
}
g_strfreev (actions);
return gimp_config_writer_finish (writer, "end of shortcutsrc", error);
}
/* Private functions */
static GTokenType
shortcuts_action_deserialize (GScanner *scanner,
GtkApplication *application)
{
GStrvBuilder *builder;
gchar *action_name;
gchar *accel;
gchar **accels;
if (! gimp_scanner_parse_string (scanner, &action_name))
return G_TOKEN_STRING;
builder = g_strv_builder_new ();
while (gimp_scanner_parse_string (scanner, &accel))
{
gchar **dup_actions;
gboolean add_accel = TRUE;
guint accelerator_key = 0;
GdkModifierType accelerator_mods = 0;
gtk_accelerator_parse (accel, &accelerator_key, &accelerator_mods);
if (accelerator_key == 0 && accelerator_mods == 0)
{
g_printerr ("INFO: invalid accelerator '%s' on '%s'.\n"
" Removing this accelerator.\n",
accel, action_name);
g_free (accel);
continue;
}
dup_actions = gtk_application_get_actions_for_accel (application, accel);
for (gint i = 0; dup_actions[i] != NULL; i++)
{
GimpAction *conflict_action;
gchar *left_paren_ptr = strchr (dup_actions[i], '(');
if (left_paren_ptr)
*left_paren_ptr = '\0'; /* ignore target part of detailed name */
/* dup_actions[i] will be the detailed name prefixed with "app." */
if (g_strcmp0 (dup_actions[i] + 4, action_name) == 0)
continue;
conflict_action = (GimpAction *) g_action_map_lookup_action (G_ACTION_MAP (application),
dup_actions[i] + 4);
if (gimp_action_use_default_accels (conflict_action))
{
/* We might simply not have scanned this action's accelerators yet
* in the shortcutsrc file. Just delete its current accelerators
* without any message.
*/
gimp_action_set_accels (conflict_action, NULL);
}
else
{
g_printerr ("INFO: duplicate accelerator '%s' on '%s' and '%s'.\n"
" Removing the accelerator from '%s'.\n",
accel, action_name, dup_actions[i], action_name);
add_accel = FALSE;
break;
}
}
g_strfreev (dup_actions);
if (add_accel)
g_strv_builder_add (builder, accel);
g_free (accel);
}
accels = g_strv_builder_end (builder);
if (g_action_group_has_action (G_ACTION_GROUP (application), action_name))
{
GimpAction *action;
gchar *detailed_name;
action = (GimpAction *) g_action_map_lookup_action (G_ACTION_MAP (application),
action_name);
detailed_name = g_strdup_printf ("app.%s", action_name);
gimp_action_set_accels (action, (const gchar **) accels);
g_free (detailed_name);
}
else
{
/* Don't set a breaking error, just output on stderr, so that we can make a
* notice while still loading other actions.
*/
g_printerr ("INFO: not existing action '%s' was ignored from the shortcutsrc file.\n",
action_name);
}
g_strv_builder_unref (builder);
g_free (action_name);
g_strfreev (accels);
if (! gimp_scanner_parse_token (scanner, G_TOKEN_RIGHT_PAREN))
return G_TOKEN_RIGHT_PAREN;
return G_TOKEN_LEFT_PAREN;
}