From 26e8b9f3727b73948dfeb53bd76d2158db859236 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Thu, 1 Aug 2013 17:21:23 -0700 Subject: [PATCH 01/21] add extra keys to qt4 backend --- lib/matplotlib/backends/backend_qt4.py | 60 +++++++++++++++----------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index 788947900df1..a1c19a156d37 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -161,31 +161,41 @@ def _timer_stop(self): class FigureCanvasQT(QtGui.QWidget, FigureCanvasBase): keyvald = {QtCore.Qt.Key_Control: 'control', - QtCore.Qt.Key_Shift: 'shift', - QtCore.Qt.Key_Alt: 'alt', - QtCore.Qt.Key_Meta: 'super', - QtCore.Qt.Key_Return: 'enter', - QtCore.Qt.Key_Left: 'left', - QtCore.Qt.Key_Up: 'up', - QtCore.Qt.Key_Right: 'right', - QtCore.Qt.Key_Down: 'down', - QtCore.Qt.Key_Escape: 'escape', - QtCore.Qt.Key_F1: 'f1', - QtCore.Qt.Key_F2: 'f2', - QtCore.Qt.Key_F3: 'f3', - QtCore.Qt.Key_F4: 'f4', - QtCore.Qt.Key_F5: 'f5', - QtCore.Qt.Key_F6: 'f6', - QtCore.Qt.Key_F7: 'f7', - QtCore.Qt.Key_F8: 'f8', - QtCore.Qt.Key_F9: 'f9', - QtCore.Qt.Key_F10: 'f10', - QtCore.Qt.Key_F11: 'f11', - QtCore.Qt.Key_F12: 'f12', - QtCore.Qt.Key_Home: 'home', - QtCore.Qt.Key_End: 'end', - QtCore.Qt.Key_PageUp: 'pageup', - QtCore.Qt.Key_PageDown: 'pagedown', + QtCore.Qt.Key_Shift: 'shift', + QtCore.Qt.Key_Alt: 'alt', + QtCore.Qt.Key_Meta: 'super', + QtCore.Qt.Key_Return: 'enter', + QtCore.Qt.Key_Left: 'left', + QtCore.Qt.Key_Up: 'up', + QtCore.Qt.Key_Right: 'right', + QtCore.Qt.Key_Down: 'down', + QtCore.Qt.Key_Escape: 'escape', + QtCore.Qt.Key_F1: 'f1', + QtCore.Qt.Key_F2: 'f2', + QtCore.Qt.Key_F3: 'f3', + QtCore.Qt.Key_F4: 'f4', + QtCore.Qt.Key_F5: 'f5', + QtCore.Qt.Key_F6: 'f6', + QtCore.Qt.Key_F7: 'f7', + QtCore.Qt.Key_F8: 'f8', + QtCore.Qt.Key_F9: 'f9', + QtCore.Qt.Key_F10: 'f10', + QtCore.Qt.Key_F11: 'f11', + QtCore.Qt.Key_F12: 'f12', + QtCore.Qt.Key_Home: 'home', + QtCore.Qt.Key_End: 'end', + QtCore.Qt.Key_PageUp: 'pageup', + QtCore.Qt.Key_PageDown: 'pagedown', + QtCore.Qt.Key_Tab: 'tab', + QtCore.Qt.Key_Backtab: 'backtab', + QtCore.Qt.Key_Backspace: 'backspace', + QtCore.Qt.Key_Enter: 'enter', + QtCore.Qt.Key_Insert: 'insert', + QtCore.Qt.Key_Delete: 'delete', + QtCore.Qt.Key_Pause: 'pause', + QtCore.Qt.Key_SysReq: 'sysreq', + QtCore.Qt.Key_Clear: 'clear', + QtCore.Qt.Key_Control: 'ctrl', } # define the modifier keys which are to be collected on keyboard events. From 62f564e1a84e1ea701360422eabe44a684d28521 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Thu, 1 Aug 2013 21:25:10 -0700 Subject: [PATCH 02/21] support for unicode key_press_event --- lib/matplotlib/backends/backend_qt4.py | 56 ++++++++++++++------------ 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index a1c19a156d37..21340dfba077 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -354,34 +354,40 @@ def _get_key(self, event): if event.isAutoRepeat(): return None - if event.key() < 256: - key = six.text_type(event.text()) - # if the control key is being pressed, we don't get the correct - # characters, so interpret them directly from the event.key(). - # Unfortunately, this means that we cannot handle key's case - # since event.key() is not case sensitive, whereas event.text() is, - # Finally, since it is not possible to get the CapsLock state - # we cannot accurately compute the case of a pressed key when + event_key = event.key() + event_mods = int(event.modifiers()) + + try: + # for certain keys (enter, left, backspace, etc) use a word for the + # key, rather than unicode + key = self.keyvald[event_key] + except KeyError: + # for easy cases, event.text() handles caps lock and capitalization + # for us + key = unicode(event.text()) + + # if modifier (ctrl, alt, super) keys are being pressed, + # event.text() will be the empty string. QT always gives upper case + # letters, so we have to read from shift and lower() manually. + # Finally, since it is not possible to get the CapsLock state we + # cannot accurately compute the case of a pressed key when # ctrl+shift+p is pressed. - if int(event.modifiers()) & self._ctrl_modifier: - # we always get an uppercase character - key = chr(event.key()) - # if shift is not being pressed, lowercase it (as mentioned, - # this does not take into account the CapsLock state) - if not int(event.modifiers()) & QtCore.Qt.ShiftModifier: + if key == '': + # python may barf if event_key > 0x10000 + try: + key = unichr(event_key) + except ValueError: + return None + + # if shift is not being pressed, lower() it (CapsLock is + # ignored) + if not event_mods & QtCore.Qt.ShiftModifier: key = key.lower() - else: - key = self.keyvald.get(event.key()) - - if key is not None: - # prepend the ctrl, alt, super keys if appropriate (sorted - # in that order) - for modifier, prefix, Qt_key in self._modifier_keys: - if (event.key() != Qt_key and - int(event.modifiers()) & modifier == modifier): - key = '{0}+{1}'.format(prefix, key) - + # insert modifiers in the correct order + for modifier, prefix, Qt_key in self._modifier_keys: + if event_key != Qt_key and event_mods & modifier == modifier: + key = u'{0}+{1}'.format(prefix, key) return key def new_timer(self, *args, **kwargs): From db86c4084f3ba653eac6f7f9dcb8962884840451 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Thu, 1 Aug 2013 21:25:38 -0700 Subject: [PATCH 03/21] comments --- lib/matplotlib/backends/backend_qt4.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index 21340dfba077..be1f68a7c55b 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -160,6 +160,8 @@ def _timer_stop(self): class FigureCanvasQT(QtGui.QWidget, FigureCanvasBase): + # for these unicode keys, we do *not* return the unicode char. instead we + # more recognizable key name keyvald = {QtCore.Qt.Key_Control: 'control', QtCore.Qt.Key_Shift: 'shift', QtCore.Qt.Key_Alt: 'alt', From 1226094fdfe0b268e9c820c4d0419ce8a68fbcd2 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Mon, 12 Aug 2013 09:03:57 -0700 Subject: [PATCH 04/21] pull qt keys into module variables --- lib/matplotlib/backends/backend_qt4.py | 127 +++++++++++-------------- 1 file changed, 55 insertions(+), 72 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index be1f68a7c55b..7a08560b7e57 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -41,6 +41,59 @@ backend_version = __version__ +# SPECIAL_KEYS are keys that do *not* return their unicode name +# instead they have manually specified names +SPECIAL_KEYS = {QtCore.Qt.Key_Control: 'control', + QtCore.Qt.Key_Shift: 'shift', + QtCore.Qt.Key_Alt: 'alt', + QtCore.Qt.Key_Meta: 'super', + QtCore.Qt.Key_Return: 'enter', + QtCore.Qt.Key_Left: 'left', + QtCore.Qt.Key_Up: 'up', + QtCore.Qt.Key_Right: 'right', + QtCore.Qt.Key_Down: 'down', + QtCore.Qt.Key_Escape: 'escape', + QtCore.Qt.Key_F1: 'f1', + QtCore.Qt.Key_F2: 'f2', + QtCore.Qt.Key_F3: 'f3', + QtCore.Qt.Key_F4: 'f4', + QtCore.Qt.Key_F5: 'f5', + QtCore.Qt.Key_F6: 'f6', + QtCore.Qt.Key_F7: 'f7', + QtCore.Qt.Key_F8: 'f8', + QtCore.Qt.Key_F9: 'f9', + QtCore.Qt.Key_F10: 'f10', + QtCore.Qt.Key_F11: 'f11', + QtCore.Qt.Key_F12: 'f12', + QtCore.Qt.Key_Home: 'home', + QtCore.Qt.Key_End: 'end', + QtCore.Qt.Key_PageUp: 'pageup', + QtCore.Qt.Key_PageDown: 'pagedown', + QtCore.Qt.Key_Tab: 'tab', + QtCore.Qt.Key_Backspace: 'backspace', + QtCore.Qt.Key_Enter: 'enter', + QtCore.Qt.Key_Insert: 'insert', + QtCore.Qt.Key_Delete: 'delete', + QtCore.Qt.Key_Pause: 'pause', + QtCore.Qt.Key_SysReq: 'sysreq', + QtCore.Qt.Key_Clear: 'clear', } + +# define which modifier keys are collected on keyboard events. +# elements are (mpl names, Modifier Flag, Qt Key) tuples +MODIFIER_KEYS = [('super', QtCore.Qt.MetaModifier, QtCore.Qt.Key_Meta), + ('alt', QtCore.Qt.AltModifier, QtCore.Qt.Key_Alt), + ('ctrl', QtCore.Qt.ControlModifier, QtCore.Qt.Key_Control), + ] + +if sys.platform == 'darwin': + # in OSX, the control and super (aka cmd/apple) keys are switched, so + # switch them back. + SPECIAL_KEYS.update({QtCore.Qt.Key_Control: 'super', # cmd/apple key + QtCore.Qt.Key_Meta: 'control', + }) + MODIFIER_KEYS[0] = ('super', QtCore.Qt.ControlModifier, QtCore.Qt.Key_Control) + MODIFIER_KEYS[2] = ('ctrl', QtCore.Qt.MetaModifier, QtCore.Qt.Key_Meta) + def fn_name(): return sys._getframe(1).f_code.co_name @@ -160,75 +213,6 @@ def _timer_stop(self): class FigureCanvasQT(QtGui.QWidget, FigureCanvasBase): - # for these unicode keys, we do *not* return the unicode char. instead we - # more recognizable key name - keyvald = {QtCore.Qt.Key_Control: 'control', - QtCore.Qt.Key_Shift: 'shift', - QtCore.Qt.Key_Alt: 'alt', - QtCore.Qt.Key_Meta: 'super', - QtCore.Qt.Key_Return: 'enter', - QtCore.Qt.Key_Left: 'left', - QtCore.Qt.Key_Up: 'up', - QtCore.Qt.Key_Right: 'right', - QtCore.Qt.Key_Down: 'down', - QtCore.Qt.Key_Escape: 'escape', - QtCore.Qt.Key_F1: 'f1', - QtCore.Qt.Key_F2: 'f2', - QtCore.Qt.Key_F3: 'f3', - QtCore.Qt.Key_F4: 'f4', - QtCore.Qt.Key_F5: 'f5', - QtCore.Qt.Key_F6: 'f6', - QtCore.Qt.Key_F7: 'f7', - QtCore.Qt.Key_F8: 'f8', - QtCore.Qt.Key_F9: 'f9', - QtCore.Qt.Key_F10: 'f10', - QtCore.Qt.Key_F11: 'f11', - QtCore.Qt.Key_F12: 'f12', - QtCore.Qt.Key_Home: 'home', - QtCore.Qt.Key_End: 'end', - QtCore.Qt.Key_PageUp: 'pageup', - QtCore.Qt.Key_PageDown: 'pagedown', - QtCore.Qt.Key_Tab: 'tab', - QtCore.Qt.Key_Backtab: 'backtab', - QtCore.Qt.Key_Backspace: 'backspace', - QtCore.Qt.Key_Enter: 'enter', - QtCore.Qt.Key_Insert: 'insert', - QtCore.Qt.Key_Delete: 'delete', - QtCore.Qt.Key_Pause: 'pause', - QtCore.Qt.Key_SysReq: 'sysreq', - QtCore.Qt.Key_Clear: 'clear', - QtCore.Qt.Key_Control: 'ctrl', - } - - # define the modifier keys which are to be collected on keyboard events. - # format is: [(modifier_flag, modifier_name, equivalent_key) - _modifier_keys = [ - (QtCore.Qt.MetaModifier, 'super', QtCore.Qt.Key_Meta), - (QtCore.Qt.AltModifier, 'alt', QtCore.Qt.Key_Alt), - (QtCore.Qt.ControlModifier, 'ctrl', - QtCore.Qt.Key_Control) - ] - - _ctrl_modifier = QtCore.Qt.ControlModifier - - if sys.platform == 'darwin': - # in OSX, the control and super (aka cmd/apple) keys are switched, so - # switch them back. - keyvald.update({ - QtCore.Qt.Key_Control: 'super', # cmd/apple key - QtCore.Qt.Key_Meta: 'control', - }) - - _modifier_keys = [ - (QtCore.Qt.ControlModifier, 'super', - QtCore.Qt.Key_Control), - (QtCore.Qt.AltModifier, 'alt', - QtCore.Qt.Key_Alt), - (QtCore.Qt.MetaModifier, 'ctrl', - QtCore.Qt.Key_Meta), - ] - - _ctrl_modifier = QtCore.Qt.MetaModifier # map Qt button codes to MouseEvent's ones: buttond = {QtCore.Qt.LeftButton: 1, @@ -362,7 +346,7 @@ def _get_key(self, event): try: # for certain keys (enter, left, backspace, etc) use a word for the # key, rather than unicode - key = self.keyvald[event_key] + key = SPECIAL_KEYS[event_key] except KeyError: # for easy cases, event.text() handles caps lock and capitalization # for us @@ -386,8 +370,7 @@ def _get_key(self, event): if not event_mods & QtCore.Qt.ShiftModifier: key = key.lower() - # insert modifiers in the correct order - for modifier, prefix, Qt_key in self._modifier_keys: + for prefix, modifier, Qt_key in MODIFIER_KEYS: if event_key != Qt_key and event_mods & modifier == modifier: key = u'{0}+{1}'.format(prefix, key) return key From b454f327722be5a23c6bc18ff3c918f82e713ccc Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Sat, 3 Aug 2013 11:46:35 -0700 Subject: [PATCH 05/21] add tests for Qt Key handling --- lib/matplotlib/tests/test_backend_qt4.py | 99 +++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_backend_qt4.py b/lib/matplotlib/tests/test_backend_qt4.py index a577ffafda98..87d0e351fa2b 100644 --- a/lib/matplotlib/tests/test_backend_qt4.py +++ b/lib/matplotlib/tests/test_backend_qt4.py @@ -8,9 +8,17 @@ from matplotlib._pylab_helpers import Gcf import copy +import mock + try: - import matplotlib.backends.qt4_compat + from matplotlib.backends.qt4_compat import QtCore + from matplotlib.backends.backend_qt import MODIFIER_KEYS HAS_QT = True + + ShiftModifier = QtCore.Qt.ShiftModifier + ControlModifier, ControlKey = MODIFIER_KEYS['ctrl'] + AltModifier, AltKey = MODIFIER_KEYS['alt'] + SuperModifier, SuperKey = MODIFIER_KEYS['super'] except ImportError: HAS_QT = False @@ -34,3 +42,92 @@ def test_fig_close(): # assert that we have removed the reference to the FigureManager # that got added by plt.figure() assert(init_figs == Gcf.figs) + + +def assert_correct_key(qt_key, qt_mods, qt_text, answer): + plt.switch_backend('Qt4Agg') + qt_canvas = plt.figure().canvas + + event = mock.Mock() + event.isAutoRepeat.return_value = False + event.key.return_value = qt_key + event.modifiers.return_value = qt_mods + event.text.return_value = qt_text + + def receive(event): + assert event.key == answer + + qt_canvas.mpl_connect('key_press_event', receive) + qt_canvas.keyPressEvent(event) + + +@cleanup +@knownfailureif(not HAS_QT) +def test_shift(): + assert_correct_key(QtCore.Qt.Key_A, + ShiftModifier, + u'A', + u'A') + + +@cleanup +@knownfailureif(not HAS_QT) +def test_noshift(): + assert_correct_key(QtCore.Qt.Key_A, + QtCore.Qt.NoModifier, + u'a', + u'a') + + +@cleanup +@knownfailureif(not HAS_QT) +def test_control(): + assert_correct_key(QtCore.Qt.Key_A, + ControlModifier, + u'', + u'ctrl+a') + + +@cleanup +@knownfailureif(not HAS_QT) +def test_unicode(): + assert_correct_key(QtCore.Qt.Key_Aacute, + ShiftModifier, + unichr(193), + unichr(193)) + + +@cleanup +@knownfailureif(not HAS_QT) +def test_unicode_noshift(): + assert_correct_key(QtCore.Qt.Key_Aacute, + QtCore.Qt.NoModifier, + unichr(193), + unichr(225)) + + +@cleanup +@knownfailureif(not HAS_QT) +def test_alt_control(): + assert_correct_key(QtCore.Qt.Key_Control, + AltModifier, + u'', + u'alt+control') + + +@cleanup +@knownfailureif(not HAS_QT) +def test_control_alt(): + assert_correct_key(QtCore.Qt.Key_Alt, + ControlModifier, + u'', + u'ctrl+alt') + + +@cleanup +@knownfailureif(not HAS_QT) +def test_modifier_order(): + assert_correct_key(QtCore.Qt.Key_Aacute, + (ControlModifier & AltModifier & SuperModifier), + u'', + u'ctrl+alt+super+' + unichr(225)) From b80a8e7485e09f82c4c8f8b3e7255cac419d67b1 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 13 Aug 2013 17:36:36 -0700 Subject: [PATCH 06/21] simplify key getting logic --- lib/matplotlib/backends/backend_qt4.py | 40 +++++++++----------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index 7a08560b7e57..f9ba831fd054 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -341,39 +341,25 @@ def _get_key(self, event): return None event_key = event.key() - event_mods = int(event.modifiers()) + event_mods = int(event.modifiers()) # actually a bitmask + mods = [p for p, m, k in MODIFIER_KEYS + if event_key != k and event_mods & m == m] try: # for certain keys (enter, left, backspace, etc) use a word for the # key, rather than unicode key = SPECIAL_KEYS[event_key] except KeyError: - # for easy cases, event.text() handles caps lock and capitalization - # for us - key = unicode(event.text()) - - # if modifier (ctrl, alt, super) keys are being pressed, - # event.text() will be the empty string. QT always gives upper case - # letters, so we have to read from shift and lower() manually. - # Finally, since it is not possible to get the CapsLock state we - # cannot accurately compute the case of a pressed key when - # ctrl+shift+p is pressed. - if key == '': - # python may barf if event_key > 0x10000 - try: - key = unichr(event_key) - except ValueError: - return None - - # if shift is not being pressed, lower() it (CapsLock is - # ignored) - if not event_mods & QtCore.Qt.ShiftModifier: - key = key.lower() - - for prefix, modifier, Qt_key in MODIFIER_KEYS: - if event_key != Qt_key and event_mods & modifier == modifier: - key = u'{0}+{1}'.format(prefix, key) - return key + key = unichr(event_key) + # qt delivers capitalized letters. fix capitalization + # note that capslock is ignored + if 'shift' in mods: + mods.remove('shift') + else: + key = key.lower() + + mods.reverse() + return u'+'.join(mods + [key]) def new_timer(self, *args, **kwargs): """ From 41fdfaeb324d74e8ab3905cd41c85648784c2319 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 13 Aug 2013 17:58:51 -0700 Subject: [PATCH 07/21] expose modifier key indexes --- lib/matplotlib/backends/backend_qt4.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index f9ba831fd054..a922183f417c 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -80,9 +80,14 @@ # define which modifier keys are collected on keyboard events. # elements are (mpl names, Modifier Flag, Qt Key) tuples +SUPER = 0 +ALT = 1 +CTRL = 2 +SHIFT = 3 MODIFIER_KEYS = [('super', QtCore.Qt.MetaModifier, QtCore.Qt.Key_Meta), ('alt', QtCore.Qt.AltModifier, QtCore.Qt.Key_Alt), ('ctrl', QtCore.Qt.ControlModifier, QtCore.Qt.Key_Control), + ('shift', QtCore.Qt.ShiftModifier, QtCore.Qt.Key_Shift), ] if sys.platform == 'darwin': From b22193bbff356d67c04339ed758d7756b27b904f Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 13 Aug 2013 17:59:37 -0700 Subject: [PATCH 08/21] fix tests --- lib/matplotlib/tests/test_backend_qt4.py | 27 ++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_qt4.py b/lib/matplotlib/tests/test_backend_qt4.py index 87d0e351fa2b..c3710fd94629 100644 --- a/lib/matplotlib/tests/test_backend_qt4.py +++ b/lib/matplotlib/tests/test_backend_qt4.py @@ -12,14 +12,15 @@ try: from matplotlib.backends.qt4_compat import QtCore - from matplotlib.backends.backend_qt import MODIFIER_KEYS - HAS_QT = True + from matplotlib.backends.backend_qt4 import MODIFIER_KEYS, SUPER, ALT, CTRL, SHIFT + + _, ControlModifier, ControlKey = MODIFIER_KEYS[CTRL] + _, AltModifier, AltKey = MODIFIER_KEYS[ALT] + _, SuperModifier, SuperKey = MODIFIER_KEYS[SUPER] + _, ShiftModifier, ShiftKey = MODIFIER_KEYS[SHIFT] - ShiftModifier = QtCore.Qt.ShiftModifier - ControlModifier, ControlKey = MODIFIER_KEYS['ctrl'] - AltModifier, AltKey = MODIFIER_KEYS['alt'] - SuperModifier, SuperKey = MODIFIER_KEYS['super'] -except ImportError: + HAS_QT = True +except ImportError as e: HAS_QT = False @@ -72,7 +73,7 @@ def test_shift(): @cleanup @knownfailureif(not HAS_QT) -def test_noshift(): +def test_lower(): assert_correct_key(QtCore.Qt.Key_A, QtCore.Qt.NoModifier, u'a', @@ -90,7 +91,7 @@ def test_control(): @cleanup @knownfailureif(not HAS_QT) -def test_unicode(): +def test_unicode_upper(): assert_correct_key(QtCore.Qt.Key_Aacute, ShiftModifier, unichr(193), @@ -99,7 +100,7 @@ def test_unicode(): @cleanup @knownfailureif(not HAS_QT) -def test_unicode_noshift(): +def test_unicode_lower(): assert_correct_key(QtCore.Qt.Key_Aacute, QtCore.Qt.NoModifier, unichr(193), @@ -109,7 +110,7 @@ def test_unicode_noshift(): @cleanup @knownfailureif(not HAS_QT) def test_alt_control(): - assert_correct_key(QtCore.Qt.Key_Control, + assert_correct_key(ControlKey, AltModifier, u'', u'alt+control') @@ -118,7 +119,7 @@ def test_alt_control(): @cleanup @knownfailureif(not HAS_QT) def test_control_alt(): - assert_correct_key(QtCore.Qt.Key_Alt, + assert_correct_key(AltKey, ControlModifier, u'', u'ctrl+alt') @@ -128,6 +129,6 @@ def test_control_alt(): @knownfailureif(not HAS_QT) def test_modifier_order(): assert_correct_key(QtCore.Qt.Key_Aacute, - (ControlModifier & AltModifier & SuperModifier), + (ControlModifier | AltModifier | SuperModifier), u'', u'ctrl+alt+super+' + unichr(225)) From 16ed6e86415221d7fa0ca0c551e6d42b8807e64a Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 13 Aug 2013 19:10:28 -0700 Subject: [PATCH 09/21] remove unused text() from qt key testing --- lib/matplotlib/tests/test_backend_qt4.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_qt4.py b/lib/matplotlib/tests/test_backend_qt4.py index c3710fd94629..815e9359ff84 100644 --- a/lib/matplotlib/tests/test_backend_qt4.py +++ b/lib/matplotlib/tests/test_backend_qt4.py @@ -45,7 +45,7 @@ def test_fig_close(): assert(init_figs == Gcf.figs) -def assert_correct_key(qt_key, qt_mods, qt_text, answer): +def assert_correct_key(qt_key, qt_mods, answer): plt.switch_backend('Qt4Agg') qt_canvas = plt.figure().canvas @@ -53,7 +53,6 @@ def assert_correct_key(qt_key, qt_mods, qt_text, answer): event.isAutoRepeat.return_value = False event.key.return_value = qt_key event.modifiers.return_value = qt_mods - event.text.return_value = qt_text def receive(event): assert event.key == answer @@ -67,7 +66,6 @@ def receive(event): def test_shift(): assert_correct_key(QtCore.Qt.Key_A, ShiftModifier, - u'A', u'A') @@ -76,7 +74,6 @@ def test_shift(): def test_lower(): assert_correct_key(QtCore.Qt.Key_A, QtCore.Qt.NoModifier, - u'a', u'a') @@ -85,7 +82,6 @@ def test_lower(): def test_control(): assert_correct_key(QtCore.Qt.Key_A, ControlModifier, - u'', u'ctrl+a') @@ -94,7 +90,6 @@ def test_control(): def test_unicode_upper(): assert_correct_key(QtCore.Qt.Key_Aacute, ShiftModifier, - unichr(193), unichr(193)) @@ -103,7 +98,6 @@ def test_unicode_upper(): def test_unicode_lower(): assert_correct_key(QtCore.Qt.Key_Aacute, QtCore.Qt.NoModifier, - unichr(193), unichr(225)) @@ -112,7 +106,6 @@ def test_unicode_lower(): def test_alt_control(): assert_correct_key(ControlKey, AltModifier, - u'', u'alt+control') @@ -121,7 +114,6 @@ def test_alt_control(): def test_control_alt(): assert_correct_key(AltKey, ControlModifier, - u'', u'ctrl+alt') @@ -130,5 +122,4 @@ def test_control_alt(): def test_modifier_order(): assert_correct_key(QtCore.Qt.Key_Aacute, (ControlModifier | AltModifier | SuperModifier), - u'', u'ctrl+alt+super+' + unichr(225)) From 60407dc63bee83cdf804172c600c8a7e8fa0a316 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 13 Aug 2013 19:11:09 -0700 Subject: [PATCH 10/21] backspace tests --- lib/matplotlib/tests/test_backend_qt4.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/matplotlib/tests/test_backend_qt4.py b/lib/matplotlib/tests/test_backend_qt4.py index 815e9359ff84..6fe645ecf8c8 100644 --- a/lib/matplotlib/tests/test_backend_qt4.py +++ b/lib/matplotlib/tests/test_backend_qt4.py @@ -123,3 +123,17 @@ def test_modifier_order(): assert_correct_key(QtCore.Qt.Key_Aacute, (ControlModifier | AltModifier | SuperModifier), u'ctrl+alt+super+' + unichr(225)) + +@cleanup +@knownfailureif(not HAS_QT) +def test_backspace(): + assert_correct_key(QtCore.Qt.Key_Backspace, + QtCore.Qt.NoModifier, + u'backspace') + +@cleanup +@knownfailureif(not HAS_QT) +def test_backspace(): + assert_correct_key(QtCore.Qt.Key_Backspace, + ControlModifier, + u'ctrl+backspace') From bf4e43d451f1be9c2caddf8fe3f7c777f7116794 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Wed, 14 Aug 2013 06:47:25 -0700 Subject: [PATCH 11/21] parens for clarity --- lib/matplotlib/backends/backend_qt4.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index a922183f417c..370d7be634fc 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -348,8 +348,8 @@ def _get_key(self, event): event_key = event.key() event_mods = int(event.modifiers()) # actually a bitmask - mods = [p for p, m, k in MODIFIER_KEYS - if event_key != k and event_mods & m == m] + mods = [name for name, mod_key, qt_key in MODIFIER_KEYS + if event_key != qt_key and (event_mods & mod_key) == mod_key] try: # for certain keys (enter, left, backspace, etc) use a word for the # key, rather than unicode From 32ddf1e978afecdf6bc2c067d757ee399b824fd5 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Wed, 14 Aug 2013 10:09:17 -0700 Subject: [PATCH 12/21] require mock --- lib/matplotlib/tests/test_backend_qt4.py | 16 +++++++++++----- setupext.py | 22 ++++------------------ 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_qt4.py b/lib/matplotlib/tests/test_backend_qt4.py index 6fe645ecf8c8..dd36b9109d98 100644 --- a/lib/matplotlib/tests/test_backend_qt4.py +++ b/lib/matplotlib/tests/test_backend_qt4.py @@ -8,19 +8,23 @@ from matplotlib._pylab_helpers import Gcf import copy -import mock +try: + # mock in python 3.3+ + from unittest import mock +except ImportError: + import mock try: from matplotlib.backends.qt4_compat import QtCore - from matplotlib.backends.backend_qt4 import MODIFIER_KEYS, SUPER, ALT, CTRL, SHIFT + from matplotlib.backends.backend_qt4 import (MODIFIER_KEYS, + SUPER, ALT, CTRL, SHIFT) _, ControlModifier, ControlKey = MODIFIER_KEYS[CTRL] _, AltModifier, AltKey = MODIFIER_KEYS[ALT] _, SuperModifier, SuperKey = MODIFIER_KEYS[SUPER] _, ShiftModifier, ShiftKey = MODIFIER_KEYS[SHIFT] - HAS_QT = True -except ImportError as e: +except ImportError: HAS_QT = False @@ -124,6 +128,7 @@ def test_modifier_order(): (ControlModifier | AltModifier | SuperModifier), u'ctrl+alt+super+' + unichr(225)) + @cleanup @knownfailureif(not HAS_QT) def test_backspace(): @@ -131,9 +136,10 @@ def test_backspace(): QtCore.Qt.NoModifier, u'backspace') + @cleanup @knownfailureif(not HAS_QT) -def test_backspace(): +def test_backspace_mod(): assert_correct_key(QtCore.Qt.Key_Backspace, ControlModifier, u'ctrl+backspace') diff --git a/setupext.py b/setupext.py index 6504263e6d9d..3fe8ce7e49df 100644 --- a/setupext.py +++ b/setupext.py @@ -612,23 +612,6 @@ def get_namespace_packages(self): class Tests(OptionalPackage): name = "tests" - def check(self): - super(Tests, self).check() - - try: - import nose - except ImportError: - return ( - "nose 0.11.1 or later is required to run the " - "matplotlib test suite") - - if nose.__versioninfo__ < (0, 11, 1): - return ( - "nose 0.11.1 or later is required to run the " - "matplotlib test suite") - - return 'using nose version %s' % nose.__version__ - def get_packages(self): return [ 'matplotlib.tests', @@ -648,7 +631,10 @@ def get_package_data(self): ]} def get_install_requires(self): - return ['nose'] + requires = ['nose>=0.11.1'] + if not is_min_version(sys.version_info, '3.3'): + requires += ['mock'] + return requires class Numpy(SetupPackage): From c9721feed3c58a136b997703fe6921952cecbdcf Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Wed, 14 Aug 2013 10:15:52 -0700 Subject: [PATCH 13/21] compare python versions correctly --- setupext.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setupext.py b/setupext.py index 3fe8ce7e49df..538cc00a8c6c 100644 --- a/setupext.py +++ b/setupext.py @@ -632,7 +632,7 @@ def get_package_data(self): def get_install_requires(self): requires = ['nose>=0.11.1'] - if not is_min_version(sys.version_info, '3.3'): + if not sys.version_info > (3, 3): requires += ['mock'] return requires From 5cfbeb4256457ee4f0f4e3d7a6fa00607fc33c21 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Wed, 14 Aug 2013 11:22:26 -0700 Subject: [PATCH 14/21] Tests reports found version of nose/mock --- setupext.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/setupext.py b/setupext.py index 538cc00a8c6c..935196fcb118 100644 --- a/setupext.py +++ b/setupext.py @@ -611,6 +611,40 @@ def get_namespace_packages(self): class Tests(OptionalPackage): name = "tests" + nose_min_version = '0.11.1' + + def check(self): + super(Tests, self).check() + + msgs = [] + msg_template = ('{package} is required to run the matplotlib test ' + 'suite. pip/easy_install may attempt to install it ' + 'after matplotlib.') + ) + + + bad_nose = msg_template.format( + package='nose %s or later' % self.nose_min_version + try: + import nose + if is_min_version(nose.__version__, self.nose_min_version): + msgs += ['using nose version %s' % nose.__version__] + else: + msgs += [bad_nose] + except ImportError: + msgs += [bad_nose] + + + if sys.version_info >= (3, 3): + msgs += ['using unittest.mock'] + else: + try: + import mock + msgs += ['using mock %s' % mock.__version__] + except ImportError: + msgs += [msg_template.format(package='mock')] + + return ' / '.join(msgs) def get_packages(self): return [ @@ -631,8 +665,8 @@ def get_package_data(self): ]} def get_install_requires(self): - requires = ['nose>=0.11.1'] - if not sys.version_info > (3, 3): + requires = ['nose>=%s' % self.nose_min_version] + if not sys.version_info >= (3, 3): requires += ['mock'] return requires From 6770646fa4a1fa6ca33066c34e408685e874a880 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Wed, 14 Aug 2013 12:22:58 -0700 Subject: [PATCH 15/21] stray paren --- setupext.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setupext.py b/setupext.py index 935196fcb118..41f619369d2a 100644 --- a/setupext.py +++ b/setupext.py @@ -620,11 +620,11 @@ def check(self): msg_template = ('{package} is required to run the matplotlib test ' 'suite. pip/easy_install may attempt to install it ' 'after matplotlib.') - ) bad_nose = msg_template.format( package='nose %s or later' % self.nose_min_version + ) try: import nose if is_min_version(nose.__version__, self.nose_min_version): From 2bea38e060da0f45dbf19444fb71b49787b5de00 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Thu, 5 Sep 2013 15:21:46 -0700 Subject: [PATCH 16/21] remove unused imports --- lib/matplotlib/backends/backend_qt4.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index 370d7be634fc..da2c010f6f68 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -2,7 +2,6 @@ import six -import math # might not ever be used import os import re import signal @@ -10,14 +9,6 @@ import matplotlib -####### might not ever be used -from matplotlib import verbose -from matplotlib.cbook import onetrue -from matplotlib.backend_bases import GraphicsContextBase -from matplotlib.backend_bases import RendererBase -from matplotlib.backend_bases import IdleEvent -from matplotlib.mathtext import MathTextParser -####### from matplotlib.cbook import is_string_like from matplotlib.backend_bases import FigureManagerBase from matplotlib.backend_bases import FigureCanvasBase From 8f682522a10a487815b133c490b89efe6b990b00 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Thu, 5 Sep 2013 15:21:56 -0700 Subject: [PATCH 17/21] whitespace --- lib/matplotlib/backends/backend_qt4.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index da2c010f6f68..b3db437faa1f 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -355,7 +355,7 @@ def _get_key(self, event): key = key.lower() mods.reverse() - return u'+'.join(mods + [key]) + return u'+'.join(mods + [key]) def new_timer(self, *args, **kwargs): """ From ec406398f63c6707ee230c2f13557755b7b981df Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Fri, 6 Sep 2013 09:45:53 -0700 Subject: [PATCH 18/21] describe origin of modifier list --- lib/matplotlib/backends/backend_qt4.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index b3db437faa1f..a114eb88949d 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -339,6 +339,9 @@ def _get_key(self, event): event_key = event.key() event_mods = int(event.modifiers()) # actually a bitmask + # get names of the pressed modifier keys + # bit twiddling to pick out modifier keys from event_mods bitmask, + # if event_key is a MODIFIER, it should not be duplicated in mods mods = [name for name, mod_key, qt_key in MODIFIER_KEYS if event_key != qt_key and (event_mods & mod_key) == mod_key] try: From 989ce390a3de7b2abd66ff5c1b375e64b91b93ab Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Fri, 6 Sep 2013 09:52:36 -0700 Subject: [PATCH 19/21] document assert_correct_key --- lib/matplotlib/tests/test_backend_qt4.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/matplotlib/tests/test_backend_qt4.py b/lib/matplotlib/tests/test_backend_qt4.py index dd36b9109d98..2f29a35eb6d5 100644 --- a/lib/matplotlib/tests/test_backend_qt4.py +++ b/lib/matplotlib/tests/test_backend_qt4.py @@ -50,6 +50,12 @@ def test_fig_close(): def assert_correct_key(qt_key, qt_mods, answer): + """ + Make a figure + Send a key_press_event event (using non-public, qt4 backend specific api) + Catch the event + Assert sent and caught keys are the same + """ plt.switch_backend('Qt4Agg') qt_canvas = plt.figure().canvas From 2359f0958125a9b89003c3320e3355dcec24fc12 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Thu, 19 Sep 2013 10:17:33 -0700 Subject: [PATCH 20/21] filter out non-unicode codepoints and add test --- lib/matplotlib/backends/backend_qt4.py | 9 +++++++++ lib/matplotlib/tests/test_backend_qt4.py | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index a114eb88949d..f92c5f86f3df 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -349,6 +349,15 @@ def _get_key(self, event): # key, rather than unicode key = SPECIAL_KEYS[event_key] except KeyError: + # unicode defines code points up to 0x0010ffff + # QT will use Key_Codes larger than that for keyboard keys that are + # are not unicode characters (like multimedia keys) + # skip these + # if you really want them, you should add them to SPECIAL_KEYS + MAX_UNICODE = 0x10ffff + if event_key > MAX_UNICODE: + return None + key = unichr(event_key) # qt delivers capitalized letters. fix capitalization # note that capslock is ignored diff --git a/lib/matplotlib/tests/test_backend_qt4.py b/lib/matplotlib/tests/test_backend_qt4.py index 2f29a35eb6d5..465c8f14142b 100644 --- a/lib/matplotlib/tests/test_backend_qt4.py +++ b/lib/matplotlib/tests/test_backend_qt4.py @@ -149,3 +149,11 @@ def test_backspace_mod(): assert_correct_key(QtCore.Qt.Key_Backspace, ControlModifier, u'ctrl+backspace') + + +@cleanup +@knownfailureif(not HAS_QT) +def test_non_unicode_key(): + assert_correct_key(QtCore.Qt.Key_Play, + QtCore.Qt.NoModifier, + None) From 2a70affb96347641eeb11755367980c4f7eefbb7 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 21 Jan 2014 23:19:25 -0500 Subject: [PATCH 21/21] minor pep8 fix --- lib/matplotlib/backends/backend_qt4.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index f92c5f86f3df..74fe8261a8f7 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -87,8 +87,10 @@ SPECIAL_KEYS.update({QtCore.Qt.Key_Control: 'super', # cmd/apple key QtCore.Qt.Key_Meta: 'control', }) - MODIFIER_KEYS[0] = ('super', QtCore.Qt.ControlModifier, QtCore.Qt.Key_Control) - MODIFIER_KEYS[2] = ('ctrl', QtCore.Qt.MetaModifier, QtCore.Qt.Key_Meta) + MODIFIER_KEYS[0] = ('super', QtCore.Qt.ControlModifier, + QtCore.Qt.Key_Control) + MODIFIER_KEYS[2] = ('ctrl', QtCore.Qt.MetaModifier, + QtCore.Qt.Key_Meta) def fn_name():