11import functools
22import importlib
3+ import operator
34import os
45import signal
56import sys
1516from matplotlib .backends .qt_editor ._formsubplottool import UiSubplotTool
1617from .import qt_compat
1718from .qt_compat import (
18- QtCore ,QtGui ,QtWidgets ,__version__ ,QT_API ,
19+ QtCore ,QtGui ,QtWidgets ,Qt , __version__ ,QT_API ,
1920_devicePixelRatioF ,_isdeleted ,_setDevicePixelRatio ,
2021)
2122
2223backend_version = __version__
2324
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+ }
6067if sys .platform == 'darwin' :
6168# in OSX, the control and super (aka cmd/apple) keys are switched, so
6269# 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+ })
6674# 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.
6876# Order determines the modifier order (ctrl+alt+...) reported by Matplotlib.
6977_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
7482]
7583cursord = {
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+ }
8290SUPER = 0 # Deprecated.
8391ALT = 1 # Deprecated.
8492CTRL = 2 # Deprecated.
8795 (SPECIAL_KEYS [key ],mod ,key )for mod ,key in _MODIFIER_KEYS ]
8896
8997
98+ def _to_int (x ):
99+ return x .value if QT_API == "PyQt6" else int (x )
100+
101+
90102# make place holder
91103qApp = None
92104
@@ -140,17 +152,17 @@ def _allow_super_init(__init__):
140152 Decorator for ``__init__`` to allow ``super().__init__`` on PyQt4/PySide2.
141153 """
142154
143- if QT_API == "PyQt5" :
155+ if QT_API in [ "PyQt5" , "PyQt6" ] :
144156
145157return __init__
146158
147159else :
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
150162# patch QWidget.__init__ by a cooperative version, that first calls
151163# QWidget.__init__ with no additional arguments, and then finds the
152164# 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}
154166# or Shiboken packages), and manually call its `__init__`, once again
155167# passing the additional arguments.
156168
@@ -162,7 +174,9 @@ def cooperative_qwidget_init(self, *args, **kwargs):
162174next_coop_init = next (
163175cls for cls in mro [mro .index (QtWidgets .QWidget )+ 1 :]
164176if cls .__module__ .split ("." )[0 ]not in [
165- "PyQt4" ,"sip" ,"PySide" ,"PySide2" ,"Shiboken" ])
177+ "PyQt4" ,"sip" ,
178+ "PySide" ,"PySide2" ,"PySide6" ,"Shiboken" ,
179+ ])
166180next_coop_init .__init__ (self ,* args ,** kwargs )
167181
168182@functools .wraps (__init__ )
@@ -207,13 +221,13 @@ class FigureCanvasQT(QtWidgets.QWidget, FigureCanvasBase):
207221required_interactive_framework = "qt5"
208222_timer_cls = TimerQT
209223
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+ }
217231
218232@_allow_super_init
219233def __init__ (self ,figure ):
@@ -233,11 +247,11 @@ def __init__(self, figure):
233247self ._is_drawing = False
234248self ._draw_rect_callback = lambda painter :None
235249
236- self .setAttribute (QtCore . Qt .WA_OpaquePaintEvent )
250+ self .setAttribute (4 ) # Qt.WidgetAttribute. WA_OpaquePaintEvent
237251self .setMouseTracking (True )
238252self .resize (* self .get_width_height ())
239253
240- palette = QtGui .QPalette (QtCore . Qt . white )
254+ palette = QtGui .QPalette (QtGui . QColor ( " white" ) )
241255self .setPalette (palette )
242256
243257def _update_figure_dpi (self ):
@@ -283,7 +297,7 @@ def get_width_height(self):
283297
284298def enterEvent (self ,event ):
285299try :
286- x ,y = self .mouseEventCoords (event . pos ( ))
300+ x ,y = self .mouseEventCoords (self . _get_position ( event ))
287301except AttributeError :
288302# the event from PyQt4 does not include the position
289303x = y = None
@@ -293,6 +307,9 @@ def leaveEvent(self, event):
293307QtWidgets .QApplication .restoreOverrideCursor ()
294308FigureCanvasBase .leave_notify_event (self ,guiEvent = event )
295309
310+ _get_position = operator .methodcaller (
311+ "position" if QT_API in ["PyQt6" ,"PySide6" ]else "pos" )
312+
296313def mouseEventCoords (self ,pos ):
297314"""
298315 Calculate mouse coordinates in physical pixels.
@@ -310,34 +327,34 @@ def mouseEventCoords(self, pos):
310327return x * dpi_ratio ,y * dpi_ratio
311328
312329def 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 () ))
315332if button is not None :
316333FigureCanvasBase .button_press_event (self ,x ,y ,button ,
317334guiEvent = event )
318335
319336def 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 () ))
322339if button is not None :
323340FigureCanvasBase .button_press_event (self ,x ,y ,
324341button ,dblclick = True ,
325342guiEvent = event )
326343
327344def mouseMoveEvent (self ,event ):
328- x ,y = self .mouseEventCoords (event )
345+ x ,y = self .mouseEventCoords (self . _get_position ( event ) )
329346FigureCanvasBase .motion_notify_event (self ,x ,y ,guiEvent = event )
330347
331348def 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 () ))
334351if button is not None :
335352FigureCanvasBase .button_release_event (self ,x ,y ,button ,
336353guiEvent = event )
337354
338355if QtCore .qVersion ()>= "5." :
339356def wheelEvent (self ,event ):
340- x ,y = self .mouseEventCoords (event )
357+ x ,y = self .mouseEventCoords (self . _get_position ( event ) )
341358# from QWheelEvent::delta doc
342359if event .pixelDelta ().x ()== 0 and event .pixelDelta ().y ()== 0 :
343360steps = event .angleDelta ().y ()/ 120
@@ -368,6 +385,9 @@ def keyReleaseEvent(self, event):
368385FigureCanvasBase .key_release_event (self ,key ,guiEvent = event )
369386
370387def resizeEvent (self ,event ):
388+ frame = sys ._getframe ()
389+ if frame .f_code is frame .f_back .f_code :# Prevent PyQt6 recursion.
390+ return
371391w = event .size ().width ()* self ._dpi_ratio
372392h = event .size ().height ()* self ._dpi_ratio
373393dpival = self .figure .dpi
@@ -388,7 +408,7 @@ def minumumSizeHint(self):
388408
389409def _get_key (self ,event ):
390410event_key = event .key ()
391- event_mods = int (event .modifiers ())# actually a bitmask
411+ event_mods = _to_int (event .modifiers ())# actually a bitmask
392412
393413# get names of the pressed modifier keys
394414# 'control' is named 'control' when a standalone key, but 'ctrl' when a
@@ -433,7 +453,7 @@ def start_event_loop(self, timeout=0):
433453if timeout > 0 :
434454timer = QtCore .QTimer .singleShot (int (timeout * 1000 ),
435455event_loop .quit )
436- event_loop . exec_ ( )
456+ qt_compat . _exec ( event_loop )
437457
438458def stop_event_loop (self ,event = None ):
439459# docstring inherited
@@ -575,7 +595,7 @@ def __init__(self, canvas, num):
575595# StrongFocus accepts both tab and click to focus and will enable the
576596# canvas to process event without clicking.
577597# 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
579599self .canvas .setFocus ()
580600
581601self .window .raise_ ()
@@ -654,8 +674,8 @@ class NavigationToolbar2QT(NavigationToolbar2, QtWidgets.QToolBar):
654674def __init__ (self ,canvas ,parent ,coordinates = True ):
655675"""coordinates: should we show the coordinates on the right?"""
656676QtWidgets .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 ) )
659679
660680self .coordinates = coordinates
661681self ._actions = {}# mapping of toolitem method names to QActions.
@@ -677,11 +697,10 @@ def __init__(self, canvas, parent, coordinates=True):
677697# will resize this label instead of the buttons.
678698if self .coordinates :
679699self .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 ) )
682702self .locLabel .setSizePolicy (
683- QtWidgets .QSizePolicy (QtWidgets .QSizePolicy .Expanding ,
684- QtWidgets .QSizePolicy .Ignored ))
703+ QtWidgets .QSizePolicy (7 ,8 ))# Expanding, Ignored
685704labelAction = self .addWidget (self .locLabel )
686705labelAction .setVisible (True )
687706
@@ -714,8 +733,8 @@ def _icon(self, name):
714733_setDevicePixelRatio (pm ,_devicePixelRatioF (self ))
715734if self .palette ().color (self .backgroundRole ()).value ()< 128 :
716735icon_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
719738pm .fill (icon_color )
720739pm .setMask (mask )
721740return QtGui .QIcon (pm )
@@ -785,7 +804,7 @@ def configure_subplots(self):
785804image = str (cbook ._get_data_path ('images/matplotlib.png' ))
786805dia = SubplotToolQt (self .canvas .figure ,self .canvas .parent ())
787806dia .setWindowIcon (QtGui .QIcon (image ))
788- dia . exec_ ( )
807+ qt_compat . _exec ( dia )
789808
790809def save_figure (self ,* args ):
791810filetypes = self .canvas .get_supported_filetypes_grouped ()
@@ -874,7 +893,7 @@ def _export_values(self):
874893QtGui .QFontMetrics (text .document ().defaultFont ())
875894 .size (0 ,text .toPlainText ()).height ()+ 20 )
876895text .setMaximumSize (size )
877- dialog . exec_ ( )
896+ qt_compat . _exec ( dialog )
878897
879898def _on_value_changed (self ):
880899self ._figure .subplots_adjust (** {attr :self ._widgets [attr ].value ()
@@ -899,14 +918,13 @@ class ToolbarQt(ToolContainerBase, QtWidgets.QToolBar):
899918def __init__ (self ,toolmanager ,parent ):
900919ToolContainerBase .__init__ (self ,toolmanager )
901920QtWidgets .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 ) )
904923message_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 ) )
907926message_label .setSizePolicy (
908- QtWidgets .QSizePolicy (QtWidgets .QSizePolicy .Expanding ,
909- QtWidgets .QSizePolicy .Ignored ))
927+ QtWidgets .QSizePolicy (7 ,8 ))# Expanding, Ignored
910928self ._message_action = self .addWidget (message_label )
911929self ._toolitems = {}
912930self ._groups = {}
@@ -1031,7 +1049,7 @@ def mainloop():
10311049if is_python_signal_handler :
10321050signal .signal (signal .SIGINT ,signal .SIG_DFL )
10331051try :
1034- qApp . exec_ ( )
1052+ qt_compat . _exec ( qApp )
10351053finally :
10361054# reset the SIGINT exception handler
10371055if is_python_signal_handler :