1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
|
"""Configurable keybindings.
Part of the tagit module.
A copy of the license is provided with the project.
Author: Matthias Baumgartner, 2022
"""
# standard imports
import typing
# tagit imports
from tagit.utils import errors
# exports
__all__: typing.Sequence[str] = (
'Binding',
)
## code ##
class Binding(object):
"""Handle keybindings.
A keybinding is a set of three constraints:
* Key code
* Inclusive modifiers
* Exclusive modifiers
Inclusive modifiers must be present, exclusive ones must not be present.
Modifiers occuring in neither of the two lists are ignored.
Modifiers are always lowercase strings. Additionally to SHIFT, CTRL and ALT,
the modifiers "all" and "rest" can be used.
"all" is a shortcut for all of the modifiers known.
"rest" means all modifiers not consumed by the other list yet. "rest" can
therefore only occur in at most one of the lists.
Usage example:
>>> # From settings, with PGUP w/o modifiers as default
>>> Binding.check(evt, self.cfg("bindings", "browser", "page_prev",
... default=Binding.simple(Binding.PGUP, None, Binding.mALL)))
>>> # ESC or CTRL + SHIFT + a
>>> Binding.check(evt, Binding.multi((Binding.ESC, ),
... (97, (Binding.mCTRL, Binding.mSHIFT), Binding.mREST))))
"""
# Modifiers
mSHIFT = 'shift'
mCTRL = 'ctrl'
mALT = 'alt'
mCMD = 'cmd'
mALTGR = 'altgr'
mNUMLOCK = 'numlock'
mCAPSLOCK = 'capslock'
# Modifier specials
mALL = 'all'
mREST = 'rest'
# Special keys
BACKSPACE = 8
TAB = 9
ENTER = 13
ESC = 27
SPACEBAR = 32
DEL = 127
UP = 273
DOWN = 274
RIGHT = 275
LEFT = 276
INSERT = 277
HOME = 278
END = 279
PGUP = 280
PGDN = 281
F1 = 282
F2 = 283
F3 = 284
F4 = 285
F5 = 286
F6 = 287
F7 = 288
F8 = 289
F9 = 290
F10 = 291
F11 = 292
F12 = 293
CAPSLOCK = 301
RIGHT_SHIFT = 303
LEFT_SHIFT = 304
LEFT_CTRL = 305
RIGHT_CTRL = 306
ALTGR = 307
ALT = 308
CMD = 309
@staticmethod
def simple(code, inclusive=None, exclusive=None):
"""Create a binding constraint."""
# handle strings
inclusive = (inclusive, ) if isinstance(inclusive, str) else inclusive
exclusive = (exclusive, ) if isinstance(exclusive, str) else exclusive
# handle None, ensure tuple
inclusive = tuple(inclusive) if inclusive is not None else tuple()
exclusive = tuple(exclusive) if exclusive is not None else tuple()
# handle code
code = Binding.str_to_key(code.lower()) if isinstance(code, str) else code
if code is None:
raise errors.ProgrammingError('invalid key code')
# build constraint
return [(code, inclusive, exclusive)]
@staticmethod
def multi(*args):
"""Return binding for multiple constraints."""
return [Binding.simple(*arg)[0] for arg in args]
@staticmethod
def from_string(string):
mods = (Binding.mSHIFT, Binding.mCTRL, Binding.mALT, Binding.mCMD,
Binding.mALTGR, Binding.mNUMLOCK, Binding.mCAPSLOCK)
bindings = []
for kcombo in (itm.strip() for itm in string.split(';')):
strokes = [key.lower().strip() for key in kcombo.split('+')]
# modifiers; ignore lock modifiers
inc = [key for key in strokes if key in mods]
inc = [key for key in inc if key not in (Binding.mNUMLOCK, Binding.mCAPSLOCK)]
# key
code = [key for key in strokes if key not in mods]
if len(code) != 1:
raise errors.ProgrammingError('there must be exactly one key code in a keybinding')
code = Binding.str_to_key(code[0])
if code is None:
raise errors.ProgrammingError('invalid key code')
bindings.append((code, tuple(inc), (Binding.mREST, )))
return bindings
@staticmethod
def to_string(constraints):
values = []
for code, inc, exc in constraints:
values.append(
' + '.join([m.upper() for m in inc] + [Binding.key_to_str(code)]))
return '; '.join(values)
@staticmethod
def check(stroke, constraint):
"""Return True if *evt* matches the *constraint*."""
code, char, modifiers = stroke
all_ = {Binding.mSHIFT, Binding.mCTRL, Binding.mALT, Binding.mCMD, Binding.mALTGR}
for key, inclusive, exclusive in constraint:
inclusive, exclusive = set(inclusive), set(exclusive)
if key in (code, char): # Otherwise, we don't have to process the modifiers
# Handle specials
if 'all' in inclusive:
inclusive = all_
if 'all' in exclusive:
exclusive = all_
if 'rest' in inclusive:
inclusive = all_ - exclusive
if 'rest' in exclusive:
exclusive = all_ - inclusive
if (all([mod in modifiers for mod in inclusive]) and
all([mod not in modifiers for mod in exclusive])):
# Code and modifiers match
return True
# No matching constraint found
return False
@staticmethod
def key_to_str(code, default='?'):
if isinstance(code, str):
return code
if 32 <= code and code <= 226 and code != 127:
return chr(code)
return {
Binding.BACKSPACE : 'BACKSPACE',
Binding.TAB : 'TAB',
Binding.ENTER : 'ENTER',
Binding.ESC : 'ESC',
Binding.SPACEBAR : 'SPACEBAR',
Binding.DEL : 'DEL',
Binding.UP : 'UP',
Binding.DOWN : 'DOWN',
Binding.RIGHT : 'RIGHT',
Binding.LEFT : 'LEFT',
Binding.INSERT : 'INSERT',
Binding.HOME : 'HOME',
Binding.END : 'END',
Binding.PGUP : 'PGUP',
Binding.PGDN : 'PGDN',
Binding.F1 : 'F1',
Binding.F2 : 'F2',
Binding.F3 : 'F3',
Binding.F4 : 'F4',
Binding.F5 : 'F5',
Binding.F6 : 'F6',
Binding.F7 : 'F7',
Binding.F8 : 'F8',
Binding.F9 : 'F9',
Binding.F10 : 'F10',
Binding.F11 : 'F11',
Binding.F12 : 'F12',
Binding.CAPSLOCK : 'CAPSLOCK',
Binding.RIGHT_SHIFT : 'RIGHT_SHIFT',
Binding.LEFT_SHIFT : 'LEFT_SHIFT',
Binding.LEFT_CTRL : 'LEFT_CTRL',
Binding.RIGHT_CTRL : 'RIGHT_CTRL',
Binding.ALTGR : 'ALTGR',
Binding.ALT : 'ALT',
Binding.CMD : 'CMD',
}.get(code, default)
@staticmethod
def str_to_key(char, default=None):
if isinstance(char, int):
return char
try:
# check if ascii
code = ord(char)
if 32 <= code and code <= 226:
return code
except TypeError:
pass
return {
'BACKSPACE' : Binding.BACKSPACE,
'TAB' : Binding.TAB,
'ENTER' : Binding.ENTER,
'ESC' : Binding.ESC,
'SPACEBAR' : Binding.SPACEBAR,
'DEL' : Binding.DEL,
'UP' : Binding.UP,
'DOWN' : Binding.DOWN,
'RIGHT' : Binding.RIGHT,
'LEFT' : Binding.LEFT,
'INSERT' : Binding.INSERT,
'HOME' : Binding.HOME,
'END' : Binding.END,
'PGUP' : Binding.PGUP,
'PGDN' : Binding.PGDN,
'F1' : Binding.F1,
'F2' : Binding.F2,
'F3' : Binding.F3,
'F4' : Binding.F4,
'F5' : Binding.F5,
'F6' : Binding.F6,
'F7' : Binding.F7,
'F8' : Binding.F8,
'F9' : Binding.F9,
'F10' : Binding.F10,
'F11' : Binding.F11,
'F12' : Binding.F12,
'CAPSLOCK' : Binding.CAPSLOCK,
'RIGHT_SHIFT' : Binding.RIGHT_SHIFT,
'LEFT_SHIFT' : Binding.LEFT_SHIFT,
'LEFT_CTRL' : Binding.LEFT_CTRL,
'RIGHT_CTRL' : Binding.RIGHT_CTRL,
'ALTGR' : Binding.ALTGR,
'ALT' : Binding.ALT,
'CMD' : Binding.CMD,
}.get(char, default)
## EOF ##
|