gimp/app/menus/shortcuts-rc.c

343 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;
}