/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * 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 . */ #include "config.h" #include #include #include "libpikabase/pikabase.h" #include "libpikabase/pikaprotocol.h" #include "libpikaconfig/pikaconfig.h" #include "menus-types.h" #include "widgets/pikaaction.h" #include "widgets/pikaradioaction.h" #include "shortcuts-rc.h" #include "pika-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 { PROTOCOL_VERSION = 1, FILE_VERSION, ACTION, }; gboolean shortcuts_rc_parse (GtkApplication *application, GFile *file, GError **error) { GScanner *scanner; gint protocol_version = PIKA_PROTOCOL_VERSION; 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 = pika_scanner_new_file (file, error); if (! scanner) return FALSE; g_scanner_scope_add_symbol (scanner, 0, "protocol-version", GINT_TO_POINTER (PROTOCOL_VERSION)); 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 (protocol_version == PIKA_PROTOCOL_VERSION && file_version == SHORTCUTS_RC_FILE_VERSION && 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 PROTOCOL_VERSION: token = G_TOKEN_INT; if (pika_scanner_parse_int (scanner, &protocol_version)) token = G_TOKEN_RIGHT_PAREN; break; case FILE_VERSION: token = G_TOKEN_INT; if (pika_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_RIGHT_PAREN: token = G_TOKEN_LEFT_PAREN; break; default: /* do nothing */ break; } } if (protocol_version != PIKA_PROTOCOL_VERSION || file_version != SHORTCUTS_RC_FILE_VERSION || token != G_TOKEN_LEFT_PAREN) { if (protocol_version != PIKA_PROTOCOL_VERSION) { g_set_error (error, PIKA_CONFIG_ERROR, PIKA_CONFIG_ERROR_VERSION, _("Skipping '%s': wrong PIKA protocol version."), pika_file_get_utf8_name (file)); } else if (file_version != SHORTCUTS_RC_FILE_VERSION) { g_set_error (error, PIKA_CONFIG_ERROR, PIKA_CONFIG_ERROR_VERSION, _("Skipping '%s': wrong shortcutsrc file format version."), pika_file_get_utf8_name (file)); } else 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; } pika_scanner_unref (scanner); return TRUE; } gboolean shortcuts_rc_write (GtkApplication *application, GFile *file, GError **error) { PikaConfigWriter *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 = pika_config_writer_new_from_file (file, FALSE, "PIKA 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)); pika_config_writer_open (writer, "protocol-version"); pika_config_writer_printf (writer, "%d", PIKA_PROTOCOL_VERSION); pika_config_writer_close (writer); pika_config_writer_open (writer, "file-version"); pika_config_writer_printf (writer, "%d", SHORTCUTS_RC_FILE_VERSION); pika_config_writer_close (writer); pika_config_writer_linefeed (writer); for (gint i = 0; actions[i] != NULL; i++) { PikaAction *action; gchar **accels; gchar *detailed_name; gboolean commented = FALSE; action = (PikaAction *) g_action_map_lookup_action (G_ACTION_MAP (application), actions[i]); if (PIKA_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 (pika_action_use_default_accels (action)) commented = TRUE; pika_config_writer_comment_mode (writer, commented); pika_config_writer_open (writer, "action"); pika_config_writer_string (writer, actions[i]); for (gint j = 0; accels[j]; j++) pika_config_writer_string (writer, accels[j]); pika_config_writer_close (writer); pika_config_writer_comment_mode (writer, FALSE); g_strfreev (accels); g_free (detailed_name); } g_strfreev (actions); return pika_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 (! pika_scanner_parse_string (scanner, &action_name)) return G_TOKEN_STRING; builder = g_strv_builder_new (); while (pika_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++) { PikaAction *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 = (PikaAction *) g_action_map_lookup_action (G_ACTION_MAP (application), dup_actions[i] + 4); if (pika_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. */ pika_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)) { PikaAction *action; gchar *detailed_name; action = (PikaAction *) g_action_map_lookup_action (G_ACTION_MAP (application), action_name); detailed_name = g_strdup_printf ("app.%s", action_name); pika_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 (! pika_scanner_parse_token (scanner, G_TOKEN_RIGHT_PAREN)) return G_TOKEN_RIGHT_PAREN; return G_TOKEN_LEFT_PAREN; }