Qt6 Build Test

Some more compatibility for Qt5&6, and first try at build scripts.
This commit is contained in:
Hydrus Network Developer 2022-07-31 19:45:12 -05:00
parent d912b67f7c
commit b1ab9931b5
20 changed files with 554 additions and 210 deletions

View File

@ -5,7 +5,7 @@ on:
- 'v*'
jobs:
build-macos:
build-macos-Qt5:
runs-on: macos-11
steps:
-
@ -32,7 +32,7 @@ jobs:
cd $GITHUB_WORKSPACE
cp ${{ steps.setup_ffmpeg.outputs.ffmpeg-path }} bin/
cp static/build_files/macos/pyoxidizer.bzl pyoxidizer.bzl
cp static/build_files/macos/requirements.txt requirements.txt
cp static/build_files/macos/requirementsQt5.txt requirements.txt
basename $(rustc --print sysroot) | sed -e "s/^stable-//" > triple.txt
pyoxidizer build --release
cd build/$(head -n 1 triple.txt)/release
@ -49,17 +49,71 @@ jobs:
cd $GITHUB_WORKSPACE
temp_dmg="$(mktemp).dmg"
hdiutil create "$temp_dmg" -ov -volname "HydrusNetwork" -fs HFS+ -srcfolder "$GITHUB_WORKSPACE/build/$(head -n 1 triple.txt)/release"
hdiutil convert "$temp_dmg" -format UDZO -o HydrusNetwork.dmg
hdiutil convert "$temp_dmg" -format UDZO -o HydrusNetwork5.dmg
-
name: Upload a Build Artifact
uses: actions/upload-artifact@v2.2.1
with:
name: MacOS-DMG
path: HydrusNetwork.dmg
path: HydrusNetwork5.dmg
if-no-files-found: error
retention-days: 2
build-ubuntu:
build-macos-Qt6:
runs-on: macos-11
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Setup FFMPEG
uses: FedericoCarboni/setup-ffmpeg@v1
id: setup_ffmpeg
with:
token: ${{ secrets.GITHUB_TOKEN }}
-
name: Install mkdocs-material
run: python3 -m pip install mkdocs-material
-
name: Build docs to /help
run: mkdocs build -d help
-
name: Install PyOxidizer
run: python3 -m pip install pyoxidizer
-
name: Build Hydrus
run: |
cd $GITHUB_WORKSPACE
cp ${{ steps.setup_ffmpeg.outputs.ffmpeg-path }} bin/
cp static/build_files/macos/pyoxidizer.bzl pyoxidizer.bzl
cp static/build_files/macos/requirementsQt6.txt requirements.txt
basename $(rustc --print sysroot) | sed -e "s/^stable-//" > triple.txt
pyoxidizer build --release
cd build/$(head -n 1 triple.txt)/release
mkdir -p "Hydrus Network.app/Contents/MacOS"
mkdir -p "Hydrus Network.app/Contents/Resources"
mkdir -p "Hydrus Network.app/Contents/Frameworks"
mv install/static/icon.icns "Hydrus Network.app/Contents/Resources/icon.icns"
cp install/static/build_files/macos/Info.plist "Hydrus Network.app/Contents/Info.plist"
cp install/static/build_files/macos/ReadMeFirst.rtf ./ReadMeFirst.rtf
cp install/static/build_files/macos/running_from_app "install/running_from_app"
ln -s /Applications ./Applications
mv install/* "Hydrus Network.app/Contents/MacOS/"
rm -rf install
cd $GITHUB_WORKSPACE
temp_dmg="$(mktemp).dmg"
hdiutil create "$temp_dmg" -ov -volname "HydrusNetwork" -fs HFS+ -srcfolder "$GITHUB_WORKSPACE/build/$(head -n 1 triple.txt)/release"
hdiutil convert "$temp_dmg" -format UDZO -o HydrusNetwork6.dmg
-
name: Upload a Build Artifact
uses: actions/upload-artifact@v2.2.1
with:
name: MacOS-DMG
path: HydrusNetwork6.dmg
if-no-files-found: error
retention-days: 2
build-ubuntu-Qt5:
runs-on: ubuntu-18.04
steps:
-
@ -104,7 +158,7 @@ jobs:
uses: BSFishy/pip-action@v1
with:
packages: pyinstaller
requirements: hydrus/static/build_files/linux/requirements.txt
requirements: hydrus/static/build_files/linux/requirementsQt5.txt
-
name: Build Hydrus
run: |
@ -127,17 +181,95 @@ jobs:
name: Compress Client
run: |
mv dist/client "dist/Hydrus Network"
tar -czvf Ubuntu-Extract.tar.gz -C dist "Hydrus Network"
tar -czvf Ubuntu-Extract5.tar.gz -C dist "Hydrus Network"
-
name: Upload a Build Artifact
uses: actions/upload-artifact@v2
with:
name: Ubuntu-Extract
path: Ubuntu-Extract.tar.gz
path: Ubuntu-Extract5.tar.gz
if-no-files-found: error
retention-days: 2
build-windows:
build-ubuntu-Qt6:
runs-on: ubuntu-20.04
steps:
-
name: Checkout
uses: actions/checkout@v2
with:
path: hydrus
-
name: Setup Python
uses: actions/setup-python@v2
with:
python-version: 3.8
architecture: x64
-
name: Install mkdocs-material
run: pip install mkdocs-material
-
name: Build docs to /help
run: mkdocs build -d help
working-directory: hydrus
#- name: Cache Qt
# id: cache-qt
# uses: actions/cache@v1
# with:
# path: Qt
# key: ${{ runner.os }}-QtCache
#-
# name: Install Qt
# uses: jurplel/install-qt-action@v2
# with:
# install-deps: true
# setup-python: 'false'
# modules: qtcharts qtwidgets qtgui qtcore
# cached: ${{ steps.cache-qt.outputs.cache-hit }}
-
name: APT Install
run: |
sudo apt-get update
sudo apt-get install -y libmpv1
-
name: Pip Installer
uses: BSFishy/pip-action@v1
with:
packages: pyinstaller
requirements: hydrus/static/build_files/linux/requirementsQt6.txt
-
name: Build Hydrus
run: |
cp hydrus/static/build_files/linux/client.spec client.spec
cp hydrus/static/build_files/linux/server.spec server.spec
pyinstaller server.spec
pyinstaller client.spec
-
name: Remove Chonk
run: |
find dist/client/ -type f -name "*.pyc" -delete
while read line; do find dist/client/ -type f -name "${line}" -delete ; done < hydrus/static/build_files/linux/files_to_delete.txt
-
name: Set Permissions
run: |
sudo chown --recursive 1000:1000 dist/client
sudo find dist/client -type d -exec chmod 0755 {} \;
sudo chmod +x dist/client/client dist/client/server dist/client/bin/swfrender_linux
-
name: Compress Client
run: |
mv dist/client "dist/Hydrus Network"
tar -czvf Ubuntu-Extract6.tar.gz -C dist "Hydrus Network"
-
name: Upload a Build Artifact
uses: actions/upload-artifact@v2
with:
name: Ubuntu-Extract
path: Ubuntu-Extract6.tar.gz
if-no-files-found: error
retention-days: 2
build-windows-Qt5:
runs-on: windows-2019
steps:
-
@ -184,7 +316,7 @@ jobs:
uses: BSFishy/pip-action@v1
with:
packages: pyinstaller
requirements: hydrus\static\build_files\windows\requirements.txt
requirements: hydrus\static\build_files\windows\requirementsQt5.txt
-
name: Download mpv-dev
uses: carlosperate/download-file-action@v1.0.3
@ -220,7 +352,7 @@ jobs:
name: Compress Client
run: |
cd .\dist
7z.exe a -tzip -mm=Deflate -mx=5 ..\Windows-Extract.zip 'Hydrus Network'
7z.exe a -tzip -mm=Deflate -mx=5 ..\Windows-Extract5.zip 'Hydrus Network'
cd ..
-
name: Upload a Build Artifact
@ -235,14 +367,101 @@ jobs:
uses: actions/upload-artifact@v2
with:
name: Windows-Extract
path: Windows-Extract.zip
path: Windows-Extract5.zip
if-no-files-found: error
retention-days: 2
build-windows-Qt6:
runs-on: windows-2019
steps:
-
name: Checkout
uses: actions/checkout@v2
with:
path: hydrus
-
name: Setup FFMPEG
uses: FedericoCarboni/setup-ffmpeg@v1
id: setup_ffmpeg
with:
token: ${{ secrets.GITHUB_TOKEN }}
-
name: Setup Python
uses: actions/setup-python@v2
with:
python-version: 3.8
architecture: x64
-
name: Install mkdocs-material
run: pip install mkdocs-material
-
name: Build docs to /help
run: mkdocs build -d help
working-directory: hydrus
-
name: Cache Qt
id: cache_qt
uses: actions/cache@v1
with:
path: ../Qt
key: ${{ runner.os }}-QtCache
-
name: Install Qt
uses: jurplel/install-qt-action@v2
with:
install-deps: true
setup-python: 'false'
modules: qtcharts qtwidgets qtgui qtcore
cached: ${{ steps.cache_qt.outputs.cache-hit }}
-
name: PIP Install Packages
uses: BSFishy/pip-action@v1
with:
packages: pyinstaller
requirements: hydrus\static\build_files\windows\requirementsQt6.txt
-
name: Download mpv-dev
uses: carlosperate/download-file-action@v1.0.3
id: download_mpv
with:
file-url: 'https://sourceforge.net/projects/mpv-player-windows/files/libmpv/mpv-dev-x86_64-20210228-git-d1be8bb.7z'
file-name: 'mpv-dev-x86_64.7z'
location: '.'
-
name: Process mpv-dev
run: |
7z x ${{ steps.download_mpv.outputs.file-path }}
move mpv-1.dll hydrus\
-
name: Build Hydrus
run: |
move ${{ steps.setup_ffmpeg.outputs.ffmpeg-path }} hydrus\bin\
move hydrus\static\build_files\windows\sqlite3.dll hydrus\
move hydrus\static\build_files\windows\sqlite3.exe hydrus\db
move hydrus\static\build_files\windows\client-win.spec client-win.spec
move hydrus\static\build_files\windows\server-win.spec server-win.spec
pyinstaller server-win.spec
pyinstaller client-win.spec
dir -r
-
name: Compress Client
run: |
cd .\dist
7z.exe a -tzip -mm=Deflate -mx=5 ..\Windows-Extract6.zip 'Hydrus Network'
cd ..
-
name: Upload a Build Artifact
uses: actions/upload-artifact@v2
with:
name: Windows-Extract
path: Windows-Extract6.zip
if-no-files-found: error
retention-days: 2
create-release:
name: Create Release Entry
runs-on: ubuntu-20.04
needs: [build-windows, build-ubuntu, build-macos]
needs: [build-windows-Qt5, build-windows-Qt6, build-ubuntu-Qt5, build-ubuntu-Qt6, build-macos-Qt5, build-macos-Qt6]
steps:
-
name: Checkout code
@ -260,19 +479,25 @@ jobs:
name: Rename Files
run: |
mkdir ubuntu windows
mv MacOS-DMG/HydrusNetwork.dmg Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.macOS.-.App.dmg
mv Windows-Install/HydrusInstaller.exe Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Windows.-.Installer.exe
mv Windows-Extract/Windows-Extract.zip Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Windows.-.Extract.only.zip
mv Ubuntu-Extract/Ubuntu-Extract.tar.gz Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Linux.-.Executable.tar.gz
mv MacOS-DMG/HydrusNetwork5.dmg Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.macOS.Qt5.-.App.dmg
mv MacOS-DMG/HydrusNetwork6.dmg Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.macOS.Qt6.-.App.dmg
mv Windows-Install/HydrusInstaller.exe Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Windows.Qt5.-.Installer.exe
mv Windows-Extract/Windows-Extract5.zip Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Windows.Qt5.-.Extract.only.zip
mv Windows-Extract/Windows-Extract6.zip Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Windows.Qt6.-.Extract.only.zip
mv Ubuntu-Extract/Ubuntu-Extract5.tar.gz Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Linux.Qt5.-.Executable.tar.gz
mv Ubuntu-Extract/Ubuntu-Extract6.tar.gz Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Linux.Qt6.-.Executable.tar.gz
-
name: Release new
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Windows.-.Installer.exe
Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Windows.-.Extract.only.zip
Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Linux.-.Executable.tar.gz
Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.macOS.-.App.dmg
Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Windows.Qt5.-.Installer.exe
Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Windows.Qt5.-.Extract.only.zip
Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Windows.Qt6.-.Extract.only.zip
Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Linux.Qt5.-.Executable.tar.gz
Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.Linux.Qt6.-.Executable.tar.gz
Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.macOS.Qt5.-.App.dmg
Hydrus.Network.${{ steps.meta.outputs.version_short }}.-.macOS.Qt6.-.App.dmg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -64,14 +64,20 @@ pip3 install -r requirements_windows.txt
If you prefer to do things manually, inspect the document and install the modules yourself.
## PyQt5 support { id="pyqt5" }
## Qt { id="qt" }
For Qt, either PySide2 (default) or PyQt5 are supported, through qtpy. For PyQt5, go:
Qt is the UI library. You can run PySide2, PySide6, PyQt5, or PyQt6. A wrapper library called `qtpy` allows this. The default for now is PySide2, but it will soon be PySide6. For PyQt5 or PyQt6, go:
```
pip3 install qtpy PyQtChart PyQt5
-or-
pip3 install qtpy PyQt6-Charts PyQt6
```
If you have multiple Qts installed, then select which one you want to use by setting the `QT_API` environment variable to 'pyside2', 'pyside6', 'pyqt5', or 'pyqt6'. Check _help->about_ to make sure it loaded the right one.
If you run Windows 7, you cannot run Qt6. Please try PySide2 or PyQt5.
## FFMPEG { id="ffmpeg" }
If you don't have FFMPEG in your PATH and you want to import anything more fun than jpegs, you will need to put a static [FFMPEG](https://ffmpeg.org/) executable in your PATH or the `install_dir/bin` directory. If you can't find a static exe on Windows, you can copy the exe from one of my extractable releases.

View File

@ -42,6 +42,7 @@ from hydrus.client.gui import ClientGUIScrolledPanelsManagement
from hydrus.client.gui import ClientGUISplash
from hydrus.client.gui import ClientGUIStyle
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.gui import QtInit
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.lists import ClientGUIListManager
from hydrus.client.importing import ClientImportSubscriptions
@ -1588,7 +1589,7 @@ class Controller( HydrusController.HydrusController ):
def Run( self ):
QP.MonkeyPatchMissingMethods()
QtInit.MonkeyPatchMissingMethods()
from hydrus.client.gui import ClientGUICore

View File

@ -76,6 +76,7 @@ from hydrus.client.gui import ClientGUITopLevelWindows
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.gui import QLocator
from hydrus.client.gui import ClientGUILocatorSearchProviders
from hydrus.client.gui import QtInit
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.canvas import ClientGUIMPV
from hydrus.client.gui.networking import ClientGUIHydrusNetwork
@ -696,38 +697,45 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes, CAC.ApplicationCo
library_versions.append( ( 'Qt', QC.__version__ ) )
if qtpy.PYSIDE2:
if QtInit.WE_ARE_QT5:
import PySide2
import shiboken2
if QtInit.WE_ARE_PYSIDE:
import PySide2
import shiboken2
library_versions.append( ( 'PySide2', PySide2.__version__ ) )
library_versions.append( ( 'shiboken2', shiboken2.__version__ ) )
elif QtInit.WE_ARE_PYQT:
from PyQt5.Qt import PYQT_VERSION_STR # pylint: disable=E0401,E0611
from PyQt5.sip import SIP_VERSION_STR # pylint: disable=E0401
library_versions.append( ( 'PyQt5', PYQT_VERSION_STR ) )
library_versions.append( ( 'sip', SIP_VERSION_STR ) )
library_versions.append( ( 'PySide2', PySide2.__version__ ) )
library_versions.append( ( 'shiboken2', shiboken2.__version__ ) )
elif QtInit.WE_ARE_QT6:
elif qtpy.PYQT5:
from PyQt5.Qt import PYQT_VERSION_STR # pylint: disable=E0401,E0611
from PyQt5.sip import SIP_VERSION_STR # pylint: disable=E0401
library_versions.append( ( 'PyQt5', PYQT_VERSION_STR ) )
library_versions.append( ( 'sip', SIP_VERSION_STR ) )
if QtInit.WE_ARE_PYSIDE:
import PySide6
import shiboken6
library_versions.append( ( 'PySide6', PySide6.__version__ ) )
library_versions.append( ( 'shiboken6', shiboken6.__version__ ) )
elif QtInit.WE_ARE_PYQT:
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 ) )
elif QP.WE_ARE_PYSIDE and QP.WE_ARE_QT6:
import PySide6
import shiboken6
library_versions.append( ( 'PySide6', PySide6.__version__ ) )
library_versions.append( ( 'shiboken6', shiboken6.__version__ ) )
elif QP.WE_ARE_PYQT and QP.WE_ARE_QT6:
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:

View File

@ -9,6 +9,7 @@ from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusText
from hydrus.client.gui import QtInit
from hydrus.client.gui import QtPorting as QP
def ClientToScreen( win: QW.QWidget, pos: QC.QPoint ) -> QC.QPoint:
@ -84,11 +85,11 @@ def ConvertQtImageToNumPy( qt_image: QG.QImage ):
data_bytearray = qt_image.bits()
if QP.WE_ARE_PYSIDE:
if QtInit.WE_ARE_PYSIDE:
data_bytes = bytes( data_bytearray )
elif QP.WE_ARE_PYQT:
elif QtInit.WE_ARE_PYQT:
data_bytes = data_bytearray.asstring( height * width * depth )

View File

@ -348,9 +348,9 @@ class RatingNumerical( QW.QWidget ):
def _GetRatingStateAndRatingFromClickEvent( self, event ):
click_pos = event.pos()
click_pos = event.position().toPoint()
x = event.pos().x()
x = click_pos.x()
BORDER = 1

View File

@ -1273,7 +1273,7 @@ class ShortcutsHandler( QC.QObject ):
if event.type() == QC.QEvent.MouseButtonPress:
self._last_click_down_position = event.globalPos()
self._last_click_down_position = event.globalPosition().toPoint()
CUMULATIVE_MOUSEWARP_MANHATTAN_LENGTH = 0
@ -1290,7 +1290,7 @@ class ShortcutsHandler( QC.QObject ):
if event.type() == QC.QEvent.MouseButtonRelease:
release_press_pos = event.globalPos()
release_press_pos = event.globalPosition().toPoint()
delta = release_press_pos - self._last_click_down_position

135
hydrus/client/gui/QtInit.py Normal file
View File

@ -0,0 +1,135 @@
import os
# If not explicitly set, prefer PySide instead of PyQt5, which is the qtpy default
# It is critical that this runs on startup *before* anything is imported from qtpy.
if 'QT_API' not in os.environ:
try:
import PySide2 # Qt5
os.environ[ 'QT_API' ] = 'pyside2'
except ImportError as e:
try:
import PySide6 # Qt6
os.environ[ 'QT_API' ] = 'pyside6'
except ImportError as e:
pass
#
import qtpy
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG
# 2022-07
# an older version of qtpy, 1.9 or so, didn't actually have attribute qtpy.PYQT6, so we'll test and assign carefully
WE_ARE_QT5 = False
WE_ARE_QT6 = False
WE_ARE_PYQT = False
WE_ARE_PYSIDE = False
if qtpy.PYQT5:
WE_ARE_QT5 = True
WE_ARE_PYQT = True
from PyQt5 import sip # pylint: disable=E0401
def isValid( obj ):
if isinstance( obj, sip.simplewrapper ):
return not sip.isdeleted( obj )
return True
elif hasattr( qtpy, 'PYQT6' ) and qtpy.PYQT6:
WE_ARE_QT6 = True
WE_ARE_PYQT = True
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:
WE_ARE_QT5 = True
WE_ARE_PYSIDE = True
import shiboken2
isValid = shiboken2.isValid
elif qtpy.PYSIDE6:
WE_ARE_QT6 = True
WE_ARE_PYSIDE = True
import shiboken6
isValid = shiboken6.isValid
else:
raise RuntimeError( 'You need one of PySide2, PySide6, PyQt5, or PyQt6' )
def MonkeyPatchMissingMethods():
if WE_ARE_QT5:
QG.QMouseEvent.globalPosition = lambda self, *args, **kwargs: QC.QPointF( self.globalPos( *args, **kwargs ) )
QG.QMouseEvent.position = lambda self, *args, **kwargs: QC.QPointF( self.pos( *args, **kwargs ) )
QG.QDropEvent.position = lambda self, *args, **kwargs: QC.QPointF( self.pos( *args, **kwargs ) )
QG.QDropEvent.modifiers = lambda self, *args, **kwargs: self.keyboardModifiers( *args, **kwargs )
if WE_ARE_PYQT:
def MonkeyPatchGetSaveFileName( original_function ):
def new_function( *args, **kwargs ):
if 'selectedFilter' in kwargs:
kwargs[ 'initialFilter' ] = kwargs[ 'selectedFilter' ]
del kwargs[ 'selectedFilter' ]
return original_function( *args, **kwargs )
return new_function
QW.QFileDialog.getSaveFileName = MonkeyPatchGetSaveFileName( QW.QFileDialog.getSaveFileName )

View File

@ -2,32 +2,6 @@
import os
# 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.
if not 'QT_API' in os.environ:
try:
import PySide2 # Qt5
os.environ[ 'QT_API' ] = 'pyside2'
except ImportError as e:
try:
import PySide6 # Qt6
os.environ[ 'QT_API' ] = 'pyside6'
except ImportError as e:
pass
#
import qtpy
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
@ -37,113 +11,26 @@ import math
from collections import defaultdict
# we can't test qtpy.PYQT6 unless it has it lmao, so we'll test and assign more carefully
WE_ARE_QT5 = False
WE_ARE_QT6 = False
WE_ARE_PYQT = False
WE_ARE_PYSIDE = False
if qtpy.PYQT5:
from PyQt5 import sip # pylint: disable=E0401
WE_ARE_QT5 = True
WE_ARE_PYQT = True
def isValid( obj ):
if isinstance( obj, sip.simplewrapper ):
return not sip.isdeleted( obj )
return True
elif hasattr( qtpy, 'PYQT6' ) and qtpy.PYQT6:
from PyQt6 import sip # pylint: disable=E0401
WE_ARE_QT6 = True
WE_ARE_PYQT = True
def isValid( obj ):
if isinstance( obj, sip.simplewrapper ):
return not sip.isdeleted( obj )
return True
elif qtpy.PYSIDE2:
import shiboken2
WE_ARE_QT5 = True
WE_ARE_PYSIDE = True
isValid = shiboken2.isValid
elif qtpy.PYSIDE6:
import shiboken6
WE_ARE_QT6 = True
WE_ARE_PYSIDE = True
isValid = shiboken6.isValid
else:
raise RuntimeError( 'You need one of PySide2, PySide6, PyQt5 or PyQt6' )
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
from hydrus.client import ClientConstants as CC
from hydrus.client.gui import QtInit
def MonkeyPatchMissingMethods():
if WE_ARE_QT5:
QG.QMouseEvent.globalPosition = lambda self, *args, **kwargs: self.globalPos( *args, **kwargs )
QG.QDropEvent.position = lambda self, *args, **kwargs: self.posF( *args, **kwargs )
if WE_ARE_PYQT:
def MonkeyPatchGetSaveFileName( original_function ):
def new_function( *args, **kwargs ):
if 'selectedFilter' in kwargs:
kwargs[ 'initialFilter' ] = kwargs[ 'selectedFilter' ]
del kwargs[ 'selectedFilter' ]
return original_function( *args, **kwargs )
return new_function
QW.QFileDialog.getSaveFileName = MonkeyPatchGetSaveFileName( QW.QFileDialog.getSaveFileName )
isValid = QtInit.isValid
def registerEventType():
if qtpy.PYSIDE2 or qtpy.PYSIDE6:
if QtInit.WE_ARE_PYSIDE:
return QC.QEvent.Type( QC.QEvent.registerEventType() )
return QC.QEvent.registerEventType()
else:
return QC.QEvent.registerEventType()
class HBoxLayout( QW.QHBoxLayout ):
@ -159,6 +46,7 @@ class HBoxLayout( QW.QHBoxLayout ):
self.setContentsMargins( val, val, val, val )
class VBoxLayout( QW.QVBoxLayout ):
@ -465,13 +353,13 @@ class TabBar( QW.QTabBar ):
def mousePressEvent( self, event ):
index = self.tabAt( event.pos() )
index = self.tabAt( event.position().toPoint() )
if event.button() == QC.Qt.LeftButton:
self._last_clicked_tab_index = index
self._last_clicked_global_pos = event.globalPosition()
self._last_clicked_global_pos = event.globalPosition().toPoint()
QW.QTabBar.mousePressEvent( self, event )
@ -479,7 +367,7 @@ class TabBar( QW.QTabBar ):
def mouseReleaseEvent( self, event ):
index = self.tabAt( event.pos() )
index = self.tabAt( event.position().toPoint() )
if event.button() == QC.Qt.MiddleButton:
@ -496,7 +384,7 @@ class TabBar( QW.QTabBar ):
def mouseDoubleClickEvent( self, event ):
index = self.tabAt( event.pos() )
index = self.tabAt( event.position().toPoint() )
if event.button() == QC.Qt.LeftButton:
@ -649,7 +537,7 @@ class TabWidgetWithDnD( QW.QTabWidget ):
def mouseMoveEvent( 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() ) ) ):
QW.QTabWidget.mouseMoveEvent( self, e )
@ -659,7 +547,7 @@ class TabWidgetWithDnD( QW.QTabWidget ):
return
my_mouse_pos = e.pos()
my_mouse_pos = e.position().toPoint()
global_mouse_pos = self.mapToGlobal( my_mouse_pos )
tab_bar_mouse_pos = self._tab_bar.mapFromGlobal( global_mouse_pos )
@ -680,7 +568,7 @@ class TabWidgetWithDnD( QW.QTabWidget ):
return
if e.globalPosition() == clicked_global_pos:
if e.globalPosition().toPoint() == clicked_global_pos:
# don't start a drag until movement
@ -714,7 +602,7 @@ class TabWidgetWithDnD( QW.QTabWidget ):
drag.exec_( QC.Qt.MoveAction )
def dragEnterEvent( self, e ):
def dragEnterEvent( self, e: QG.QDragEnterEvent ):
if self.currentWidget() and self.currentWidget().rect().contains( self.currentWidget().mapFromGlobal( self.mapToGlobal( e.position().toPoint() ) ) ):
@ -731,11 +619,11 @@ class TabWidgetWithDnD( QW.QTabWidget ):
def dragMoveEvent( self, event ):
def dragMoveEvent( self, event: QG.QDragMoveEvent ):
#if self.currentWidget() and self.currentWidget().rect().contains( self.currentWidget().mapFromGlobal( self.mapToGlobal( event.pos() ) ) ): return QW.QTabWidget.dragMoveEvent( self, event )
#if self.currentWidget() and self.currentWidget().rect().contains( self.currentWidget().mapFromGlobal( self.mapToGlobal( event.position().toPoint() ) ) ): return QW.QTabWidget.dragMoveEvent( self, event )
screen_pos = self.mapToGlobal( event.pos() )
screen_pos = self.mapToGlobal( event.position().toPoint() )
tab_pos = self._tab_bar.mapFromGlobal( screen_pos )
@ -743,7 +631,7 @@ class TabWidgetWithDnD( QW.QTabWidget ):
if tab_index != -1:
shift_down = event.keyboardModifiers() & QC.Qt.ShiftModifier
shift_down = event.modifiers() & QC.Qt.ShiftModifier
self.setCurrentIndex( tab_index )
@ -756,9 +644,9 @@ class TabWidgetWithDnD( QW.QTabWidget ):
#return QW.QTabWidget.dragMoveEvent( self, event )
def dragLeaveEvent( self, e ):
def dragLeaveEvent( self, e: QG.QDragLeaveEvent ):
#if self.currentWidget() and self.currentWidget().rect().contains( self.currentWidget().mapFromGlobal( self.mapToGlobal( e.pos() ) ) ): return QW.QTabWidget.dragLeaveEvent( self, e )
#if self.currentWidget() and self.currentWidget().rect().contains( self.currentWidget().mapFromGlobal( self.mapToGlobal( e.position().toPoint() ) ) ): return QW.QTabWidget.dragLeaveEvent( self, e )
e.accept()
@ -783,13 +671,13 @@ class TabWidgetWithDnD( QW.QTabWidget ):
QW.QTabWidget.insertTab( self, index, widget, *args, **kwargs )
def dropEvent( self, e ):
def dropEvent( self, e: QG.QDropEvent ):
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.dropEvent( self, e )
if 'application/hydrus-tab' not in e.mimeData().formats(): #Page dnd has no associated mime data
e.ignore()
@ -832,7 +720,7 @@ class TabWidgetWithDnD( QW.QTabWidget ):
counter = self.count()
screen_pos = self.mapToGlobal( e.pos() )
screen_pos = self.mapToGlobal( e.position().toPoint() )
tab_pos = self.tabBar().mapFromGlobal( screen_pos )
@ -857,8 +745,10 @@ class TabWidgetWithDnD( QW.QTabWidget ):
left_edge_rect = QC.QRect( tab_rect.topLeft(), edge_size )
right_edge_rect = QC.QRect( tab_rect.topRight() - QC.QPoint( EDGE_PADDING, 0 ), edge_size )
dropped_on_left_edge = left_edge_rect.contains( e.pos() )
dropped_on_right_edge = right_edge_rect.contains( e.pos() )
drop_pos = e.position().toPoint()
dropped_on_left_edge = left_edge_rect.contains( drop_pos )
dropped_on_right_edge = right_edge_rect.contains( drop_pos )
if counter == 0:
@ -902,8 +792,8 @@ class TabWidgetWithDnD( QW.QTabWidget ):
self.insertTab( insert_index, source_page, source_name )
shift_down = e.keyboardModifiers() & QC.Qt.ShiftModifier
shift_down = e.modifiers() & QC.Qt.ShiftModifier
follow_dropped_page = not shift_down
new_options = HG.client_controller.new_options

View File

@ -1119,7 +1119,7 @@ class ListBox( QW.QScrollArea ):
def _GetLogicalIndexUnderMouse( self, mouse_event ):
y = mouse_event.pos().y()
y = mouse_event.position().toPoint().y()
if mouse_event.type() == QC.QEvent.MouseMove:

View File

@ -2916,8 +2916,10 @@ class MediaPanelThumbnails( MediaPanel ):
def _GetThumbnailUnderMouse( self, mouse_event ):
x = mouse_event.pos().x()
y = mouse_event.pos().y()
pos = mouse_event.position().toPoint()
x = pos.x()
y = pos.y()
( t_span_x, t_span_y ) = self._GetThumbnailSpanDimensions()

View File

@ -20,7 +20,7 @@ try:
HydrusBoot.AddBaseDirToEnvPath()
# initialise Qt here, important it is done early
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui import QtInit
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python3
from hydrus.client.gui import QtInit
from hydrus.client.gui import QtPorting as QP
from qtpy import QtWidgets as QW
@ -36,7 +37,7 @@ def boot():
threading.Thread( target = reactor.run, kwargs = { 'installSignalHandlers' : 0 } ).start()
QP.MonkeyPatchMissingMethods()
QtInit.MonkeyPatchMissingMethods()
app = QW.QApplication( sys.argv )
app.call_after_catcher = QP.CallAfterEventCatcher( app )

View File

@ -3,11 +3,10 @@ on:
push:
tags:
- 'v*'
workflow_dispatch: []
jobs:
build-client:
runs-on: [ubuntu-latest]
runs-on: ubuntu-latest
steps:
-
name: Checkout
@ -53,7 +52,7 @@ jobs:
labels: ${{ steps.docker_meta.outputs.labels }}
build-server:
runs-on: [ubuntu-latest]
runs-on: ubuntu-latest
steps:
-
name: Checkout
@ -99,4 +98,4 @@ jobs:
file: ./static/build_files/docker/server/Dockerfile
platforms: linux/amd64,linux/386,linux/arm/v6,linux/arm/v7,linux/arm64
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
labels: ${{ steps.docker_meta.outputs.labels }}

View File

@ -0,0 +1,24 @@
beautifulsoup4>=4.0.0
cbor2
chardet>=3.0.4
cloudscraper>=1.2.33
html5lib>=1.0.1
lxml>=4.5.0
lz4>=3.0.0
nose>=1.3.0
numpy>=1.16.0
opencv-python-headless>=4.0.0, <=4.5.3.56
Pillow>=6.0.0
psutil>=5.0.0
pylzma>=0.5.0
pyOpenSSL>=19.1.0
PySide6>=6.0.0
PySocks>=1.7.0
python-mpv==0.4.5
PyYAML>=5.0.0
QtPy>=1.9.0
requests==2.23.0
Send2Trash>=1.5.0
service-identity>=18.1.0
six>=1.14.0
Twisted>=20.3.0

View File

@ -0,0 +1,24 @@
beautifulsoup4>=4.0.0
cbor2
chardet>=3.0.4
cloudscraper>=1.2.33
html5lib>=1.0.1
lxml>=4.5.0
lz4>=3.0.0
nose>=1.3.0
numpy>=1.16.0
opencv-python-headless>=4.0.0, <=4.5.3.56
Pillow>=6.0.0
psutil>=5.0.0
pylzma>=0.5.0
pyOpenSSL>=19.1.0
PySide6>=6.0.0
PySocks>=1.7.0
python-mpv==0.4.5
PyYAML>=5.0.0
QtPy>=1.9.0
requests==2.23.0
Send2Trash>=1.5.0
service-identity>=18.1.0
six>=1.14.0
Twisted>=20.3.0

View File

@ -0,0 +1,28 @@
beautifulsoup4>=4.0.0
cbor2
chardet>=3.0.4
cloudscraper>=1.2.33
html5lib>=1.0.1
lxml>=4.5.0
lz4>=3.0.0
nose>=1.3.0
numpy>=1.16.0
opencv-python-headless>=4.0.0, <=4.5.3.56
Pillow>=6.0.0
psutil>=5.0.0
pylzma>=0.5.0
pyOpenSSL>=19.1.0
PySide6>=6.0.0
PySocks>=1.7.0
python-mpv==0.5.2
PyYAML>=5.0.0
QtPy>=1.9.0
requests==2.23.0
Send2Trash>=1.5.0
service-identity>=18.1.0
six>=1.14.0
Twisted>=20.3.0
PyWin32
pypiwin32
pywin32-ctypes
pefile