~kissiel/checkbox/converged-keyboard-support

« back to all changes in this revision

Viewing changes to checkbox-touch/components/KeysDelegator.qml

  • Committer: Maciej Kisielewski
  • Date: 2016-07-11 12:39:35 UTC
  • Revision ID: maciej.kisielewski@canonical.com-20160711123935-v2f90k00m4gewlnl
checkbox-touch:components: add KeysDelegator component

This patch adds a helper that aids capturing of keystrokes.

Example usage:
KeysDelegator {
    id: keysDelegator
}
Keys.onPressed: keysDelegator.keyPress(event)
(...)
kd.setHandler('ctrl+q', Qt.quit)

Once handlers are assigned, it's easy to 'unset' them all at once (e.g. when
application changes the state and is supposed to watch for a different set of
keystrokes), or to change them.

Signed-off-by: Maciej Kisielewski <maciej.kisielewski@canonical.com>

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * This file is part of Checkbox
 
3
 *
 
4
 * Copyright 2015 Canonical Ltd.
 
5
 *
 
6
 * Authors:
 
7
 * - Maciej Kisielewski <maciej.kisielewski@canonical.com>
 
8
 *
 
9
 * This program is free software; you can redistribute it and/or modify
 
10
 * it under the terms of the GNU General Public License as published by
 
11
 * the Free Software Foundation; version 3.
 
12
 *
 
13
 * This program is distributed in the hope that it will be useful,
 
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
 * GNU General Public License for more details.
 
17
 *
 
18
 * You should have received a copy of the GNU General Public License
 
19
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
20
 */
 
21
import QtQuick 2.0
 
22
import Ubuntu.Components 1.3
 
23
 
 
24
/*! \brief Helper for delegating keypresses
 
25
    \inherits Item
 
26
 
 
27
    This component helps to register keystroke handlers in a human-friendly
 
28
    manner. Once handler is assigned to a given keystroke, the next time
 
29
    keyPress will be called with event matching the key combination expressed
 
30
    as registered keystroke, the supplied handler will be called.
 
31
    There are two kinds of handlers: owned (normal), and 'global'.
 
32
    Owned are registered with 'owner' parameter, that owner must match the top
 
33
    of the 'activeStack' in order for the handler to be called.
 
34
    Global handlers are handler regardless of what's on activeStack.
 
35
    Note that owned handlers take precedence over global ones. I.e. if you have
 
36
    registered two handlers for the same keystroke and the owner of the owned
 
37
    one is on activeStack, ONLY that one will be called.
 
38
    Example usage:
 
39
    KeysDelegator {
 
40
        id: keysDelegator
 
41
    }
 
42
    Keys.onPressed: keysDelegator.keyPress(event)
 
43
    (...)
 
44
    kd.setGlobalHandler('ctrl+q', Qt.quit);
 
45
    kd.setHandler('ctrl+y', yesButton.clicked);
 
46
*/
 
