Skip to content

Commit 6e7ba32

Browse files
committed
Merge pull request matplotlib#2749 from tacaswell/qt4_keys
Qt4 keys
2 parents db49c9c + 2a70aff commit 6e7ba32

File tree

3 files changed

+243
-103
lines changed

3 files changed

+243
-103
lines changed

lib/matplotlib/backends/backend_qt4.py

Lines changed: 90 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,13 @@
33

44
import six
55

6-
import math # might not ever be used
76
import os
87
import re
98
import signal
109
import sys
1110

1211
import matplotlib
1312

14-
####### might not ever be used
15-
from matplotlib import verbose
16-
from matplotlib.cbook import onetrue
17-
from matplotlib.backend_bases import GraphicsContextBase
18-
from matplotlib.backend_bases import RendererBase
19-
from matplotlib.backend_bases import IdleEvent
20-
from matplotlib.mathtext import MathTextParser
21-
#######
2213
from matplotlib.cbook import is_string_like
2314
from matplotlib.backend_bases import FigureManagerBase
2415
from matplotlib.backend_bases import FigureCanvasBase
@@ -43,6 +34,66 @@
4334

4435
backend_version = __version__
4536

37+
# SPECIAL_KEYS are keys that do *not* return their unicode name
38+
# instead they have manually specified names
39+
SPECIAL_KEYS = {QtCore.Qt.Key_Control: 'control',
40+
QtCore.Qt.Key_Shift: 'shift',
41+
QtCore.Qt.Key_Alt: 'alt',
42+
QtCore.Qt.Key_Meta: 'super',
43+
QtCore.Qt.Key_Return: 'enter',
44+
QtCore.Qt.Key_Left: 'left',
45+
QtCore.Qt.Key_Up: 'up',
46+
QtCore.Qt.Key_Right: 'right',
47+
QtCore.Qt.Key_Down: 'down',
48+
QtCore.Qt.Key_Escape: 'escape',
49+
QtCore.Qt.Key_F1: 'f1',
50+
QtCore.Qt.Key_F2: 'f2',
51+
QtCore.Qt.Key_F3: 'f3',
52+
QtCore.Qt.Key_F4: 'f4',
53+
QtCore.Qt.Key_F5: 'f5',
54+
QtCore.Qt.Key_F6: 'f6',
55+
QtCore.Qt.Key_F7: 'f7',
56+
QtCore.Qt.Key_F8: 'f8',
57+
QtCore.Qt.Key_F9: 'f9',
58+
QtCore.Qt.Key_F10: 'f10',
59+
QtCore.Qt.Key_F11: 'f11',
60+
QtCore.Qt.Key_F12: 'f12',
61+
QtCore.Qt.Key_Home: 'home',
62+
QtCore.Qt.Key_End: 'end',
63+
QtCore.Qt.Key_PageUp: 'pageup',
64+
QtCore.Qt.Key_PageDown: 'pagedown',
65+
QtCore.Qt.Key_Tab: 'tab',
66+
QtCore.Qt.Key_Backspace: 'backspace',
67+
QtCore.Qt.Key_Enter: 'enter',
68+
QtCore.Qt.Key_Insert: 'insert',
69+
QtCore.Qt.Key_Delete: 'delete',
70+
QtCore.Qt.Key_Pause: 'pause',
71+
QtCore.Qt.Key_SysReq: 'sysreq',
72+
QtCore.Qt.Key_Clear: 'clear', }
73+
74+
# define which modifier keys are collected on keyboard events.
75+
# elements are (mpl names, Modifier Flag, Qt Key) tuples
76+
SUPER = 0
77+
ALT = 1
78+
CTRL = 2
79+
SHIFT = 3
80+
MODIFIER_KEYS = [('super', QtCore.Qt.MetaModifier, QtCore.Qt.Key_Meta),
81+
('alt', QtCore.Qt.AltModifier, QtCore.Qt.Key_Alt),
82+
('ctrl', QtCore.Qt.ControlModifier, QtCore.Qt.Key_Control),
83+
('shift', QtCore.Qt.ShiftModifier, QtCore.Qt.Key_Shift),
84+
]
85+
86+
if sys.platform == 'darwin':
87+
# in OSX, the control and super (aka cmd/apple) keys are switched, so
88+
# switch them back.
89+
SPECIAL_KEYS.update({QtCore.Qt.Key_Control: 'super', # cmd/apple key
90+
QtCore.Qt.Key_Meta: 'control',
91+
})
92+
MODIFIER_KEYS[0] = ('super', QtCore.Qt.ControlModifier,
93+
QtCore.Qt.Key_Control)
94+
MODIFIER_KEYS[2] = ('ctrl', QtCore.Qt.MetaModifier,
95+
QtCore.Qt.Key_Meta)
96+
4697

