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
25+ # Foos = QFlags<Foo> are exported as Qt.Foos on PyQt6 but Qt.Foo on PyQt5, so
26+ # we just hard-code numeric values for simplicity.
27+
2428# SPECIAL_KEYS are keys that do *not* return their unicode name
2529# 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' , }
30+ SPECIAL_KEYS = {
31+ Qt .Key .Key_Control :'control' ,
32+ Qt .Key .Key_Shift :'shift' ,
33+ Qt .Key .Key_Alt :'alt' ,
34+ Qt .Key .Key_Meta :'super' ,
35+ Qt .Key .Key_Return :'enter' ,
36+ Qt .Key .Key_Left :'left' ,
37+ Qt .Key .Key_Up :'up' ,
38+ Qt .Key .Key_Right :'right' ,
39+ Qt .Key .Key_Down :'down' ,
40+ Qt .Key .Key_Escape :'escape' ,
41+ Qt .Key .Key_F1 :'f1' ,
42+ Qt .Key .Key_F2 :'f2' ,
43+ Qt .Key .Key_F3 :'f3' ,
44+ Qt .Key .Key_F4 :'f4' ,
45+ Qt .Key .Key_F5 :'f5' ,
46+ Qt .Key .Key_F6 :'f6' ,
47+ Qt .Key .Key_F7 :'f7' ,
48+ Qt .Key .Key_F8 :'f8' ,
49+ Qt .Key .Key_F9 :'f9' ,
50+ Qt .Key .Key_F10 :'f10' ,
51+ Qt .Key .Key_F11 :'f11' ,
52+ Qt .Key .Key_F12 :'f12' ,
53+ Qt .Key .Key_Home :'home' ,
54+ Qt .Key .Key_End :'end' ,
55+ Qt .Key .Key_PageUp :'pageup' ,
56+ Qt .Key .Key_PageDown :'pagedown' ,
57+ Qt .Key .Key_Tab :'tab' ,
58+ Qt .Key .Key_Backspace :'backspace' ,
59+ Qt .Key .Key_Enter :'enter' ,
60+ Qt .Key .Key_Insert :'insert' ,
61+ Qt .Key .Key_Delete :'delete' ,
62+ Qt .Key .Key_Pause :'pause' ,
63+ Qt .Key .Key_SysReq :'sysreq' ,
64+ Qt .Key .Key_Clear :'clear' ,
65+ }
6066if sys .platform == 'darwin' :
6167# in OSX, the control and super (aka cmd/apple) keys are switched, so
6268# switch them back.
63- SPECIAL_KEYS .update ({QtCore .Qt .Key_Control :'cmd' ,# cmd/apple key
64- QtCore .Qt .Key_Meta :'control' ,
65- })
69+ SPECIAL_KEYS .update ({
70+ Qt .Key .Key_Control :'cmd' ,# cmd/apple key
71+ Qt .Key .Key_Meta :'control' ,
72+ })
6673# Define which modifier keys are collected on keyboard events.
67- # Elements are (Modifier Flag , Qt Key) tuples.
74+ # Elements are (Qt::KeyboardModifier(s) , Qt Key) tuples.
6875# Order determines the modifier order (ctrl+alt+...) reported by Matplotlib.
6976_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 ),
77+ (0x02000000 , Qt . Key .Key_Shift ),
78+ (0x04000000 , Qt . Key .Key_Control ),
79+ (0x08000000 , Qt . Key .Key_Alt ),
80+ (0x10000000 , Qt . Key .Key_Meta ),
7481]
7582cursord = {
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- }
83+ cursors .MOVE :Qt . CursorShape .SizeAllCursor ,
84+ cursors .HAND :Qt . CursorShape .PointingHandCursor ,
85+ cursors .POINTER :Qt . CursorShape .ArrowCursor ,
86+ cursors .SELECT_REGION :Qt . CursorShape .CrossCursor ,
87+ cursors .WAIT :Qt . CursorShape .WaitCursor ,
88+ }
8289SUPER = 0 # Deprecated.
8390ALT = 1 # Deprecated.
8491CTRL = 2 # Deprecated.
8794 (SPECIAL_KEYS [key ],mod ,key )for mod ,key in _MODIFIER_KEYS ]
8895
8996
97+ def _to_int (x ):
98+ return x .value if QT_API == "PyQt6" else int (x )
99+
100+
90101# make place holder
91102qApp = None
92103
@@ -140,17 +151,17 @@ def _allow_super_init(__init__):
140151 Decorator for ``__init__`` to allow ``super().__init__`` on PyQt4/PySide2.
141152 """
142153
143- if QT_API == "PyQt5" :
154+ if QT_API in [ "PyQt5" , "PyQt6" ] :
144155
145156return __init__
146157
147158else :
148- # To work around lack of cooperative inheritance in PyQt4, PySide,
149- #and PySide2 , when calling FigureCanvasQT.__init__, we temporarily
159+ # To work around lack of cooperative inheritance in PyQt4 and
160+ #PySide{,2,6} , when calling FigureCanvasQT.__init__, we temporarily
150161# patch QWidget.__init__ by a cooperative version, that first calls
151162# QWidget.__init__ with no additional arguments, and then finds the
152163# next class in the MRO with an __init__ that does support cooperative
153- # inheritance (i.e., not defined by the PyQt4, PySide, PySide2, sip
164+ # inheritance (i.e., not defined by the PyQt4 or sip, or PySide{,2,6}
154165# or Shiboken packages), and manually call its `__init__`, once again
155166# passing the additional arguments.
156167
@@ -162,7 +173,9 @@ def cooperative_qwidget_init(self, *args, **kwargs):
162173next_coop_init = next (
163174cls for cls in mro [mro .index (QtWidgets .QWidget )+ 1 :]
164175if cls .__module__ .split ("." )[0 ]not in [
165- "PyQt4" ,"sip" ,"PySide" ,"PySide2" ,"Shiboken" ])
176+ "PyQt4" ,"sip" ,
177+ "PySide" ,"PySide2" ,"PySide6" ,"Shiboken" ,
178+ ])
166179next_coop_init .__init__ (self ,* args ,** kwargs )
167180
168181@functools .wraps (__init__ )
@@ -207,13 +220,13 @@ class FigureCanvasQT(QtWidgets.QWidget, FigureCanvasBase):
207220required_interactive_framework = "qt5"
208221_timer_cls = TimerQT
209222
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- }
223+ buttond = { # Map Qt::MouseButton(s) to MouseEvents.
224+ 0x01 :MouseButton .LEFT ,
225+ 0x02 :MouseButton .RIGHT ,
226+ 0x04 :MouseButton .MIDDLE ,
227+ 0x08 :MouseButton .BACK ,
228+ 0x10 :MouseButton .FORWARD ,
229+ }
217230
218231@_allow_super_init
219232def __init__ (self ,figure ):
@@ -233,11 +246,11 @@ def __init__(self, figure):
233246self ._is_drawing = False
234247self ._draw_rect_callback = lambda painter :None
235248
236- self .setAttribute (QtCore . Qt .WA_OpaquePaintEvent )
249+ self .setAttribute (Qt . WidgetAttribute .WA_OpaquePaintEvent )
237250self .setMouseTracking (True )
238251self .resize (* self .get_width_height ())
239252
240- palette = QtGui .QPalette (QtCore . Qt . white )
253+ palette = QtGui .QPalette (QtGui . QColor ( " white" ) )
241254self .setPalette (palette )
242255
243256def _update_figure_dpi (self ):
@@ -283,7 +296,7 @@ def get_width_height(self):
283296
284297def enterEvent (self ,event ):
285298try :
286- x ,y = self .mouseEventCoords (event . pos () )
299+ x ,y = self .mouseEventCoords (event )
287300except AttributeError :
288301# the event from PyQt4 does not include the position
289302x = y = None
@@ -293,7 +306,10 @@ def leaveEvent(self, event):
293306QtWidgets .QApplication .restoreOverrideCursor ()
294307FigureCanvasBase .leave_notify_event (self ,guiEvent = event )
295308
296- def mouseEventCoords (self ,pos ):
309+ _get_position = operator .methodcaller (
310+ "position" if QT_API in ["PyQt6" ,"PySide6" ]else "pos" )
311+
312+ def mouseEventCoords (self ,event ):
297313"""
298314 Calculate mouse coordinates in physical pixels.
299315
@@ -304,21 +320,22 @@ def mouseEventCoords(self, pos):
304320 Also, the origin is different and needs to be corrected.
305321 """
306322dpi_ratio = self ._dpi_ratio
323+ pos = self ._get_position (event )
307324x = pos .x ()
308325# flip y so y=0 is bottom of canvas
309326y = self .figure .bbox .height / dpi_ratio - pos .y ()
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 (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 (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 ,
@@ -330,7 +347,7 @@ def mouseMoveEvent(self, event):
330347
331348def mouseReleaseEvent (self ,event ):
332349x ,y = self .mouseEventCoords (event )
333- button = self .buttond .get (event .button ())
350+ button = self .buttond .get (_to_int ( event .button () ))
334351if button is not None :
335352FigureCanvasBase .button_release_event (self ,x ,y ,button ,
336353guiEvent = event )
@@ -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 (Qt . FocusPolicy .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,11 @@ 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 (QtWidgets .QSizePolicy .Policy . Expanding ,
704+ QtWidgets .QSizePolicy .Policy . Ignored ))
685705labelAction = self .addWidget (self .locLabel )
686706labelAction .setVisible (True )
687707
@@ -715,7 +735,7 @@ def _icon(self, name):
715735if self .palette ().color (self .backgroundRole ()).value ()< 128 :
716736icon_color = self .palette ().color (self .foregroundRole ())
717737mask = pm .createMaskFromColor (QtGui .QColor ('black' ),
718- QtCore . Qt .MaskOutColor )
738+ Qt . MaskMode .MaskOutColor )
719739pm .fill (icon_color )
720740pm .setMask (mask )
721741return QtGui .QIcon (pm )
@@ -785,7 +805,7 @@ def configure_subplots(self):
785805image = str (cbook ._get_data_path ('images/matplotlib.png' ))
786806dia = SubplotToolQt (self .canvas .figure ,self .canvas .parent ())
787807dia .setWindowIcon (QtGui .QIcon (image ))
788- dia . exec_ ( )
808+ qt_compat . _exec ( dia )
789809
790810def save_figure (self ,* args ):
791811filetypes = self .canvas .get_supported_filetypes_grouped ()
@@ -874,7 +894,7 @@ def _export_values(self):
874894QtGui .QFontMetrics (text .document ().defaultFont ())
875895 .size (0 ,text .toPlainText ()).height ()+ 20 )
876896text .setMaximumSize (size )
877- dialog . exec_ ( )
897+ qt_compat . _exec ( dialog )
878898
879899def _on_value_changed (self ):
880900self ._figure .subplots_adjust (** {attr :self ._widgets [attr ].value ()
@@ -899,14 +919,14 @@ class ToolbarQt(ToolContainerBase, QtWidgets.QToolBar):
899919def __init__ (self ,toolmanager ,parent ):
900920ToolContainerBase .__init__ (self ,toolmanager )
901921QtWidgets .QToolBar .__init__ (self ,parent )
902- self .setAllowedAreas (
903- QtCore . Qt .TopToolBarArea | QtCore . Qt . BottomToolBarArea )
922+ self .setAllowedAreas (# Qt::TopToolBarArea | BottomToolBarArea
923+ Qt .ToolBarAreas ( 0x4 | 0x8 ) )
904924message_label = QtWidgets .QLabel ("" )
905- message_label .setAlignment (
906- QtCore . Qt .AlignRight | QtCore . Qt . AlignVCenter )
925+ message_label .setAlignment (# Qt::AlignRight | AlignVCenter
926+ Qt .Alignment ( 0x02 | 0x80 ) )
907927message_label .setSizePolicy (
908- QtWidgets .QSizePolicy (QtWidgets .QSizePolicy .Expanding ,
909- QtWidgets .QSizePolicy .Ignored ))
928+ QtWidgets .QSizePolicy (QtWidgets .QSizePolicy .Policy . Expanding ,
929+ QtWidgets .QSizePolicy .Policy . Ignored ))
910930self ._message_action = self .addWidget (message_label )
911931self ._toolitems = {}
912932self ._groups = {}
@@ -1031,7 +1051,7 @@ def mainloop():
10311051if is_python_signal_handler :
10321052signal .signal (signal .SIGINT ,signal .SIG_DFL )
10331053try :
1034- qApp . exec_ ( )
1054+ qt_compat . _exec ( qApp )
10351055finally :
10361056# reset the SIGINT exception handler
10371057if is_python_signal_handler :