47
 
 
48
Item {
 
49
 
 
50
    /*!
 
51
      Gets signalled when no handler was found for given keystroke.
 
52
     */
 
53
    signal keyPressed(var event);
 
54
 
 
55
    /*!
 
56
      Handle a keyboard event.
 
57
      This is intended to be called as a handler for Keys.onPressed in your MainView.
 
58
     */
 
59
    function keyPress(event) {
 
60
        // build the keystroke string
 
61
        var keystroke ='';
 
62
        if (event.modifiers & Qt.AltModifier) keystroke += 'alt+';
 
63
        if (event.modifiers & Qt.ControlModifier) keystroke += 'ctrl+';
 
64
        if (event.modifiers & Qt.ShiftModifier) keystroke += 'shift+';
 
65
        keystroke += String.fromCharCode(event.key).toLowerCase();
 
66
 
 
67
        var ownedHandlerHit = false;
 
68
        // get the object this keystroke should be processed for
 
69
        if (activeStack.length > 0) {
 
70
            var candidate = activeStack[activeStack.length - 1];
 
71
            if (candidate in _handlers) {
 
72
                if (keystroke in _handlers[candidate]) {
 
73
                    _handlers[candidate][keystroke]();
 
74
                    ownedHandlerHit = true;
 
75
                }
 
76
            }
 
77
        }
 
78
        if (!ownedHandlerHit) {
 
79
            if (keystroke in _globalHandlers) {
 
80
                _globalHandlers[keystroke]();
 
81
            } else {
 
82
                // no handlers set, forwarding as keyPressed signal
 
83
                keyPressed(event);
 
84
            }
 
85
        }
 
86
    }
 
87
 
 
88
    /*!
 
89
      Make given page receive all (yet) unhandled keystrokes while it's visible.
 
90
      Note, page must have its own KeysDelegator exposed as 'keys'.
 
91
     */
 
92
    function forwardPressesWhileVisible(page) {
 
93
        var handler = function() {
 
94
            if (page.visible == true) {
 
95
                root.onKeyPressed.connect(page.keys.keyPress);
 
96
            } else {
 
97
                root.onKeyPressed.disconnect(page.keys.keyPress);
 
98
            }
 
99
        }
 
100
        page.onVisibleChanged.connect(handler);
 
101
        page.Component.onDestruction.connect(function() {
 
102
            page.onVisibleChanged.disconnect(handler);
 
103
        });
 
104
    }
 
105
 
 
106
    /*!
 
107
      Set handler for given keystroke.
 
108
      This function stores `handler` as the function that is to be called if
 
109
      `keystroke` is processed and owner is on the top of the activeStack.
 
110
      `keystroke` is in form of [<mod_key>+...]<key>, E.g. 'ctrl+x'
 
111
 
 
112
      If called with the keystroke that's already in the registry, it will
 
113
      overwrite the previous handler.
 
114
     */
 
115
    function setHandler(keystroke, owner, handler) {
 
116
        if (!(owner in _handlers)) {
 
117
            _handlers[owner] = [];
 
118
        }
 
119
 
 
120
        _handlers[owner][_normalizeKeystroke(keystroke)] = handler;
 
121
    }
 
122
 
 
123
    /*!
 
124
      Unset handler for a given keystroke for a given owner.
 
125
 
 
126
      It silently ignores keystroke entries that are not in the registry
 
127
     */
 
128
    function unsetHandler(keystroke, owner) {
 
129
        if (!(owner in _handlers)) {
 
130
            return;
 
131
        }
 
132
        delete _handlers[owner][_normalizeKeystroke(keystroke)];
 
133
    }
 
134
 
 
135
    /*!
 
136
      Unset handlers owned by the given owner.
 
137
     */
 
138
    function unsetHandlersByOwner(owner) {
 
139
        _handlers[owner] = [];
 
140
    }
 
141
 
 
142
    /*!
 
143
      Unset all handlers.
 
144
     */
 
145
    function unsetAllHandlers() {
 
146
        _handlers = [];
 
147
    }
 
148
 
 
149
    /*!
 
150
      Set global handler for a keystroke.
 
151
     */
 
152
    function setGlobalHandler(keystroke, handler) {
 
153
        _globalHandlers[_normalizeKeystroke(keystroke)] = handler;
 
154
    }
 
155
 
 
156
    /*!
 
157
      Unset global handler for a given keystroke.
 
158
      NOTE: owned handlers will not be affected
 
159
     */
 
160
    function unsetGlobalHandler(keystroke) {
 
161
        delete _globalHandlers[_normalizeKeystroke(keystroke)];
 
162
    }
 
163
 
 
164
    /*!
 
165
      Unset all global handlers.
 
166
      NOTE: owned handlers will not be affected
 
167
     */
 
168
    function unsetAllGlobalHandlers() {
 
169
        _globalHandlers = [];
 
170
    }
 
171
 
 
172
    property var activeStack: []
 
173
 
 
174
    property var _handlers : []
 
175
 
 
176
    property var _globalHandlers : []
 
177
 
 
178
    /*!
 
179
      Validate and normalize keystroke description.
 
180
 
 
181
      This function 'repairs' strings that depict keystroke to match following template:
 
182
      [<mod_key>+...]<key>
 
183
      where:
 
184
        * `mod_key`is one of the modifier keys defined as `allowedModifiers` below
 
185
        * given `mod_key` is supplied at most once
 
186
        * `key` is an lowercase alphanumeric character - [a-z0-9]
 
187
        * supplied `mod_key`s occur in alphabetic order
 
188
 
 
189
      Examples:
 
190
        _normalizeKeystroke('ctrl+alt+K') -> 'alt+ctrl+k'
 
191
        _normalizeKeystroke('Q') -> 'q'
 
192
        _normalizeKeystroke('shift+W') -> 'shift+w'
 
193
 
 
194
      Returns normalized keystroke
 
195
      Throws an `Error` when parsing of keystroke string failed
 
196
     */
 
197
    function _normalizeKeystroke(keystroke) {
 
198
        var allowedModifiers = ['alt', 'ctrl', 'shift'];
 
199
        var parts = keystroke.toLowerCase().split('+');
 
200
        var k = parts.pop();
 
201
        if (!k) {
 
202
           throw  new Error('Missing key in the keystroke. Got: "%1"'.arg(keystroke));
 
203
        }
 
204
        if (!k.match(/^[a-z0-9]$/)) {
 
205
            throw  new Error('Picked key "%1" is not an alphanumeric. Got: "%2"'.arg(k).arg(keystroke));
 
206
        }
 
207
 
 
208
        var modifiers = {}
 
209
 
 
210
        while (parts.length > 0) {
 
211
            var modifier = parts.shift();
 
212
            if (allowedModifiers.indexOf(modifier) < 0) {
 
213
                throw new Error('Unknown modifier key "%1". Allowed modifiers: %2'.arg(modifier).arg(allowedModifiers));
 
214
            }
 
215
            modifiers[modifier] = true;
 
216
        }
 
217
        var normalizedKeystroke = '';
 
218
        for (var i in allowedModifiers) {
 
219
            if (allowedModifiers[i] in modifiers) {
 
220
                normalizedKeystroke += allowedModifiers[i] + '+';
 
221
            }
 
222
        }
 
223
        normalizedKeystroke += k;
 
224
        return normalizedKeystroke;
 
225
    }
 
226
}