/** \file * Keyboard shortcut processing. */ /* * Authors: * Lauris Kaplinski * MenTaLguY * bulia byak * Peter Moulder * * Copyright (C) 2005 Monash University * Copyright (C) 2005 MenTaLguY * * 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. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include "shortcuts.h" #include #include #include #include #include #include #include "helper/action.h" #include "helper/action-context.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 Inkscape::IO::Resource::get_path; using Inkscape::IO::Resource::SYSTEM; using Inkscape::IO::Resource::USER; using Inkscape::IO::Resource::KEYS; static void try_shortcuts_file(char const *filename); 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, NULL); return true; } } return false; } static std::map *verbs = NULL; static std::map *primary_shortcuts = NULL; static std::map *user_shortcuts = NULL; void sp_shortcut_init() { verbs = new std::map(); primary_shortcuts = new std::map(); user_shortcuts = new std::map(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Glib::ustring shortcutfile = prefs->getString("/options/kbshortcuts/shortcutfile"); if (shortcutfile.empty()) { shortcutfile = Glib::ustring(get_path(SYSTEM, KEYS, "default.xml")); } read_shortcuts_file(shortcutfile.c_str()); try_shortcuts_file(get_path(USER, KEYS, "default.xml")); } static void try_shortcuts_file(char const *filename) { 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, true); } } /* * Inkscape expects to add the Shift modifier to any accel_keys create with Shift * For exmaple on en_US keyboard +6 = "&" - in this case return +& * See get_group0_keyval() for explanation on why */ unsigned int sp_gdkmodifier_to_shortcut(guint accel_key, Gdk::ModifierType gdkmodifier, guint hardware_keycode) { unsigned int shortcut = 0; GdkEventKey event; event.state = gdkmodifier; event.keyval = accel_key; event.hardware_keycode = hardware_keycode; guint keyval = Inkscape::UI::Tools::get_group0_keyval (&event); shortcut = accel_key | ( (gdkmodifier & GDK_SHIFT_MASK) || ( accel_key != keyval) ? SP_SHORTCUT_SHIFT_MASK : 0 ) | ( gdkmodifier & GDK_CONTROL_MASK ? SP_SHORTCUT_CONTROL_MASK : 0 ) | ( gdkmodifier & GDK_MOD1_MASK ? SP_SHORTCUT_ALT_MASK : 0 ); return shortcut; } 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(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, NULL); 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, NULL); 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), NULL); sp_repr_save_file(doc, filename, NULL); return sp_repr_read_file(filename, NULL); } /* * Get a list of keyboard shortcut files names and paths from the system and users paths * Dont add the users custom keyboards file */ void sp_shortcut_get_file_names(std::vector *names, std::vector *paths) { std::list sources; sources.push_back( Inkscape::Application::profile_path("keys") ); sources.push_back( g_strdup(INKSCAPE_KEYSDIR) ); // loop through possible keyboard shortcut file locations. while (!sources.empty()) { gchar *dirname = sources.front(); if ( Inkscape::IO::file_test( dirname, G_FILE_TEST_EXISTS ) && Inkscape::IO::file_test( dirname, G_FILE_TEST_IS_DIR )) { GError *err = 0; GDir *directory = g_dir_open(dirname, 0, &err); if (!directory) { gchar *safeDir = Inkscape::IO::sanitizeString(dirname); g_warning(_("Keyboard directory (%s) is unavailable."), safeDir); g_free(safeDir); } else { gchar *filename = 0; while ((filename = (gchar *) g_dir_read_name(directory)) != NULL) { gchar* lower = g_ascii_strdown(filename, -1); if (!strcmp(dirname, Inkscape::Application::profile_path("keys")) && !strcmp(lower, "default.xml")) { // Dont add the users custom keys file continue; } if (!strcmp(dirname, INKSCAPE_KEYSDIR) && !strcmp(lower, "inkscape.xml")) { // Dont add system inkscape.xml (since its a duplicate? of dfefault.xml) continue; } if (g_str_has_suffix(lower, ".xml")) { gchar* full = g_build_filename(dirname, filename, NULL); if (!Inkscape::IO::file_test(full, G_FILE_TEST_IS_DIR)) { // Get the "key name" from the root element of each file XML::Document *doc=sp_repr_read_file(full, NULL); if (!doc) { g_warning("Unable to read keyboard shortcut file %s", full); continue; } XML::Node *root=doc->root(); if (strcmp(root->name(), "keys")) { g_warning("Not a shortcut keys file %s", full); Inkscape::GC::release(doc); continue; } gchar const *name=root->attribute("name"); Glib::ustring label(filename); if (name) { label = Glib::ustring(name)+ " (" + filename + ")"; } if (!strcmp(filename, "default.xml")) { paths->insert(paths->begin(), full); names->insert(names->begin(), label.c_str()); } else { paths->push_back(full); names->push_back(label.c_str()); } Inkscape::GC::release(doc); } g_free(full); } g_free(lower); } g_dir_close(directory); } } g_free(dirname); sources.pop_front(); } } 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->getURI(); 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("All Files", "*"); 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("All Files", "*"); //# 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, NULL); 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, NULL); 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, NULL); if (!doc) { g_warning("Unable to read keyboard shortcut file %s", filename); return; } sp_repr_save_file(doc, exportname, NULL); 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_group0_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, NULL); 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, NULL); 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, NULL); 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); if (!modifiers.empty()) { newnode->setAttribute("modifiers", modifiers.c_str()); } 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().c_str()); if (!modifiers.empty()) { newnode->setAttribute("modifiers", modifiers.c_str()); } newnode->setAttribute("action", action); doc->root()->appendChild(newnode); } sp_repr_save_file(doc, filename, NULL); GC::release(doc); } static void read_shortcuts_file(char const *filename, bool const is_user_set) { XML::Document *doc=sp_repr_read_file(filename, NULL); 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) { 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 { 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] = 0; 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 = NULL; 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_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 = 0; 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 :