1
1
import functools
2
2
import importlib
3
+ import operator
3
4
import os
4
5
import signal
5
6
import sys
15
16
from matplotlib .backends .qt_editor ._formsubplottool import UiSubplotTool
16
17
from . import qt_compat
17
18
from .qt_compat import (
18
- QtCore , QtGui , QtWidgets , __version__ , QT_API ,
19
+ QtCore , QtGui , QtWidgets , Qt , __version__ , QT_API ,
19
20
_devicePixelRatioF , _isdeleted , _setDevicePixelRatio ,
20
21
)
21
22
22
23
backend_version = __version__
23
24
24
- # SPECIAL_KEYS are keys that do *not* return their unicode name
25
- # instead they have manually specified names
26
- SPECIAL_KEYS = {QtCore .Qt .Key_Control : 'control' ,
27
- QtCore .Qt .Key_Shift : 'shift' ,
28
- QtCore .Qt .Key_Alt : 'alt' ,
29
- QtCore .Qt .Key_Meta : 'super' ,
30
- QtCore .Qt .Key_Return : 'enter' ,
31
- QtCore .Qt .Key_Left : 'left' ,
32
- QtCore .Qt .Key_Up : 'up' ,
33
- QtCore .Qt .Key_Right : 'right' ,
34
- QtCore .Qt .Key_Down : 'down' ,
35
- QtCore .Qt .Key_Escape : 'escape' ,
36
- QtCore .Qt .Key_F1 : 'f1' ,
37
- QtCore .Qt .Key_F2 : 'f2' ,
38
- QtCore .Qt .Key_F3 : 'f3' ,
39
- QtCore .Qt .Key_F4 : 'f4' ,
40
- QtCore .Qt .Key_F5 : 'f5' ,
41
- QtCore .Qt .Key_F6 : 'f6' ,
42
- QtCore .Qt .Key_F7 : 'f7' ,
43
- QtCore .Qt .Key_F8 : 'f8' ,
44
- QtCore .Qt .Key_F9 : 'f9' ,
45
- QtCore .Qt .Key_F10 : 'f10' ,
46
- QtCore .Qt .Key_F11 : 'f11' ,
47
- QtCore .Qt .Key_F12 : 'f12' ,
48
- QtCore .Qt .Key_Home : 'home' ,
49
- QtCore .Qt .Key_End : 'end' ,
50
- QtCore .Qt .Key_PageUp : 'pageup' ,
51
- QtCore .Qt .Key_PageDown : 'pagedown' ,
52
- QtCore .Qt .Key_Tab : 'tab' ,
53
- QtCore .Qt .Key_Backspace : 'backspace' ,
54
- QtCore .Qt .Key_Enter : 'enter' ,
55
- QtCore .Qt .Key_Insert : 'insert' ,
56
- QtCore .Qt .Key_Delete : 'delete' ,
57
- QtCore .Qt .Key_Pause : 'pause' ,
58
- QtCore .Qt .Key_SysReq : 'sysreq' ,
59
- QtCore .Qt .Key_Clear : 'clear' , }
25
+ # Enums are specified using numeric values because 1) they are only available
26
+ # as enum attributes on PyQt6 and only available as Qt attributes on PyQt<5.11;
27
+ # 2) Foos = QFlags<Foo> is exported as Qt.Foos on PyQt6 but Qt.Foo on PyQt5.
28
+
29
+ # SPECIAL_KEYS are Qt::Key that do *not* return their unicode name
30
+ # instead they have manually specified names.
31
+ SPECIAL_KEYS = {
32
+ 0x1000000 : 'escape' ,
33
+ 0x1000001 : 'tab' ,
34
+ 0x1000003 : 'backspace' ,
35
+ 0x1000004 : 'enter' ,
36
+ 0x1000005 : 'enter' ,
37
+ 0x1000006 : 'insert' ,
38
+ 0x1000007 : 'delete' ,
39
+ 0x1000008 : 'pause' ,
40
+ 0x100000a : 'sysreq' ,
41
+ 0x100000b : 'clear' ,
42
+ 0x1000010 : 'home' ,
43
+ 0x1000011 : 'end' ,
44
+ 0x1000012 : 'left' ,
45
+ 0x1000013 : 'up' ,
46
+ 0x1000014 : 'right' ,
47
+ 0x1000015 : 'down' ,
48
+ 0x1000016 : 'pageup' ,
49
+ 0x1000017 : 'pagedown' ,
50
+ 0x1000020 : 'shift' ,
51
+ 0x1000021 : 'control' ,
52
+ 0x1000022 : 'super' ,
53
+ 0x1000023 : 'alt' ,
54
+ 0x1000030 : 'f1' ,
55
+ 0x1000031 : 'f2' ,
56
+ 0x1000032 : 'f3' ,
57
+ 0x1000033 : 'f4' ,
58
+ 0x1000034 : 'f5' ,
59
+ 0x1000035 : 'f6' ,
60
+ 0x1000036 : 'f7' ,
61
+ 0x1000037 : 'f8' ,
62
+ 0x1000038 : 'f9' ,
63
+ 0x1000039 : 'f10' ,
64
+ 0x100003a : 'f11' ,
65
+ 0x100003b : 'f12' ,
66
+ }
60
67
if sys .platform == 'darwin' :
61
68
# in OSX, the control and super (aka cmd/apple) keys are switched, so
62
69
# switch them back.
63
- SPECIAL_KEYS .update ({QtCore .Qt .Key_Control : 'cmd' , # cmd/apple key
64
- QtCore .Qt .Key_Meta : 'control' ,
65
- })
70
+ SPECIAL_KEYS .update ({
71
+ 0x01000021 : 'cmd' , # cmd/apple key
72
+ 0x01000022 : 'control' ,
73
+ })
66
74
# Define which modifier keys are collected on keyboard events.
67
- # Elements are (Modifier Flag , Qt Key) tuples.
75
+ # Elements are (Qt::KeyboardModifier(s) , Qt:: Key) tuples.
68
76
# Order determines the modifier order (ctrl+alt+...) reported by Matplotlib.
69
77
_MODIFIER_KEYS = [
70
- (QtCore . Qt . ShiftModifier , QtCore . Qt . Key_Shift ),
71
- (QtCore . Qt . ControlModifier , QtCore . Qt . Key_Control ),
72
- (QtCore . Qt . AltModifier , QtCore . Qt . Key_Alt ),
73
- (QtCore . Qt . MetaModifier , QtCore . Qt . Key_Meta ),
78
+ (0x02000000 , 0x01000020 ), # shift
79
+ (0x04000000 , 0x01000021 ), # control
80
+ (0x08000000 , 0x01000023 ), # alt
81
+ (0x10000000 , 0x01000022 ), # meta
74
82
]
75
83
cursord = {
76
- cursors .MOVE : QtCore . Qt .SizeAllCursor ,
77
- cursors .HAND : QtCore . Qt .PointingHandCursor ,
78
- cursors .POINTER : QtCore . Qt .ArrowCursor ,
79
- cursors .SELECT_REGION : QtCore . Qt .CrossCursor ,
80
- cursors .WAIT : QtCore . Qt .WaitCursor ,
81
- }
84
+ cursors .MOVE : Qt .CursorShape ( 9 ), # SizeAllCursor
85
+ cursors .HAND : Qt .CursorShape ( 13 ), # PointingHandCursor
86
+ cursors .POINTER : Qt .CursorShape ( 0 ), # ArrowCursor
87
+ cursors .SELECT_REGION : Qt .CursorShape ( 2 ), # CrossCursor
88
+ cursors .WAIT : Qt .CursorShape ( 3 ), # WaitCursor
89
+ }
82
90
SUPER = 0 # Deprecated.
83
91
ALT = 1 # Deprecated.
84
92
CTRL = 2 # Deprecated.
87
95
(SPECIAL_KEYS [key ], mod , key ) for mod , key in _MODIFIER_KEYS ]
88
96
89
97
98
+ def _to_int (x ):
99
+ return x .value if QT_API == "PyQt6" else int (x )
100
+
101
+
90
102
# make place holder
91
103
qApp = None
92
104
@@ -140,17 +152,17 @@ def _allow_super_init(__init__):
140
152
Decorator for ``__init__`` to allow ``super().__init__`` on PyQt4/PySide2.
141
153
"""
142
154
143
- if QT_API == "PyQt5" :
155
+ if QT_API in [ "PyQt5" , "PyQt6" ] :
144
156
145
157
return __init__
146
158
147
159
else :
148
- # To work around lack of cooperative inheritance in PyQt4, PySide,
149
- # and PySide2 , when calling FigureCanvasQT.__init__, we temporarily
160
+ # To work around lack of cooperative inheritance in PyQt4 and
161
+ # PySide{,2,6} , when calling FigureCanvasQT.__init__, we temporarily
150
162
# patch QWidget.__init__ by a cooperative version, that first calls
151
163
# QWidget.__init__ with no additional arguments, and then finds the
152
164
# next class in the MRO with an __init__ that does support cooperative
153
- # inheritance (i.e., not defined by the PyQt4, PySide, PySide2, sip
165
+ # inheritance (i.e., not defined by the PyQt4 or sip, or PySide{,2,6}
154
166
# or Shiboken packages), and manually call its `__init__`, once again
155
167
# passing the additional arguments.
156
168
@@ -162,7 +174,9 @@ def cooperative_qwidget_init(self, *args, **kwargs):
162
174
next_coop_init = next (
163
175
cls for cls in mro [mro .index (QtWidgets .QWidget ) + 1 :]
164
176
if cls .__module__ .split ("." )[0 ] not in [
165
- "PyQt4" , "sip" , "PySide" , "PySide2" , "Shiboken" ])
177
+ "PyQt4" , "sip" ,
178
+ "PySide" , "PySide2" , "PySide6" , "Shiboken" ,
179
+ ])
166
180
next_coop_init .__init__ (self , * args , ** kwargs )
167
181
168
182
@functools .wraps (__init__ )
@@ -207,13 +221,13 @@ class FigureCanvasQT(QtWidgets.QWidget, FigureCanvasBase):
207
221
required_interactive_framework = "qt5"
208
222
_timer_cls = TimerQT
209
223
210
- # map Qt button codes to MouseEvent's ones:
211
- buttond = { QtCore . Qt . LeftButton : MouseButton .LEFT ,
212
- QtCore . Qt . MidButton : MouseButton .MIDDLE ,
213
- QtCore . Qt . RightButton : MouseButton .RIGHT ,
214
- QtCore . Qt . XButton1 : MouseButton .BACK ,
215
- QtCore . Qt . XButton2 : MouseButton .FORWARD ,
216
- }
224
+ buttond = { # Map Qt::MouseButton(s) to MouseEvents.
225
+ 0x01 : MouseButton .LEFT ,
226
+ 0x02 : MouseButton .RIGHT ,
227
+ 0x04 : MouseButton .MIDDLE ,
228
+ 0x08 : MouseButton .BACK ,
229
+ 0x10 : MouseButton .FORWARD ,
230
+ }
217
231
218
232
@_allow_super_init
219
233
def __init__ (self , figure ):
@@ -233,11 +247,11 @@ def __init__(self, figure):
233
247
self ._is_drawing = False
234
248
self ._draw_rect_callback = lambda painter : None
235
249
236
- self .setAttribute (QtCore . Qt .WA_OpaquePaintEvent )
250
+ self .setAttribute (4 ) # Qt.WidgetAttribute. WA_OpaquePaintEvent
237
251
self .setMouseTracking (True )
238
252
self .resize (* self .get_width_height ())
239
253
240
- palette = QtGui .QPalette (QtCore . Qt . white )
254
+ palette = QtGui .QPalette (QtGui . QColor ( " white" ) )
241
255
self .setPalette (palette )
242
256
243
257
def _update_figure_dpi (self ):
@@ -283,7 +297,7 @@ def get_width_height(self):
283
297
284
298
def enterEvent (self , event ):
285
299
try :
286
- x , y = self .mouseEventCoords (event . pos ( ))
300
+ x , y = self .mouseEventCoords (self . _get_position ( event ))
287
301
except AttributeError :
288
302
# the event from PyQt4 does not include the position
289
303
x = y = None
@@ -293,6 +307,9 @@ def leaveEvent(self, event):
293
307
QtWidgets .QApplication .restoreOverrideCursor ()
294
308
FigureCanvasBase .leave_notify_event (self , guiEvent = event )
295
309
310
+ _get_position = operator .methodcaller (
311
+ "position" if QT_API in ["PyQt6" , "PySide6" ] else "pos" )
312
+
296
313
def mouseEventCoords (self , pos ):
297
314
"""
298
315
Calculate mouse coordinates in physical pixels.
@@ -310,34 +327,34 @@ def mouseEventCoords(self, pos):
310
327
return x * dpi_ratio , y * dpi_ratio
311
328
312
329
def mousePressEvent (self , event ):
313
- x , y = self .mouseEventCoords (event . pos ( ))
314
- button = self .buttond .get (event .button ())
330
+ x , y = self .mouseEventCoords (self . _get_position ( event ))
331
+ button = self .buttond .get (_to_int ( event .button () ))
315
332
if button is not None :
316
333
FigureCanvasBase .button_press_event (self , x , y , button ,
317
334
guiEvent = event )
318
335
319
336
def mouseDoubleClickEvent (self , event ):
320
- x , y = self .mouseEventCoords (event . pos ( ))
321
- button = self .buttond .get (event .button ())
337
+ x , y = self .mouseEventCoords (self . _get_position ( event ))
338
+ button = self .buttond .get (_to_int ( event .button () ))
322
339
if button is not None :
323
340
FigureCanvasBase .button_press_event (self , x , y ,
324
341
button , dblclick = True ,
325
342
guiEvent = event )
326
343
327
344
def mouseMoveEvent (self , event ):
328
- x , y = self .mouseEventCoords (event )
345
+ x , y = self .mouseEventCoords (self . _get_position ( event ) )
329
346
FigureCanvasBase .motion_notify_event (self , x , y , guiEvent = event )
330
347
331
348
def mouseReleaseEvent (self , event ):
332
- x , y = self .mouseEventCoords (event )
333
- button = self .buttond .get (event .button ())
349
+ x , y = self .mouseEventCoords (self . _get_position ( event ) )
350
+ button = self .buttond .get (_to_int ( event .button () ))
334
351
if button is not None :
335
352
FigureCanvasBase .button_release_event (self , x , y , button ,
336
353
guiEvent = event )
337
354
338
355
if QtCore .qVersion () >= "5." :
339
356
def wheelEvent (self , event ):
340
- x , y = self .mouseEventCoords (event )
357
+ x , y = self .mouseEventCoords (self . _get_position ( event ) )
341
358
# from QWheelEvent::delta doc
342
359
if event .pixelDelta ().x () == 0 and event .pixelDelta ().y () == 0 :
343
360
steps = event .angleDelta ().y () / 120
@@ -368,6 +385,9 @@ def keyReleaseEvent(self, event):
368
385
FigureCanvasBase .key_release_event (self , key , guiEvent = event )
369
386
370
387
def resizeEvent (self , event ):
388
+ frame = sys ._getframe ()
389
+ if frame .f_code is frame .f_back .f_code : # Prevent PyQt6 recursion.
390
+ return
371
391
w = event .size ().width () * self ._dpi_ratio
372
392
h = event .size ().height () * self ._dpi_ratio
373
393
dpival = self .figure .dpi
@@ -388,7 +408,7 @@ def minumumSizeHint(self):
388
408
389
409
def _get_key (self , event ):
390
410
event_key = event .key ()
391
- event_mods = int (event .modifiers ()) # actually a bitmask
411
+ event_mods = _to_int (event .modifiers ()) # actually a bitmask
392
412
393
413
# get names of the pressed modifier keys
394
414
# 'control' is named 'control' when a standalone key, but 'ctrl' when a
@@ -433,7 +453,7 @@ def start_event_loop(self, timeout=0):
433
453
if timeout > 0 :
434
454
timer = QtCore .QTimer .singleShot (int (timeout * 1000 ),
435
455
event_loop .quit )
436
- event_loop . exec_ ( )
456
+ qt_compat . _exec ( event_loop )
437
457
438
458
def stop_event_loop (self , event = None ):
439
459
# docstring inherited
@@ -575,7 +595,7 @@ def __init__(self, canvas, num):
575
595
# StrongFocus accepts both tab and click to focus and will enable the
576
596
# canvas to process event without clicking.
577
597
# https://doc.qt.io/qt-5/qt.html#FocusPolicy-enum
578
- self .canvas .setFocusPolicy (QtCore . Qt . StrongFocus )
598
+ self .canvas .setFocusPolicy (0x1 | 0x2 | 0x8 ) # StrongFocus
579
599
self .canvas .setFocus ()
580
600
581
601
self .window .raise_ ()
@@ -654,8 +674,8 @@ class NavigationToolbar2QT(NavigationToolbar2, QtWidgets.QToolBar):
654
674
def __init__ (self , canvas , parent , coordinates = True ):
655
675
"""coordinates: should we show the coordinates on the right?"""
656
676
QtWidgets .QToolBar .__init__ (self , parent )
657
- self .setAllowedAreas (
658
- QtCore . Qt .TopToolBarArea | QtCore . Qt . BottomToolBarArea )
677
+ self .setAllowedAreas ( # Qt::TopToolBarArea | BottomToolBarArea
678
+ Qt .ToolBarAreas ( 0x4 | 0x8 ) )
659
679
660
680
self .coordinates = coordinates
661
681
self ._actions = {} # mapping of toolitem method names to QActions.
@@ -677,11 +697,10 @@ def __init__(self, canvas, parent, coordinates=True):
677
697
# will resize this label instead of the buttons.
678
698
if self .coordinates :
679
699
self .locLabel = QtWidgets .QLabel ("" , self )
680
- self .locLabel .setAlignment (
681
- QtCore . Qt .AlignRight | QtCore . Qt . AlignVCenter )
700
+ self .locLabel .setAlignment ( # Qt::AlignRight | AlignVCenter
701
+ Qt .Alignment ( 0x02 | 0x80 ) )
682
702
self .locLabel .setSizePolicy (
683
- QtWidgets .QSizePolicy (QtWidgets .QSizePolicy .Expanding ,
684
- QtWidgets .QSizePolicy .Ignored ))
703
+ QtWidgets .QSizePolicy (7 , 8 )) # Expanding, Ignored
685
704
labelAction = self .addWidget (self .locLabel )
686
705
labelAction .setVisible (True )
687
706
@@ -714,8 +733,8 @@ def _icon(self, name):
714
733
_setDevicePixelRatio (pm , _devicePixelRatioF (self ))
715
734
if self .palette ().color (self .backgroundRole ()).value () < 128 :
716
735
icon_color = self .palette ().color (self .foregroundRole ())
717
- mask = pm .createMaskFromColor (QtGui . QColor ( 'black' ),
718
- QtCore . Qt .MaskOutColor )
736
+ mask = pm .createMaskFromColor (
737
+ QtGui . QColor ( 'black' ), 1 ) # Qt.MaskMode. MaskOutColor
719
738
pm .fill (icon_color )
720
739
pm .setMask (mask )
721
740
return QtGui .QIcon (pm )
@@ -785,7 +804,7 @@ def configure_subplots(self):
785
804
image = str (cbook ._get_data_path ('images/matplotlib.png' ))
786
805
dia = SubplotToolQt (self .canvas .figure , self .canvas .parent ())
787
806
dia .setWindowIcon (QtGui .QIcon (image ))
788
- dia . exec_ ( )
807
+ qt_compat . _exec ( dia )
789
808
790
809
def save_figure (self , * args ):
791
810
filetypes = self .canvas .get_supported_filetypes_grouped ()
@@ -874,7 +893,7 @@ def _export_values(self):
874
893
QtGui .QFontMetrics (text .document ().defaultFont ())
875
894
.size (0 , text .toPlainText ()).height () + 20 )
876
895
text .setMaximumSize (size )
877
- dialog . exec_ ( )
896
+ qt_compat . _exec ( dialog )
878
897
879
898
def _on_value_changed (self ):
880
899
self ._figure .subplots_adjust (** {attr : self ._widgets [attr ].value ()
@@ -899,14 +918,13 @@ class ToolbarQt(ToolContainerBase, QtWidgets.QToolBar):
899
918
def __init__ (self , toolmanager , parent ):
900
919
ToolContainerBase .__init__ (self , toolmanager )
901
920
QtWidgets .QToolBar .__init__ (self , parent )
902
- self .setAllowedAreas (
903
- QtCore . Qt .TopToolBarArea | QtCore . Qt . BottomToolBarArea )
921
+ self .setAllowedAreas ( # Qt::TopToolBarArea | BottomToolBarArea
922
+ Qt .ToolBarAreas ( 0x4 | 0x8 ) )
904
923
message_label = QtWidgets .QLabel ("" )
905
- message_label .setAlignment (
906
- QtCore . Qt .AlignRight | QtCore . Qt . AlignVCenter )
924
+ message_label .setAlignment ( # Qt::AlignRight | AlignVCenter
925
+ Qt .Alignment ( 0x02 | 0x80 ) )
907
926
message_label .setSizePolicy (
908
- QtWidgets .QSizePolicy (QtWidgets .QSizePolicy .Expanding ,
909
- QtWidgets .QSizePolicy .Ignored ))
927
+ QtWidgets .QSizePolicy (7 , 8 )) # Expanding, Ignored
910
928
self ._message_action = self .addWidget (message_label )
911
929
self ._toolitems = {}
912
930
self ._groups = {}
@@ -1031,7 +1049,7 @@ def mainloop():
1031
1049
if is_python_signal_handler :
1032
1050
signal .signal (signal .SIGINT , signal .SIG_DFL )
1033
1051
try :
1034
- qApp . exec_ ( )
1052
+ qt_compat . _exec ( qApp )
1035
1053
finally :
1036
1054
# reset the SIGINT exception handler
1037
1055
if is_python_signal_handler :
0 commit comments