Code

c8ff5b3296c9e328efc3193e659138e064355ecf
[inkscape.git] / src / shortcuts.cpp
1 #define __SP_SHORTCUTS_C__
3 /** \file
4  * Keyboard shortcut processing.
5  */
6 /*
7  * Authors:
8  *   Lauris Kaplinski <lauris@kaplinski.com>
9  *   MenTaLguY <mental@rydia.net>
10  *   bulia byak <buliabyak@users.sf.net>
11  *   Peter Moulder <pmoulder@mail.csse.monash.edu.au>
12  *
13  * Copyright (C) 2005  Monash University
14  * Copyright (C) 2005  MenTaLguY <mental@rydia.net>
15  *
16  * You may redistribute and/or modify this file under the terms of the GNU General Public License
17  * as published by the Free Software Foundation; either version 2 of the License, or (at your
18  * option) any later version.
19  */
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
25 #include <vector>
26 #include <cstring>
27 #include <string>
29 #include <gdk/gdkkeys.h>
30 #include <gdk/gdkkeysyms.h>
31 #include <gtk/gtk.h>
33 #include "helper/action.h"
34 #include "io/sys.h"
35 #include "io/resource.h"
36 #include "shortcuts.h"
37 #include "verbs.h"
38 #include "xml/node-iterators.h"
39 #include "xml/repr.h"
41 using namespace Inkscape;
43 static void sp_shortcut_set(unsigned int const shortcut, Inkscape::Verb *const verb, bool const is_primary);
44 static void try_shortcuts_file(char const *filename);
45 static void read_shortcuts_file(char const *filename);
47 /* Returns true if action was performed */
49 bool
50 sp_shortcut_invoke(unsigned int shortcut, Inkscape::UI::View::View *view)
51 {
52     Inkscape::Verb *verb = sp_shortcut_get_verb(shortcut);
53     if (verb) {
54         SPAction *action = verb->get_action(view);
55         if (action) {
56             sp_action_perform(action, NULL);
57             return true;
58         }
59     }
60     return false;
61 }
63 static GHashTable *verbs = NULL;
64 static GHashTable *primary_shortcuts = NULL;
66 static void
67 sp_shortcut_init()
68 {
69     using Inkscape::IO::Resource::get_path;
70     using Inkscape::IO::Resource::SYSTEM;
71     using Inkscape::IO::Resource::USER;
72     using Inkscape::IO::Resource::KEYS;
74     verbs = g_hash_table_new(NULL, NULL);
75     primary_shortcuts = g_hash_table_new(NULL, NULL);
77     read_shortcuts_file(get_path(SYSTEM, KEYS, "default.xml"));
78     try_shortcuts_file(get_path(USER, KEYS, "default.xml"));
79 }
81 static void try_shortcuts_file(char const *filename) {
82     using Inkscape::IO::file_test;
84     /* ah, if only we had an exception to catch... (permission, forgiveness) */
85     if (file_test(filename, G_FILE_TEST_EXISTS)) {
86         read_shortcuts_file(filename);
87     }
88 }
90 static void read_shortcuts_file(char const *filename) {
91     XML::Document *doc=sp_repr_read_file(filename, NULL);
92     if (!doc) {
93         g_warning("Unable to read keys file %s", filename);
94         return;
95     }
97     XML::Node const *root=doc->root();
98     g_return_if_fail(!strcmp(root->name(), "keys"));
99     XML::NodeConstSiblingIterator iter=root->firstChild();
100     for ( ; iter ; ++iter ) {
101         bool is_primary;
103         if (!strcmp(iter->name(), "bind")) {
104             is_primary = iter->attribute("display") && strcmp(iter->attribute("display"), "false") && strcmp(iter->attribute("display"), "0");
105         } else {
106             // some unknown element, do not complain
107             continue;
108         }
110         gchar const *verb_name=iter->attribute("action");
111         if (!verb_name) {
112             g_warning("Missing verb name (action= attribute) for shortcut");
113             continue;
114         }
116         Inkscape::Verb *verb=Inkscape::Verb::getbyid(verb_name);
117         if (!verb) {
118             g_warning("Unknown verb name: %s", verb_name);
119             continue;
120         }
122         gchar const *keyval_name=iter->attribute("key");
123         if (!keyval_name || !*keyval_name) {
124             // that's ok, it's just listed for reference without assignment, skip it
125             continue;
126         }
128         guint keyval=gdk_keyval_from_name(keyval_name);
129         if (keyval == GDK_VoidSymbol || keyval == 0) {
130             g_warning("Unknown keyval %s for %s", keyval_name, verb_name);
131             continue;
132         }
134         guint modifiers=0;
136         gchar const *modifiers_string=iter->attribute("modifiers");
137         if (modifiers_string) {
138             gchar const *iter=modifiers_string;
139             while (*iter) {
140                 size_t length=strcspn(iter, ",");
141                 gchar *mod=g_strndup(iter, length);
142                 if (!strcmp(mod, "Control") || !strcmp(mod, "Ctrl")) {
143                     modifiers |= SP_SHORTCUT_CONTROL_MASK;
144                 } else if (!strcmp(mod, "Shift")) {
145                     modifiers |= SP_SHORTCUT_SHIFT_MASK;
146                 } else if (!strcmp(mod, "Alt")) {
147                     modifiers |= SP_SHORTCUT_ALT_MASK;
148                 } else {
149                     g_warning("Unknown modifier %s for %s", mod, verb_name);
150                 }
151                 g_free(mod);
152                 iter += length;
153                 if (*iter) iter++;
154             }
155         }
157         sp_shortcut_set(keyval | modifiers, verb, is_primary);
158     }
160     GC::release(doc);
163 /**
164  * Adds a keyboard shortcut for the given verb.
165  * (Removes any existing binding for the given shortcut, including appropriately
166  * adjusting sp_shortcut_get_primary if necessary.)
167  *
168  * \param is_primary True iff this is the shortcut to be written in menu items or buttons.
169  *
170  * \post sp_shortcut_get_verb(shortcut) == verb.
171  * \post !is_primary or sp_shortcut_get_primary(verb) == shortcut.
172  */
173 static void
174 sp_shortcut_set(unsigned int const shortcut, Inkscape::Verb *const verb, bool const is_primary)
176     if (!verbs) sp_shortcut_init();
178     Inkscape::Verb *old_verb = (Inkscape::Verb *)(g_hash_table_lookup(verbs, GINT_TO_POINTER(shortcut)));
179     g_hash_table_insert(verbs, GINT_TO_POINTER(shortcut), (gpointer)(verb));
181     /* Maintain the invariant that sp_shortcut_get_primary(v) returns either 0 or a valid shortcut for v. */
182     if (old_verb && old_verb != verb) {
183         unsigned int const old_primary = (unsigned int)GPOINTER_TO_INT(g_hash_table_lookup(primary_shortcuts, (gpointer)old_verb));
185         if (old_primary == shortcut) {
186             g_hash_table_insert(primary_shortcuts, (gpointer)old_verb, GINT_TO_POINTER(0));
187         }
188     }
190     if (is_primary) {
191         g_hash_table_insert(primary_shortcuts, (gpointer)(verb), GINT_TO_POINTER(shortcut));
192     }
195 Inkscape::Verb *
196 sp_shortcut_get_verb(unsigned int shortcut)
198     if (!verbs) sp_shortcut_init();
199     return (Inkscape::Verb *)(g_hash_table_lookup(verbs, GINT_TO_POINTER(shortcut)));
202 unsigned int sp_shortcut_get_primary(Inkscape::Verb *verb)
204     unsigned int result = GDK_VoidSymbol;
205     if (!primary_shortcuts) {
206         sp_shortcut_init();
207     }
208     gpointer value = 0;
209     if (g_hash_table_lookup_extended(primary_shortcuts, static_cast<gpointer>(verb), NULL, &value)) {
210         result = static_cast<unsigned int>(GPOINTER_TO_INT(value));
211     }
212     return result;
215 gchar *sp_shortcut_get_label(unsigned int shortcut)
217     // The comment below was copied from the function sp_ui_shortcut_string in interface.cpp (which was subsequently removed)
218     /* TODO: This function shouldn't exist.  Our callers should use GtkAccelLabel instead of
219      * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
220      * Will probably need to change sp_shortcut_invoke callers.
221      *
222      * The existing gtk_label_new_with_mnemonic call can be replaced with
223      * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
224      * gtk_label_set_text_with_mnemonic(lbl, str).
225      */
226     gchar *result = 0;
227     if (shortcut != GDK_VoidSymbol) {
228         result = gtk_accelerator_get_label(
229             shortcut & (~SP_SHORTCUT_MODIFIER_MASK), static_cast<GdkModifierType>(
230                 ((shortcut & SP_SHORTCUT_SHIFT_MASK) ? GDK_SHIFT_MASK : 0) |
231                 ((shortcut & SP_SHORTCUT_CONTROL_MASK) ? GDK_CONTROL_MASK : 0) |
232                 ((shortcut & SP_SHORTCUT_ALT_MASK) ? GDK_MOD1_MASK : 0)
233                 ));
234     }
235     return result;
238 /*
239   Local Variables:
240   mode:c++
241   c-file-style:"stroustrup"
242   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
243   indent-tabs-mode:nil
244   fill-column:99
245   End:
246 */
247 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :