diff options
| author | Ted Gould <ted@gould.cx> | 2012-11-25 19:41:24 +0000 |
|---|---|---|
| committer | Ted Gould <ted@gould.cx> | 2012-11-25 19:41:24 +0000 |
| commit | 18be0e5e3ab74823043e19dd6ea46c4b6b130e86 (patch) | |
| tree | a62925ec4473c1a21e1c99d1415f4cccab59b432 /src/shortcuts.cpp | |
| parent | Getting all the filter headers (diff) | |
| parent | Fix for 1036059 : Keyboard shortcut editor (diff) | |
| download | inkscape-18be0e5e3ab74823043e19dd6ea46c4b6b130e86.tar.gz inkscape-18be0e5e3ab74823043e19dd6ea46c4b6b130e86.zip | |
Update to current trunk
(bzr r11804.1.8)
Diffstat (limited to 'src/shortcuts.cpp')
| -rw-r--r-- | src/shortcuts.cpp | 524 |
1 files changed, 510 insertions, 14 deletions
diff --git a/src/shortcuts.cpp b/src/shortcuts.cpp index 796962b11..9ececbb3b 100644 --- a/src/shortcuts.cpp +++ b/src/shortcuts.cpp @@ -28,6 +28,7 @@ #include <gdk/gdk.h> #include <gdk/gdkkeysyms.h> #include <gtk/gtk.h> +#include <glibmm/i18n.h> #include "helper/action.h" #include "io/sys.h" @@ -36,12 +37,24 @@ #include "verbs.h" #include "xml/node-iterators.h" #include "xml/repr.h" +#include "document.h" +#include "preferences.h" +#include "event-context.h" +#include "inkscape.h" +#include "desktop.h" +#include "path-prefix.h" +#include "ui/dialog/filedialog.h" using namespace Inkscape; -static void sp_shortcut_set(unsigned int const shortcut, Inkscape::Verb *const verb, bool const is_primary); +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); +static void read_shortcuts_file(char const *filename, bool const is_user_set=false); /* Returns true if action was performed */ @@ -61,19 +74,22 @@ sp_shortcut_invoke(unsigned int shortcut, Inkscape::UI::View::View *view) static std::map<unsigned int, Inkscape::Verb * > *verbs = NULL; static std::map<Inkscape::Verb *, unsigned int> *primary_shortcuts = NULL; +static std::map<Inkscape::Verb *, unsigned int> *user_shortcuts = NULL; -static void -sp_shortcut_init() +void sp_shortcut_init() { - using Inkscape::IO::Resource::get_path; - using Inkscape::IO::Resource::SYSTEM; - using Inkscape::IO::Resource::USER; - using Inkscape::IO::Resource::KEYS; 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>(); + + 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(get_path(SYSTEM, KEYS, "default.xml")); + read_shortcuts_file(shortcutfile.c_str()); try_shortcuts_file(get_path(USER, KEYS, "default.xml")); } @@ -82,11 +98,451 @@ static void try_shortcuts_file(char const *filename) { /* ah, if only we had an exception to catch... (permission, forgiveness) */ if (file_test(filename, G_FILE_TEST_EXISTS)) { - read_shortcuts_file(filename); + read_shortcuts_file(filename, true); } } -static void read_shortcuts_file(char const *filename) { +/* + * Inkscape expects to add the Shift modifier to any accel_keys create with Shift + * For exmaple on en_US keyboard <Shift>+6 = "&" - in this case return <Shift>+& + * 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 = 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 = + "<?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), 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<Glib::ustring> *names, std::vector<Glib::ustring> *paths) { + + std::list<gchar *> sources; + sources.push_back( 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, 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 (shortcut & (~SP_SHORTCUT_MODIFIER_MASK)); + 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 (shortcut & (~SP_SHORTCUT_MODIFIER_MASK)); + 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); @@ -153,13 +609,37 @@ static void read_shortcuts_file(char const *filename) { } } - sp_shortcut_set(keyval | modifiers, verb, is_primary); + 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; + } + + } +} +/** * 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.) @@ -169,8 +649,8 @@ static void read_shortcuts_file(char const *filename) { * \post sp_shortcut_get_verb(shortcut) == verb. * \post !is_primary or sp_shortcut_get_primary(verb) == shortcut. */ -static void -sp_shortcut_set(unsigned int const shortcut, Inkscape::Verb *const verb, bool const is_primary) +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(); @@ -183,11 +663,13 @@ sp_shortcut_set(unsigned int const shortcut, Inkscape::Verb *const verb, bool co 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; } } @@ -211,6 +693,20 @@ unsigned int sp_shortcut_get_primary(Inkscape::Verb *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) |
