diff --git a/hydrus/client/ClientController.py b/hydrus/client/ClientController.py index af9fe574..fa0c4b66 100644 --- a/hydrus/client/ClientController.py +++ b/hydrus/client/ClientController.py @@ -56,7 +56,7 @@ if not HG.twisted_is_broke: from twisted.internet import threads, reactor, defer -PubSubEventType = QC.QEvent.Type( QC.QEvent.registerEventType() ) +PubSubEventType = QP.registerEventType() class PubSubEvent( QC.QEvent ): @@ -95,7 +95,7 @@ class PubSubEventCatcher( QC.QObject ): def MessageHandler( msg_type, context, text ): - if msg_type not in ( QC.QtDebugMsg, QC.QtInfoMsg ): + if msg_type not in ( QC.QtMsgType.QtDebugMsg, QC.QtMsgType.QtInfoMsg ): # Set a breakpoint here to be able to see where the warnings originate from. HydrusData.Print( text ) @@ -542,7 +542,7 @@ class Controller( HydrusController.HydrusController ): else: - QP.CallAfter( QW.QApplication.instance().quit ) + QP.CallAfter( QW.QApplication.instance().exit ) elif sig == signal.SIGTERM: @@ -2104,7 +2104,7 @@ class Controller( HydrusController.HydrusController ): self._DestroySplash() - QP.CallAfter( QW.QApplication.quit ) + QP.CallAfter( QW.QApplication.exit ) return @@ -2125,7 +2125,7 @@ class Controller( HydrusController.HydrusController ): self.CleanRunningFile() - QP.CallAfter( QW.QApplication.quit ) + QP.CallAfter( QW.QApplication.exit ) except Exception as e: @@ -2200,7 +2200,7 @@ class Controller( HydrusController.HydrusController ): self._program_is_shut_down = True - QP.CallAfter( QW.QApplication.quit ) + QP.CallAfter( QW.QApplication.exit ) diff --git a/hydrus/client/gui/ClientGUI.py b/hydrus/client/gui/ClientGUI.py index 5ecabc6a..05f91219 100644 --- a/hydrus/client/gui/ClientGUI.py +++ b/hydrus/client/gui/ClientGUI.py @@ -692,6 +692,8 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes, CAC.ApplicationCo library_versions.append( ( 'sqlite', sqlite3.sqlite_version ) ) + library_versions.append( ( 'qtpy', qtpy.__version__ ) ) + library_versions.append( ( 'Qt', QC.__version__ ) ) if qtpy.PYSIDE2: @@ -705,11 +707,27 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes, CAC.ApplicationCo elif qtpy.PYQT5: from PyQt5.Qt import PYQT_VERSION_STR # pylint: disable=E0401,E0611 - from sip import SIP_VERSION_STR # pylint: disable=E0401 + from PyQt5.sip import SIP_VERSION_STR # pylint: disable=E0401 library_versions.append( ( 'PyQt5', PYQT_VERSION_STR ) ) library_versions.append( ( 'sip', SIP_VERSION_STR ) ) + elif qtpy.PYSIDE6: + + import PySide6 + import shiboken6 + + library_versions.append( ( 'PySide6', PySide6.__version__ ) ) + library_versions.append( ( 'shiboken6', shiboken6.__version__ ) ) + + elif qtpy.PYQT6: + + from PyQt6.QtCore import PYQT_VERSION_STR # pylint: disable=E0401 + from PyQt6.sip import SIP_VERSION_STR # pylint: disable=E0401 + + library_versions.append( ( 'PyQt6', PYQT_VERSION_STR ) ) + library_versions.append( ( 'sip', SIP_VERSION_STR ) ) + CBOR_AVAILABLE = False try: @@ -3315,7 +3333,7 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes, CAC.ApplicationCo ClientGUIMenus.AppendMenuItem( data_actions, 'flush log', 'Command the log to write any buffered contents to hard drive.', HydrusData.DebugPrint, 'Flushing log' ) ClientGUIMenus.AppendMenuItem( data_actions, 'enable truncated image loading', 'Enable the truncated image loading to test out broken jpegs.', self._EnableLoadTruncatedImages ) ClientGUIMenus.AppendSeparator( data_actions ) - ClientGUIMenus.AppendMenuItem( data_actions, 'simulate program quit signal', 'Kill the program via a QApplication quit.', QW.QApplication.instance().quit ) + ClientGUIMenus.AppendMenuItem( data_actions, 'simulate program exit signal', 'Kill the program via a QApplication exit.', QW.QApplication.instance().exit ) ClientGUIMenus.AppendMenu( debug, data_actions, 'data actions' ) diff --git a/hydrus/client/gui/ClientGUIDragDrop.py b/hydrus/client/gui/ClientGUIDragDrop.py index e945d44a..6992c984 100644 --- a/hydrus/client/gui/ClientGUIDragDrop.py +++ b/hydrus/client/gui/ClientGUIDragDrop.py @@ -193,7 +193,7 @@ class FileDropTarget( QC.QObject ): if event.type() == QC.QEvent.Drop: - if self.OnDrop( event.pos().x(), event.pos().y() ): + if self.OnDrop( event.position().toPoint().x(), event.position().toPoint().y() ): event.setDropAction( self.OnData( event.mimeData(), event.proposedAction() ) ) diff --git a/hydrus/client/gui/ClientGUIFunctions.py b/hydrus/client/gui/ClientGUIFunctions.py index 0fc254c6..59fc568d 100644 --- a/hydrus/client/gui/ClientGUIFunctions.py +++ b/hydrus/client/gui/ClientGUIFunctions.py @@ -84,11 +84,11 @@ def ConvertQtImageToNumPy( qt_image: QG.QImage ): data_bytearray = qt_image.bits() - if QP.qtpy.PYSIDE2: + if QP.qtpy.PYSIDE2 or QP.qtpy.PYSIDE6: data_bytes = bytes( data_bytearray ) - elif QP.qtpy.PYQT5: + elif QP.qtpy.PYQT5 or QP.qtpy.PYQT6: data_bytes = data_bytearray.asstring( height * width * depth ) @@ -162,11 +162,11 @@ def GetDifferentLighterDarkerColour( colour, intensity = 3 ): def GetDisplayPosition( window ): - return QW.QApplication.desktop().availableGeometry( window ).topLeft() + return window.screen().availableGeometry().topLeft() def GetDisplaySize( window ): - return QW.QApplication.desktop().availableGeometry( window ).size() + return window.screen().availableGeometry().size() def GetLighterDarkerColour( colour, intensity = 3 ): diff --git a/hydrus/client/gui/ClientGUIRatings.py b/hydrus/client/gui/ClientGUIRatings.py index 8148663c..d527fc71 100644 --- a/hydrus/client/gui/ClientGUIRatings.py +++ b/hydrus/client/gui/ClientGUIRatings.py @@ -26,16 +26,16 @@ default_numerical_colours[ ClientRatings.MIXED ] = ( ( 0, 0, 0 ), ( 95, 95, 95 ) STAR_COORDS = [] -STAR_COORDS.append( QC.QPoint( 6, 0 ) ) # top -STAR_COORDS.append( QC.QPoint( 9, 4 ) ) -STAR_COORDS.append( QC.QPoint( 12, 4 ) ) # right -STAR_COORDS.append( QC.QPoint( 9, 8 ) ) -STAR_COORDS.append( QC.QPoint( 10, 12 ) ) # bottom right -STAR_COORDS.append( QC.QPoint( 6, 10 ) ) -STAR_COORDS.append( QC.QPoint( 2, 12 ) ) # bottom left -STAR_COORDS.append( QC.QPoint( 3, 8 ) ) -STAR_COORDS.append( QC.QPoint( 0, 4 ) ) # left -STAR_COORDS.append( QC.QPoint( 3, 4 ) ) +STAR_COORDS.append( QC.QPointF( 6, 0 ) ) # top +STAR_COORDS.append( QC.QPointF( 9, 4 ) ) +STAR_COORDS.append( QC.QPointF( 12, 4 ) ) # right +STAR_COORDS.append( QC.QPointF( 9, 8 ) ) +STAR_COORDS.append( QC.QPointF( 10, 12 ) ) # bottom right +STAR_COORDS.append( QC.QPointF( 6, 10 ) ) +STAR_COORDS.append( QC.QPointF( 2, 12 ) ) # bottom left +STAR_COORDS.append( QC.QPointF( 3, 8 ) ) +STAR_COORDS.append( QC.QPointF( 0, 4 ) ) # left +STAR_COORDS.append( QC.QPointF( 3, 4 ) ) def DrawLike( painter, x, y, service_key, rating_state ): diff --git a/hydrus/client/gui/ClientGUIScrolledPanels.py b/hydrus/client/gui/ClientGUIScrolledPanels.py index cd12fa4e..890a0543 100644 --- a/hydrus/client/gui/ClientGUIScrolledPanels.py +++ b/hydrus/client/gui/ClientGUIScrolledPanels.py @@ -86,7 +86,7 @@ class ResizingScrolledPanel( QW.QScrollArea ): #visible_size = self.widget().visibleRegion().boundingRect().size() #size_hint = self.widget().sizeHint() + self.size() - visible_size - available_screen_size = QW.QApplication.desktop().availableGeometry( self ).size() + available_screen_size = self.screen().availableGeometry().size() screen_fill_factor = 0.85 # don't let size hint be bigger than this percentage of the available screen width/height diff --git a/hydrus/client/gui/QtPorting.py b/hydrus/client/gui/QtPorting.py index c89fcebc..031bafa2 100644 --- a/hydrus/client/gui/QtPorting.py +++ b/hydrus/client/gui/QtPorting.py @@ -2,7 +2,7 @@ import os -# If not explicitely set, prefer PySide2 instead of the qtpy default which is PyQt5 +# If not explicitly set, prefer PySide2/PySide6 instead of the qtpy default which is PyQt5 # It is important that this runs on startup *before* anything is imported from qtpy. # Since test.py, client.py and client.pyw all import this module first before any other Qt related ones, this requirement is satisfied. @@ -10,13 +10,21 @@ if not 'QT_API' in os.environ: try: - import PySide2 + import PySide2 # Qt5 os.environ[ 'QT_API' ] = 'pyside2' except ImportError as e: - pass + try: + + import PySide6 # Qt6 + + os.environ[ 'QT_API' ] = 'pyside6' + + except ImportError as e: + + pass # @@ -31,7 +39,7 @@ from collections import defaultdict if qtpy.PYQT5: - import sip # pylint: disable=E0401 + from PyQt5 import sip # pylint: disable=E0401 def isValid( obj ): @@ -42,16 +50,34 @@ if qtpy.PYQT5: return True +elif qtpy.PYQT6: + from PyQt6 import sip # pylint: disable=E0401 + + def isValid( obj ): + + if isinstance( obj, sip.simplewrapper ): + + return not sip.isdeleted( obj ) + + + return True + elif qtpy.PYSIDE2: import shiboken2 isValid = shiboken2.isValid + +elif qtpy.PYSIDE6: + import shiboken6 + + isValid = shiboken6.isValid + else: - raise RuntimeError( 'You need either PySide2 or PyQt5' ) + raise RuntimeError( 'You need one of PySide2, PySide6, PyQt5 or PyQt6' ) from hydrus.core import HydrusConstants as HC @@ -62,7 +88,12 @@ from hydrus.client import ClientConstants as CC def MonkeyPatchMissingMethods(): - if qtpy.PYQT5: + if qtpy.PYQT5 or qtpy.PYSIDE2: + + QG.QMouseEvent.globalPosition = lambda self, *args, **kwargs: self.globalPos( *args, **kwargs ) + QG.QDropEvent.position = lambda self, *args, **kwargs: self.posF( *args, **kwargs ) + + if qtpy.PYQT5 or qtpy.PYQT6: def MonkeyPatchGetSaveFileName( original_function ): @@ -81,7 +112,15 @@ def MonkeyPatchMissingMethods(): QW.QFileDialog.getSaveFileName = MonkeyPatchGetSaveFileName( QW.QFileDialog.getSaveFileName ) + + +def registerEventType(): + + if qtpy.PYSIDE2 or qtpy.PYSIDE6: + return QC.QEvent.Type( QC.QEvent.registerEventType() ) + + return QC.QEvent.registerEventType() class HBoxLayout( QW.QHBoxLayout ): @@ -410,7 +449,7 @@ class TabBar( QW.QTabBar ): self._last_clicked_tab_index = index - self._last_clicked_global_pos = event.globalPos() + self._last_clicked_global_pos = event.globalPosition() QW.QTabBar.mousePressEvent( self, event ) @@ -483,7 +522,7 @@ class TabBar( QW.QTabBar ): if 'application/hydrus-tab' not in event.mimeData().formats(): - tab_index = self.tabAt( event.pos() ) + tab_index = self.tabAt( event.position().toPoint() ) if tab_index != -1: @@ -619,7 +658,7 @@ class TabWidgetWithDnD( QW.QTabWidget ): return - if e.globalPos() == clicked_global_pos: + if e.globalPosition() == clicked_global_pos: # don't start a drag until movement @@ -655,7 +694,7 @@ class TabWidgetWithDnD( QW.QTabWidget ): def dragEnterEvent( self, e ): - if self.currentWidget() and self.currentWidget().rect().contains( self.currentWidget().mapFromGlobal( self.mapToGlobal( e.pos() ) ) ): + if self.currentWidget() and self.currentWidget().rect().contains( self.currentWidget().mapFromGlobal( self.mapToGlobal( e.position().toPoint() ) ) ): return QW.QTabWidget.dragEnterEvent( self, e ) @@ -1161,19 +1200,25 @@ def AdjustOpacity( image, opacity_factor ): def ToKeySequence( modifiers, key ): - if isinstance( modifiers, QC.Qt.KeyboardModifiers ): + if qtpy.PYQT5 or qtpy.PYSIDE2: - seq_str = '' + if isinstance( modifiers, QC.Qt.KeyboardModifiers ): + + seq_str = '' + + for modifier in [ QC.Qt.ShiftModifier, QC.Qt.ControlModifier, QC.Qt.AltModifier, QC.Qt.MetaModifier, QC.Qt.KeypadModifier, QC.Qt.GroupSwitchModifier ]: + + if modifiers & modifier: seq_str += QG.QKeySequence( modifier ).toString() + + seq_str += QG.QKeySequence( key ).toString() + + return QG.QKeySequence( seq_str ) + + else: return QG.QKeySequence( key + modifiers ) - for modifier in [ QC.Qt.ShiftModifier, QC.Qt.ControlModifier, QC.Qt.AltModifier, QC.Qt.MetaModifier, QC.Qt.KeypadModifier, QC.Qt.GroupSwitchModifier ]: - - if modifiers & modifier: seq_str += QG.QKeySequence( modifier ).toString() - - seq_str += QG.QKeySequence( key ).toString() - - return QG.QKeySequence( seq_str ) - - else: return QG.QKeySequence( key + modifiers ) + else: + + return QG.QKeySequence( QC.QKeyCombination( modifiers, key ) ) def AddShortcut( widget, modifier, key, callable, *args ): @@ -1191,7 +1236,7 @@ def GetBackgroundColour( widget ): return widget.palette().color( QG.QPalette.Window ) -CallAfterEventType = QC.QEvent.Type( QC.QEvent.registerEventType() ) +CallAfterEventType = registerEventType() class CallAfterEvent( QC.QEvent ):