1/**
2 * Some of these scripts were taken from TinyMCE (http://tinymce.moxiecode.com/) and were modified for DokuWiki
3 *
4 * Class handles accesskeys using javascript and also provides ability
5 * to register and use other hotkeys as well.
6 *
7 * @author Marek Sacha <sachamar@fel.cvut.cz>
8 */
9function Hotkeys() {
10
11    this.shortcuts = new Array();
12
13    /**
14     * Set modifier keys, for instance:
15     *  this.modifier = 'ctrl';
16     *  this.modifier = 'ctrl+shift';
17     *  this.modifier = 'ctrl+alt+shift';
18     *  this.modifier = 'alt';
19     *  this.modifier = 'alt+shift';
20     *
21     *  overwritten in intitialize (see below)
22     */
23    this.modifier = 'ctrl+alt';
24
25    /**
26     * Initialization
27     *
28     * This function looks up all the accesskeys used in the current page
29     * (at anchor elements and button elements [type="submit"]) and registers
30     * appropriate shortcuts.
31     *
32     * Secondly, initialization registers listeners on document to catch all
33     * keyboard events.
34     *
35     * @author Marek Sacha <sachamar@fel.cvut.cz>
36     */
37    this.initialize = function() {
38        var t = this;
39
40        //switch modifier key based on OS FS#1958
41        if(is_macos){
42            t.modifier = 'ctrl+alt';
43        }else{
44            t.modifier = 'alt';
45        }
46
47        /**
48         * Lookup all anchors with accesskey and register event - go to anchor
49         * target.
50         */
51        var anchors = document.getElementsByTagName("a");
52        t.each(anchors, function(a) {
53            if (a.accessKey != "") {
54                t.addShortcut(t.modifier + '+' + a.accessKey, function() {
55                    location.href = a.href;
56                });
57                a.accessKey = '';
58            }
59        });
60
61        /**
62         * Lookup all button [type="submit"] with accesskey and register event -
63         * perform "click" on a button.
64         */
65        var inputs = document.getElementsByTagName("button");
66        t.each(inputs, function(i) {
67            if (i.type == "submit" && i.accessKey != "") {
68                t.addShortcut(t.modifier + '+' + i.accessKey, function() {
69                    i.click();
70                });
71                i.accessKey = '';
72            }
73        });
74
75        /**
76         * Lookup all buttons with accesskey and register event -
77         * perform "click" on a button.
78         */
79        var buttons = document.getElementsByTagName("button");
80        t.each(buttons, function(b) {
81            if (b.accessKey != "") {
82                t.addShortcut(t.modifier + '+' + b.accessKey, function() {
83                    b.click();
84                });
85                b.accessKey = '';
86            }
87        });
88
89        /**
90         * Register listeners on document to catch keyboard events.
91         */
92
93        addEvent(document,'keyup',function (e) {
94            return t.onkeyup.call(t,e);
95        });
96
97        addEvent(document,'keypress',function (e) {
98            return t.onkeypress.call(t,e);
99        });
100
101        addEvent(document,'keydown',function (e) {
102            return t.onkeydown.call(t,e);
103        });
104    };
105
106    /**
107     * Keyup processing function
108     * Function returns true if keyboard event has registered handler, and
109     * executes the handler function.
110     *
111     * @param e KeyboardEvent
112     * @author Marek Sacha <sachamar@fel.cvut.cz>
113     * @return b boolean
114     */
115    this.onkeyup = function(e) {
116        var t = this;
117        var v = t.findShortcut(e);
118        if (v != null && v != false) {
119            v.func.call(t);
120            return false;
121        }
122        return true;
123    };
124
125    /**
126     * Keydown processing function
127     * Function returns true if keyboard event has registered handler
128     *
129     * @param e KeyboardEvent
130     * @author Marek Sacha <sachamar@fel.cvut.cz>
131     * @return b boolean
132     */
133    this.onkeydown = function(e) {
134        var t = this;
135        var v = t.findShortcut(e);
136        if (v != null && v != false) {
137            return false;
138        }
139        return true;
140    };
141
142    /**
143     * Keypress processing function
144     * Function returns true if keyboard event has registered handler
145     *
146     * @param e KeyboardEvent
147     * @author Marek Sacha <sachamar@fel.cvut.cz>
148     * @return b
149     */
150    this.onkeypress = function(e) {
151        var t = this;
152        var v = t.findShortcut(e);
153        if (v != null && v != false) {
154            return false;
155        }
156        return true;
157    };
158
159    /**
160     * Register new shortcut
161     *
162     * This function registers new shortcuts, each shortcut is defined by its
163     * modifier keys and a key (with + as delimiter). If shortcut is pressed
164     * cmd_function is performed.
165     *
166     * For example:
167     *  pa = "ctrl+alt+p";
168     *  pa = "shift+alt+s";
169     *
170     * Full example of method usage:
171     *  hotkeys.addShortcut('ctrl+s',function() {
172     *      document.getElementByID('form_1').submit();
173     *  });
174     *
175     * @param pa String description of the shortcut (ctrl+a, ctrl+shift+p, .. )
176     * @param cmd_func Function to be called if shortcut is pressed
177     * @author Marek Sacha <sachamar@fel.cvut.cz>
178     */
179    this.addShortcut = function(pa, cmd_func) {
180        var t = this;
181
182        var o = {
183            func : cmd_func,
184            alt : false,
185            ctrl : false,
186            shift : false
187        };
188
189        t.each(t.explode(pa, '+'), function(v) {
190            switch (v) {
191                case 'alt':
192                case 'ctrl':
193                case 'shift':
194                    o[v] = true;
195                    break;
196
197                default:
198                    o.charCode = v.charCodeAt(0);
199                    o.keyCode = v.toUpperCase().charCodeAt(0);
200            }
201        });
202
203        t.shortcuts.push((o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode,  o);
204
205        return true;
206    };
207
208    /**
209     * @property isMac
210     */
211    this.isMac = is_macos;
212
213    /**
214     * Apply function cb on each element of o in the namespace of s
215     * @param o Array of objects
216     * @param cb Function to be called on each object
217     * @param s Namespace to be used during call of cb (default namespace is o)
218     * @author Marek Sacha <sachamar@fel.cvut.cz>
219     */
220    this.each = function(o, cb, s) {
221        var n, l;
222
223        if (!o)
224            return 0;
225
226        s = s || o;
227
228        if (o.length !== undefined) {
229            // Indexed arrays, needed for Safari
230            for (n=0, l = o.length; n < l; n++) {
231                if (cb.call(s, o[n], n, o) === false)
232                    return 0;
233            }
234        } else {
235            // Hashtables
236            for (n in o) {
237                if (o.hasOwnProperty(n)) {
238                    if (cb.call(s, o[n], n, o) === false)
239                        return 0;
240                }
241            }
242        }
243
244        return 1;
245    };
246
247    /**
248     * Explode string according to delimiter
249     * @param s String
250     * @param d Delimiter (default ',')
251     * @author Marek Sacha <sachamar@fel.cvut.cz>
252     * @return a Array of tokens
253     */
254    this.explode = function(s, d) {
255        return  s.split(d || ',');
256    };
257
258    /**
259     * Find if the shortcut was registered
260     *
261     * @param e KeyboardEvent
262     * @author Marek Sacha <sachamar@fel.cvut.cz>
263     * @return v Shortcut structure or null if not found
264     */
265    this.findShortcut = function (e) {
266        var t = this;
267        var v = null;
268
269        /* No modifier key used - shortcut does not exist */
270        if (!e.altKey && !e.ctrlKey && !e.metaKey) {
271            return v;
272        }
273
274        t.each(t.shortcuts, function(o) {
275            if (o.ctrl != e.ctrlKey)
276                return;
277
278            if (o.alt != e.altKey)
279                return;
280
281            if (o.shift != e.shiftKey)
282                return;
283
284            if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
285                v = o;
286                return;
287            }
288        });
289        return v;
290    };
291}
292
293/**
294 * Init function for hotkeys. Called from js.php, to ensure hotkyes are initialized after toolbar.
295 * Call of addInitEvent(initializeHotkeys) is unnecessary now.
296 *
297 * @author Marek Sacha <sachamar@fel.cvut.cz>
298 */
299function initializeHotkeys() {
300    var hotkeys = new Hotkeys();
301    hotkeys.initialize();
302}
303