diff options
Diffstat (limited to '')
-rw-r--r-- | src/shortcuts.cpp | 913 |
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 : |