4798
def fn_name():
4899
return sys._getframe(1).f_code.co_name
@@ -162,63 +213,6 @@ def _timer_stop(self):
162213

163214

164215
class FigureCanvasQT(QtGui.QWidget, FigureCanvasBase):
165-
keyvald = {QtCore.Qt.Key_Control: 'control',
166-
QtCore.Qt.Key_Shift: 'shift',
167-
QtCore.Qt.Key_Alt: 'alt',
168-
QtCore.Qt.Key_Meta: 'super',
169-
QtCore.Qt.Key_Return: 'enter',
170-
QtCore.Qt.Key_Left: 'left',
171-
QtCore.Qt.Key_Up: 'up',
172-
QtCore.Qt.Key_Right: 'right',
173-
QtCore.Qt.Key_Down: 'down',
174-
QtCore.Qt.Key_Escape: 'escape',
175-
QtCore.Qt.Key_F1: 'f1',
176-
QtCore.Qt.Key_F2: 'f2',
177-
QtCore.Qt.Key_F3: 'f3',
178-
QtCore.Qt.Key_F4: 'f4',
179-
QtCore.Qt.Key_F5: 'f5',
180-
QtCore.Qt.Key_F6: 'f6',
181-
QtCore.Qt.Key_F7: 'f7',
182-
QtCore.Qt.Key_F8: 'f8',
183-
QtCore.Qt.Key_F9: 'f9',
184-
QtCore.Qt.Key_F10: 'f10',
185-
QtCore.Qt.Key_F11: 'f11',
186-
QtCore.Qt.Key_F12: 'f12',
187-
QtCore.Qt.Key_Home: 'home',
188-
QtCore.Qt.Key_End: 'end',
189-
QtCore.Qt.Key_PageUp: 'pageup',
190-
QtCore.Qt.Key_PageDown: 'pagedown',
191-
}
192-
193-
# define the modifier keys which are to be collected on keyboard events.
194-
# format is: [(modifier_flag, modifier_name, equivalent_key)
195-
_modifier_keys = [
196-
(QtCore.Qt.MetaModifier, 'super', QtCore.Qt.Key_Meta),
197-
(QtCore.Qt.AltModifier, 'alt', QtCore.Qt.Key_Alt),
198-
(QtCore.Qt.ControlModifier, 'ctrl',
199-
QtCore.Qt.Key_Control)
200-
]
201-
202-
_ctrl_modifier = QtCore.Qt.ControlModifier
203-
204-
if sys.platform == 'darwin':
205-
# in OSX, the control and super (aka cmd/apple) keys are switched, so
206-
# switch them back.
207-
keyvald.update({
208-
QtCore.Qt.Key_Control: 'super', # cmd/apple key
209-
QtCore.Qt.Key_Meta: 'control',
210-
})
211-
212-
_modifier_keys = [
213-
(QtCore.Qt.ControlModifier, 'super',
214-
QtCore.Qt.Key_Control),
215-
(QtCore.Qt.AltModifier, 'alt',
216-
QtCore.Qt.Key_Alt),
217-
(QtCore.Qt.MetaModifier, 'ctrl',
218-
QtCore.Qt.Key_Meta),
219-
]
220-
221-
_ctrl_modifier = QtCore.Qt.MetaModifier
222216

223217
# map Qt button codes to MouseEvent's ones:
224218
buttond = {QtCore.Qt.LeftButton: 1,
@@ -346,35 +340,38 @@ def _get_key(self, event):
346340
if event.isAutoRepeat():
347341
return None
348342

349-
if event.key() < 256:
350-
key = six.text_type(event.text())
351-
# if the control key is being pressed, we don't get the correct
352-
# characters, so interpret them directly from the event.key().
353-
# Unfortunately, this means that we cannot handle key's case
354-
# since event.key() is not case sensitive, whereas event.text() is,
355-
# Finally, since it is not possible to get the CapsLock state
356-
# we cannot accurately compute the case of a pressed key when
357-
# ctrl+shift+p is pressed.
358-
if int(event.modifiers()) & self._ctrl_modifier:
359-
# we always get an uppercase character
360-
key = chr(event.key())
361-
# if shift is not being pressed, lowercase it (as mentioned,
362-
# this does not take into account the CapsLock state)
363-
if not int(event.modifiers()) & QtCore.Qt.ShiftModifier:
364-
key = key.lower()
343+
event_key = event.key()
344+
event_mods = int(event.modifiers()) # actually a bitmask
365345

366-
else:
367-
key = self.keyvald.get(event.key())
368-
369-
if key is not None:
370-
# prepend the ctrl, alt, super keys if appropriate (sorted
371-
# in that order)
372-
for modifier, prefix, Qt_key in self._modifier_keys:
373-
if (event.key() != Qt_key and
374-
int(event.modifiers()) & modifier == modifier):
375-
key = '{0}+{1}'.format(prefix, key)
346+
# get names of the pressed modifier keys
347+
# bit twiddling to pick out modifier keys from event_mods bitmask,
348+
# if event_key is a MODIFIER, it should not be duplicated in mods
349+
mods = [name for name, mod_key, qt_key in MODIFIER_KEYS
350+
if event_key != qt_key and (event_mods & mod_key) == mod_key]
351+
try:
352+
# for certain keys (enter, left, backspace, etc) use a word for the
353+
# key, rather than unicode
354+
key = SPECIAL_KEYS[event_key]
355+
except KeyError:
356+
# unicode defines code points up to 0x0010ffff
357+
# QT will use Key_Codes larger than that for keyboard keys that are
358+
# are not unicode characters (like multimedia keys)
359+
# skip these
360+
# if you really want them, you should add them to SPECIAL_KEYS
361+
MAX_UNICODE = 0x10ffff
362+
if event_key > MAX_UNICODE:
363+
return None
364+
365+
key = unichr(event_key)
366+
# qt delivers capitalized letters. fix capitalization
367+
# note that capslock is ignored
368+
if 'shift' in mods:
369+
mods.remove('shift')
370+
else:
371+
key = key.lower()
376372

377-
return key
373+
mods.reverse()
374+
return u'+'.join(mods + [key])
378375

379376
def new_timer(self, *args, **kwargs):
380377
"""

lib/matplotlib/tests/test_backend_qt4.py

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,20 @@
1010
import copy
1111

1212
try:
13-
import matplotlib.backends.qt4_compat
13+
# mock in python 3.3+
14+
from unittest import mock
15+
except ImportError:
16+
import mock
17+
18+
try:
19+
from matplotlib.backends.qt4_compat import QtCore
20+
from matplotlib.backends.backend_qt4 import (MODIFIER_KEYS,
21+
SUPER, ALT, CTRL, SHIFT)
22+
23+
_, ControlModifier, ControlKey = MODIFIER_KEYS[CTRL]
24+
_, AltModifier, AltKey = MODIFIER_KEYS[ALT]
25+
_, SuperModifier, SuperKey = MODIFIER_KEYS[SUPER]
26+
_, ShiftModifier, ShiftKey = MODIFIER_KEYS[SHIFT]
1427
HAS_QT = True
1528
except ImportError:
1629
HAS_QT = False
@@ -35,3 +48,113 @@ def test_fig_close():
3548
# assert that we have removed the reference to the FigureManager
3649
# that got added by plt.figure()
3750
assert(init_figs == Gcf.figs)
51+
52+
53+
def assert_correct_key(qt_key, qt_mods, answer):
54+
"""
55+
Make a figure
56+
Send a key_press_event event (using non-public, qt4 backend specific api)
57+
Catch the event
58+
Assert sent and caught keys are the same
59+
"""
60+
plt.switch_backend('Qt4Agg')
61+
qt_canvas = plt.figure().canvas
62+
63+
event = mock.Mock()
64+
event.isAutoRepeat.return_value = False
65+
event.key.return_value = qt_key
66+
event.modifiers.return_value = qt_mods
67+
68+
def receive(event):
69+
assert event.key == answer
70+
71+
qt_canvas.mpl_connect('key_press_event', receive)
72+
qt_canvas.keyPressEvent(event)
73+
74+
75+
@cleanup
76+
@knownfailureif(not HAS_QT)
77+
def test_shift():
78+
assert_correct_key(QtCore.Qt.Key_A,
79+
ShiftModifier,
80+
u'A')
81+
82+
83+
@cleanup
84+
@knownfailureif(not HAS_QT)
85+
def test_lower():
86+
assert_correct_key(QtCore.Qt.Key_A,
87+
QtCore.Qt.NoModifier,
88+
u'a')
89+
90+
91+
@cleanup
92+
@knownfailureif(not HAS_QT)
93+
def test_control():
94+
assert_correct_key(QtCore.Qt.Key_A,
95+
ControlModifier,
96+
u'ctrl+a')
97+
98+
99+
@cleanup
100+
@knownfailureif(not HAS_QT)
101+
def test_unicode_upper():
102+
assert_correct_key(QtCore.Qt.Key_Aacute,
103+
ShiftModifier,
104+
unichr(193))
105+
106+
107+
@cleanup
108+
@knownfailureif(not HAS_QT)
109+
def test_unicode_lower():
110+
assert_correct_key(QtCore.Qt.Key_Aacute,
111+
QtCore.Qt.NoModifier,
112+
unichr(225))
113+
114+
115+
@cleanup
116+
@knownfailureif(not HAS_QT)
117+
def test_alt_control():
118+
assert_correct_key(ControlKey,
119+
AltModifier,
120+
u'alt+control')
121+
122+
123+
@cleanup
124+
@knownfailureif(not HAS_QT)
125+
def test_control_alt():
126+
assert_correct_key(AltKey,
127+
ControlModifier,
128+
u'ctrl+alt')
129+
130+
131+
@cleanup
132+
@knownfailureif(not HAS_QT)
133+
def test_modifier_order():
134+
assert_correct_key(QtCore.Qt.Key_Aacute,
135+
(ControlModifier | AltModifier | SuperModifier),
136+
u'ctrl+alt+super+' + unichr(225))
137+
138+
139+
@cleanup
140+
@knownfailureif(not HAS_QT)
141+
def test_backspace():
142+
assert_correct_key(QtCore.Qt.Key_Backspace,
143+
QtCore.Qt.NoModifier,
144+
u'backspace')
145+
146+
147+
@cleanup
148+
@knownfailureif(not HAS_QT)
149+
def test_backspace_mod():
150+
assert_correct_key(QtCore.Qt.Key_Backspace,
151+
ControlModifier,
152+
u'ctrl+backspace')
153+
154+
155+
@cleanup
156+
@knownfailureif(not HAS_QT)
157+
def test_non_unicode_key():
158+
assert_correct_key(QtCore.Qt.Key_Play,
159+
QtCore.Qt.NoModifier,
160+
None)

0 commit comments

Comments
 (0)