8000 Merge pull request #2749 from tacaswell/qt4_keys · matplotlib/matplotlib@6e7ba32 · GitHub
[go: up one dir, main page]

Skip to content

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

8000
Appearance settings

Commit 6e7ba32

Browse files
committed
Merge pull request #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
8 8000 7
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)
0