// SPDX-License-Identifier: GPL-2.0-or-later /** @file * TODO: insert short description here *//* * Authors: * see git history * Lauris Kaplinski * MenTaLguY * bulia byak * Peter Moulder * 2005 Monash University * 2005 MenTaLguY * * 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 #include #include #include #include "shortcuts.h" #include #include #include #include #include #include #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 *verbs = nullptr; static std::map *primary_shortcuts = nullptr; static std::map *user_shortcuts = nullptr; void sp_shortcut_init() { verbs = new std::map(); primary_shortcuts = new std::map(); user_shortcuts = new std::map(); // 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 = " " "" ""; 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 *names, std::vector *paths) { using namespace Inkscape::IO::Resource; std::vector filenames = get_filenames(SYSTEM, KEYS, {".xml"}); std::vector 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> 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 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 pair1, std::pair 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& 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& pair) { return pair.first; }); std::transform(names_and_paths.begin(),names_and_paths.end(), std::back_inserter(*paths), [](const std::pair& 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( ((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 :