summaryrefslogtreecommitdiffstats
path: root/src/shortcuts.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/shortcuts.cpp')
-rw-r--r--src/shortcuts.cpp913
1 files changed, 913 insertions, 0 deletions
diff --git a/src/shortcuts.cpp b/src/shortcuts.cpp
new file mode 100644
index 0000000..ae38e89
--- /dev/null
+++ b/src/shortcuts.cpp
@@ -0,0 +1,913 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * see git history
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * MenTaLguY <mental@rydia.net>
+ * bulia byak <buliabyak@users.sf.net>
+ * Peter Moulder <pmoulder@mail.csse.monash.edu.au>
+ * 2005 Monash University
+ * 2005 MenTaLguY <mental@rydia.net>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/** \file
+ * Keyboard shortcut processing.
+ */
+/*
+ * Authors:
+ *
+ *
+ * You may redistribute and/or modify this file under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <vector>
+#include <cstring>
+#include <string>
+#include <map>
+
+#include "shortcuts.h"
+#include <gdk/gdkkeysyms.h>
+#include <gdkmm/display.h>
+#include <gtk/gtk.h>
+
+#include <glibmm/i18n.h>
+#include <glibmm/convert.h>
+#include <glibmm/miscutils.h>
+
+#include "helper/action.h"
+#include "io/dir-util.h"
+#include "io/sys.h"
+#include "io/resource.h"
+#include "verbs.h"
+#include "xml/node-iterators.h"
+#include "xml/repr.h"
+#include "document.h"
+#include "preferences.h"
+#include "ui/tools/tool-base.h"
+#include "inkscape.h"
+#include "desktop.h"
+#include "path-prefix.h"
+#include "ui/dialog/filedialog.h"
+
+using namespace Inkscape;
+using namespace Inkscape::IO::Resource;
+
+static bool try_shortcuts_file(char const *filename, bool const is_user_set=false);
+static void read_shortcuts_file(char const *filename, bool const is_user_set=false);
+
+unsigned int sp_shortcut_get_key(unsigned int const shortcut);
+GdkModifierType sp_shortcut_get_modifiers(unsigned int const shortcut);
+
+/* Returns true if action was performed */
+
+bool
+sp_shortcut_invoke(unsigned int shortcut, Inkscape::UI::View::View *view)
+{
+ Inkscape::Verb *verb = sp_shortcut_get_verb(shortcut);
+ if (verb) {
+ SPAction *action = verb->get_action(Inkscape::ActionContext(view));
+ if (action) {
+ sp_action_perform(action, nullptr);
+ return true;
+ }
+ }
+ return false;
+}
+
+static std::map<unsigned int, Inkscape::Verb * > *verbs = nullptr;
+static std::map<Inkscape::Verb *, unsigned int> *primary_shortcuts = nullptr;
+static std::map<Inkscape::Verb *, unsigned int> *user_shortcuts = nullptr;
+
+void sp_shortcut_init()
+{
+ verbs = new std::map<unsigned int, Inkscape::Verb * >();
+ primary_shortcuts = new std::map<Inkscape::Verb *, unsigned int>();
+ user_shortcuts = new std::map<Inkscape::Verb *, unsigned int>();
+
+ // try to load shortcut file as set in preferences
+ // if preference is unset or loading fails fallback to share/keys/default.xml and finally share/keys/inkscape.xml
+ // make paths relative to share/keys/ if possible (handle parallel installations of Inkscape gracefully)
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ std::string shortcutfile = prefs->getString("/options/kbshortcuts/shortcutfile");
+ bool success = false;
+ gchar const *reason;
+ if (shortcutfile.empty()) {
+ reason = "No key file set in preferences";
+ } else {
+ reason = "Unable to read key file set in preferences";
+
+ bool absolute = Glib::path_is_absolute(shortcutfile);
+ if (absolute) {
+ success = try_shortcuts_file(shortcutfile.c_str());
+ } else {
+ success = try_shortcuts_file(get_path(SYSTEM, KEYS, shortcutfile.c_str()));
+ }
+
+ // store shortcutfile location relative to INKSCAPE_DATADIR
+ if (absolute && success) {
+ shortcutfile = sp_relative_path_from_path(shortcutfile, std::string(get_path(SYSTEM, KEYS)));
+ prefs->setString("/options/kbshortcuts/shortcutfile", shortcutfile.c_str());
+ }
+ }
+#ifdef WITH_CARBON_INTEGRATION
+ if (!success) {
+ g_info("%s. Falling back to 'carbon.xml' for MacOSX keyboards.", reason);
+ success = try_shortcuts_file(get_path(SYSTEM, KEYS, "carbon.xml"));
+ }
+#endif
+ if (!success) {
+ g_info("%s. Falling back to 'default.xml'.", reason);
+ success = try_shortcuts_file(get_path(SYSTEM, KEYS, "default.xml"));
+ }
+ if (!success) {
+ g_info("Could not load 'default.xml' either. Falling back to 'inkscape.xml'.");
+ success = try_shortcuts_file(get_path(SYSTEM, KEYS, "inkscape.xml"));
+ }
+ if (!success) {
+ g_warning("Could not load any keyboard shortcut file (including fallbacks to 'default.xml' and 'inkscape.xml').");
+ }
+
+ // load shortcuts adjusted by user
+ try_shortcuts_file(get_path(USER, KEYS, "default.xml"), true);
+}
+
+static bool try_shortcuts_file(char const *filename, bool const is_user_set) {
+ using Inkscape::IO::file_test;
+
+ /* ah, if only we had an exception to catch... (permission, forgiveness) */
+ if (file_test(filename, G_FILE_TEST_EXISTS)) {
+ read_shortcuts_file(filename, is_user_set);
+ return true;
+ }
+
+ g_info("Unable to read keyboard shortcuts from %s (does not exist)", filename);
+ return false;
+}
+
+/*
+ * Return the keyval corresponding to the key event in group 0 and the effective modifiers.
+ *
+ * Usage of group 0 (i.e. the main, typically English layout) instead of simply event->keyval
+ * ensures that shortcuts work regardless of the active keyboard layouts (e.g. Cyrillic).
+ *
+ * The effective modifiers are the modifiers that were not "consumed" by the translation and
+ * can be used by the application to define a shortcut, e.g.
+ * - when pressing "Shift+9" the resulting character is "("
+ * the shift key was "consumed" to make this character and should not be part of the shortcut
+ * - when pressing "Ctrl+9" the resulting character is also "9"
+ * the ctrl key was *not* consumed to make this character and must be included in the shortcut
+ * - Exception: letter keys like [A-Z] always need the shift modifier,
+ * otherwise lower case and uper case keys are treated as equivalent
+ * The modifier values are already transformed from the default GDK_*_MASK into the equivalent high-bit masks
+ * defined by SP_SHORTCUT_*_MASK to allow for subsequent packing of the whole shortcut into a single int
+ *
+ * Note: Don't call this function directly but use the wrappers
+ * - sp_shortcut_get_from_event() - create a new shortcut from a key event
+ * - sp_shortcut_get_for_event() - get an existing shortcut for a key event
+ * (they correctly handle the packing of modifier keys into the keyval)
+ */
+guint sp_shortcut_translate_event(GdkEventKey const *event, guint *effective_modifiers) {
+ guint keyval = 0;
+
+ guint initial_modifiers = event->state;
+ guint consumed_modifiers = 0;
+ guint remaining_modifiers = 0;
+ guint resulting_modifiers = 0; // remaining modifiers encoded in high-bit mask
+
+ keyval = Inkscape::UI::Tools::get_latin_keyval(event, &consumed_modifiers);
+
+ // Observe case convertible key values always as lower case and don't consume the "Shift"
+ // modifier for them
+ bool is_case_convertible = !(gdk_keyval_is_upper(keyval) && gdk_keyval_is_lower(keyval));
+ if (is_case_convertible) {
+ keyval = gdk_keyval_to_lower(keyval);
+ consumed_modifiers &= ~GDK_SHIFT_MASK;
+ }
+
+ remaining_modifiers = initial_modifiers & ~consumed_modifiers;
+ resulting_modifiers = ( remaining_modifiers & GDK_SHIFT_MASK ? SP_SHORTCUT_SHIFT_MASK : 0 ) |
+ ( remaining_modifiers & GDK_CONTROL_MASK ? SP_SHORTCUT_CONTROL_MASK : 0 ) |
+ ( remaining_modifiers & GDK_SUPER_MASK ? SP_SHORTCUT_SUPER_MASK : 0 ) |
+ ( remaining_modifiers & GDK_HYPER_MASK ? SP_SHORTCUT_HYPER_MASK : 0 ) |
+ ( remaining_modifiers & GDK_META_MASK ? SP_SHORTCUT_META_MASK : 0 ) |
+ ( remaining_modifiers & GDK_MOD1_MASK ? SP_SHORTCUT_ALT_MASK : 0 );
+
+ *effective_modifiers = resulting_modifiers;
+ return keyval;
+}
+
+/*
+ * Returns a new Inkscape shortcut parsed from a key event.
+ */
+unsigned int sp_shortcut_get_from_event(GdkEventKey const *event) {
+ guint effective_modifiers;
+
+ sp_shortcut_translate_event(event, &effective_modifiers);
+
+ // return the actual keyval and the corresponding modifiers for creating the shortcut
+ // we must not return the translated keyval, otherwise we end up with illegal shortcuts like "Shift+9" instead of "("
+ return (event->keyval) | effective_modifiers;
+}
+
+/*
+ * Returns a new Inkscape shortcut parsed from a key event.
+ * (equivalent to sp_shortcut_get_from_event() but accepts the arguments of Gtk::CellRendererAccel::signal_accel_edited)
+ */
+unsigned int sp_shortcut_get_from_gdk_event(guint accel_key, Gdk::ModifierType accel_mods, guint hardware_keycode) {
+ GdkEventKey event;
+ event.keyval = accel_key;
+ event.state = accel_mods;
+ event.hardware_keycode = hardware_keycode;
+
+ return sp_shortcut_get_from_event(&event);
+}
+
+/*
+ * Returns the Inkscape-internal integral shortcut representation for a key event.
+ * Use this to compare the received key event to known shortcuts.
+ */
+unsigned int sp_shortcut_get_for_event(GdkEventKey const *event) {
+ guint keyval;
+ guint effective_modifiers;
+
+ keyval = sp_shortcut_translate_event(event, &effective_modifiers);
+
+ // return the keyval translated to group 0 (English keyboard layout) and corresponding modifiers
+ return keyval | effective_modifiers;
+}
+
+Glib::ustring sp_shortcut_to_label(unsigned int const shortcut) {
+
+ Glib::ustring modifiers = "";
+
+ if (shortcut & SP_SHORTCUT_CONTROL_MASK)
+ modifiers += "Ctrl,";
+ if (shortcut & SP_SHORTCUT_SHIFT_MASK)
+ modifiers += "Shift,";
+ if (shortcut & SP_SHORTCUT_ALT_MASK)
+ modifiers += "Alt,";
+ if (shortcut & SP_SHORTCUT_SUPER_MASK)
+ modifiers += "Super,";
+ if (shortcut & SP_SHORTCUT_HYPER_MASK)
+ modifiers += "Hyper,";
+ if (shortcut & SP_SHORTCUT_META_MASK)
+ modifiers += "Meta,";
+
+ if(modifiers.length() > 0 &&
+ modifiers.find(',',modifiers.length()-1)!=modifiers.npos) {
+ modifiers.erase(modifiers.length()-1, 1);
+ }
+
+ return modifiers;
+}
+
+/*
+ * REmove all shortucts from the users file
+ */
+
+void sp_shortcuts_delete_all_from_file() {
+
+
+ char const *filename = get_path(USER, KEYS, "default.xml");
+
+ XML::Document *doc=sp_repr_read_file(filename, nullptr);
+ if (!doc) {
+ g_warning("Unable to read keys file %s", filename);
+ return;
+ }
+
+ XML::Node *root=doc->root();
+ g_return_if_fail(!strcmp(root->name(), "keys"));
+
+ XML::Node *iter=root->firstChild();
+ while (iter) {
+
+ if (strcmp(iter->name(), "bind")) {
+ // some unknown element, do not complain
+ iter = iter->next();
+ continue;
+ }
+
+ // Delete node
+ sp_repr_unparent(iter);
+ iter=root->firstChild();
+ }
+
+
+ sp_repr_save_file(doc, filename, nullptr);
+
+ GC::release(doc);
+}
+
+Inkscape::XML::Document *sp_shortcut_create_template_file(char const *filename) {
+
+ gchar const *buffer =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?> "
+ "<keys name=\"My custom shortcuts\">"
+ "</keys>";
+
+ Inkscape::XML::Document *doc = sp_repr_read_mem(buffer, strlen(buffer), nullptr);
+ sp_repr_save_file(doc, filename, nullptr);
+
+ return sp_repr_read_file(filename, nullptr);
+}
+
+/*
+ * Get a list of keyboard shortcut files names and paths from the system and users paths
+ * Don't add the users custom keyboards file
+ */
+void sp_shortcut_get_file_names(std::vector<Glib::ustring> *names, std::vector<Glib::ustring> *paths) {
+ using namespace Inkscape::IO::Resource;
+
+ std::vector<Glib::ustring> filenames = get_filenames(SYSTEM, KEYS, {".xml"});
+ std::vector<Glib::ustring> filenames_user = get_filenames(USER, KEYS, {".xml"}, {"default.xml"}); // exclude default.xml as it only includes the user's modifications
+ filenames.insert(filenames.end(), filenames_user.begin(), filenames_user.end());
+
+ std::vector<std::pair<Glib::ustring, Glib::ustring>> names_and_paths;
+ for(auto &filename: filenames) {
+ Glib::ustring label = Glib::path_get_basename(filename);
+ Glib::ustring filename_relative = sp_relative_path_from_path(filename, std::string(get_path(SYSTEM, KEYS)));
+
+ XML::Document *doc = sp_repr_read_file(filename.c_str(), nullptr);
+ if (!doc) {
+ g_warning("Unable to read keyboard shortcut file %s", filename.c_str());
+ continue;
+ }
+
+ // Get the "key name" from the root element of each file
+ XML::Node *root=doc->root();
+ if (!strcmp(root->name(), "keys")) {
+ gchar const *name=root->attribute("name");
+ if (name) {
+ label = Glib::ustring(name) + " (" + label + ")";
+ }
+ std::pair<Glib::ustring, Glib::ustring> name_and_path;
+ name_and_path = std::make_pair(label, filename_relative);
+ names_and_paths.push_back(name_and_path);
+ } else {
+ g_warning("Not a shortcut keys file %s", filename.c_str());
+ }
+ Inkscape::GC::release(doc);
+ }
+
+ // sort by name
+ std::sort(names_and_paths.begin(), names_and_paths.end(),
+ [](std::pair<Glib::ustring, Glib::ustring> pair1, std::pair<Glib::ustring, Glib::ustring> pair2) {
+ return Glib::path_get_basename(pair1.first).compare(Glib::path_get_basename(pair2.first)) < 0;
+ });
+ auto it_default = std::find_if(names_and_paths.begin(), names_and_paths.end(),
+ [](std::pair<Glib::ustring, Glib::ustring>& pair) {
+ return !Glib::path_get_basename(pair.second).compare("default.xml");
+ });
+ if (it_default != names_and_paths.end()) {
+ std::rotate(names_and_paths.begin(), it_default, it_default+1);
+ }
+
+ // transform pairs to output vectors
+ std::transform(names_and_paths.begin(),names_and_paths.end(), std::back_inserter(*names),
+ [](const std::pair<Glib::ustring, Glib::ustring>& pair) { return pair.first; });
+ std::transform(names_and_paths.begin(),names_and_paths.end(), std::back_inserter(*paths),
+ [](const std::pair<Glib::ustring, Glib::ustring>& pair) { return pair.second; });
+}
+
+Glib::ustring sp_shortcut_get_file_path()
+{
+ //# Get the current directory for finding files
+ Glib::ustring open_path;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ Glib::ustring attr = prefs->getString("/dialogs/save_export/path");
+ if (!attr.empty()) open_path = attr;
+
+ //# Test if the open_path directory exists
+ if (!Inkscape::IO::file_test(open_path.c_str(),
+ (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
+ open_path = "";
+
+ if (open_path.empty()) {
+ /* Grab document directory */
+ const gchar* docURI = SP_ACTIVE_DOCUMENT->getDocumentURI();
+ if (docURI) {
+ open_path = Glib::path_get_dirname(docURI);
+ open_path.append(G_DIR_SEPARATOR_S);
+ }
+ }
+
+ //# If no open path, default to our home directory
+ if (open_path.empty())
+ {
+ open_path = g_get_home_dir();
+ open_path.append(G_DIR_SEPARATOR_S);
+ }
+
+ return open_path;
+}
+
+//static Inkscape::UI::Dialog::FileSaveDialog * saveDialog = NULL;
+
+void sp_shortcut_file_export()
+{
+ Glib::ustring open_path = sp_shortcut_get_file_path();
+ open_path.append("shortcut_keys.xml");
+
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ Glib::ustring filename;
+
+ Inkscape::UI::Dialog::FileSaveDialog *saveDialog =
+ Inkscape::UI::Dialog::FileSaveDialog::create(
+ *(desktop->getToplevel()),
+ open_path,
+ Inkscape::UI::Dialog::CUSTOM_TYPE,
+ _("Select a filename for exporting"),
+ "",
+ "",
+ Inkscape::Extension::FILE_SAVE_METHOD_SAVE_AS
+ );
+ saveDialog->addFileType(_("Inkscape shortcuts (*.xml)"), ".xml");
+
+
+ bool success = saveDialog->show();
+ if (!success) {
+ delete saveDialog;
+ return;
+ }
+
+ Glib::ustring fileName = saveDialog->getFilename();
+ if (fileName.size() > 0) {
+ Glib::ustring newFileName = Glib::filename_to_utf8(fileName);
+ sp_shortcut_file_export_do(newFileName.c_str());
+ }
+
+ delete saveDialog;
+}
+
+bool sp_shortcut_file_import() {
+
+ Glib::ustring open_path = sp_shortcut_get_file_path();
+
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+
+ Inkscape::UI::Dialog::FileOpenDialog *importFileDialog =
+ Inkscape::UI::Dialog::FileOpenDialog::create(
+ *desktop->getToplevel(),
+ open_path,
+ Inkscape::UI::Dialog::CUSTOM_TYPE,
+ _("Select a file to import"));
+ importFileDialog->addFilterMenu(_("Inkscape shortcuts (*.xml)"), "*.xml");
+
+ //# Show the dialog
+ bool const success = importFileDialog->show();
+
+ if (!success) {
+ delete importFileDialog;
+ return false;
+ }
+
+ Glib::ustring fileName = importFileDialog->getFilename();
+ sp_shortcut_file_import_do(fileName.c_str());
+
+ delete importFileDialog;
+
+ return true;
+}
+
+void sp_shortcut_file_import_do(char const *importname) {
+
+ XML::Document *doc=sp_repr_read_file(importname, nullptr);
+ if (!doc) {
+ g_warning("Unable to read keyboard shortcut file %s", importname);
+ return;
+ }
+
+ char const *filename = get_path(USER, KEYS, "default.xml");
+ sp_repr_save_file(doc, filename, nullptr);
+
+ GC::release(doc);
+
+ sp_shortcut_init();
+}
+
+void sp_shortcut_file_export_do(char const *exportname) {
+
+ char const *filename = get_path(USER, KEYS, "default.xml");
+
+ XML::Document *doc=sp_repr_read_file(filename, nullptr);
+ if (!doc) {
+ g_warning("Unable to read keyboard shortcut file %s", filename);
+ return;
+ }
+
+ sp_repr_save_file(doc, exportname, nullptr);
+
+ GC::release(doc);
+}
+/*
+ * Add or delete a shortcut to the users default.xml keys file
+ * @param addremove - when true add/override a shortcut, when false remove shortcut
+ * @param addshift - when true addthe Shifg modifier to the non-display element
+ *
+ * Shortcut file consists of pairs of bind elements :
+ * Element (a) is used for shortcut display in menus (display="True") and contains the gdk_keyval_name of the shortcut key
+ * Element (b) is used in shortcut lookup and contains an uppercase version of the gdk_keyval_name,
+ * or a gdk_keyval_name name and the "Shift" modifier for Shift altered hardware code keys (see get_latin_keyval() for explanation)
+ */
+void sp_shortcut_delete_from_file(char const * /*action*/, unsigned int const shortcut) {
+
+ char const *filename = get_path(USER, KEYS, "default.xml");
+
+ XML::Document *doc=sp_repr_read_file(filename, nullptr);
+ if (!doc) {
+ g_warning("Unable to read keyboard shortcut file %s", filename);
+ return;
+ }
+
+ gchar *key = gdk_keyval_name (sp_shortcut_get_key(shortcut));
+ std::string modifiers = sp_shortcut_to_label(shortcut & (SP_SHORTCUT_MODIFIER_MASK));
+
+ if (!key) {
+ g_warning("Unknown key for shortcut %u", shortcut);
+ return;
+ }
+
+ //g_message("Removing key %s, mods %s action %s", key, modifiers.c_str(), action);
+
+ XML::Node *root=doc->root();
+ g_return_if_fail(!strcmp(root->name(), "keys"));
+ XML::Node *iter=root->firstChild();
+ while (iter) {
+
+ if (strcmp(iter->name(), "bind")) {
+ // some unknown element, do not complain
+ iter = iter->next();
+ continue;
+ }
+
+ gchar const *verb_name=iter->attribute("action");
+ if (!verb_name) {
+ iter = iter->next();
+ continue;
+ }
+
+ gchar const *keyval_name = iter->attribute("key");
+ if (!keyval_name || !*keyval_name) {
+ // that's ok, it's just listed for reference without assignment, skip it
+ iter = iter->next();
+ continue;
+ }
+
+ if (Glib::ustring(key).lowercase() != Glib::ustring(keyval_name).lowercase()) {
+ // If deleting, then delete both the upper and lower case versions
+ iter = iter->next();
+ continue;
+ }
+
+ gchar const *modifiers_string = iter->attribute("modifiers");
+ if ((modifiers_string && !strcmp(modifiers.c_str(), modifiers_string)) ||
+ (!modifiers_string && modifiers.empty())) {
+ //Looks like a match
+ // Delete node
+ sp_repr_unparent(iter);
+ iter = root->firstChild();
+ continue;
+ }
+ iter = iter->next();
+ }
+
+ sp_repr_save_file(doc, filename, nullptr);
+
+ GC::release(doc);
+
+}
+
+void sp_shortcut_add_to_file(char const *action, unsigned int const shortcut) {
+
+ char const *filename = get_path(USER, KEYS, "default.xml");
+
+ XML::Document *doc=sp_repr_read_file(filename, nullptr);
+ if (!doc) {
+ g_warning("Unable to read keyboard shortcut file %s, creating ....", filename);
+ doc = sp_shortcut_create_template_file(filename);
+ if (!doc) {
+ g_warning("Unable to create keyboard shortcut file %s", filename);
+ return;
+ }
+ }
+
+ gchar *key = gdk_keyval_name (sp_shortcut_get_key(shortcut));
+ std::string modifiers = sp_shortcut_to_label(shortcut & (SP_SHORTCUT_MODIFIER_MASK));
+
+ if (!key) {
+ g_warning("Unknown key for shortcut %u", shortcut);
+ return;
+ }
+
+ //g_message("Adding key %s, mods %s action %s", key, modifiers.c_str(), action);
+
+ // Add node
+ Inkscape::XML::Node *newnode;
+ newnode = doc->createElement("bind");
+ newnode->setAttribute("key", key);
+ newnode->setAttributeOrRemoveIfEmpty("modifiers", modifiers);
+ newnode->setAttribute("action", action);
+ newnode->setAttribute("display", "true");
+
+ doc->root()->appendChild(newnode);
+
+ if (strlen(key) == 1) {
+ // Add another uppercase version if a character
+ Inkscape::XML::Node *newnode;
+ newnode = doc->createElement("bind");
+ newnode->setAttribute("key", Glib::ustring(key).uppercase());
+ newnode->setAttributeOrRemoveIfEmpty("modifiers", modifiers);
+ newnode->setAttribute("action", action);
+ doc->root()->appendChild(newnode);
+ }
+
+ sp_repr_save_file(doc, filename, nullptr);
+
+ GC::release(doc);
+
+}
+static void read_shortcuts_file(char const *filename, bool const is_user_set) {
+ XML::Document *doc=sp_repr_read_file(filename, nullptr);
+ if (!doc) {
+ g_warning("Unable to read keys file %s", filename);
+ return;
+ }
+
+ XML::Node const *root=doc->root();
+ g_return_if_fail(!strcmp(root->name(), "keys"));
+ XML::NodeConstSiblingIterator iter=root->firstChild();
+ for ( ; iter ; ++iter ) {
+ bool is_primary;
+
+ if (!strcmp(iter->name(), "bind")) {
+ is_primary = iter->attribute("display") && strcmp(iter->attribute("display"), "false") && strcmp(iter->attribute("display"), "0");
+ } else {
+ // some unknown element, do not complain
+ continue;
+ }
+
+ gchar const *verb_name=iter->attribute("action");
+ if (!verb_name) {
+ g_warning("Missing verb name (action= attribute) for shortcut");
+ continue;
+ }
+
+ Inkscape::Verb *verb=Inkscape::Verb::getbyid(verb_name);
+ if (!verb
+#ifndef HAVE_ASPELL
+ && strcmp(verb_name, "DialogSpellcheck") != 0
+#endif
+ ) {
+ g_warning("Unknown verb name: %s", verb_name);
+ continue;
+ }
+
+ gchar const *keyval_name=iter->attribute("key");
+ if (!keyval_name || !*keyval_name) {
+ // that's ok, it's just listed for reference without assignment, skip it
+ continue;
+ }
+
+ guint keyval=gdk_keyval_from_name(keyval_name);
+ if (keyval == GDK_KEY_VoidSymbol || keyval == 0) {
+ g_warning("Unknown keyval %s for %s", keyval_name, verb_name);
+ continue;
+ }
+
+ guint modifiers=0;
+
+ gchar const *modifiers_string=iter->attribute("modifiers");
+ if (modifiers_string) {
+ gchar const *iter=modifiers_string;
+ while (*iter) {
+ size_t length=strcspn(iter, ",");
+ gchar *mod=g_strndup(iter, length);
+ if (!strcmp(mod, "Control") || !strcmp(mod, "Ctrl")) {
+ modifiers |= SP_SHORTCUT_CONTROL_MASK;
+ } else if (!strcmp(mod, "Shift")) {
+ modifiers |= SP_SHORTCUT_SHIFT_MASK;
+ } else if (!strcmp(mod, "Alt")) {
+ modifiers |= SP_SHORTCUT_ALT_MASK;
+ } else if (!strcmp(mod, "Super")) {
+ modifiers |= SP_SHORTCUT_SUPER_MASK;
+ } else if (!strcmp(mod, "Hyper") || !strcmp(mod, "Cmd")) {
+ modifiers |= SP_SHORTCUT_HYPER_MASK;
+ } else if (!strcmp(mod, "Meta")) {
+ modifiers |= SP_SHORTCUT_META_MASK;
+ } else if (!strcmp(mod, "Primary")) {
+ auto display = Gdk::Display::get_default();
+ if (display) {
+ GdkKeymap* keymap = display->get_keymap();
+ GdkModifierType mod =
+ gdk_keymap_get_modifier_mask (keymap, GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR);
+ gdk_keymap_add_virtual_modifiers(keymap, &mod);
+ if (mod & GDK_CONTROL_MASK)
+ modifiers |= SP_SHORTCUT_CONTROL_MASK;
+ else if (mod & GDK_META_MASK)
+ modifiers |= SP_SHORTCUT_META_MASK;
+ else {
+ g_warning("unsupported primary accelerator ");
+ modifiers |= SP_SHORTCUT_CONTROL_MASK;
+ }
+ } else {
+ modifiers |= SP_SHORTCUT_CONTROL_MASK;
+ }
+ } else {
+ g_warning("Unknown modifier %s for %s", mod, verb_name);
+ }
+ g_free(mod);
+ iter += length;
+ if (*iter) iter++;
+ }
+ }
+
+ sp_shortcut_set(keyval | modifiers, verb, is_primary, is_user_set);
+ }
+
+ GC::release(doc);
+}
+
+/**
+ * Removes a keyboard shortcut for the given verb.
+ * (Removes any existing binding for the given shortcut, including appropriately
+ * adjusting sp_shortcut_get_primary if necessary.)*
+ */
+void
+sp_shortcut_unset(unsigned int const shortcut)
+{
+ if (!verbs) sp_shortcut_init();
+
+ Inkscape::Verb *verb = (*verbs)[shortcut];
+
+ /* Maintain the invariant that sp_shortcut_get_primary(v) returns either 0 or a valid shortcut for v. */
+ if (verb) {
+
+ (*verbs)[shortcut] = nullptr;
+
+ unsigned int const old_primary = (*primary_shortcuts)[verb];
+ if (old_primary == shortcut) {
+ (*primary_shortcuts)[verb] = 0;
+ }
+
+ }
+}
+
+GtkAccelGroup *
+sp_shortcut_get_accel_group()
+{
+ static GtkAccelGroup *accel_group = nullptr;
+
+ if (!accel_group) {
+ accel_group = gtk_accel_group_new ();
+ }
+
+ return accel_group;
+}
+
+/**
+ * Adds a gtk accelerator to a widget
+ * Used to display the keyboard shortcuts in the main menu items
+ */
+void
+sp_shortcut_add_accelerator(GtkWidget *item, unsigned int const shortcut)
+{
+ if (shortcut == GDK_KEY_VoidSymbol) {
+ return;
+ }
+
+ unsigned int accel_key = sp_shortcut_get_key(shortcut);
+ if (accel_key > 0) {
+ gtk_widget_add_accelerator (item,
+ "activate",
+ sp_shortcut_get_accel_group(),
+ accel_key,
+ sp_shortcut_get_modifiers(shortcut),
+ GTK_ACCEL_VISIBLE);
+ }
+}
+
+
+unsigned int
+sp_shortcut_get_key(unsigned int const shortcut)
+{
+ return (shortcut & (~SP_SHORTCUT_MODIFIER_MASK));
+}
+
+GdkModifierType
+sp_shortcut_get_modifiers(unsigned int const shortcut)
+{
+ return static_cast<GdkModifierType>(
+ ((shortcut & SP_SHORTCUT_SHIFT_MASK) ? GDK_SHIFT_MASK : 0) |
+ ((shortcut & SP_SHORTCUT_CONTROL_MASK) ? GDK_CONTROL_MASK : 0) |
+ ((shortcut & SP_SHORTCUT_SUPER_MASK) ? GDK_SUPER_MASK : 0) |
+ ((shortcut & SP_SHORTCUT_HYPER_MASK) ? GDK_HYPER_MASK : 0) |
+ ((shortcut & SP_SHORTCUT_META_MASK) ? GDK_META_MASK : 0) |
+ ((shortcut & SP_SHORTCUT_ALT_MASK) ? GDK_MOD1_MASK : 0)
+ );
+}
+
+/**
+ * Adds a keyboard shortcut for the given verb.
+ * (Removes any existing binding for the given shortcut, including appropriately
+ * adjusting sp_shortcut_get_primary if necessary.)
+ *
+ * \param is_primary True iff this is the shortcut to be written in menu items or buttons.
+ *
+ * \post sp_shortcut_get_verb(shortcut) == verb.
+ * \post !is_primary or sp_shortcut_get_primary(verb) == shortcut.
+ */
+void
+sp_shortcut_set(unsigned int const shortcut, Inkscape::Verb *const verb, bool const is_primary, bool const is_user_set)
+{
+ if (!verbs) sp_shortcut_init();
+
+ Inkscape::Verb *old_verb = (*verbs)[shortcut];
+ (*verbs)[shortcut] = verb;
+
+ /* Maintain the invariant that sp_shortcut_get_primary(v) returns either 0 or a valid shortcut for v. */
+ if (old_verb && old_verb != verb) {
+ unsigned int const old_primary = (*primary_shortcuts)[old_verb];
+
+ if (old_primary == shortcut) {
+ (*primary_shortcuts)[old_verb] = 0;
+ (*user_shortcuts)[old_verb] = 0;
+ }
+ }
+
+ if (is_primary) {
+ (*primary_shortcuts)[verb] = shortcut;
+ (*user_shortcuts)[verb] = is_user_set;
+ }
+}
+
+Inkscape::Verb *
+sp_shortcut_get_verb(unsigned int shortcut)
+{
+ if (!verbs) sp_shortcut_init();
+ return (*verbs)[shortcut];
+}
+
+unsigned int sp_shortcut_get_primary(Inkscape::Verb *verb)
+{
+ unsigned int result = GDK_KEY_VoidSymbol;
+ if (!primary_shortcuts) {
+ sp_shortcut_init();
+ }
+
+ if (primary_shortcuts->count(verb)) {
+ result = (*primary_shortcuts)[verb];
+ }
+ return result;
+}
+
+bool sp_shortcut_is_user_set(Inkscape::Verb *verb)
+{
+ unsigned int result = false;
+ if (!primary_shortcuts) {
+ sp_shortcut_init();
+ }
+
+ if (primary_shortcuts->count(verb)) {
+ result = (*user_shortcuts)[verb];
+ }
+ return result;
+}
+
+gchar *sp_shortcut_get_label(unsigned int shortcut)
+{
+ // The comment below was copied from the function sp_ui_shortcut_string in interface.cpp (which was subsequently removed)
+ /* TODO: This function shouldn't exist. Our callers should use GtkAccelLabel instead of
+ * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
+ * Will probably need to change sp_shortcut_invoke callers.
+ *
+ * The existing gtk_label_new_with_mnemonic call can be replaced with
+ * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
+ * gtk_label_set_text_with_mnemonic(lbl, str).
+ */
+ gchar *result = nullptr;
+ if (shortcut != GDK_KEY_VoidSymbol) {
+ result = gtk_accelerator_get_label(
+ sp_shortcut_get_key(shortcut),
+ sp_shortcut_get_modifiers(shortcut));
+ }
+ return result;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :