diff --git a/contrib/build-linux/appimage/Dockerfile b/contrib/build-linux/appimage/Dockerfile index 30cfd0fb6..9c5742656 100644 --- a/contrib/build-linux/appimage/Dockerfile +++ b/contrib/build-linux/appimage/Dockerfile @@ -60,6 +60,7 @@ RUN apt-get update -q && \ libxcb-util0 \ #libxcb-util1 \ libxcb-render-util0 \ + libxcb-cursor0 \ libx11-xcb1 \ libc6-dev \ libc6 \ diff --git a/contrib/build-linux/appimage/make_appimage.sh b/contrib/build-linux/appimage/make_appimage.sh index db315d58c..97de4aaa9 100755 --- a/contrib/build-linux/appimage/make_appimage.sh +++ b/contrib/build-linux/appimage/make_appimage.sh @@ -150,7 +150,7 @@ info "Installing build dependencies." # note: re pip installing from PyPI, # we prefer compiling C extensions ourselves, instead of using binary wheels, # hence "--no-binary :all:" flags. However, we specifically allow -# - PyQt5, as it's harder to build from source +# - PyQt6, as it's harder to build from source # - cryptography, as it's harder to build from source # - the whole of "requirements-build-base.txt", which includes pip and friends, as it also includes "wheel", # and I am not quite sure how to break the circular dependence there (I guess we could introduce @@ -163,7 +163,7 @@ info "Installing build dependencies." info "installing electrum and its dependencies." "$python" -m pip install --no-build-isolation --no-dependencies --no-binary :all: --no-warn-script-location \ --cache-dir "$PIP_CACHE_DIR" -r "$CONTRIB/deterministic-build/requirements.txt" -"$python" -m pip install --no-build-isolation --no-dependencies --no-binary :all: --only-binary PyQt5,PyQt5-Qt5,cryptography --no-warn-script-location \ +"$python" -m pip install --no-build-isolation --no-dependencies --no-binary :all: --only-binary PyQt6,PyQt6-Qt6,cryptography --no-warn-script-location \ --cache-dir "$PIP_CACHE_DIR" -r "$CONTRIB/deterministic-build/requirements-binaries.txt" "$python" -m pip install --no-build-isolation --no-dependencies --no-binary :all: --no-warn-script-location \ --cache-dir "$PIP_CACHE_DIR" -r "$CONTRIB/deterministic-build/requirements-hw.txt" @@ -240,19 +240,23 @@ rm -rf "$PYDIR"/config-3.*-x86_64-linux-gnu rm -rf "$PYDIR"/site-packages/{opt,pip,setuptools,wheel} rm -rf "$PYDIR"/site-packages/Cryptodome/SelfTest rm -rf "$PYDIR"/site-packages/{psutil,qrcode,websocket}/tests -# rm lots of unused parts of Qt/PyQt. (assuming PyQt 5.15.3+ layout) +# rm lots of unused parts of Qt/PyQt. (assuming PyQt 6 layout) for component in connectivity declarative help location multimedia quickcontrols2 serialport webengine websockets xmlpatterns ; do - rm -rf "$PYDIR"/site-packages/PyQt5/Qt5/translations/qt${component}_* - rm -rf "$PYDIR"/site-packages/PyQt5/Qt5/resources/qt${component}_* + rm -rf "$PYDIR"/site-packages/PyQt6/Qt6/translations/qt${component}_* + rm -rf "$PYDIR"/site-packages/PyQt6/Qt6/resources/qt${component}_* done -rm -rf "$PYDIR"/site-packages/PyQt5/Qt5/{qml,libexec} -rm -rf "$PYDIR"/site-packages/PyQt5/{pyrcc*.so,pylupdate*.so,uic} -rm -rf "$PYDIR"/site-packages/PyQt5/Qt5/plugins/{bearer,gamepads,geometryloaders,geoservices,playlistformats,position,renderplugins,sceneparsers,sensors,sqldrivers,texttospeech,webview} -for component in Bluetooth Concurrent Designer Help Location NetworkAuth Nfc Positioning PositioningQuick Qml Quick Sensors SerialPort Sql Test Web Xml ; do - rm -rf "$PYDIR"/site-packages/PyQt5/Qt5/lib/libQt5${component}* - rm -rf "$PYDIR"/site-packages/PyQt5/Qt${component}* +rm -rf "$PYDIR"/site-packages/PyQt6/Qt6/{qml,libexec} +rm -rf "$PYDIR"/site-packages/PyQt6/{pyrcc*.so,pylupdate*.so,uic} +rm -rf "$PYDIR"/site-packages/PyQt6/Qt6/plugins/{bearer,gamepads,geometryloaders,geoservices,playlistformats,position,renderplugins,sceneparsers,sensors,sqldrivers,texttospeech,webview} +for component in Bluetooth Concurrent Designer Help Location NetworkAuth Nfc Positioning PositioningQuick Qml Quick Sensors SerialPort Sql Test Web Xml Labs ShaderTools SpatialAudio ; do + rm -rf "$PYDIR"/site-packages/PyQt6/Qt6/lib/libQt6${component}* + rm -rf "$PYDIR"/site-packages/PyQt6/Qt${component}* + rm -rf "$PYDIR"/site-packages/PyQt6/bindings/Qt${component}* done -rm -rf "$PYDIR"/site-packages/PyQt5/Qt.so +for component in Qml Quick ; do + rm -rf "$PYDIR"/site-packages/PyQt6/Qt6/lib/libQt6*${component}.so* +done +rm -rf "$PYDIR"/site-packages/PyQt6/Qt.so # these are deleted as they were not deterministic; and are not needed anyway find "$APPDIR" -path '*/__pycache__*' -delete diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh index d4f87a7b0..39b78b40f 100755 --- a/contrib/build-wine/build-electrum-git.sh +++ b/contrib/build-wine/build-electrum-git.sh @@ -39,7 +39,7 @@ $WINE_PYTHON -m pip install --no-build-isolation --no-dependencies --no-binary : info "Installing dependencies specific to binaries..." # TODO tighten "--no-binary :all:" (but we don't have a C compiler...) $WINE_PYTHON -m pip install --no-build-isolation --no-dependencies --no-warn-script-location \ - --no-binary :all: --only-binary cffi,cryptography,PyQt5,PyQt5-Qt5,PyQt5-sip \ + --no-binary :all: --only-binary cffi,cryptography,PyQt6,PyQt6-Qt6,PyQt6-sip \ --cache-dir "$WINE_PIP_CACHE_DIR" -r "$CONTRIB"/deterministic-build/requirements-binaries.txt info "Installing hardware wallet requirements..." $WINE_PYTHON -m pip install --no-build-isolation --no-dependencies --no-warn-script-location \ diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec index 013c55a97..25607e23c 100644 --- a/contrib/build-wine/deterministic.spec +++ b/contrib/build-wine/deterministic.spec @@ -22,7 +22,7 @@ hiddenimports += collect_submodules(f"{PYPKG}.plugins") binaries = [] # Workaround for "Retro Look": -binaries += [b for b in collect_dynamic_libs('PyQt5') if 'qwindowsvista' in b[0]] +binaries += [b for b in collect_dynamic_libs('PyQt6') if 'qwindowsvista' in b[0]] # add libsecp256k1, libusb, etc: binaries += [(f"{PROJECT_ROOT}/{PYPKG}/*.dll", '.')] @@ -69,8 +69,19 @@ for d in a.datas: break # Strip out parts of Qt that we never use. Reduces binary size by tens of MBs. see #4815 -qt_bins2remove=('qt5web', 'qt53d', 'qt5game', 'qt5designer', 'qt5quick', - 'qt5location', 'qt5test', 'qt5xml', r'pyqt5\qt\qml\qtquick') +qt_bins2remove=( + r'pyqt6\qt6\qml', + r'pyqt6\qt6\bin\qt6quick', + r'pyqt6\qt6\bin\qt6qml', + r'pyqt6\qt6\bin\qt6multimediaquick', + r'pyqt6\qt6\bin\qt6pdfquick', + r'pyqt6\qt6\bin\qt6positioning', + r'pyqt6\qt6\bin\qt6spatialaudio', + r'pyqt6\qt6\bin\qt6shadertools', + r'pyqt6\qt6\bin\qt6sensors', + r'pyqt6\qt6\bin\qt6web', + r'pyqt6\qt6\bin\qt6test', +) print("Removing Qt binaries:", *qt_bins2remove) for x in a.binaries.copy(): for r in qt_bins2remove: @@ -78,7 +89,10 @@ for x in a.binaries.copy(): a.binaries.remove(x) print('----> Removed x =', x) -qt_data2remove=(r'pyqt5\qt\translations\qtwebengine_locales',) +qt_data2remove=( + r'pyqt6\qt6\translations\qtwebengine_locales', + r'pyqt6\qt6\qml', +) print("Removing Qt datas:", *qt_data2remove) for x in a.datas.copy(): for r in qt_data2remove: diff --git a/contrib/deterministic-build/requirements-binaries-mac.txt b/contrib/deterministic-build/requirements-binaries-mac.txt index 34867e0b7..9b5f192cc 100644 --- a/contrib/deterministic-build/requirements-binaries-mac.txt +++ b/contrib/deterministic-build/requirements-binaries-mac.txt @@ -90,40 +90,42 @@ pip==22.3.1 \ pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 -PyQt5==5.15.10 \ - --hash=sha256:501355f327e9a2c38db0428e1a236d25ebcb99304cd6e668c05d1188d514adec \ - --hash=sha256:862cea3be95b4b0a2b9678003b3a18edf7bd5eafd673860f58820f246d4bf616 \ - --hash=sha256:93288d62ebd47b1933d80c27f5d43c7c435307b84d480af689cef2474e87e4c8 \ - --hash=sha256:b89478d16d4118664ff58ed609e0a804d002703c9420118de7e4e70fa1cb5486 \ - --hash=sha256:d46b7804b1b10a4ff91753f8113e5b5580d2b4462f3226288e2d84497334898a \ - --hash=sha256:ff99b4f91aa8eb60510d5889faad07116d3340041916e46c07d519f7cad344e1 -PyQt5-Qt5==5.15.2 \ - --hash=sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a \ - --hash=sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962 \ - --hash=sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154 \ - --hash=sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327 -PyQt5-sip==12.13.0 \ - --hash=sha256:0f85fb633a522f04e48008de49dce1ff1d947011b48885b8428838973fbca412 \ - --hash=sha256:108a15f603e1886988c4b0d9d41cb74c9f9815bf05cefc843d559e8c298a10ce \ - --hash=sha256:1c8371682f77852256f1f2d38c41e2e684029f43330f0635870895ab01c02f6c \ - --hash=sha256:205cd449d08a2b024a468fb6100cd7ed03e946b4f49706f508944006f955ae1a \ - --hash=sha256:29fa9cc964517c9fc3f94f072b9a2aeef4e7a2eda1879cb835d9e06971161cdf \ - --hash=sha256:3188a06956aef86f604fb0d14421a110fad70d2a9e943dbacbfc3303f651dade \ - --hash=sha256:3a4498f3b1b15f43f5d12963accdce0fd652b0bcaae6baf8008663365827444c \ - --hash=sha256:5338773bbaedaa4f16a73c142fb23cc18c327be6c338813af70260b756c7bc92 \ - --hash=sha256:6e4ac714252370ca037c7d609da92388057165edd4f94e63354f6d65c3ed9d53 \ - --hash=sha256:773731b1b5ab1a7cf5621249f2379c95e3d2905e9bd96ff3611b119586daa876 \ - --hash=sha256:7f321daf84b9c9dbca61b80e1ef37bdaffc0e93312edae2cd7da25b953971d91 \ - --hash=sha256:7fe3375b508c5bc657d73b9896bba8a768791f1f426c68053311b046bcebdddf \ - --hash=sha256:96414c93f3d33963887cf562d50d88b955121fbfd73f937c8eca46643e77bf61 \ - --hash=sha256:9a8cdd6cb66adcbe5c941723ed1544eba05cf19b6c961851b58ccdae1c894afb \ - --hash=sha256:9b984c2620a7a7eaf049221b09ae50a345317add2624c706c7d2e9e6632a9587 \ - --hash=sha256:a7e3623b2c743753625c4650ec7696362a37fb36433b61824cf257f6d3d43cca \ - --hash=sha256:bbc7cd498bf19e0862097be1ad2243e824dea56726f00c11cff1b547c2d31d01 \ - --hash=sha256:d5032da3fff62da055104926ffe76fd6044c1221f8ad35bb60804bcb422fe866 \ - --hash=sha256:db228cd737f5cbfc66a3c3e50042140cb80b30b52edc5756dbbaa2346ec73137 \ - --hash=sha256:ec60162e034c42fb99859206d62b83b74f987d58937b3a82bdc07b5c3d190dec \ - --hash=sha256:fb4a5271fa3f6bc2feb303269a837a95a6d8dd16be553aa40e530de7fb81bfdf +PyQt6==6.7.1 \ + --hash=sha256:0adb7914c732ad1dee46d9cec838a98cb2b11bc38cc3b7b36fbd8701ae64bf47 \ + --hash=sha256:2d771fa0981514cb1ee937633dfa64f14caa902707d9afffab66677f3a73e3da \ + --hash=sha256:3672a82ccd3a62e99ab200a13903421e2928e399fda25ced98d140313ad59cb9 \ + --hash=sha256:7f397f4b38b23b5588eb2c0933510deb953d96b1f0323a916c4839c2a66ccccc \ + --hash=sha256:c2f202b7941aa74e5c7e1463a6f27d9131dbc1e6cabe85571d7364f5b3de7397 \ + --hash=sha256:f053378e3aef6248fa612c8afddda17f942fb63f9fe8a9aeb2a6b6b4cbb0eba9 \ + --hash=sha256:fa3954698233fe286a8afc477b84d8517f0788eb46b74da69d3ccc0170d3714c +PyQt6-Qt6==6.7.2 \ + --hash=sha256:05f2c7d195d316d9e678a92ecac0252a24ed175bd2444cc6077441807d756580 \ + --hash=sha256:065415589219a2f364aba29d6a98920bb32810286301acbfa157e522d30369e3 \ + --hash=sha256:7f817efa86a0e8eda9152c85b73405463fbf3266299090f32bbb2266da540ead \ + --hash=sha256:b2d7e5ddb1b9764cd60f1d730fa7bf7a1f0f61b2630967c81761d3d0a5a8a2e0 \ + --hash=sha256:fc93945eaef4536d68bd53566535efcbe78a7c05c2a533790a8fd022bac8bfaa +PyQt6-sip==13.8.0 \ + --hash=sha256:056af69d1d8d28d5968066ec5da908afd82fc0be07b67cf2b84b9f02228416ce \ + --hash=sha256:08dd81037a2864982ece2bf9891f3bf4558e247034e112993ea1a3fe239458cb \ + --hash=sha256:2559afa68825d08de09d71c42f3b6ad839dcc30f91e7c6d0785e07830d5541a5 \ + --hash=sha256:2f74cf3d6d9cab5152bd9f49d570b2dfb87553ebb5c4919abfde27f5b9fd69d4 \ + --hash=sha256:33d9b399fc9c9dc99496266842b0fb2735d924604774e97cf9b555667cc0fc59 \ + --hash=sha256:6bce6bc5870d9e87efe5338b1ee4a7b9d7d26cdd16a79a5757d80b6f25e71edc \ + --hash=sha256:755beb5d271d081e56618fb30342cdd901464f721450495cb7cb0212764da89e \ + --hash=sha256:7a0bbc0918eab5b6351735d40cf22cbfa5aa2476b55e0d5fe881aeed7d871c29 \ + --hash=sha256:7f84c472afdc7d316ff683f63129350d645ef82d9b3fd75a609b08472d1f7291 \ + --hash=sha256:835ed22eab977f75fd77e60d4ff308a1fa794b1d0c04849311f36d2a080cdf3b \ + --hash=sha256:9ea9223c94906efd68148f12ae45b51a21d67e86704225ddc92bce9c54e4d93c \ + --hash=sha256:a5c086b7c9c7996ea9b7522646cc24eebbf3591ec9dd38f65c0a3fdb0dbeaac7 \ + --hash=sha256:b1bf29e95f10a8a00819dac804ca7e5eba5fc1769adcd74c837c11477bf81954 \ + --hash=sha256:b203b6fbae4a8f2d27f35b7df46200057033d9ecd9134bcf30e3eab66d43572c \ + --hash=sha256:beaddc1ec96b342f4e239702f91802706a80cb403166c2da318cec4ad8b790cb \ + --hash=sha256:cd81144b0770084e8005d3a121c9382e6f9bc8d0bb320dd618718ffe5090e0e6 \ + --hash=sha256:cedd554c643e54c4c2e12b5874781a87441a1b405acf3650a4a2e1df42aae231 \ + --hash=sha256:d8b22a6850917c68ce83fc152a8b606ecb2efaaeed35be53110468885d6cdd9d \ + --hash=sha256:dd168667addf01f8a4b0fa7755323e43e4cd12ca4bade558c61f713a5d48ba1a \ + --hash=sha256:f57275b5af774529f9838adcfb58869ba3ebdaf805daea113bb0697a96a3f3cb \ + --hash=sha256:fbb249b82c53180f1420571ece5dc24fea1188ba435923edd055599dffe7abfb setuptools==65.5.1 \ --hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \ --hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f diff --git a/contrib/deterministic-build/requirements-binaries.txt b/contrib/deterministic-build/requirements-binaries.txt index 34867e0b7..9b5f192cc 100644 --- a/contrib/deterministic-build/requirements-binaries.txt +++ b/contrib/deterministic-build/requirements-binaries.txt @@ -90,40 +90,42 @@ pip==22.3.1 \ pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 -PyQt5==5.15.10 \ - --hash=sha256:501355f327e9a2c38db0428e1a236d25ebcb99304cd6e668c05d1188d514adec \ - --hash=sha256:862cea3be95b4b0a2b9678003b3a18edf7bd5eafd673860f58820f246d4bf616 \ - --hash=sha256:93288d62ebd47b1933d80c27f5d43c7c435307b84d480af689cef2474e87e4c8 \ - --hash=sha256:b89478d16d4118664ff58ed609e0a804d002703c9420118de7e4e70fa1cb5486 \ - --hash=sha256:d46b7804b1b10a4ff91753f8113e5b5580d2b4462f3226288e2d84497334898a \ - --hash=sha256:ff99b4f91aa8eb60510d5889faad07116d3340041916e46c07d519f7cad344e1 -PyQt5-Qt5==5.15.2 \ - --hash=sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a \ - --hash=sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962 \ - --hash=sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154 \ - --hash=sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327 -PyQt5-sip==12.13.0 \ - --hash=sha256:0f85fb633a522f04e48008de49dce1ff1d947011b48885b8428838973fbca412 \ - --hash=sha256:108a15f603e1886988c4b0d9d41cb74c9f9815bf05cefc843d559e8c298a10ce \ - --hash=sha256:1c8371682f77852256f1f2d38c41e2e684029f43330f0635870895ab01c02f6c \ - --hash=sha256:205cd449d08a2b024a468fb6100cd7ed03e946b4f49706f508944006f955ae1a \ - --hash=sha256:29fa9cc964517c9fc3f94f072b9a2aeef4e7a2eda1879cb835d9e06971161cdf \ - --hash=sha256:3188a06956aef86f604fb0d14421a110fad70d2a9e943dbacbfc3303f651dade \ - --hash=sha256:3a4498f3b1b15f43f5d12963accdce0fd652b0bcaae6baf8008663365827444c \ - --hash=sha256:5338773bbaedaa4f16a73c142fb23cc18c327be6c338813af70260b756c7bc92 \ - --hash=sha256:6e4ac714252370ca037c7d609da92388057165edd4f94e63354f6d65c3ed9d53 \ - --hash=sha256:773731b1b5ab1a7cf5621249f2379c95e3d2905e9bd96ff3611b119586daa876 \ - --hash=sha256:7f321daf84b9c9dbca61b80e1ef37bdaffc0e93312edae2cd7da25b953971d91 \ - --hash=sha256:7fe3375b508c5bc657d73b9896bba8a768791f1f426c68053311b046bcebdddf \ - --hash=sha256:96414c93f3d33963887cf562d50d88b955121fbfd73f937c8eca46643e77bf61 \ - --hash=sha256:9a8cdd6cb66adcbe5c941723ed1544eba05cf19b6c961851b58ccdae1c894afb \ - --hash=sha256:9b984c2620a7a7eaf049221b09ae50a345317add2624c706c7d2e9e6632a9587 \ - --hash=sha256:a7e3623b2c743753625c4650ec7696362a37fb36433b61824cf257f6d3d43cca \ - --hash=sha256:bbc7cd498bf19e0862097be1ad2243e824dea56726f00c11cff1b547c2d31d01 \ - --hash=sha256:d5032da3fff62da055104926ffe76fd6044c1221f8ad35bb60804bcb422fe866 \ - --hash=sha256:db228cd737f5cbfc66a3c3e50042140cb80b30b52edc5756dbbaa2346ec73137 \ - --hash=sha256:ec60162e034c42fb99859206d62b83b74f987d58937b3a82bdc07b5c3d190dec \ - --hash=sha256:fb4a5271fa3f6bc2feb303269a837a95a6d8dd16be553aa40e530de7fb81bfdf +PyQt6==6.7.1 \ + --hash=sha256:0adb7914c732ad1dee46d9cec838a98cb2b11bc38cc3b7b36fbd8701ae64bf47 \ + --hash=sha256:2d771fa0981514cb1ee937633dfa64f14caa902707d9afffab66677f3a73e3da \ + --hash=sha256:3672a82ccd3a62e99ab200a13903421e2928e399fda25ced98d140313ad59cb9 \ + --hash=sha256:7f397f4b38b23b5588eb2c0933510deb953d96b1f0323a916c4839c2a66ccccc \ + --hash=sha256:c2f202b7941aa74e5c7e1463a6f27d9131dbc1e6cabe85571d7364f5b3de7397 \ + --hash=sha256:f053378e3aef6248fa612c8afddda17f942fb63f9fe8a9aeb2a6b6b4cbb0eba9 \ + --hash=sha256:fa3954698233fe286a8afc477b84d8517f0788eb46b74da69d3ccc0170d3714c +PyQt6-Qt6==6.7.2 \ + --hash=sha256:05f2c7d195d316d9e678a92ecac0252a24ed175bd2444cc6077441807d756580 \ + --hash=sha256:065415589219a2f364aba29d6a98920bb32810286301acbfa157e522d30369e3 \ + --hash=sha256:7f817efa86a0e8eda9152c85b73405463fbf3266299090f32bbb2266da540ead \ + --hash=sha256:b2d7e5ddb1b9764cd60f1d730fa7bf7a1f0f61b2630967c81761d3d0a5a8a2e0 \ + --hash=sha256:fc93945eaef4536d68bd53566535efcbe78a7c05c2a533790a8fd022bac8bfaa +PyQt6-sip==13.8.0 \ + --hash=sha256:056af69d1d8d28d5968066ec5da908afd82fc0be07b67cf2b84b9f02228416ce \ + --hash=sha256:08dd81037a2864982ece2bf9891f3bf4558e247034e112993ea1a3fe239458cb \ + --hash=sha256:2559afa68825d08de09d71c42f3b6ad839dcc30f91e7c6d0785e07830d5541a5 \ + --hash=sha256:2f74cf3d6d9cab5152bd9f49d570b2dfb87553ebb5c4919abfde27f5b9fd69d4 \ + --hash=sha256:33d9b399fc9c9dc99496266842b0fb2735d924604774e97cf9b555667cc0fc59 \ + --hash=sha256:6bce6bc5870d9e87efe5338b1ee4a7b9d7d26cdd16a79a5757d80b6f25e71edc \ + --hash=sha256:755beb5d271d081e56618fb30342cdd901464f721450495cb7cb0212764da89e \ + --hash=sha256:7a0bbc0918eab5b6351735d40cf22cbfa5aa2476b55e0d5fe881aeed7d871c29 \ + --hash=sha256:7f84c472afdc7d316ff683f63129350d645ef82d9b3fd75a609b08472d1f7291 \ + --hash=sha256:835ed22eab977f75fd77e60d4ff308a1fa794b1d0c04849311f36d2a080cdf3b \ + --hash=sha256:9ea9223c94906efd68148f12ae45b51a21d67e86704225ddc92bce9c54e4d93c \ + --hash=sha256:a5c086b7c9c7996ea9b7522646cc24eebbf3591ec9dd38f65c0a3fdb0dbeaac7 \ + --hash=sha256:b1bf29e95f10a8a00819dac804ca7e5eba5fc1769adcd74c837c11477bf81954 \ + --hash=sha256:b203b6fbae4a8f2d27f35b7df46200057033d9ecd9134bcf30e3eab66d43572c \ + --hash=sha256:beaddc1ec96b342f4e239702f91802706a80cb403166c2da318cec4ad8b790cb \ + --hash=sha256:cd81144b0770084e8005d3a121c9382e6f9bc8d0bb320dd618718ffe5090e0e6 \ + --hash=sha256:cedd554c643e54c4c2e12b5874781a87441a1b405acf3650a4a2e1df42aae231 \ + --hash=sha256:d8b22a6850917c68ce83fc152a8b606ecb2efaaeed35be53110468885d6cdd9d \ + --hash=sha256:dd168667addf01f8a4b0fa7755323e43e4cd12ca4bade558c61f713a5d48ba1a \ + --hash=sha256:f57275b5af774529f9838adcfb58869ba3ebdaf805daea113bb0697a96a3f3cb \ + --hash=sha256:fbb249b82c53180f1420571ece5dc24fea1188ba435923edd055599dffe7abfb setuptools==65.5.1 \ --hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \ --hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f diff --git a/contrib/osx/README_macos.md b/contrib/osx/README_macos.md index ce03731f7..9549d6ed3 100644 --- a/contrib/osx/README_macos.md +++ b/contrib/osx/README_macos.md @@ -17,7 +17,7 @@ $ git submodule update --init Run install (this should install most dependencies): ``` -$ python3 -m pip install --user -e ".[crypto]" +$ python3 -m pip install --user -e ".[gui,crypto]" ``` ### 2. Install libsecp256k1 @@ -26,23 +26,7 @@ $ brew install autoconf automake libtool coreutils $ contrib/make_libsecp256k1.sh ``` -### 3. Install PyQt5 - -On Intel-based (x86_64) Macs: -``` -$ python3 -m pip install --user pyqt5 -``` - -Re ARM-based Macs (Apple M1), there are no prebuilt wheels on PyPI. -As a workaround, we can install it from `brew`: -``` -$ brew install pyqt5 -$ echo 'export PATH="/opt/homebrew/opt/qt@5/bin:$PATH"' >> ~/.zshrc -$ echo 'export PATH="/opt/homebrew/opt/pyqt@5/5.15.4_1/bin:$PATH"' >> ~/.zshrc -$ source ~/.zshrc -``` - -### 4. Run electrum: +### 3. Run electrum: ``` $ ./run_electrum ``` diff --git a/contrib/osx/make_osx.sh b/contrib/osx/make_osx.sh index 03352e4f0..5621d5d55 100755 --- a/contrib/osx/make_osx.sh +++ b/contrib/osx/make_osx.sh @@ -71,7 +71,7 @@ info "Installing build dependencies" # note: re pip installing from PyPI, # we prefer compiling C extensions ourselves, instead of using binary wheels, # hence "--no-binary :all:" flags. However, we specifically allow -# - PyQt5, as it's harder to build from source +# - PyQt6, as it's harder to build from source # - cryptography, as it's harder to build from source # - the whole of "requirements-build-base.txt", which includes pip and friends, as it also includes "wheel", # and I am not quite sure how to break the circular dependence there (I guess we could introduce @@ -181,7 +181,7 @@ python3 -m pip install --no-build-isolation --no-dependencies --no-binary :all: || fail "Could not install hardware wallet requirements" info "Installing dependencies specific to binaries..." -python3 -m pip install --no-build-isolation --no-dependencies --no-binary :all: --only-binary PyQt5,PyQt5-Qt5,cryptography \ +python3 -m pip install --no-build-isolation --no-dependencies --no-binary :all: --only-binary PyQt6,PyQt6-Qt6,cryptography \ --no-warn-script-location \ -Ir ./contrib/deterministic-build/requirements-binaries-mac.txt \ || fail "Could not install dependencies specific to binaries" diff --git a/contrib/osx/osx.spec b/contrib/osx/osx.spec index bf15dce3c..7c7ca063d 100644 --- a/contrib/osx/osx.spec +++ b/contrib/osx/osx.spec @@ -25,7 +25,7 @@ hiddenimports += collect_submodules(f"{PYPKG}.plugins") binaries = [] # Workaround for "Retro Look": -binaries += [b for b in collect_dynamic_libs('PyQt5') if 'macstyle' in b[0]] +binaries += [b for b in collect_dynamic_libs('PyQt6') if 'macstyle' in b[0]] # add libsecp256k1, libusb, etc: binaries += [(f"{PROJECT_ROOT}/{PYPKG}/*.dylib", ".")] @@ -72,7 +72,19 @@ for d in a.datas: break # Strip out parts of Qt that we never use. Reduces binary size by tens of MBs. see #4815 -qt_bins2remove=('qtweb', 'qt3d', 'qtgame', 'qtdesigner', 'qtquick', 'qtlocation', 'qttest', 'qtxml') +qt_bins2remove=( + 'pyqt6/qt6/qml', + 'pyqt6/qt6/lib/qtqml', + 'pyqt6/qt6/lib/qtquick', + 'pyqt6/qt6/lib/qtshadertools', + 'pyqt6/qt6/lib/qtspatialaudio', + 'pyqt6/qt6/lib/qtmultimediaquick', + 'pyqt6/qt6/lib/qtweb', + 'pyqt6/qt6/lib/qtpositioning', + 'pyqt6/qt6/lib/qtsensors', + 'pyqt6/qt6/lib/qtpdfquick', + 'pyqt6/qt6/lib/qttest', +) print("Removing Qt binaries:", *qt_bins2remove) for x in a.binaries.copy(): for r in qt_bins2remove: @@ -80,6 +92,16 @@ for x in a.binaries.copy(): a.binaries.remove(x) print('----> Removed x =', x) +qt_data2remove=( + 'pyqt6/qt6/qml', +) +print("Removing Qt datas:", *qt_data2remove) +for x in a.datas.copy(): + for r in qt_data2remove: + if x[0].lower().startswith(r): + a.datas.remove(x) + print('----> Removed x =', x) + pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE( diff --git a/contrib/requirements/requirements-binaries-mac.txt b/contrib/requirements/requirements-binaries-mac.txt index 876a9da6d..7742ff023 100644 --- a/contrib/requirements/requirements-binaries-mac.txt +++ b/contrib/requirements/requirements-binaries-mac.txt @@ -1,2 +1,2 @@ -PyQt5>=5.15.2 +PyQt6 cryptography>=2.6 diff --git a/contrib/requirements/requirements-binaries.txt b/contrib/requirements/requirements-binaries.txt index b00dd7c76..b41089680 100644 --- a/contrib/requirements/requirements-binaries.txt +++ b/contrib/requirements/requirements-binaries.txt @@ -1,4 +1,4 @@ -PyQt5 +PyQt6 # we need at least cryptography>=2.1 for electrum.crypto, # and at least cryptography>=2.6 for dnspython[DNSSEC] diff --git a/electrum-env b/electrum-env index d70dfde62..6fb31a0d9 100755 --- a/electrum-env +++ b/electrum-env @@ -5,7 +5,7 @@ # If 'env' already exists, it is activated and Electrum is started # without any installations. Additionally, the PYTHONPATH environment # variable is set so that system packages such as e.g. apt installed -# PyQt5 will also be visible. +# PyQt will also be visible. # # By default, only pure python dependencies are installed. # If you would like more extras to be installed, do e.g.: diff --git a/electrum/_vendor/pyperclip/README.md b/electrum/_vendor/pyperclip/README.md index 061efca0e..e4f43ef6a 100644 --- a/electrum/_vendor/pyperclip/README.md +++ b/electrum/_vendor/pyperclip/README.md @@ -1,8 +1,11 @@ This is a stripped-down copy of the 3rd-party `pyperclip` package. -No modifications besides excluding most files. It is used by the "text" GUI. At revision https://github.com/asweigart/pyperclip/blob/781603ea491eefce3b58f4f203bf748dbf9ff003/src/pyperclip/__init__.py (version 1.8.2) + +Modifications: +- excluded most files +- added support for pyqt6 diff --git a/electrum/_vendor/pyperclip/__init__.py b/electrum/_vendor/pyperclip/__init__.py index 300c9ce6c..8da648bd7 100644 --- a/electrum/_vendor/pyperclip/__init__.py +++ b/electrum/_vendor/pyperclip/__init__.py @@ -179,9 +179,12 @@ def init_qt_clipboard(): from qtpy.QtWidgets import QApplication except: try: - from PyQt5.QtWidgets import QApplication + from PyQt6.QtWidgets import QApplication except: - from PyQt4.QtGui import QApplication + try: + from PyQt5.QtWidgets import QApplication + except: + from PyQt4.QtGui import QApplication app = QApplication.instance() if app is None: @@ -530,7 +533,7 @@ def determine_clipboard(): accordingly. ''' - global Foundation, AppKit, gtk, qtpy, PyQt4, PyQt5 + global Foundation, AppKit, gtk, qtpy, PyQt4, PyQt5, PyQt6 # Setup for the CYGWIN platform: if 'cygwin' in platform.system().lower(): # Cygwin has a variety of values returned by platform.system(), such as 'CYGWIN_NT-6.1' @@ -587,12 +590,17 @@ def determine_clipboard(): except ImportError: # If qtpy isn't installed, fall back on importing PyQt4. try: - import PyQt5 # check if PyQt5 is installed + import PyQt6 # check if PyQt6 is installed except ImportError: try: - import PyQt4 # check if PyQt4 is installed + import PyQt5 # check if PyQt5 is installed except ImportError: - pass # We want to fail fast for all non-ImportError exceptions. + try: + import PyQt4 # check if PyQt4 is installed + except ImportError: + pass # We want to fail fast for all non-ImportError exceptions. + else: + return init_qt_clipboard() else: return init_qt_clipboard() else: diff --git a/electrum/gui/common_qt/__init__.py b/electrum/gui/common_qt/__init__.py index b2be0c9a4..dec8c4193 100644 --- a/electrum/gui/common_qt/__init__.py +++ b/electrum/gui/common_qt/__init__.py @@ -10,7 +10,7 @@ def get_qt_major_version() -> int: _GUI_QT_VERSION = getattr(sys, '_GUI_QT_VERSION', None) if _GUI_QT_VERSION is None: # used by pyinstaller when building (analysis phase) - _GUI_QT_VERSION = 5 + _GUI_QT_VERSION = 6 if _GUI_QT_VERSION in (5, 6): return _GUI_QT_VERSION raise Exception(f"unexpected {_GUI_QT_VERSION=}") diff --git a/electrum/gui/default_lang.py b/electrum/gui/default_lang.py index aacf8112f..3528b8771 100644 --- a/electrum/gui/default_lang.py +++ b/electrum/gui/default_lang.py @@ -20,7 +20,7 @@ if "ANDROID_DATA" in os.environ: def get_default_language(*, gui_name: Optional[str] = None) -> str: if gui_name == "qt": - from PyQt5.QtCore import QLocale + from PyQt6.QtCore import QLocale name = QLocale.system().name() return name if name in languages else "en_UK" elif gui_name == "qml": diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index d181b61eb..7f66c9239 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -30,25 +30,25 @@ import threading from typing import Optional, TYPE_CHECKING, List, Sequence try: - import PyQt5 - import PyQt5.QtGui + import PyQt6 + import PyQt6.QtGui except Exception as e: from electrum import GuiImportError raise GuiImportError( - "Error: Could not import PyQt5. On Linux systems, " - "you may try 'sudo apt-get install python3-pyqt5'") from e + "Error: Could not import PyQt6. On Linux systems, " + "you may try 'sudo apt-get install python3-pyqt6'") from e -from PyQt5.QtGui import QGuiApplication -from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QWidget, QMenu, QMessageBox -from PyQt5.QtCore import QObject, pyqtSignal, QTimer, Qt -import PyQt5.QtCore as QtCore -sys._GUI_QT_VERSION = 5 # used by gui/common_qt +from PyQt6.QtGui import QGuiApplication +from PyQt6.QtWidgets import QApplication, QSystemTrayIcon, QWidget, QMenu, QMessageBox, QDialog +from PyQt6.QtCore import QObject, pyqtSignal, QTimer, Qt +import PyQt6.QtCore as QtCore +sys._GUI_QT_VERSION = 6 # used by gui/common_qt try: # Preload QtMultimedia at app start, if available. # We use QtMultimedia on some platforms for camera-handling, and - # lazy-loading it later led to some crashes. Maybe due to bugs in PyQt5. (see #7725) - from PyQt5.QtMultimedia import QCameraInfo; del QCameraInfo + # lazy-loading it later led to some crashes. Maybe due to bugs in PyQt. (see #7725) + from PyQt6.QtMultimedia import QMediaDevices; del QMediaDevices except ImportError as e: pass # failure is ok; it is an optional dependency. @@ -86,7 +86,7 @@ class OpenFileEventFilter(QObject): super(OpenFileEventFilter, self).__init__() def eventFilter(self, obj, event): - if event.type() == QtCore.QEvent.FileOpen: + if event.type() == QtCore.QEvent.Type.FileOpen: if len(self.windows) >= 1: self.windows[0].set_payment_identifier(event.url().toString()) return True @@ -142,10 +142,10 @@ class ElectrumGui(BaseElectrumGui, Logger): self._num_wizards_in_progress = 0 self._num_wizards_lock = threading.Lock() self.dark_icon = self.config.GUI_QT_DARK_TRAY_ICON - self.tray = None + self.tray = None # type: Optional[QSystemTrayIcon] self._init_tray() self.app.new_window_signal.connect(self.start_new_window) - self.app.quit_signal.connect(self.app.quit, Qt.QueuedConnection) + self.app.quit_signal.connect(self.app.quit, Qt.ConnectionType.QueuedConnection) # maybe set dark theme self._default_qtstylesheet = self.app.styleSheet() self.reload_app_stylesheet() @@ -228,7 +228,7 @@ class ElectrumGui(BaseElectrumGui, Logger): self.tray.setIcon(self.tray_icon()) def tray_activated(self, reason): - if reason == QSystemTrayIcon.DoubleClick: + if reason == QSystemTrayIcon.ActivationReason.DoubleClick: if all([w.is_hidden() for w in self.windows]): for w in self.windows: w.bring_to_top() @@ -261,7 +261,7 @@ class ElectrumGui(BaseElectrumGui, Logger): self.timer.stop() self.timer = None # clipboard persistence. see http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17328.html - event = QtCore.QEvent(QtCore.QEvent.Clipboard) + event = QtCore.QEvent(QtCore.QEvent.Type.Clipboard) self.app.sendEvent(self.app.clipboard(), event) if self.tray: self.tray.hide() @@ -359,7 +359,7 @@ class ElectrumGui(BaseElectrumGui, Logger): except Exception as e: self.logger.exception('') err_text = str(e) if isinstance(e, WalletFileException) else repr(e) - custom_message_box(icon=QMessageBox.Warning, + custom_message_box(icon=QMessageBox.Icon.Warning, parent=None, title=_('Error'), text=_('Cannot load wallet') + ' (1):\n' + err_text) @@ -384,7 +384,7 @@ class ElectrumGui(BaseElectrumGui, Logger): except Exception as e: self.logger.exception('') err_text = str(e) if isinstance(e, WalletFileException) else repr(e) - custom_message_box(icon=QMessageBox.Warning, + custom_message_box(icon=QMessageBox.Icon.Warning, parent=None, title=_('Error'), text=_('Cannot load wallet') + '(2) :\n' + err_text) @@ -406,7 +406,7 @@ class ElectrumGui(BaseElectrumGui, Logger): return self.start_new_window(path, uri=None, force_wizard=True) return window.bring_to_top() - window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) + window.setWindowState(window.windowState() & ~Qt.WindowState.WindowMinimized | Qt.WindowState.WindowActive) window.activateWindow() if uri: window.show_send_tab() @@ -418,7 +418,7 @@ class ElectrumGui(BaseElectrumGui, Logger): result = wizard.exec() # TODO: use dialog.open() instead to avoid new event loop spawn? self.logger.info(f'wizard dialog exec result={result}') - if result == QENewWalletWizard.Rejected: + if result == QDialog.DialogCode.Rejected: self.logger.info('wizard dialog cancelled by user') return @@ -466,7 +466,7 @@ class ElectrumGui(BaseElectrumGui, Logger): wizard = QENewWalletWizard(self.config, self.app, self.plugins, self.daemon, path, start_viewstate=WizardViewState('trustedcoin_tos', data, {})) result = wizard.exec() - if result == QENewWalletWizard.Rejected: + if result == QDialog.DialogCode.Rejected: self.logger.info('wizard dialog cancelled by user') return db.put('x3', wizard.get_wizard_data()['x3']) @@ -494,7 +494,7 @@ class ElectrumGui(BaseElectrumGui, Logger): if not self.config.cv.NETWORK_AUTO_CONNECT.is_set(): dialog = QEServerConnectWizard(self.config, self.app, self.plugins, self.daemon) result = dialog.exec() - if result == QEServerConnectWizard.Rejected: + if result == QDialog.DialogCode.Rejected: self.logger.info('network wizard dialog cancelled by user') raise UserCancelled() @@ -530,7 +530,7 @@ class ElectrumGui(BaseElectrumGui, Logger): # We will shutdown when the user closes that window, via lastWindowClosed signal. # main loop self.logger.info("starting Qt main loop") - self.app.exec_() + self.app.exec() # on some platforms the exec_ call may not return, so use _cleanup_before_exit def stop(self): @@ -543,6 +543,6 @@ class ElectrumGui(BaseElectrumGui, Logger): "qt.version": QtCore.QT_VERSION_STR, "pyqt.version": QtCore.PYQT_VERSION_STR, } - if hasattr(PyQt5, "__path__"): - ret["pyqt.path"] = ", ".join(PyQt5.__path__ or []) + if hasattr(PyQt6, "__path__"): + ret["pyqt.path"] = ", ".join(PyQt6.__path__ or []) return ret diff --git a/electrum/gui/qt/address_dialog.py b/electrum/gui/qt/address_dialog.py index c34799e6f..06a22ead2 100644 --- a/electrum/gui/qt/address_dialog.py +++ b/electrum/gui/qt/address_dialog.py @@ -25,7 +25,7 @@ from typing import TYPE_CHECKING -from PyQt5.QtWidgets import QVBoxLayout, QLabel +from PyQt6.QtWidgets import QVBoxLayout, QLabel from electrum.i18n import _ diff --git a/electrum/gui/qt/address_list.py b/electrum/gui/qt/address_list.py index 20c92b89d..64a679314 100644 --- a/electrum/gui/qt/address_list.py +++ b/electrum/gui/qt/address_list.py @@ -27,9 +27,9 @@ import enum from enum import IntEnum from typing import TYPE_CHECKING -from PyQt5.QtCore import Qt, QPersistentModelIndex, QModelIndex -from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont -from PyQt5.QtWidgets import QAbstractItemView, QComboBox, QLabel, QMenu +from PyQt6.QtCore import Qt, QPersistentModelIndex, QModelIndex +from PyQt6.QtGui import QStandardItemModel, QStandardItem, QFont +from PyQt6.QtWidgets import QAbstractItemView, QComboBox, QLabel, QMenu from electrum.i18n import _ from electrum.util import block_explorer_URL, profiler @@ -88,8 +88,8 @@ class AddressList(MyTreeView): filter_columns = [Columns.TYPE, Columns.ADDRESS, Columns.LABEL, Columns.COIN_BALANCE] - ROLE_SORT_ORDER = Qt.UserRole + 1000 - ROLE_ADDRESS_STR = Qt.UserRole + 1001 + ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 1000 + ROLE_ADDRESS_STR = Qt.ItemDataRole.UserRole + 1001 key_role = ROLE_ADDRESS_STR def __init__(self, main_window: 'ElectrumWindow'): @@ -99,7 +99,7 @@ class AddressList(MyTreeView): editable_columns=[self.Columns.LABEL], ) self.wallet = self.main_window.wallet - self.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) self.setSortingEnabled(True) self.show_change = AddressTypeFilter.ALL # type: AddressTypeFilter self.show_used = AddressUsageStateFilter.ALL # type: AddressUsageStateFilter @@ -116,7 +116,7 @@ class AddressList(MyTreeView): self.proxy.setSourceModel(self.std_model) self.setModel(self.proxy) self.update() - self.sortByColumn(self.Columns.TYPE, Qt.AscendingOrder) + self.sortByColumn(self.Columns.TYPE, Qt.SortOrder.AscendingOrder) if self.config: self.configvar_show_toolbar = self.config.cv.GUI_QT_ADDRESSES_TAB_SHOW_TOOLBAR @@ -207,11 +207,11 @@ class AddressList(MyTreeView): address_item = [QStandardItem(e) for e in labels] # align text and set fonts for i, item in enumerate(address_item): - item.setTextAlignment(Qt.AlignVCenter) + item.setTextAlignment(Qt.AlignmentFlag.AlignVCenter) if i not in (self.Columns.TYPE, self.Columns.LABEL): item.setFont(QFont(MONOSPACE_FONT)) self.set_editability(address_item) - address_item[self.Columns.FIAT_BALANCE].setTextAlignment(Qt.AlignRight | Qt.AlignVCenter) + address_item[self.Columns.FIAT_BALANCE].setTextAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) # setup column 0 if self.wallet.is_change(address): address_item[self.Columns.TYPE].setText(_('change')) @@ -333,7 +333,7 @@ class AddressList(MyTreeView): menu.addAction(_("Add to coin control"), lambda: self.main_window.utxo_list.add_to_coincontrol(coins)) run_hook('receive_menu', menu, addrs, self.wallet) - menu.exec_(self.viewport().mapToGlobal(position)) + menu.exec(self.viewport().mapToGlobal(position)) def place_text_on_clipboard(self, text: str, *, title: str = None) -> None: if is_address(text): diff --git a/electrum/gui/qt/amountedit.py b/electrum/gui/qt/amountedit.py index e755152cf..b738ddd80 100644 --- a/electrum/gui/qt/amountedit.py +++ b/electrum/gui/qt/amountedit.py @@ -3,9 +3,9 @@ from decimal import Decimal from typing import Union -from PyQt5.QtCore import pyqtSignal, Qt, QSize -from PyQt5.QtGui import QPalette, QPainter -from PyQt5.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame, QSizePolicy) +from PyQt6.QtCore import pyqtSignal, Qt, QSize +from PyQt6.QtGui import QPalette, QPainter +from PyQt6.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame, QSizePolicy) from .util import char_width_in_lineedit, ColorScheme @@ -32,7 +32,7 @@ class SizedFreezableLineEdit(FreezableLineEdit): def __init__(self, *, width: int, parent=None): super().__init__(parent) self._width = width - self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) + self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) self.setMaximumWidth(width) def sizeHint(self) -> QSize: @@ -88,11 +88,11 @@ class AmountEdit(SizedFreezableLineEdit): if self.base_unit: panel = QStyleOptionFrame() self.initStyleOption(panel) - textRect = self.style().subElementRect(QStyle.SE_LineEditContents, panel, self) + textRect = self.style().subElementRect(QStyle.SubElement.SE_LineEditContents, panel, self) textRect.adjust(2, 0, -10, 0) painter = QPainter(self) painter.setPen(ColorScheme.GRAY.as_color()) - painter.drawText(textRect, int(Qt.AlignRight | Qt.AlignVCenter), self.base_unit()) + painter.drawText(textRect, int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter), self.base_unit()) def _get_amount_from_text(self, text: str) -> Union[None, Decimal, int]: try: diff --git a/electrum/gui/qt/balance_dialog.py b/electrum/gui/qt/balance_dialog.py index 94f9ac7cd..a1d15aaf0 100644 --- a/electrum/gui/qt/balance_dialog.py +++ b/electrum/gui/qt/balance_dialog.py @@ -25,11 +25,11 @@ from typing import TYPE_CHECKING -from PyQt5.QtWidgets import (QVBoxLayout, QCheckBox, QHBoxLayout, QLineEdit, +from PyQt6.QtWidgets import (QVBoxLayout, QCheckBox, QHBoxLayout, QLineEdit, QLabel, QCompleter, QDialog, QStyledItemDelegate, QScrollArea, QWidget, QPushButton, QGridLayout, QToolButton) -from PyQt5.QtCore import QRect, QEventLoop, Qt, pyqtSignal -from PyQt5.QtGui import QPalette, QPen, QPainter, QPixmap +from PyQt6.QtCore import QRect, QEventLoop, Qt, pyqtSignal +from PyQt6.QtGui import QPalette, QPen, QPainter, QPixmap from electrum.i18n import _ @@ -45,23 +45,22 @@ if TYPE_CHECKING: # show lightning funds that are not usable # pie chart mouse interactive, to prepare a swap -COLOR_CONFIRMED = Qt.green -COLOR_UNCONFIRMED = Qt.red -COLOR_UNMATURED = Qt.magenta +COLOR_CONFIRMED = Qt.GlobalColor.green +COLOR_UNCONFIRMED = Qt.GlobalColor.red +COLOR_UNMATURED = Qt.GlobalColor.magenta COLOR_FROZEN = ColorScheme.BLUE.as_color(True) -COLOR_LIGHTNING = Qt.yellow -COLOR_FROZEN_LIGHTNING = Qt.cyan +COLOR_LIGHTNING = Qt.GlobalColor.yellow +COLOR_FROZEN_LIGHTNING = Qt.GlobalColor.cyan class PieChartObject: def paintEvent(self, event): - bgcolor = self.palette().color(QPalette.Background) - pen = QPen(Qt.gray, 1, Qt.SolidLine) + pen = QPen(Qt.GlobalColor.gray, 1, Qt.PenStyle.SolidLine) qp = QPainter() qp.begin(self) qp.setPen(pen) - qp.setRenderHint(QPainter.Antialiasing) - qp.setBrush(Qt.gray) + qp.setRenderHint(QPainter.RenderHint.Antialiasing) + qp.setBrush(Qt.GlobalColor.gray) total = sum([x[2] for x in self._list]) if total == 0: return @@ -140,12 +139,11 @@ class LegendWidget(QWidget): self.setMaximumHeight(self.size) def paintEvent(self, event): - bgcolor = self.palette().color(QPalette.Background) - pen = QPen(Qt.gray, 1, Qt.SolidLine) + pen = QPen(Qt.GlobalColor.gray, 1, Qt.PenStyle.SolidLine) qp = QPainter() qp.begin(self) qp.setPen(pen) - qp.setRenderHint(QPainter.Antialiasing) + qp.setRenderHint(QPainter.RenderHint.Antialiasing) qp.setBrush(self.color) qp.drawRect(self.R) qp.end() @@ -192,39 +190,39 @@ class BalanceDialog(WindowModalDialog): vbox.addWidget(piechart) grid = QGridLayout() #grid.addWidget(QLabel(_("Onchain") + ':'), 0, 1) - #grid.addWidget(QLabel(onchain_str), 0, 2, alignment=Qt.AlignRight) - #grid.addWidget(QLabel(onchain_fiat_str), 0, 3, alignment=Qt.AlignRight) + #grid.addWidget(QLabel(onchain_str), 0, 2, alignment=Qt.AlignmentFlag.AlignRight) + #grid.addWidget(QLabel(onchain_fiat_str), 0, 3, alignment=Qt.AlignmentFlag.AlignRight) if frozen: grid.addWidget(LegendWidget(COLOR_FROZEN), 0, 0) grid.addWidget(QLabel(_("Frozen") + ':'), 0, 1) - grid.addWidget(AmountLabel(frozen_str), 0, 2, alignment=Qt.AlignRight) - grid.addWidget(AmountLabel(frozen_fiat_str), 0, 3, alignment=Qt.AlignRight) + grid.addWidget(AmountLabel(frozen_str), 0, 2, alignment=Qt.AlignmentFlag.AlignRight) + grid.addWidget(AmountLabel(frozen_fiat_str), 0, 3, alignment=Qt.AlignmentFlag.AlignRight) if unconfirmed: grid.addWidget(LegendWidget(COLOR_UNCONFIRMED), 2, 0) grid.addWidget(QLabel(_("Unconfirmed") + ':'), 2, 1) - grid.addWidget(AmountLabel(unconfirmed_str), 2, 2, alignment=Qt.AlignRight) - grid.addWidget(AmountLabel(unconfirmed_fiat_str), 2, 3, alignment=Qt.AlignRight) + grid.addWidget(AmountLabel(unconfirmed_str), 2, 2, alignment=Qt.AlignmentFlag.AlignRight) + grid.addWidget(AmountLabel(unconfirmed_fiat_str), 2, 3, alignment=Qt.AlignmentFlag.AlignRight) if unmatured: grid.addWidget(LegendWidget(COLOR_UNMATURED), 3, 0) grid.addWidget(QLabel(_("Unmatured") + ':'), 3, 1) - grid.addWidget(AmountLabel(unmatured_str), 3, 2, alignment=Qt.AlignRight) - grid.addWidget(AmountLabel(unmatured_fiat_str), 3, 3, alignment=Qt.AlignRight) + grid.addWidget(AmountLabel(unmatured_str), 3, 2, alignment=Qt.AlignmentFlag.AlignRight) + grid.addWidget(AmountLabel(unmatured_fiat_str), 3, 3, alignment=Qt.AlignmentFlag.AlignRight) if confirmed: grid.addWidget(LegendWidget(COLOR_CONFIRMED), 1, 0) grid.addWidget(QLabel(_("On-chain") + ':'), 1, 1) - grid.addWidget(AmountLabel(confirmed_str), 1, 2, alignment=Qt.AlignRight) - grid.addWidget(AmountLabel(confirmed_fiat_str), 1, 3, alignment=Qt.AlignRight) + grid.addWidget(AmountLabel(confirmed_str), 1, 2, alignment=Qt.AlignmentFlag.AlignRight) + grid.addWidget(AmountLabel(confirmed_fiat_str), 1, 3, alignment=Qt.AlignmentFlag.AlignRight) if lightning: grid.addWidget(LegendWidget(COLOR_LIGHTNING), 4, 0) grid.addWidget(QLabel(_("Lightning") + ':'), 4, 1) - grid.addWidget(AmountLabel(lightning_str), 4, 2, alignment=Qt.AlignRight) - grid.addWidget(AmountLabel(lightning_fiat_str), 4, 3, alignment=Qt.AlignRight) + grid.addWidget(AmountLabel(lightning_str), 4, 2, alignment=Qt.AlignmentFlag.AlignRight) + grid.addWidget(AmountLabel(lightning_fiat_str), 4, 3, alignment=Qt.AlignmentFlag.AlignRight) if f_lightning: grid.addWidget(LegendWidget(COLOR_FROZEN_LIGHTNING), 5, 0) grid.addWidget(QLabel(_("Lightning (frozen)") + ':'), 5, 1) - grid.addWidget(AmountLabel(f_lightning_str), 5, 2, alignment=Qt.AlignRight) - grid.addWidget(AmountLabel(f_lightning_fiat_str), 5, 3, alignment=Qt.AlignRight) + grid.addWidget(AmountLabel(f_lightning_str), 5, 2, alignment=Qt.AlignmentFlag.AlignRight) + grid.addWidget(AmountLabel(f_lightning_fiat_str), 5, 3, alignment=Qt.AlignmentFlag.AlignRight) vbox.addLayout(grid) vbox.addStretch(1) @@ -234,4 +232,4 @@ class BalanceDialog(WindowModalDialog): self.setLayout(vbox) def run(self): - self.exec_() + self.exec() diff --git a/electrum/gui/qt/bip39_recovery_dialog.py b/electrum/gui/qt/bip39_recovery_dialog.py index b833588b7..d291d851b 100644 --- a/electrum/gui/qt/bip39_recovery_dialog.py +++ b/electrum/gui/qt/bip39_recovery_dialog.py @@ -5,8 +5,8 @@ import asyncio import concurrent.futures -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QGridLayout, QLabel, QListWidget, QListWidgetItem +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QGridLayout, QLabel, QListWidget, QListWidgetItem from electrum.i18n import _ from electrum.network import Network @@ -22,7 +22,7 @@ _logger = get_logger(__name__) class Bip39RecoveryDialog(WindowModalDialog): - ROLE_ACCOUNT = Qt.UserRole + ROLE_ACCOUNT = Qt.ItemDataRole.UserRole def __init__(self, parent: QWidget, get_account_xpub, on_account_select): self.get_account_xpub = get_account_xpub diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py index 68172f3d1..3385b1a2f 100644 --- a/electrum/gui/qt/channel_details.py +++ b/electrum/gui/qt/channel_details.py @@ -1,9 +1,9 @@ from typing import TYPE_CHECKING, Sequence -import PyQt5.QtGui as QtGui -import PyQt5.QtWidgets as QtWidgets -import PyQt5.QtCore as QtCore -from PyQt5.QtWidgets import QLabel, QLineEdit, QHBoxLayout, QGridLayout +import PyQt6.QtGui as QtGui +import PyQt6.QtWidgets as QtWidgets +import PyQt6.QtCore as QtCore +from PyQt6.QtWidgets import QLabel, QLineEdit, QHBoxLayout, QGridLayout from electrum.util import EventListener, ShortID from electrum.i18n import _ @@ -28,7 +28,7 @@ class HTLCItem(QtGui.QStandardItem): class SelectableLabel(QtWidgets.QLabel): def __init__(self, text=''): super().__init__(text) - self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + self.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextSelectableByMouse) class LinkedLabel(QtWidgets.QLabel): def __init__(self, text, on_clicked): @@ -269,7 +269,7 @@ class ChannelDetailsDialog(QtWidgets.QDialog, MessageBoxMixin, QtEventListener): for htlc_with_status in plist: htlc_list.append(htlc_with_status) w.setModel(self.make_model(htlc_list)) - w.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) + w.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeMode.ResizeToContents) return w def closeEvent(self, event): diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py index 257256cf1..c39484e9f 100644 --- a/electrum/gui/qt/channels_list.py +++ b/electrum/gui/qt/channels_list.py @@ -4,12 +4,12 @@ import enum from typing import Sequence, Optional, Dict, TYPE_CHECKING from abc import abstractmethod, ABC -from PyQt5 import QtCore, QtGui -from PyQt5.QtCore import Qt, QRect, QSize -from PyQt5.QtWidgets import (QMenu, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout, QLineEdit, +from PyQt6 import QtCore, QtGui +from PyQt6.QtCore import Qt, QRect, QSize +from PyQt6.QtWidgets import (QMenu, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QPushButton, QAbstractItemView, QComboBox, QCheckBox, QToolTip) -from PyQt5.QtGui import QFont, QStandardItem, QBrush, QPainter, QIcon, QHelpEvent +from PyQt6.QtGui import QFont, QStandardItem, QBrush, QPainter, QIcon, QHelpEvent from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates from electrum.i18n import _ @@ -29,7 +29,7 @@ if TYPE_CHECKING: from .main_window import ElectrumWindow -ROLE_CHANNEL_ID = Qt.UserRole +ROLE_CHANNEL_ID = Qt.ItemDataRole.UserRole class ChannelsList(MyTreeView): @@ -73,7 +73,7 @@ class ChannelsList(MyTreeView): stretch_column=self.Columns.NODE_ALIAS, ) self.setModel(QtGui.QStandardItemModel(self)) - self.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) self.gossip_db_loaded.connect(self.on_gossip_db) self.update_rows.connect(self.do_update_rows) self.update_single_row.connect(self.do_update_single_row) @@ -232,13 +232,13 @@ class ChannelsList(MyTreeView): menu.setSeparatorsCollapsible(True) # consecutive separators are merged together selected = self.selected_in_column(self.Columns.NODE_ALIAS) if not selected: - menu.exec_(self.viewport().mapToGlobal(position)) + menu.exec(self.viewport().mapToGlobal(position)) return if len(selected) == 2: chan1, chan2 = self.get_rebalance_pair() if chan1 and chan2: menu.addAction(_("Rebalance channels"), lambda: self.main_window.rebalance_dialog(chan1, chan2)) - menu.exec_(self.viewport().mapToGlobal(position)) + menu.exec(self.viewport().mapToGlobal(position)) return elif len(selected) > 2: return @@ -283,7 +283,7 @@ class ChannelsList(MyTreeView): menu.addAction(_("Delete"), lambda: self.remove_channel_backup(channel_id)) else: menu.addAction(_("Delete"), lambda: self.remove_channel(channel_id)) - menu.exec_(self.viewport().mapToGlobal(position)) + menu.exec(self.viewport().mapToGlobal(position)) @QtCore.pyqtSlot(Abstract_Wallet, AbstractChannel) def do_update_single_row(self, wallet: Abstract_Wallet, chan: AbstractChannel): @@ -294,7 +294,7 @@ class ChannelsList(MyTreeView): if item.data(ROLE_CHANNEL_ID) != chan.channel_id: continue for column, v in self.format_fields(chan).items(): - self.model().item(row, column).setData(v, QtCore.Qt.DisplayRole) + self.model().item(row, column).setData(v, QtCore.Qt.ItemDataRole.DisplayRole) items = [self.model().item(row, column) for column in self.Columns] self._update_chan_frozen_bg(chan=chan, items=items) if wallet.lnworker: @@ -331,7 +331,7 @@ class ChannelsList(MyTreeView): self.model().insertRow(0, items) # FIXME sorting by SHORT_CHANID should treat values as tuple, not as string ( 50x1x1 > 8x1x1 ) - self.sortByColumn(self.Columns.SHORT_CHANID, Qt.DescendingOrder) + self.sortByColumn(self.Columns.SHORT_CHANID, Qt.SortOrder.DescendingOrder) def _update_chan_frozen_bg(self, *, chan: AbstractChannel, items: Sequence[QStandardItem]): assert self._default_item_bg_brush is not None @@ -389,7 +389,7 @@ class ChannelsList(MyTreeView): h.addWidget(QLabel(capacity), 2, 1) vbox.addLayout(h) vbox.addLayout(Buttons(OkButton(d))) - d.exec_() + d.exec() def set_visibility_of_columns(self): def set_visible(col: int, b: bool): diff --git a/electrum/gui/qt/completion_text_edit.py b/electrum/gui/qt/completion_text_edit.py index be5ed7558..230c5f18f 100644 --- a/electrum/gui/qt/completion_text_edit.py +++ b/electrum/gui/qt/completion_text_edit.py @@ -23,9 +23,9 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from PyQt5.QtGui import QTextCursor -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QCompleter, QPlainTextEdit, QApplication +from PyQt6.QtGui import QTextCursor +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QCompleter, QPlainTextEdit, QApplication from .util import ButtonsTextEdit @@ -35,7 +35,7 @@ class CompletionTextEdit(ButtonsTextEdit): def __init__(self): ButtonsTextEdit.__init__(self) self.completer = None - self.moveCursor(QTextCursor.End) + self.moveCursor(QTextCursor.MoveOperation.End) self.disable_suggestions() def set_completer(self, completer): @@ -44,7 +44,7 @@ class CompletionTextEdit(ButtonsTextEdit): def initialize_completer(self): self.completer.setWidget(self) - self.completer.setCompletionMode(QCompleter.PopupCompletion) + self.completer.setCompletionMode(QCompleter.CompletionMode.PopupCompletion) self.completer.activated.connect(self.insert_completion) self.enable_suggestions() @@ -53,8 +53,8 @@ class CompletionTextEdit(ButtonsTextEdit): return text_cursor = self.textCursor() extra = len(completion) - len(self.completer.completionPrefix()) - text_cursor.movePosition(QTextCursor.Left) - text_cursor.movePosition(QTextCursor.EndOfWord) + text_cursor.movePosition(QTextCursor.MoveOperation.Left) + text_cursor.movePosition(QTextCursor.MoveOperation.EndOfWord) if extra == 0: text_cursor.insertText(" ") else: @@ -63,7 +63,7 @@ class CompletionTextEdit(ButtonsTextEdit): def text_under_cursor(self): tc = self.textCursor() - tc.select(QTextCursor.WordUnderCursor) + tc.select(QTextCursor.SelectionType.WordUnderCursor) return tc.selectedText() def enable_suggestions(self): @@ -84,7 +84,8 @@ class CompletionTextEdit(ButtonsTextEdit): if self.isReadOnly(): # if field became read-only *after* keyPress, exit now return - ctrlOrShift = bool(int(e.modifiers()) & int(Qt.ControlModifier | Qt.ShiftModifier)) + ctrlOrShift = ((Qt.KeyboardModifier.ControlModifier in e.modifiers()) + or (Qt.KeyboardModifier.ShiftModifier in e.modifiers())) if self.completer is None or (ctrlOrShift and not e.text()): return @@ -92,7 +93,7 @@ class CompletionTextEdit(ButtonsTextEdit): return eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=" - hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift + hasModifier = (e.modifiers() != Qt.KeyboardModifier.NoModifier) and not ctrlOrShift completionPrefix = self.text_under_cursor() if hasModifier or not e.text() or len(completionPrefix) < 1 or eow.find(e.text()[-1]) >= 0: @@ -109,9 +110,9 @@ class CompletionTextEdit(ButtonsTextEdit): def is_special_key(self, e): if self.completer and self.completer.popup().isVisible(): - if e.key() in (Qt.Key_Enter, Qt.Key_Return): + if e.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return): return True - if e.key() == Qt.Key_Tab: + if e.key() == Qt.Key.Key_Tab: return True return False @@ -121,4 +122,4 @@ if __name__ == "__main__": te = CompletionTextEdit() te.set_completer(completer) te.show() - app.exec_() + app.exec() diff --git a/electrum/gui/qt/confirm_tx_dialog.py b/electrum/gui/qt/confirm_tx_dialog.py index a720b9093..cc52d1bdd 100644 --- a/electrum/gui/qt/confirm_tx_dialog.py +++ b/electrum/gui/qt/confirm_tx_dialog.py @@ -27,10 +27,10 @@ from decimal import Decimal from functools import partial from typing import TYPE_CHECKING, Optional, Union, Callable -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QIcon +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QIcon -from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QLabel, QGridLayout, QPushButton, QLineEdit, QToolButton, QMenu +from PyQt6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QLabel, QGridLayout, QPushButton, QLineEdit, QToolButton, QMenu from electrum.i18n import _ from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates @@ -151,18 +151,18 @@ class TxEditor(WindowModalDialog): def create_fee_controls(self): self.fee_label = QLabel('') - self.fee_label.setTextInteractionFlags(Qt.TextSelectableByMouse) + self.fee_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) self.size_label = TxSizeLabel() - self.size_label.setAlignment(Qt.AlignCenter) + self.size_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.size_label.setAmount(0) self.size_label.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet()) self.feerate_label = QLabel('') - self.feerate_label.setTextInteractionFlags(Qt.TextSelectableByMouse) + self.feerate_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) self.fiat_fee_label = TxFiatLabel() - self.fiat_fee_label.setAlignment(Qt.AlignCenter) + self.fiat_fee_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.fiat_fee_label.setAmount(0) self.fiat_fee_label.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet()) @@ -185,7 +185,7 @@ class TxEditor(WindowModalDialog): self.fee_target = QLabel('') self.fee_slider = FeeSlider(self, self.config, self.fee_slider_callback) self.fee_combo = FeeComboBox(self.fee_slider) - self.fee_combo.setFocusPolicy(Qt.NoFocus) + self.fee_combo.setFocusPolicy(Qt.FocusPolicy.NoFocus) def feerounding_onclick(): text = (self.feerounding_text() + '\n\n' + @@ -417,8 +417,8 @@ class TxEditor(WindowModalDialog): self.pref_button = QToolButton() self.pref_button.setIcon(read_QIcon("preferences.png")) self.pref_button.setMenu(self.pref_menu) - self.pref_button.setPopupMode(QToolButton.InstantPopup) - self.pref_button.setFocusPolicy(Qt.NoFocus) + self.pref_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) + self.pref_button.setFocusPolicy(Qt.FocusPolicy.NoFocus) hbox = QHBoxLayout() hbox.addWidget(QLabel(text)) hbox.addStretch() @@ -504,7 +504,7 @@ class TxEditor(WindowModalDialog): w.setVisible(b) def run(self): - cancelled = not self.exec_() + cancelled = not self.exec() self.stop_editor_updates() self.deleteLater() # see #3956 return self.tx if not cancelled else None @@ -681,7 +681,7 @@ class ConfirmTxDialog(TxEditor): msg = (_('The amount to be received by the recipient.') + ' ' + _('Fees are paid by the sender.')) self.amount_label = QLabel('') - self.amount_label.setTextInteractionFlags(Qt.TextSelectableByMouse) + self.amount_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) grid.addWidget(HelpLabel(_("Amount to be sent") + ": ", msg), 0, 0) grid.addWidget(self.amount_label, 0, 1) @@ -702,7 +702,7 @@ class ConfirmTxDialog(TxEditor): self.extra_fee_label = QLabel(_("Additional fees") + ": ") self.extra_fee_label.setVisible(False) self.extra_fee_value = QLabel('') - self.extra_fee_value.setTextInteractionFlags(Qt.TextSelectableByMouse) + self.extra_fee_value.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) self.extra_fee_value.setVisible(False) grid.addWidget(self.extra_fee_label, 5, 0) grid.addWidget(self.extra_fee_value, 5, 1) diff --git a/electrum/gui/qt/console.py b/electrum/gui/qt/console.py index c05cc43a0..0cd87b9b3 100644 --- a/electrum/gui/qt/console.py +++ b/electrum/gui/qt/console.py @@ -6,9 +6,10 @@ import os import re import traceback -from PyQt5 import QtCore -from PyQt5 import QtGui -from PyQt5 import QtWidgets +from PyQt6 import QtCore +from PyQt6.QtCore import Qt +from PyQt6 import QtGui +from PyQt6 import QtWidgets from electrum import util from electrum.i18n import _ @@ -36,7 +37,7 @@ class OverlayLabel(QtWidgets.QLabel): self.setGeometry(0, 0, self.width(), self.height()) self.setStyleSheet(self.STYLESHEET) self.setMargin(0) - parent.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + parent.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setWordWrap(True) def mousePressEvent(self, e): @@ -56,9 +57,9 @@ class Console(QtWidgets.QPlainTextEdit): self.construct = [] self.setGeometry(50, 75, 600, 400) - self.setWordWrapMode(QtGui.QTextOption.WrapAnywhere) + self.setWordWrapMode(QtGui.QTextOption.WrapMode.WrapAnywhere) self.setUndoRedoEnabled(False) - self.setFont(QtGui.QFont(MONOSPACE_FONT, 10, QtGui.QFont.Normal)) + self.setFont(QtGui.QFont(MONOSPACE_FONT, 10, QtGui.QFont.Weight.Normal)) self.newPrompt("") # make sure there is always a prompt, even before first server.banner self.updateNamespace({'run':self.run_script}) @@ -114,7 +115,7 @@ class Console(QtWidgets.QPlainTextEdit): self.completions_visible = False self.appendPlainText(prompt) - self.moveCursor(QtGui.QTextCursor.End) + self.moveCursor(QtGui.QTextCursor.MoveOperation.End) def getCommand(self, *, strip=True): doc = self.document() @@ -130,13 +131,13 @@ class Console(QtWidgets.QPlainTextEdit): doc = self.document() curr_line = doc.findBlockByLineNumber(doc.lineCount() - 1).text() - self.moveCursor(QtGui.QTextCursor.End) + self.moveCursor(QtGui.QTextCursor.MoveOperation.End) for i in range(len(curr_line) - len(sys.ps1)): - self.moveCursor(QtGui.QTextCursor.Left, QtGui.QTextCursor.KeepAnchor) + self.moveCursor(QtGui.QTextCursor.MoveOperation.Left, QtGui.QTextCursor.MoveMode.KeepAnchor) self.textCursor().removeSelectedText() self.textCursor().insertText(command) - self.moveCursor(QtGui.QTextCursor.End) + self.moveCursor(QtGui.QTextCursor.MoveOperation.End) def show_completions(self, completions): if self.completions_visible: @@ -152,7 +153,7 @@ class Console(QtWidgets.QPlainTextEdit): c.insertText(t) self.completions_end = c.position() - self.moveCursor(QtGui.QTextCursor.End) + self.moveCursor(QtGui.QTextCursor.MoveOperation.End) self.completions_visible = True def hide_completions(self): @@ -163,7 +164,7 @@ class Console(QtWidgets.QPlainTextEdit): l = self.completions_end - self.completions_pos for x in range(l): c.deleteChar() - self.moveCursor(QtGui.QTextCursor.End) + self.moveCursor(QtGui.QTextCursor.MoveOperation.End) self.completions_visible = False def getConstruct(self, command): @@ -211,9 +212,9 @@ class Console(QtWidgets.QPlainTextEdit): return c.position() - c.block().position() - len(sys.ps1) def setCursorPosition(self, position): - self.moveCursor(QtGui.QTextCursor.StartOfLine) + self.moveCursor(QtGui.QTextCursor.MoveOperation.StartOfLine) for i in range(len(sys.ps1) + position): - self.moveCursor(QtGui.QTextCursor.Right) + self.moveCursor(QtGui.QTextCursor.MoveOperation.Right) def run_command(self): command = self.getCommand() @@ -279,32 +280,32 @@ class Console(QtWidgets.QPlainTextEdit): sys.stdout = tmp_stdout def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Tab: + if event.key() == Qt.Key.Key_Tab: self.completions() return self.hide_completions() - if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): + if event.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return): self.run_command() return - if event.key() == QtCore.Qt.Key_Home: + if event.key() == Qt.Key.Key_Home: self.setCursorPosition(0) return - if event.key() == QtCore.Qt.Key_PageUp: + if event.key() == Qt.Key.Key_PageUp: return - elif event.key() in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Backspace): + elif event.key() in (Qt.Key.Key_Left, Qt.Key.Key_Backspace): if self.getCursorPosition() == 0: return - elif event.key() == QtCore.Qt.Key_Up: + elif event.key() == Qt.Key.Key_Up: self.setCommand(self.getPrevHistoryEntry()) return - elif event.key() == QtCore.Qt.Key_Down: + elif event.key() == Qt.Key.Key_Down: self.setCommand(self.getNextHistoryEntry()) return - elif event.key() == QtCore.Qt.Key_L and event.modifiers() == QtCore.Qt.ControlModifier: + elif event.key() == Qt.Key.Key_L and event.modifiers() == Qt.KeyboardModifier.ControlModifier: self.clear() - elif event.key() == QtCore.Qt.Key_C and event.modifiers() == QtCore.Qt.ControlModifier: + elif event.key() == Qt.Key.Key_C and event.modifiers() == Qt.KeyboardModifier.ControlModifier: if not self.textCursor().selectedText(): self.keyboard_interrupt() diff --git a/electrum/gui/qt/contact_list.py b/electrum/gui/qt/contact_list.py index 712d7f6f9..652be2baa 100644 --- a/electrum/gui/qt/contact_list.py +++ b/electrum/gui/qt/contact_list.py @@ -26,9 +26,9 @@ import enum from typing import TYPE_CHECKING -from PyQt5.QtGui import QStandardItemModel, QStandardItem -from PyQt5.QtCore import Qt, QPersistentModelIndex, QModelIndex -from PyQt5.QtWidgets import (QAbstractItemView, QMenu) +from PyQt6.QtGui import QStandardItemModel, QStandardItem +from PyQt6.QtCore import Qt, QPersistentModelIndex, QModelIndex +from PyQt6.QtWidgets import (QAbstractItemView, QMenu) from electrum.i18n import _ from electrum.bitcoin import is_address @@ -54,7 +54,7 @@ class ContactList(MyTreeView): } filter_columns = [Columns.NAME, Columns.ADDRESS] - ROLE_CONTACT_KEY = Qt.UserRole + 1000 + ROLE_CONTACT_KEY = Qt.ItemDataRole.UserRole + 1000 key_role = ROLE_CONTACT_KEY def __init__(self, main_window: 'ElectrumWindow'): @@ -64,7 +64,7 @@ class ContactList(MyTreeView): editable_columns=[self.Columns.NAME], ) self.setModel(QStandardItemModel(self)) - self.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) self.setSortingEnabled(True) self.std_model = self.model() self.update() @@ -100,7 +100,7 @@ class ContactList(MyTreeView): menu.addAction(_("View on block explorer"), lambda: [webopen(u) for u in URLs]) run_hook('create_contact_menu', menu, selected_keys) - menu.exec_(self.viewport().mapToGlobal(position)) + menu.exec(self.viewport().mapToGlobal(position)) def update(self): if self.maybe_defer_update(): @@ -125,7 +125,7 @@ class ContactList(MyTreeView): set_current = QPersistentModelIndex(idx) self.set_current_idx(set_current) # FIXME refresh loses sort order; so set "default" here: - self.sortByColumn(self.Columns.NAME, Qt.AscendingOrder) + self.sortByColumn(self.Columns.NAME, Qt.SortOrder.AscendingOrder) self.filter() run_hook('update_contacts_tab', self) diff --git a/electrum/gui/qt/custom_model.py b/electrum/gui/qt/custom_model.py index eaaa5e298..46e665a5f 100644 --- a/electrum/gui/qt/custom_model.py +++ b/electrum/gui/qt/custom_model.py @@ -1,7 +1,7 @@ # loosely based on # http://trevorius.com/scrapbook/uncategorized/pyqt-custom-abstractitemmodel/ -from PyQt5 import QtCore, QtWidgets +from PyQt6 import QtCore, QtWidgets class CustomNode: diff --git a/electrum/gui/qt/exception_window.py b/electrum/gui/qt/exception_window.py index 820d7db1a..090498335 100644 --- a/electrum/gui/qt/exception_window.py +++ b/electrum/gui/qt/exception_window.py @@ -25,9 +25,9 @@ import sys import html from typing import TYPE_CHECKING, Optional, Set -from PyQt5.QtCore import QObject -import PyQt5.QtCore as QtCore -from PyQt5.QtWidgets import (QWidget, QLabel, QPushButton, QTextEdit, +from PyQt6.QtCore import QObject +import PyQt6.QtCore as QtCore +from PyQt6.QtWidgets import (QWidget, QLabel, QPushButton, QTextEdit, QMessageBox, QHBoxLayout, QVBoxLayout) from electrum.i18n import _ @@ -67,7 +67,7 @@ class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin, Logger): collapse_info = QPushButton(_("Show report contents")) collapse_info.clicked.connect( - lambda: self.msg_box(QMessageBox.NoIcon, + lambda: self.msg_box(QMessageBox.Icon.NoIcon, self, _("Report contents"), self.get_report_string(), rich_text=True)) diff --git a/electrum/gui/qt/fee_slider.py b/electrum/gui/qt/fee_slider.py index 34bb0cca7..4fb2aa8e0 100644 --- a/electrum/gui/qt/fee_slider.py +++ b/electrum/gui/qt/fee_slider.py @@ -1,8 +1,8 @@ import threading -from PyQt5.QtGui import QCursor -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QSlider, QToolTip, QComboBox +from PyQt6.QtGui import QCursor +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QSlider, QToolTip, QComboBox from electrum.i18n import _ @@ -31,7 +31,7 @@ class FeeComboBox(QComboBox): class FeeSlider(QSlider): def __init__(self, window, config, callback): - QSlider.__init__(self, Qt.Horizontal) + QSlider.__init__(self, Qt.Orientation.Horizontal) self.config = config self.window = window self.callback = callback diff --git a/electrum/gui/qt/history_list.py b/electrum/gui/qt/history_list.py index 00521ac19..5ace1cb2d 100644 --- a/electrum/gui/qt/history_list.py +++ b/electrum/gui/qt/history_list.py @@ -33,10 +33,10 @@ import threading import enum from decimal import Decimal -from PyQt5.QtGui import QFont, QBrush, QColor -from PyQt5.QtCore import (Qt, QPersistentModelIndex, QModelIndex, QAbstractItemModel, +from PyQt6.QtGui import QFont, QBrush, QColor +from PyQt6.QtCore import (Qt, QPersistentModelIndex, QModelIndex, QAbstractItemModel, QSortFilterProxyModel, QVariant, QItemSelectionModel, QDate, QPoint) -from PyQt5.QtWidgets import (QMenu, QHeaderView, QLabel, QMessageBox, +from PyQt6.QtWidgets import (QMenu, QHeaderView, QLabel, QMessageBox, QPushButton, QComboBox, QVBoxLayout, QCalendarWidget, QGridLayout) @@ -77,7 +77,7 @@ TX_ICONS = [ ] -ROLE_SORT_ORDER = Qt.UserRole + 1000 +ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 1000 class HistorySortModel(QSortFilterProxyModel): @@ -155,11 +155,11 @@ class HistoryNode(CustomNode): return QVariant(d[col]) if role == MyTreeView.ROLE_EDIT_KEY: return QVariant(get_item_key(tx_item)) - if role not in (Qt.DisplayRole, Qt.EditRole, MyTreeView.ROLE_CLIPBOARD_DATA): - if col == HistoryColumns.STATUS and role == Qt.DecorationRole: + if role not in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole, MyTreeView.ROLE_CLIPBOARD_DATA): + if col == HistoryColumns.STATUS and role == Qt.ItemDataRole.DecorationRole: icon = "lightning" if is_lightning else TX_ICONS[status] return QVariant(read_QIcon(icon)) - elif col == HistoryColumns.STATUS and role == Qt.ToolTipRole: + elif col == HistoryColumns.STATUS and role == Qt.ItemDataRole.ToolTipRole: if is_lightning: msg = 'lightning transaction' else: # on-chain @@ -171,19 +171,19 @@ class HistoryNode(CustomNode): else: msg = str(conf) + _(" confirmation" + ("s" if conf != 1 else "")) return QVariant(msg) - elif col > HistoryColumns.DESCRIPTION and role == Qt.TextAlignmentRole: - return QVariant(int(Qt.AlignRight | Qt.AlignVCenter)) - elif col > HistoryColumns.DESCRIPTION and role == Qt.FontRole: + elif col > HistoryColumns.DESCRIPTION and role == Qt.ItemDataRole.TextAlignmentRole: + return QVariant(int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)) + elif col > HistoryColumns.DESCRIPTION and role == Qt.ItemDataRole.FontRole: monospace_font = QFont(MONOSPACE_FONT) return QVariant(monospace_font) - #elif col == HistoryColumns.DESCRIPTION and role == Qt.DecorationRole and not is_lightning\ + #elif col == HistoryColumns.DESCRIPTION and role == Qt.ItemDataRole.DecorationRole and not is_lightning\ # and self.parent.wallet.invoices.paid.get(tx_hash): # return QVariant(read_QIcon("seal")) elif col in (HistoryColumns.DESCRIPTION, HistoryColumns.AMOUNT) \ - and role == Qt.ForegroundRole and tx_item['value'].value < 0: + and role == Qt.ItemDataRole.ForegroundRole and tx_item['value'].value < 0: red_brush = QBrush(QColor("#BC1E1E")) return QVariant(red_brush) - elif col == HistoryColumns.FIAT_VALUE and role == Qt.ForegroundRole \ + elif col == HistoryColumns.FIAT_VALUE and role == Qt.ItemDataRole.ForegroundRole \ and not tx_item.get('fiat_default') and tx_item.get('fiat_value') is not None: blue_brush = QBrush(QColor("#1E1EFF")) return QVariant(blue_brush) @@ -247,7 +247,7 @@ class HistoryModel(CustomModel, Logger): tx_item = index.internalPointer().get_data() tx_item['label'] = self.window.wallet.get_label_for_txid(get_item_key(tx_item)) topLeft = bottomRight = self.createIndex(index.row(), HistoryColumns.DESCRIPTION) - self.dataChanged.emit(topLeft, bottomRight, [Qt.DisplayRole]) + self.dataChanged.emit(topLeft, bottomRight, [Qt.ItemDataRole.DisplayRole]) self.window.utxo_list.update() def get_domain(self): @@ -319,7 +319,9 @@ class HistoryModel(CustomModel, Logger): self.endInsertRows() if selected_row: - self.view.selectionModel().select(self.createIndex(selected_row, 0), QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent) + self.view.selectionModel().select( + self.createIndex(selected_row, 0), + QItemSelectionModel.SelectionFlag.Rows | QItemSelectionModel.SelectionFlag.SelectCurrent) self.view.filter() # update time filter if not self.view.years and self.transactions: @@ -362,7 +364,7 @@ class HistoryModel(CustomModel, Logger): fiat_fields = self.window.wallet.get_tx_item_fiat( tx_hash=txid, amount_sat=value, fx=self.window.fx, tx_fee=fee.value if fee else None) tx_item.update(fiat_fields) - self.dataChanged.emit(idx, idx, [Qt.DisplayRole, Qt.ForegroundRole]) + self.dataChanged.emit(idx, idx, [Qt.ItemDataRole.DisplayRole, Qt.ForegroundRole]) def update_tx_mined_status(self, tx_hash: str, tx_mined_info: TxMinedInfo): try: @@ -392,8 +394,8 @@ class HistoryModel(CustomModel, Logger): self.update_tx_mined_status(tx_hash, tx_mined_info) def headerData(self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole): - assert orientation == Qt.Horizontal - if role != Qt.DisplayRole: + assert orientation == Qt.Orientation.Horizontal + if role != Qt.ItemDataRole.DisplayRole: return None fx = self.window.fx fiat_title = 'n/a fiat value' @@ -415,11 +417,11 @@ class HistoryModel(CustomModel, Logger): HistoryColumns.SHORT_ID: 'Short ID', }[section] - def flags(self, idx: QModelIndex) -> int: - extra_flags = Qt.NoItemFlags # type: Qt.ItemFlag + def flags(self, idx: QModelIndex) -> Qt.ItemFlag: + extra_flags = Qt.ItemFlag.NoItemFlags # type: Qt.ItemFlag if idx.column() in self.view.editable_columns: - extra_flags |= Qt.ItemIsEditable - return super().flags(idx) | int(extra_flags) + extra_flags |= Qt.ItemFlag.ItemIsEditable + return super().flags(idx) | extra_flags @staticmethod def _tx_mined_info_from_tx_item(tx_item: Dict[str, Any]) -> TxMinedInfo: @@ -493,11 +495,11 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): self.period_combo.addItems([_('All'), _('Custom')]) self.period_combo.activated.connect(self.on_combo) self.wallet = self.main_window.wallet # type: Abstract_Wallet - self.sortByColumn(HistoryColumns.STATUS, Qt.AscendingOrder) + self.sortByColumn(HistoryColumns.STATUS, Qt.SortOrder.AscendingOrder) self.setRootIsDecorated(True) self.header().setStretchLastSection(False) for col in HistoryColumns: - sm = QHeaderView.Stretch if col == self.stretch_column else QHeaderView.ResizeToContents + sm = QHeaderView.ResizeMode.Stretch if col == self.stretch_column else QHeaderView.ResizeMode.ResizeToContents self.header().setSectionResizeMode(col, sm) if self.config: self.configvar_show_toolbar = self.config.cv.GUI_QT_HISTORY_TAB_SHOW_TOOLBAR @@ -580,7 +582,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): vbox.addWidget(cal) vbox.addLayout(Buttons(OkButton(d), CancelButton(d))) d.setLayout(vbox) - if d.exec_(): + if d.exec(): if d.date is None: return None date = d.date.toPyDate() @@ -658,7 +660,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): vbox.addLayout(grid2) vbox.addLayout(Buttons(CloseButton(d))) d.setLayout(vbox) - d.exec_() + d.exec() def plot_history_dialog(self): try: @@ -711,11 +713,11 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): for column in HistoryColumns: if self.isColumnHidden(column): continue - column_title = self.hm.headerData(column, Qt.Horizontal, Qt.DisplayRole) + column_title = self.hm.headerData(column, Qt.Orientation.Horizontal, Qt.ItemDataRole.DisplayRole) idx2 = idx.sibling(idx.row(), column) clipboard_data = self.hm.data(idx2, self.ROLE_CLIPBOARD_DATA).value() if clipboard_data is None: - clipboard_data = (self.hm.data(idx2, Qt.DisplayRole).value() or '').strip() + clipboard_data = (self.hm.data(idx2, Qt.ItemDataRole.DisplayRole).value() or '').strip() cc.addAction( column_title, lambda text=clipboard_data, title=column_title: @@ -739,7 +741,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): log = self.wallet.lnworker.logs.get(key) if log: menu.addAction(_("View log"), lambda: self.main_window.send_tab.invoice_list.show_log(key, log)) - menu.exec_(self.viewport().mapToGlobal(position)) + menu.exec(self.viewport().mapToGlobal(position)) return tx_hash = tx_item['txid'] tx = self.wallet.adb.get_transaction(tx_hash) @@ -757,7 +759,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): menu_edit = menu.addMenu(_("Edit")) for c in self.editable_columns: if self.isColumnHidden(c): continue - label = self.hm.headerData(c, Qt.Horizontal, Qt.DisplayRole) + label = self.hm.headerData(c, Qt.Orientation.Horizontal, Qt.ItemDataRole.DisplayRole) # TODO use siblingAtColumn when min Qt version is >=5.11 persistent = QPersistentModelIndex(org_idx.sibling(org_idx.row(), c)) menu_edit.addAction(_("{}").format(label), lambda p=persistent: self.edit(QModelIndex(p))) @@ -781,7 +783,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): menu_invs.addAction(_("View invoice"), lambda inv=inv: self.main_window.show_onchain_invoice(inv)) if tx_URL: menu.addAction(_("View on block explorer"), lambda: webopen(tx_URL)) - menu.exec_(self.viewport().mapToGlobal(position)) + menu.exec(self.viewport().mapToGlobal(position)) def remove_local_tx(self, tx_hash: str): num_child_txs = len(self.wallet.adb.get_depending_transactions(tx_hash)) @@ -821,7 +823,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): vbox.addLayout(hbox) #run_hook('export_history_dialog', self, hbox) self.update() - if not d.exec_(): + if not d.exec(): return filename = filename_e.text() if not filename: @@ -867,7 +869,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop): f.write(json_encode(txns)) def get_text_from_coordinate(self, row, col): - return self.get_role_data_from_coordinate(row, col, role=Qt.DisplayRole) + return self.get_role_data_from_coordinate(row, col, role=Qt.ItemDataRole.DisplayRole) def get_role_data_from_coordinate(self, row, col, *, role): idx = self.model().mapToSource(self.model().index(row, col)) diff --git a/electrum/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py index 946d92ef2..569ac1be9 100644 --- a/electrum/gui/qt/invoice_list.py +++ b/electrum/gui/qt/invoice_list.py @@ -26,10 +26,10 @@ import enum from typing import Sequence, TYPE_CHECKING -from PyQt5.QtCore import Qt, QItemSelectionModel -from PyQt5.QtGui import QStandardItemModel, QStandardItem -from PyQt5.QtWidgets import QAbstractItemView -from PyQt5.QtWidgets import QMenu, QVBoxLayout, QTreeWidget, QTreeWidgetItem, QHeaderView +from PyQt6.QtCore import Qt, QItemSelectionModel +from PyQt6.QtGui import QStandardItemModel, QStandardItem +from PyQt6.QtWidgets import QAbstractItemView +from PyQt6.QtWidgets import QMenu, QVBoxLayout, QTreeWidget, QTreeWidgetItem, QHeaderView from electrum.i18n import _ from electrum.util import format_time @@ -47,9 +47,9 @@ if TYPE_CHECKING: from .send_tab import SendTab -ROLE_REQUEST_TYPE = Qt.UserRole -ROLE_REQUEST_ID = Qt.UserRole + 1 -ROLE_SORT_ORDER = Qt.UserRole + 2 +ROLE_REQUEST_TYPE = Qt.ItemDataRole.UserRole +ROLE_REQUEST_ID = Qt.ItemDataRole.UserRole + 1 +ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 2 class InvoiceList(MyTreeView): @@ -82,7 +82,7 @@ class InvoiceList(MyTreeView): self.proxy.setSourceModel(self.std_model) self.setModel(self.proxy) self.setSortingEnabled(True) - self.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) def on_double_click(self, idx): key = idx.sibling(idx.row(), self.Columns.DATE).data(ROLE_REQUEST_ID) @@ -139,7 +139,7 @@ class InvoiceList(MyTreeView): self.filter() self.proxy.setDynamicSortFilter(True) # sort requests by date - self.sortByColumn(self.Columns.DATE, Qt.DescendingOrder) + self.sortByColumn(self.Columns.DATE, Qt.SortOrder.DescendingOrder) self.hide_if_empty() def show_invoice(self, key): @@ -165,7 +165,7 @@ class InvoiceList(MyTreeView): if can_batch_pay: menu.addAction(_("Batch pay invoices") + "...", lambda: self.send_tab.pay_multiple_invoices(invoices)) menu.addAction(_("Delete invoices"), lambda: self.delete_invoices(keys)) - menu.exec_(self.viewport().mapToGlobal(position)) + menu.exec(self.viewport().mapToGlobal(position)) return idx = self.indexAt(position) item = self.item_from_index(idx) @@ -193,7 +193,7 @@ class InvoiceList(MyTreeView): if log: menu.addAction(_("View log"), lambda: self.show_log(key, log)) menu.addAction(_("Delete"), lambda: self.delete_invoices([key])) - menu.exec_(self.viewport().mapToGlobal(position)) + menu.exec(self.viewport().mapToGlobal(position)) def show_log(self, key, log: Sequence[HtlcLog]): d = WindowModalDialog(self, _("Payment log")) @@ -201,15 +201,15 @@ class InvoiceList(MyTreeView): vbox = QVBoxLayout(d) log_w = QTreeWidget() log_w.setHeaderLabels([_('Hops'), _('Channel ID'), _('Message')]) - log_w.header().setSectionResizeMode(2, QHeaderView.Stretch) - log_w.header().setSectionResizeMode(1, QHeaderView.ResizeToContents) + log_w.header().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) + log_w.header().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) for payment_attempt_log in log: route_str, chan_str, message = payment_attempt_log.formatted_tuple() x = QTreeWidgetItem([route_str, chan_str, message]) log_w.addTopLevelItem(x) vbox.addWidget(log_w) vbox.addLayout(Buttons(CloseButton(d))) - d.exec_() + d.exec() def delete_invoices(self, keys): for key in keys: diff --git a/electrum/gui/qt/lightning_dialog.py b/electrum/gui/qt/lightning_dialog.py index ee90a02c6..4c6fb37f3 100644 --- a/electrum/gui/qt/lightning_dialog.py +++ b/electrum/gui/qt/lightning_dialog.py @@ -25,7 +25,7 @@ from typing import TYPE_CHECKING -from PyQt5.QtWidgets import (QDialog, QLabel, QVBoxLayout, QPushButton) +from PyQt6.QtWidgets import (QDialog, QLabel, QVBoxLayout, QPushButton) from electrum.i18n import _ diff --git a/electrum/gui/qt/lightning_tx_dialog.py b/electrum/gui/qt/lightning_tx_dialog.py index 845aa1d4a..6abab8f89 100644 --- a/electrum/gui/qt/lightning_tx_dialog.py +++ b/electrum/gui/qt/lightning_tx_dialog.py @@ -27,8 +27,8 @@ from typing import TYPE_CHECKING from decimal import Decimal import datetime -from PyQt5.QtGui import QFont -from PyQt5.QtWidgets import QVBoxLayout, QLabel, QGridLayout +from PyQt6.QtGui import QFont +from PyQt6.QtWidgets import QVBoxLayout, QLabel, QGridLayout from electrum.i18n import _ from electrum.lnworker import PaymentDirection diff --git a/electrum/gui/qt/locktimeedit.py b/electrum/gui/qt/locktimeedit.py index f81f3b884..9d72dfbd8 100644 --- a/electrum/gui/qt/locktimeedit.py +++ b/electrum/gui/qt/locktimeedit.py @@ -6,9 +6,9 @@ import time from datetime import datetime from typing import Optional, Any -from PyQt5.QtCore import Qt, QDateTime, pyqtSignal -from PyQt5.QtGui import QPalette, QPainter -from PyQt5.QtWidgets import (QWidget, QLineEdit, QStyle, QStyleOptionFrame, QComboBox, +from PyQt6.QtCore import Qt, QDateTime, pyqtSignal +from PyQt6.QtGui import QPalette, QPainter +from PyQt6.QtWidgets import (QWidget, QLineEdit, QStyle, QStyleOptionFrame, QComboBox, QHBoxLayout, QDateTimeEdit) from electrum.i18n import _ @@ -145,11 +145,11 @@ class LockTimeHeightEdit(LockTimeRawEdit): super().paintEvent(event) panel = QStyleOptionFrame() self.initStyleOption(panel) - textRect = self.style().subElementRect(QStyle.SE_LineEditContents, panel, self) + textRect = self.style().subElementRect(QStyle.SubElement.SE_LineEditContents, panel, self) textRect.adjust(2, 0, -10, 0) painter = QPainter(self) painter.setPen(ColorScheme.GRAY.as_color()) - painter.drawText(textRect, int(Qt.AlignRight | Qt.AlignVCenter), "height") + painter.drawText(textRect, int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter), "height") def get_max_allowed_timestamp() -> int: diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 9b8f7b4fb..ec4a0240f 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -37,15 +37,15 @@ import asyncio from typing import Optional, TYPE_CHECKING, Sequence, Union, Dict, Mapping import concurrent.futures -from PyQt5.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont, QFontMetrics -from PyQt5.QtCore import Qt, QRect, QStringListModel, QSize, pyqtSignal -from PyQt5.QtWidgets import (QMessageBox, QSystemTrayIcon, QTabWidget, +from PyQt6.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont, QFontMetrics, QAction, QShortcut +from PyQt6.QtCore import Qt, QRect, QStringListModel, QSize, pyqtSignal +from PyQt6.QtWidgets import (QMessageBox, QSystemTrayIcon, QTabWidget, QMenuBar, QFileDialog, QCheckBox, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QHBoxLayout, QPushButton, QScrollArea, QTextEdit, - QShortcut, QMainWindow, QInputDialog, + QMainWindow, QInputDialog, QWidget, QSizePolicy, QStatusBar, QToolTip, - QMenu, QAction, QToolButton) + QMenu, QToolButton) import electrum from electrum.gui import messages @@ -113,21 +113,21 @@ class StatusBarButton(QToolButton): self.setText('') self.setIcon(icon) self.setToolTip(tooltip) - self.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) + self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) self.setAutoRaise(True) size = max(25, round(0.9 * sb_height)) self.setMaximumWidth(size) self.clicked.connect(self.onPress) self.func = func self.setIconSize(QSize(size, size)) - self.setCursor(QCursor(Qt.PointingHandCursor)) + self.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) def onPress(self, checked=False): - '''Drops the unwanted PyQt5 "checked" argument''' + '''Drops the unwanted PyQt "checked" argument''' self.func() def keyPressEvent(self, e): - if e.key() in [Qt.Key_Return, Qt.Key_Enter]: + if e.key() in [Qt.Key.Key_Return, Qt.Key.Key_Enter]: self.func() @@ -229,7 +229,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): add_optional_tab(tabs, self.console_tab, read_QIcon("tab_console.png"), _("Con&sole")) add_optional_tab(tabs, self.notes_tab, read_QIcon("pen.png"), _("&Notes")) - tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + tabs.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) central_widget = QScrollArea() vbox = QVBoxLayout(central_widget) @@ -596,7 +596,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): cb_checked = False def on_cb(x): nonlocal cb_checked - cb_checked = x == Qt.Checked + cb_checked = x == Qt.CheckState.Checked cb.stateChanged.connect(on_cb) self.show_warning(msg, title=_('Testnet'), checkbox=cb) if cb_checked: @@ -641,7 +641,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): vbox.addLayout(grid) vbox.addWidget(WWLabel(msg)) vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) - if not d.exec_(): + if not d.exec(): return False backup_dir = self.config.get_backup_dir() if backup_dir is None: @@ -700,9 +700,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): file_menu = menubar.addMenu(_("&File")) self.recently_visited_menu = file_menu.addMenu(_("&Recently open")) - file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open) - file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New) - file_menu.addAction(_("&Save backup"), self.backup_wallet).setShortcut(QKeySequence.SaveAs) + file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.StandardKey.Open) + file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.StandardKey.New) + file_menu.addAction(_("&Save backup"), self.backup_wallet).setShortcut(QKeySequence.StandardKey.SaveAs) file_menu.addAction(_("Delete"), self.remove_wallet) file_menu.addSeparator() file_menu.addAction(_("&Quit"), self.close) @@ -747,7 +747,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): # preferences_action will get picked up based on name (and put into a standardized location, # and given a standard reserved hotkey) # Hence, this menu item will be at a "uniform location re macOS processes" - preferences_action.setMenuRole(QAction.PreferencesRole) # make sure OS recognizes it as preferences + preferences_action.setMenuRole(QAction.MenuRole.PreferencesRole) # make sure OS recognizes it as preferences # Add another preferences item, to also have a "uniform location for Electrum between different OSes" tools_menu.addAction(_("Electrum preferences"), self.settings_dialog) @@ -773,7 +773,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): help_menu.addAction(_("&Check for updates"), self.show_update_check) help_menu.addAction(_("&Official website"), lambda: webopen("https://electrum.org")) help_menu.addSeparator() - help_menu.addAction(_("&Documentation"), lambda: webopen("http://docs.electrum.org/")).setShortcut(QKeySequence.HelpContents) + help_menu.addAction(_("&Documentation"), lambda: webopen("http://docs.electrum.org/")).setShortcut(QKeySequence.StandardKey.HelpContents) if not constants.net.TESTNET: help_menu.addAction(_("&Bitcoin Paper"), self.show_bitcoin_paper) help_menu.addAction(_("&Report Bug"), self.show_report_bug) @@ -861,11 +861,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): def notify(self, message): if self.tray: - try: - # this requires Qt 5.9 - self.tray.showMessage("Electrum", message, read_QIcon("electrum_dark_icon"), 20000) - except TypeError: - self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000) + self.tray.showMessage("Electrum", message, read_QIcon("electrum_dark_icon"), 20000) def timer_actions(self): # refresh invoices and requests because they show ETA @@ -1088,12 +1084,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): def show_address(self, addr: str, *, parent: QWidget = None): from . import address_dialog d = address_dialog.AddressDialog(self, addr, parent=parent) - d.exec_() + d.exec() def show_utxo(self, utxo): from . import utxo_dialog d = utxo_dialog.UTXODialog(self, utxo) - d.exec_() + d.exec() def show_channel_details(self, chan): from .channel_details import ChannelDetailsDialog @@ -1508,7 +1504,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): buttons = Buttons(CloseButton(d)) vbox.addLayout(grid) vbox.addLayout(buttons) - d.exec_() + d.exec() def show_lightning_invoice(self, invoice: Invoice): from electrum.util import format_short_id @@ -1554,7 +1550,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): grid.addWidget(routing_e, 9, 1) vbox.addLayout(grid) vbox.addLayout(Buttons(CloseButton(d),)) - d.exec_() + d.exec() def create_console_tab(self): from .console import Console @@ -1563,10 +1559,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): return console def create_notes_tab(self): - from PyQt5 import QtGui, QtWidgets + from PyQt6 import QtGui, QtWidgets notes_tab = QtWidgets.QPlainTextEdit() - notes_tab.setWordWrapMode(QtGui.QTextOption.WrapAnywhere) - notes_tab.setFont(QtGui.QFont(MONOSPACE_FONT, 10, QtGui.QFont.Normal)) + notes_tab.setWordWrapMode(QtGui.QTextOption.WrapMode.WrapAnywhere) + notes_tab.setFont(QtGui.QFont(MONOSPACE_FONT, 10, QtGui.QFont.Weight.Normal)) notes_tab.setPlainText(self.wallet.db.get('notes_text', '')) notes_tab.is_shown_cv = self.config.cv.GUI_QT_SHOW_TAB_NOTES notes_tab.textChanged.connect(self.maybe_save_notes_text) @@ -1646,7 +1642,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): self.update_check_button = QPushButton("") self.update_check_button.setFlat(True) - self.update_check_button.setCursor(QCursor(Qt.PointingHandCursor)) + self.update_check_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.update_check_button.setIcon(read_QIcon("update.png")) self.update_check_button.hide() sb.addPermanentWidget(self.update_check_button) @@ -1694,8 +1690,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): + ColorScheme.GREEN.as_stylesheet(True)) self.coincontrol_label = QLabel() - self.coincontrol_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) - self.coincontrol_label.setTextInteractionFlags(Qt.TextSelectableByMouse) + self.coincontrol_label.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) + self.coincontrol_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) sb.addWidget(self.coincontrol_label) clear_cc_button = EnterButton(_('Reset'), lambda: self.utxo_list.clear_coincontrol()) @@ -1830,7 +1826,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): grid.addWidget(line2, 2, 1) vbox.addLayout(grid) vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) - if d.exec_(): + if d.exec(): self.set_contact(line2.text(), line1.text()) def init_lightning_dialog(self, dialog): @@ -1857,7 +1853,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): def show_wallet_info(self): from .wallet_info_dialog import WalletInfoDialog d = WalletInfoDialog(self, window=self) - d.exec_() + d.exec() def remove_wallet(self): if self.question('\n'.join([ @@ -1891,7 +1887,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): return from .seed_dialog import SeedDialog d = SeedDialog(self, seed, passphrase, config=self.config) - d.exec_() + d.exec() def show_qrcode(self, data, title=None, parent=None, *, help_text=None, show_copy_text_btn=False): @@ -1907,7 +1903,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): show_copy_text_btn=show_copy_text_btn, config=self.config, ) - d.exec_() + d.exec() @protected def show_private_key(self, address, password): @@ -1931,7 +1927,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): vbox.addWidget(keys_e) vbox.addLayout(Buttons(CloseButton(d))) d.setLayout(vbox) - d.exec_() + d.exec() msg_sign = _("Signing with an address actually means signing with the corresponding " "private key, and verifying with the corresponding public key. The " @@ -2021,7 +2017,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): b.clicked.connect(d.accept) hbox.addWidget(b) layout.addLayout(hbox, 4, 1) - d.exec_() + d.exec() @protected def do_decrypt(self, message_e, pubkey_e, encrypted_e, password): @@ -2092,7 +2088,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): hbox.addWidget(b) layout.addLayout(hbox, 4, 1) - d.exec_() + d.exec() def password_dialog(self, msg=None, parent=None): from .password_dialog import PasswordDialog @@ -2303,7 +2299,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): d.finished.connect(on_dialog_closed) threading.Thread(target=privkeys_thread).start() - if not d.exec_(): + if not d.exec(): done = True return @@ -2373,7 +2369,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): vbox = QVBoxLayout(d) hbox_top = QHBoxLayout() hbox_top.addWidget(QLabel(_("Enter private keys to sweep coins from:"))) - hbox_top.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight) + hbox_top.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignmentFlag.AlignRight) vbox.addLayout(hbox_top) keys_e = ScanQRTextEdit(allow_multi=True, config=self.config) keys_e.setTabChangesFocus(True) @@ -2417,7 +2413,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): address_e.textChanged.connect(on_edit) address_e.textChanged.connect(on_address) on_address(str(address_e.text())) - if not d.exec_(): + if not d.exec(): return # user pressed "sweep" addr = get_address() @@ -2480,7 +2476,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): title = _('Import private keys') header_layout = QHBoxLayout() header_layout.addWidget(QLabel(_("Enter private keys")+':')) - header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight) + header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignmentFlag.AlignRight) self._do_import(title, header_layout, lambda x: self.wallet.import_private_keys(x, password)) def refresh_amount_edits(self): @@ -2502,7 +2498,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): def settings_dialog(self): from .settings_dialog import SettingsDialog d = SettingsDialog(self, self.config) - d.exec_() + d.exec() if self.fx: self.fx.trigger_update() run_hook('close_settings_dialog') @@ -2547,7 +2543,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): def plugins_dialog(self): from .plugins_dialog import PluginsDialog d = PluginsDialog(self) - d.exec_() + d.exec() def cpfp_dialog(self, parent_tx: Transaction) -> None: new_tx = self.wallet.cpfp(parent_tx, 0) @@ -2624,7 +2620,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): grid.addWidget(combined_feerate, 6, 1) vbox.addLayout(grid) vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) - if not d.exec_(): + if not d.exec(): return fee = fee_e.get_amount() if fee is None: diff --git a/electrum/gui/qt/my_treeview.py b/electrum/gui/qt/my_treeview.py index 5f4892873..0e998beae 100644 --- a/electrum/gui/qt/my_treeview.py +++ b/electrum/gui/qt/my_treeview.py @@ -39,20 +39,20 @@ from functools import partial, lru_cache, wraps from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, Union, List, Dict, Any, Sequence, Iterable, Tuple, Type) -from PyQt5 import QtWidgets, QtCore -from PyQt5.QtGui import (QFont, QColor, QCursor, QPixmap, QStandardItem, QImage, - QPalette, QIcon, QFontMetrics, QShowEvent, QPainter, QHelpEvent, QMouseEvent) -from PyQt5.QtCore import (Qt, QPersistentModelIndex, QModelIndex, pyqtSignal, +from PyQt6 import QtWidgets, QtCore +from PyQt6.QtGui import (QFont, QColor, QCursor, QPixmap, QStandardItem, QImage, QStandardItemModel, + QPalette, QIcon, QFontMetrics, QShowEvent, QPainter, QHelpEvent, QMouseEvent, QAction) +from PyQt6.QtCore import (Qt, QPersistentModelIndex, QModelIndex, pyqtSignal, QCoreApplication, QItemSelectionModel, QThread, QSortFilterProxyModel, QSize, QLocale, QAbstractItemModel, QEvent, QRect, QPoint, QObject) -from PyQt5.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, +from PyQt6.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, QAbstractItemView, QVBoxLayout, QLineEdit, QStyle, QDialog, QGroupBox, QButtonGroup, QRadioButton, QFileDialog, QWidget, QToolButton, QTreeView, QPlainTextEdit, QHeaderView, QApplication, QToolTip, QTreeWidget, QStyledItemDelegate, QMenu, QStyleOptionViewItem, QLayout, QLayoutItem, QAbstractButton, - QGraphicsEffect, QGraphicsScene, QGraphicsPixmapItem, QSizePolicy, QAction) + QGraphicsEffect, QGraphicsScene, QGraphicsPixmapItem, QSizePolicy) from electrum.i18n import _, languages from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, resource_path @@ -117,8 +117,8 @@ def create_toolbar_with_menu(config: 'SimpleConfig', title): toolbar_button = QToolButton() toolbar_button.setIcon(read_QIcon("preferences.png")) toolbar_button.setMenu(menu) - toolbar_button.setPopupMode(QToolButton.InstantPopup) - toolbar_button.setFocusPolicy(Qt.NoFocus) + toolbar_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) + toolbar_button.setFocusPolicy(Qt.FocusPolicy.NoFocus) toolbar = QHBoxLayout() toolbar.addWidget(QLabel(title)) toolbar.addStretch() @@ -133,8 +133,9 @@ class MySortModel(QSortFilterProxyModel): self._sort_role = sort_role def lessThan(self, source_left: QModelIndex, source_right: QModelIndex): - item1 = self.sourceModel().itemFromIndex(source_left) - item2 = self.sourceModel().itemFromIndex(source_right) + parent_model = self.sourceModel() # type: QStandardItemModel + item1 = parent_model.itemFromIndex(source_left) + item2 = parent_model.itemFromIndex(source_right) data1 = item1.data(self._sort_role) data2 = item2.data(self._sort_role) if data1 is not None and data2 is not None: @@ -186,7 +187,7 @@ class ElectrumItemDelegate(QStyledItemDelegate): if custom_data is None: return super().helpEvent(evt, view, option, idx) else: - if evt.type() == QEvent.ToolTip: + if evt.type() == QEvent.Type.ToolTip: if custom_data.show_tooltip(evt): return True return super().helpEvent(evt, view, option, idx) @@ -201,10 +202,10 @@ class ElectrumItemDelegate(QStyledItemDelegate): class MyTreeView(QTreeView): - ROLE_CLIPBOARD_DATA = Qt.UserRole + 100 - ROLE_CUSTOM_PAINT = Qt.UserRole + 101 - ROLE_EDIT_KEY = Qt.UserRole + 102 - ROLE_FILTER_DATA = Qt.UserRole + 103 + ROLE_CLIPBOARD_DATA = Qt.ItemDataRole.UserRole + 100 + ROLE_CUSTOM_PAINT = Qt.ItemDataRole.UserRole + 101 + ROLE_EDIT_KEY = Qt.ItemDataRole.UserRole + 102 + ROLE_FILTER_DATA = Qt.ItemDataRole.UserRole + 103 filter_columns: Iterable[int] @@ -229,7 +230,7 @@ class MyTreeView(QTreeView): self.main_window = main_window self.config = self.main_window.config if self.main_window else None self.stretch_column = stretch_column - self.setContextMenuPolicy(Qt.CustomContextMenu) + self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self.create_menu) self.setUniformRowHeights(True) @@ -294,7 +295,7 @@ class MyTreeView(QTreeView): if set_current: assert isinstance(set_current, QPersistentModelIndex) assert set_current.isValid() - self.selectionModel().select(QModelIndex(set_current), QItemSelectionModel.SelectCurrent) + self.selectionModel().select(QModelIndex(set_current), QItemSelectionModel.SelectionFlag.SelectCurrent) def update_headers(self, headers: Union[List[str], Dict[int, str]]): # headers is either a list of column names, or a dict: (col_idx->col_name) @@ -304,13 +305,13 @@ class MyTreeView(QTreeView): self.original_model().setHorizontalHeaderLabels(col_names) self.header().setStretchLastSection(False) for col_idx in headers: - sm = QHeaderView.Stretch if col_idx == self.stretch_column else QHeaderView.ResizeToContents + sm = QHeaderView.ResizeMode.Stretch if col_idx == self.stretch_column else QHeaderView.ResizeMode.ResizeToContents self.header().setSectionResizeMode(col_idx, sm) def keyPressEvent(self, event): if self.itemDelegate().opened: return - if event.key() in [Qt.Key_F2, Qt.Key_Return, Qt.Key_Enter]: + if event.key() in [Qt.Key.Key_F2, Qt.Key.Key_Return, Qt.Key.Key_Enter]: self.on_activated(self.selectionModel().currentIndex()) return super().keyPressEvent(event) @@ -333,7 +334,7 @@ class MyTreeView(QTreeView): pt.setX(50) self.customContextMenuRequested.emit(pt) - def edit(self, idx, trigger=QAbstractItemView.AllEditTriggers, event=None): + def edit(self, idx, trigger=QAbstractItemView.EditTrigger.AllEditTriggers, event=None): """ this is to prevent: edit: editing failed diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py index 1ad70a090..a7609c0f6 100644 --- a/electrum/gui/qt/network_dialog.py +++ b/electrum/gui/qt/network_dialog.py @@ -29,11 +29,11 @@ from enum import IntEnum from typing import Tuple, TYPE_CHECKING import threading -from PyQt5.QtCore import Qt, pyqtSignal, QThread -from PyQt5.QtWidgets import (QTreeWidget, QTreeWidgetItem, QMenu, QGridLayout, QComboBox, +from PyQt6.QtCore import Qt, pyqtSignal, QThread +from PyQt6.QtWidgets import (QTreeWidget, QTreeWidgetItem, QMenu, QGridLayout, QComboBox, QLineEdit, QDialog, QVBoxLayout, QHeaderView, QCheckBox, QTabWidget, QWidget, QLabel) -from PyQt5.QtGui import QIntValidator +from PyQt6.QtGui import QIntValidator from electrum.i18n import _ from electrum import constants, blockchain, util @@ -86,9 +86,9 @@ class NetworkDialog(QDialog, QtEventListener): class NodesListWidget(QTreeWidget): """List of connected servers.""" - SERVER_ADDR_ROLE = Qt.UserRole + 100 - CHAIN_ID_ROLE = Qt.UserRole + 101 - ITEMTYPE_ROLE = Qt.UserRole + 102 + SERVER_ADDR_ROLE = Qt.ItemDataRole.UserRole + 100 + CHAIN_ID_ROLE = Qt.ItemDataRole.UserRole + 101 + ITEMTYPE_ROLE = Qt.ItemDataRole.UserRole + 102 class ItemType(IntEnum): CHAIN = 0 @@ -103,7 +103,7 @@ class NodesListWidget(QTreeWidget): def __init__(self, *, network: Network): QTreeWidget.__init__(self) self.setHeaderLabels([_('Server'), _('Height')]) - self.setContextMenuPolicy(Qt.CustomContextMenu) + self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self.create_menu) self.network = network @@ -137,10 +137,10 @@ class NodesListWidget(QTreeWidget): menu.addAction(_("Follow this branch"), do_follow_chain) else: return - menu.exec_(self.viewport().mapToGlobal(position)) + menu.exec(self.viewport().mapToGlobal(position)) def keyPressEvent(self, event): - if event.key() in [Qt.Key_F2, Qt.Key_Return, Qt.Key_Enter]: + if event.key() in [Qt.Key.Key_F2, Qt.Key.Key_Return, Qt.Key.Key_Enter]: self.on_activated(self.currentItem(), self.currentColumn()) else: QTreeWidget.keyPressEvent(self, event) @@ -220,8 +220,8 @@ class NodesListWidget(QTreeWidget): # headers h = self.header() h.setStretchLastSection(False) - h.setSectionResizeMode(0, QHeaderView.Stretch) - h.setSectionResizeMode(1, QHeaderView.ResizeToContents) + h.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) + h.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) super().update() diff --git a/electrum/gui/qt/new_channel_dialog.py b/electrum/gui/qt/new_channel_dialog.py index e2f7d7094..2881011e7 100644 --- a/electrum/gui/qt/new_channel_dialog.py +++ b/electrum/gui/qt/new_channel_dialog.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING, Optional -from PyQt5.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton, QComboBox, QLineEdit, QSpacerItem, QWidget, QHBoxLayout +from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton, QComboBox, QLineEdit, QSpacerItem, QWidget, QHBoxLayout from electrum.i18n import _ from electrum.transaction import PartialTxOutput, PartialTransaction @@ -134,7 +134,7 @@ class NewChannelDialog(WindowModalDialog): self.amount_e.setAmount(amount) def run(self): - if not self.exec_(): + if not self.exec(): return if self.max_button.isChecked() and self.amount_e.get_amount() < self.config.LIGHTNING_MAX_FUNDING_SAT: # if 'max' enabled and amount is strictly less than max allowed, diff --git a/electrum/gui/qt/password_dialog.py b/electrum/gui/qt/password_dialog.py index fd4d4849b..924a1f3c9 100644 --- a/electrum/gui/qt/password_dialog.py +++ b/electrum/gui/qt/password_dialog.py @@ -27,9 +27,9 @@ import re import math from functools import partial -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPixmap -from PyQt5.QtWidgets import QLineEdit, QLabel, QGridLayout, QVBoxLayout, QCheckBox +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QPixmap +from PyQt6.QtWidgets import QLineEdit, QLabel, QGridLayout, QVBoxLayout, QCheckBox from electrum.i18n import _ from electrum.plugin import run_hook @@ -93,7 +93,7 @@ class PasswordLayout(object): logo_grid.setColumnStretch(1,1) logo = QLabel() - logo.setAlignment(Qt.AlignCenter) + logo.setAlignment(Qt.AlignmentFlag.AlignCenter) logo_grid.addWidget(logo, 0, 0) logo_grid.addWidget(label, 0, 1, 1, 2) @@ -108,7 +108,7 @@ class PasswordLayout(object): else: lockfile = "unlock.png" logo.setPixmap(QPixmap(icon_path(lockfile)) - .scaledToWidth(36, mode=Qt.SmoothTransformation)) + .scaledToWidth(36, mode=Qt.TransformationMode.SmoothTransformation)) grid.addWidget(QLabel(msgs[0]), 1, 0) grid.addWidget(self.new_pw, 1, 1) @@ -196,7 +196,7 @@ class PasswordLayoutForHW(object): logo_grid.setColumnStretch(1,1) logo = QLabel() - logo.setAlignment(Qt.AlignCenter) + logo.setAlignment(Qt.AlignmentFlag.AlignCenter) logo_grid.addWidget(logo, 0, 0) logo_grid.addWidget(label, 0, 1, 1, 2) @@ -207,7 +207,7 @@ class PasswordLayoutForHW(object): else: lockfile = "unlock.png" logo.setPixmap(QPixmap(icon_path(lockfile)) - .scaledToWidth(36, mode=Qt.SmoothTransformation)) + .scaledToWidth(36, mode=Qt.TransformationMode.SmoothTransformation)) vbox.addLayout(grid) @@ -268,7 +268,7 @@ class ChangePasswordDialogForSW(ChangePasswordDialogBase): def run(self): try: - if not self.exec_(): + if not self.exec(): return False, None, None, None return True, self.playout.old_password(), self.playout.new_password(), self.playout.encrypt_cb.isChecked() finally: @@ -290,7 +290,7 @@ class ChangePasswordDialogForHW(ChangePasswordDialogBase): self.playout = PasswordLayoutForHW(msg) def run(self): - if not self.exec_(): + if not self.exec(): return False, None return True, self.playout.encrypt_cb.isChecked() @@ -314,7 +314,7 @@ class PasswordDialog(WindowModalDialog): def run(self): try: - if not self.exec_(): + if not self.exec(): return return self.pw.text() finally: diff --git a/electrum/gui/qt/paytoedit.py b/electrum/gui/qt/paytoedit.py index 315c97d7e..a6db8ffba 100644 --- a/electrum/gui/qt/paytoedit.py +++ b/electrum/gui/qt/paytoedit.py @@ -26,10 +26,10 @@ from functools import partial from typing import Optional, TYPE_CHECKING -from PyQt5.QtCore import Qt, QTimer, QSize -from PyQt5.QtCore import QObject, pyqtSignal -from PyQt5.QtGui import QFontMetrics, QFont -from PyQt5.QtWidgets import QApplication, QTextEdit, QWidget, QLineEdit, QStackedLayout, QSizePolicy +from PyQt6.QtCore import Qt, QTimer, QSize +from PyQt6.QtCore import QObject, pyqtSignal +from PyQt6.QtGui import QFontMetrics, QFont +from PyQt6.QtWidgets import QApplication, QTextEdit, QWidget, QLineEdit, QStackedLayout, QSizePolicy from electrum.payment_identifier import PaymentIdentifier from electrum.logging import Logger @@ -85,7 +85,7 @@ class ResizingTextEdit(QTextEdit): h = min(max(h, self.heightMin), self.heightMax) self.setMinimumHeight(int(h)) self.setMaximumHeight(int(h)) - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.verticalScrollBar().setHidden(docHeight + self.verticalMargins < self.heightMax) self.setLineWrapMode(QTextEdit.LineWrapMode.WidgetWidth) self.resized.emit() @@ -199,7 +199,7 @@ class PayToEdit(QWidget, Logger, GenericInputHandler): self.line_edit.setText(text) self.text_edit.setText(text) - def setFocus(self, reason=Qt.OtherFocusReason) -> None: + def setFocus(self, reason=Qt.FocusReason.OtherFocusReason) -> None: if self.multiline: self.text_edit.setFocus(reason) else: diff --git a/electrum/gui/qt/plugins_dialog.py b/electrum/gui/qt/plugins_dialog.py index ca5d76102..a88b8b1cf 100644 --- a/electrum/gui/qt/plugins_dialog.py +++ b/electrum/gui/qt/plugins_dialog.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING, Optional from functools import partial -from PyQt5.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton, QComboBox, QLineEdit, QSpacerItem, QWidget, QHBoxLayout, QScrollArea, QCheckBox +from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton, QComboBox, QLineEdit, QSpacerItem, QWidget, QHBoxLayout, QScrollArea, QCheckBox from electrum.i18n import _ from electrum.gui import messages diff --git a/electrum/gui/qt/qrcodewidget.py b/electrum/gui/qt/qrcodewidget.py index 80c1f4364..10321f26f 100644 --- a/electrum/gui/qt/qrcodewidget.py +++ b/electrum/gui/qt/qrcodewidget.py @@ -3,10 +3,10 @@ from typing import Optional import qrcode import qrcode.exceptions -from PyQt5.QtGui import QColor, QPen -import PyQt5.QtGui as QtGui -from PyQt5.QtCore import Qt, QRect -from PyQt5.QtWidgets import ( +from PyQt6.QtGui import QColor, QPen +import PyQt6.QtGui as QtGui +from PyQt6.QtCore import Qt, QRect +from PyQt6.QtWidgets import ( QApplication, QVBoxLayout, QTextEdit, QHBoxLayout, QPushButton, QWidget, QFileDialog, ) @@ -63,7 +63,7 @@ class QRCodeWidget(QWidget): grey = QColor(196, 196, 196, 255) white = QColor(255, 255, 255, 255) black_pen = QPen(black) if self.isEnabled() else QPen(grey) - black_pen.setJoinStyle(Qt.MiterJoin) + black_pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin) if not self.qr: qp = QtGui.QPainter() diff --git a/electrum/gui/qt/qrreader/__init__.py b/electrum/gui/qt/qrreader/__init__.py index a3a3f5072..4febc7d5b 100644 --- a/electrum/gui/qt/qrreader/__init__.py +++ b/electrum/gui/qt/qrreader/__init__.py @@ -11,7 +11,7 @@ # to access the camera # - zbar fails to access the camera on macOS # - qtmultimedia seems to support more cameras on Windows than zbar -# - qtmultimedia is often not packaged with PyQt5 +# - qtmultimedia is often not packaged with PyQt # in particular, on debian, you need both "python3-pyqt5" and "python3-pyqt5.qtmultimedia" # - older versions of qtmultimedia don't seem to work reliably # @@ -24,8 +24,8 @@ import sys from typing import Callable, Optional, TYPE_CHECKING, Mapping, Sequence -from PyQt5.QtWidgets import QMessageBox, QWidget -from PyQt5.QtGui import QImage +from PyQt6.QtWidgets import QMessageBox, QWidget +from PyQt6.QtGui import QImage from electrum.i18n import _ from electrum.util import UserFacingException @@ -59,12 +59,13 @@ def scan_qrcode( def scan_qr_from_image(image: QImage) -> Sequence[QrCodeResult]: """Might raise exception: MissingQrDetectionLib.""" qr_reader = get_qr_reader() - image_y800 = image.convertToFormat(QImage.Format_Grayscale8) + image_y800 = image.convertToFormat(QImage.Format.Format_Grayscale8) res = qr_reader.read_qr_code( - image_y800.constBits().__int__(), image_y800.byteCount(), + image_y800.constBits().__int__(), + image_y800.sizeInBytes(), image_y800.bytesPerLine(), image_y800.width(), - image_y800.height() + image_y800.height(), ) return res @@ -124,10 +125,11 @@ def _scan_qrcode_using_qtmultimedia( try: from .qtmultimedia import QrReaderCameraDialog, CameraError except ImportError as e: - icon = QMessageBox.Warning + icon = QMessageBox.Icon.Warning title = _("QR Reader Error") message = _("QR reader failed to load. This may happen if " - "you are using an older version of PyQt5.") + "\n\n" + str(e) + "you are using an older version of PyQt.") + "\n\n" + str(e) + _logger.exception(message) if isinstance(parent, MessageBoxMixin): parent.msg_box(title=title, text=message, icon=icon, parent=None) else: diff --git a/electrum/gui/qt/qrreader/qtmultimedia/__init__.py b/electrum/gui/qt/qrreader/qtmultimedia/__init__.py index b159db948..e7db9400b 100644 --- a/electrum/gui/qt/qrreader/qtmultimedia/__init__.py +++ b/electrum/gui/qt/qrreader/qtmultimedia/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -# Electron Cash - lightweight Bitcoin client # Copyright (C) 2019 Axel Gembe +# Copyright (c) 2024 The Electrum developers # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files @@ -26,7 +26,7 @@ from typing import Mapping from .camera_dialog import (QrReaderCameraDialog, CameraError, NoCamerasFound, - NoCameraResolutionsFound) + get_camera_path) from .validator import (QrReaderValidatorResult, AbstractQrReaderValidator, QrReaderValidatorCounting, QrReaderValidatorColorizing, QrReaderValidatorStrong, QrReaderValidatorCounted) @@ -34,6 +34,6 @@ from .validator import (QrReaderValidatorResult, AbstractQrReaderValidator, def find_system_cameras() -> Mapping[str, str]: """Returns a camera_description -> camera_path map.""" - from PyQt5.QtMultimedia import QCameraInfo - system_cameras = QCameraInfo.availableCameras() - return {cam.description(): cam.deviceName() for cam in system_cameras} + from PyQt6.QtMultimedia import QMediaDevices + system_cameras = QMediaDevices.videoInputs() + return {cam.description(): get_camera_path(cam) for cam in system_cameras} diff --git a/electrum/gui/qt/qrreader/qtmultimedia/camera_dialog.py b/electrum/gui/qt/qrreader/qtmultimedia/camera_dialog.py index db62c2dbb..b915b3b6f 100644 --- a/electrum/gui/qt/qrreader/qtmultimedia/camera_dialog.py +++ b/electrum/gui/qt/qrreader/qtmultimedia/camera_dialog.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -# Electron Cash - lightweight Bitcoin client # Copyright (C) 2019 Axel Gembe +# Copyright (c) 2024 The Electrum developers # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files @@ -29,10 +29,10 @@ import sys import os from typing import List, Optional -from PyQt5.QtMultimedia import QCameraInfo, QCamera, QCameraViewfinderSettings -from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QCheckBox, QPushButton, QLabel, QWidget -from PyQt5.QtGui import QImage, QPixmap -from PyQt5.QtCore import QSize, QRect, Qt, pyqtSignal, PYQT_VERSION +from PyQt6.QtMultimedia import QMediaDevices, QCamera, QMediaCaptureSession, QCameraDevice +from PyQt6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QCheckBox, QPushButton, QLabel, QWidget +from PyQt6.QtGui import QImage, QPixmap +from PyQt6.QtCore import QSize, QRect, Qt, pyqtSignal, PYQT_VERSION from electrum.simple_config import SimpleConfig from electrum.i18n import _ @@ -55,8 +55,10 @@ class NoCamerasFound(CameraError): ''' Raised by start_scan if no usable cameras were found. Interested code can catch this specific exception.''' -class NoCameraResolutionsFound(CameraError): - ''' Raised internally if no usable camera resolutions were found. ''' + +def get_camera_path(cam: 'QCameraDevice') -> str: + return bytes(cam.id()).decode('ascii') + class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog): """ @@ -84,6 +86,7 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog): self.qr_frame_counter: int = 0 self.last_qr_scan_ts: float = 0.0 self.camera: QCamera = None + self.media_capture_session: QMediaCaptureSession = None self._error_message: str = None self._ok_done: bool = False self.camera_sc_conn = None @@ -96,10 +99,10 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog): # Set up the window, add the maximize button flags = self.windowFlags() - flags = flags | Qt.WindowMaximizeButtonHint + flags = flags | Qt.WindowType.WindowMaximizeButtonHint self.setWindowFlags(flags) self.setWindowTitle(_("Scan QR Code")) - self.setWindowModality(Qt.WindowModal if parent else Qt.ApplicationModal) + self.setWindowModality(Qt.WindowModality.WindowModal if parent else Qt.WindowModality.ApplicationModal) # Create video widget and fixed aspect ratio layout to contain it self.video_widget = QrReaderVideoWidget() @@ -114,12 +117,6 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog): vbox.setContentsMargins(0, 0, 0, 0) vbox.addLayout(self.video_layout) - self.lowres_label = QLabel(_("Note: This camera generates frames of relatively low resolution; QR scanning accuracy may be affected")) - self.lowres_label.setWordWrap(True) - self.lowres_label.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter) - vbox.addWidget(self.lowres_label) - self.lowres_label.setHidden(True) - # Create a layout for the controls controls_layout = QHBoxLayout() controls_layout.addStretch(2) @@ -151,57 +148,12 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog): # self.reject() and self.accept() in this class to kill the scan -- # and we do it from within callback functions. If you don't use # queued connections here, bad things can happen. - self.finished.connect(self._boilerplate_cleanup, Qt.QueuedConnection) - self.finished.connect(self._on_finished, Qt.QueuedConnection) + self.finished.connect(self._boilerplate_cleanup, Qt.ConnectionType.QueuedConnection) + self.finished.connect(self._on_finished, Qt.ConnectionType.QueuedConnection) def _on_flip_x_changed(self, _state: int): self.config.QR_READER_FLIP_X = self.flip_x.isChecked() - def _get_resolution(self, resolutions: List[QSize], min_size: int) -> QSize: - """ - Given a list of resolutions that the camera supports this function picks the - lowest resolution that is at least min_size in both width and height. - If no resolution is found, NoCameraResolutionsFound is raised. - """ - def res_list_to_str(res_list: List[QSize]) -> str: - return ', '.join(['{}x{}'.format(r.width(), r.height()) for r in res_list]) - - def check_res(res: QSize): - return res.width() >= min_size and res.height() >= min_size - - self.logger.info('searching for at least {0}x{0}'.format(min_size)) - - # Query and display all resolutions the camera supports - format_str = 'camera resolutions: {}' - self.logger.info(format_str.format(res_list_to_str(resolutions))) - - # Filter to those that are at least min_size in both width and height - candidate_resolutions = [] - ideal_resolutions = [r for r in resolutions if check_res(r)] - less_than_ideal_resolutions = [r for r in resolutions if r not in ideal_resolutions] - format_str = 'ideal resolutions: {}, less-than-ideal resolutions: {}' - self.logger.info(format_str.format(res_list_to_str(ideal_resolutions), res_list_to_str(less_than_ideal_resolutions))) - - # Raise an error if we have no usable resolutions - if not ideal_resolutions and not less_than_ideal_resolutions: - raise NoCameraResolutionsFound(_("Cannot start QR scanner, no usable camera resolution found.") + self._linux_pyqt5bug_msg()) - - if not ideal_resolutions: - self.logger.warning('No ideal resolutions found, falling back to less-than-ideal resolutions -- QR recognition may fail!') - candidate_resolutions = less_than_ideal_resolutions - is_ideal = False - else: - candidate_resolutions = ideal_resolutions - is_ideal = True - - - # Sort the usable resolutions, least number of pixels first, get the first element - resolution = sorted(candidate_resolutions, key=lambda r: r.width() * r.height(), reverse=not is_ideal)[0] - format_str = 'chosen resolution is {}x{}' - self.logger.info(format_str.format(resolution.width(), resolution.height())) - - return resolution, is_ideal - @staticmethod def _get_crop(resolution: QSize, scan_size: int) -> QRect: """ @@ -211,20 +163,6 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog): scan_pos_y = (resolution.height() - scan_size) // 2 return QRect(scan_pos_x, scan_pos_y, scan_size, scan_size) - @staticmethod - def _linux_pyqt5bug_msg(): - ''' Returns a string that may be appended to an exception error message - only if on Linux and PyQt5 < 5.12.2, otherwise returns an empty string. ''' - if (sys.platform == 'linux' and PYQT_VERSION < 0x050c02 # Check if PyQt5 < 5.12.2 on linux - # Also: this warning is not relevant to APPIMAGE; so make sure - # we are not running from APPIMAGE. - and not os.environ.get('APPIMAGE')): - # In this case it's possible we couldn't detect a camera because - # of that missing libQt5MultimediaGstTools.so problem. - return ("\n\n" + _('If you indeed do have a usable camera connected, then this error may be caused by bugs in previous PyQt5 versions on Linux. Try installing the latest PyQt5:') - + "\n\n" + "python3 -m pip install --user -I pyqt5") - return '' - def start_scan(self, device: str = ''): """ Scans a QR code from the given camera device. @@ -237,17 +175,17 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog): device_info = None - for camera in QCameraInfo.availableCameras(): - if camera.deviceName() == device: + for camera in QMediaDevices.videoInputs(): + if get_camera_path(camera) == device: device_info = camera break if not device_info: self.logger.info('Failed to open selected camera, trying to use default camera') - device_info = QCameraInfo.defaultCamera() + device_info = QMediaDevices.defaultVideoInput() if not device_info or device_info.isNull(): - raise NoCamerasFound(_("Cannot start QR scanner, no usable camera found.") + self._linux_pyqt5bug_msg()) + raise NoCamerasFound(_("Cannot start QR scanner, no usable camera found.")) self._init_stats() self.qrreader_res = [] @@ -259,29 +197,14 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog): self.logger.info("Warning: start_scan already called for this instance.") self.camera = QCamera(device_info) - self.camera.setViewfinder(self.video_surface) - self.camera.setCaptureMode(QCamera.CaptureViewfinder) - - # this operates on camera from within the signal handler, so should be a queued connection - self.camera_sc_conn = self.camera.statusChanged.connect(self._on_camera_status_changed, Qt.QueuedConnection) - self.camera.error.connect(self._on_camera_error) # log the errors we get, if any, for debugging - # Camera needs to be loaded to query resolutions, this tries to open the camera - self.camera.load() - - _camera_status_names = { - QCamera.UnavailableStatus: _('unavailable'), - QCamera.UnloadedStatus: _('unloaded'), - QCamera.UnloadingStatus: _('unloading'), - QCamera.LoadingStatus: _('loading'), - QCamera.LoadedStatus: _('loaded'), - QCamera.StandbyStatus: _('standby'), - QCamera.StartingStatus: _('starting'), - QCamera.StoppingStatus: _('stopping'), - QCamera.ActiveStatus: _('active') - } - - def _get_camera_status_name(self, status: QCamera.Status): - return self._camera_status_names.get(status, _('unknown')) + self.camera.start() + self.camera.errorOccurred.connect(self._on_camera_error) # log the errors we get, if any, for debugging + + self.media_capture_session = QMediaCaptureSession() + self.media_capture_session.setCamera(self.camera) + self.media_capture_session.setVideoSink(self.video_surface) + + self.open() def _set_resolution(self, resolution: QSize): self.resolution = resolution @@ -297,50 +220,8 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog): # Set up the crop blur effect self.crop_blur_effect.setCrop(self.qr_crop) - def _on_camera_status_changed(self, status: QCamera.Status): - if self._ok_done: - # camera/scan is quitting, abort. - return - - self.logger.info('camera status changed to {}'.format(self._get_camera_status_name(status))) - - if status == QCamera.LoadedStatus: - # Determine the optimal resolution and compute the crop rect - camera_resolutions = self.camera.supportedViewfinderResolutions() - try: - resolution, was_ideal = self._get_resolution(camera_resolutions, self.SCAN_SIZE) - except RuntimeError as e: - self._error_message = str(e) - self.reject() - return - self._set_resolution(resolution) - - # Set the camera resolution - viewfinder_settings = QCameraViewfinderSettings() - viewfinder_settings.setResolution(resolution) - self.camera.setViewfinderSettings(viewfinder_settings) - - # Counter for the QR scanner frame number - self.frame_id = 0 - - self.camera.start() - self.lowres_label.setVisible(not was_ideal) # if they have a low res camera, show the warning label. - elif status == QCamera.UnloadedStatus or status == QCamera.UnavailableStatus: - self._error_message = _("Cannot start QR scanner, camera is unavailable.") - self.reject() - elif status == QCamera.ActiveStatus: - self.open() - - CameraErrorStrings = { - QCamera.NoError : "No Error", - QCamera.CameraError : "Camera Error", - QCamera.InvalidRequestError : "Invalid Request Error", - QCamera.ServiceMissingError : "Service Missing Error", - QCamera.NotSupportedFeatureError : "Unsupported Feature Error" - } - def _on_camera_error(self, errorCode): - errStr = self.CameraErrorStrings.get(errorCode, "Unknown Error") - self.logger.info(f"QCamera error: {errStr}") + def _on_camera_error(self, error: QCamera.Error, error_str: str): + self.logger.info(f"QCamera error: {error}. {error_str}") def accept(self): self._ok_done = True # immediately blocks further processing @@ -357,15 +238,11 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog): def _close_camera(self): if self.camera: - self.camera.setViewfinder(None) - if self.camera_sc_conn: - self.camera.statusChanged.disconnect(self.camera_sc_conn) - self.camera_sc_conn = None - self.camera.unload() + self.camera.stop() self.camera = None def _on_finished(self, code): - res = ( (code == QDialog.Accepted + res = ( (code == QDialog.DialogCode.Accepted and self.validator_res and self.validator_res.accepted and self.validator_res.simple_result) or '' ) @@ -374,7 +251,7 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog): self.logger.info(f'closed {res}') - self.qr_finished.emit(code == QDialog.Accepted, self._error_message, res) + self.qr_finished.emit(code == QDialog.DialogCode.Accepted, self._error_message, res) def _on_frame_available(self, frame: QImage): if self._ok_done: @@ -382,12 +259,7 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog): self.frame_id += 1 - if frame.size() != self.resolution: - self.logger.info('Getting video data at {}x{} instead of the requested {}x{}, switching resolution.'.format( - frame.size().width(), frame.size().height(), - self.resolution.width(), self.resolution.height() - )) - self._set_resolution(frame.size()) + self._set_resolution(frame.size()) flip_x = self.flip_x.isChecked() @@ -400,14 +272,16 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog): # Convert to Y800 / GREY FourCC (single 8-bit channel) # This creates a copy, so we don't need to keep the frame around anymore - frame_y800 = frame_cropped.convertToFormat(QImage.Format_Grayscale8) + frame_y800 = frame_cropped.convertToFormat(QImage.Format.Format_Grayscale8) # Read the QR codes from the frame self.qrreader_res = self.qrreader.read_qr_code( - frame_y800.constBits().__int__(), frame_y800.byteCount(), + frame_y800.constBits().__int__(), + frame_y800.sizeInBytes(), frame_y800.bytesPerLine(), frame_y800.width(), - frame_y800.height(), self.frame_id + frame_y800.height(), + self.frame_id, ) # Call the validator to see if the scanned results are acceptable diff --git a/electrum/gui/qt/qrreader/qtmultimedia/crop_blur_effect.py b/electrum/gui/qt/qrreader/qtmultimedia/crop_blur_effect.py index d1e4612e6..d1d5f2d6b 100644 --- a/electrum/gui/qt/qrreader/qtmultimedia/crop_blur_effect.py +++ b/electrum/gui/qt/qrreader/qtmultimedia/crop_blur_effect.py @@ -23,9 +23,9 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from PyQt5.QtWidgets import QGraphicsBlurEffect, QGraphicsEffect -from PyQt5.QtGui import QPainter, QTransform, QRegion -from PyQt5.QtCore import QObject, QRect, QPoint, Qt +from PyQt6.QtWidgets import QGraphicsBlurEffect, QGraphicsEffect +from PyQt6.QtGui import QPainter, QTransform, QRegion +from PyQt6.QtCore import QObject, QRect, QPoint, Qt class QrReaderCropBlurEffect(QGraphicsBlurEffect): @@ -56,7 +56,7 @@ class QrReaderCropBlurEffect(QGraphicsBlurEffect): # Fill with black and set opacity so that the blurred region is drawn darker if self.BLUR_DARKEN > 0.0: - painter.fillRect(painter.viewport(), Qt.black) + painter.fillRect(painter.viewport(), Qt.GlobalColor.black) painter.setOpacity(1 - self.BLUR_DARKEN) # Draw the blur effect @@ -67,7 +67,7 @@ class QrReaderCropBlurEffect(QGraphicsBlurEffect): painter.setOpacity(1.0) # Get the source pixmap - pixmap, offset = self.sourcePixmap(Qt.DeviceCoordinates, QGraphicsEffect.NoPad) + pixmap, offset = self.sourcePixmap(Qt.CoordinateSystem.DeviceCoordinates, QGraphicsEffect.PixmapPadMode.NoPad) painter.setWorldTransform(QTransform()) # Get the source by adding the offset to the crop location diff --git a/electrum/gui/qt/qrreader/qtmultimedia/validator.py b/electrum/gui/qt/qrreader/qtmultimedia/validator.py index 58b311539..a00a64624 100644 --- a/electrum/gui/qt/qrreader/qtmultimedia/validator.py +++ b/electrum/gui/qt/qrreader/qtmultimedia/validator.py @@ -26,8 +26,8 @@ from typing import List, Dict, Callable, Any from abc import ABC, abstractmethod -from PyQt5.QtGui import QColor -from PyQt5.QtCore import Qt +from PyQt6.QtGui import QColor +from PyQt6.QtCore import Qt from electrum.i18n import _ from electrum.qrreader import QrCodeResult @@ -101,8 +101,8 @@ class QrReaderValidatorColorizing(QrReaderValidatorCounting): based on the counts maintained by `QrReaderValidatorCounting`. """ - WEAK_COLOR: QColor = QColor(Qt.red) - STRONG_COLOR: QColor = QColor(Qt.green) + WEAK_COLOR: QColor = QColor(Qt.GlobalColor.red) + STRONG_COLOR: QColor = QColor(Qt.GlobalColor.green) strong_count: int = 10 diff --git a/electrum/gui/qt/qrreader/qtmultimedia/video_overlay.py b/electrum/gui/qt/qrreader/qtmultimedia/video_overlay.py index 3c361567e..16a7c4a8b 100644 --- a/electrum/gui/qt/qrreader/qtmultimedia/video_overlay.py +++ b/electrum/gui/qt/qrreader/qtmultimedia/video_overlay.py @@ -25,9 +25,9 @@ from typing import List -from PyQt5.QtWidgets import QWidget -from PyQt5.QtGui import QPainter, QPaintEvent, QPen, QPainterPath, QColor, QTransform -from PyQt5.QtCore import QPoint, QSize, QRect, QRectF, Qt +from PyQt6.QtWidgets import QWidget +from PyQt6.QtGui import QPainter, QPaintEvent, QPen, QPainterPath, QColor, QTransform +from PyQt6.QtCore import QPoint, QSize, QRect, QRectF, Qt from electrum.qrreader import QrCodeResult @@ -53,16 +53,16 @@ class QrReaderVideoOverlay(QWidget): self.resolution = None self.qr_outline_pen = QPen() - self.qr_outline_pen.setColor(Qt.red) + self.qr_outline_pen.setColor(Qt.GlobalColor.red) self.qr_outline_pen.setWidth(3) - self.qr_outline_pen.setStyle(Qt.DotLine) + self.qr_outline_pen.setStyle(Qt.PenStyle.DotLine) self.text_pen = QPen() - self.text_pen.setColor(Qt.black) + self.text_pen.setColor(Qt.GlobalColor.black) self.bg_rect_pen = QPen() - self.bg_rect_pen.setColor(Qt.black) - self.bg_rect_pen.setStyle(Qt.DotLine) + self.bg_rect_pen.setColor(Qt.GlobalColor.black) + self.bg_rect_pen.setStyle(Qt.PenStyle.DotLine) self.bg_rect_fill = QColor(255, 255, 255, int(255 * self.BG_RECT_OPACITY)) def set_results(self, results: List[QrCodeResult], flip_x: bool, @@ -102,7 +102,7 @@ class QrReaderVideoOverlay(QWidget): return QPoint(point[0], point[1]) # Starting from here we care about AA - painter.setRenderHint(QPainter.Antialiasing) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) # Draw all the QR code results for res in self.results: @@ -148,7 +148,7 @@ class QrReaderVideoOverlay(QWidget): bg_rect = QRect(bg_rect_pos, bg_rect_size) bg_rect_path = QPainterPath() radius = self.BG_RECT_CORNER_RADIUS - bg_rect_path.addRoundedRect(QRectF(bg_rect), radius, radius, Qt.AbsoluteSize) + bg_rect_path.addRoundedRect(QRectF(bg_rect), radius, radius, Qt.SizeMode.AbsoluteSize) painter.setPen(self.bg_rect_pen) painter.fillPath(bg_rect_path, self.bg_rect_fill) painter.drawPath(bg_rect_path) diff --git a/electrum/gui/qt/qrreader/qtmultimedia/video_surface.py b/electrum/gui/qt/qrreader/qtmultimedia/video_surface.py index d325622b4..6573673e6 100644 --- a/electrum/gui/qt/qrreader/qtmultimedia/video_surface.py +++ b/electrum/gui/qt/qrreader/qtmultimedia/video_surface.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -# Electron Cash - lightweight Bitcoin client # Copyright (C) 2019 Axel Gembe +# Copyright (c) 2024 The Electrum developers # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files @@ -25,10 +25,9 @@ from typing import List -from PyQt5.QtMultimedia import (QVideoFrame, QAbstractVideoBuffer, QAbstractVideoSurface, - QVideoSurfaceFormat) -from PyQt5.QtGui import QImage -from PyQt5.QtCore import QObject, pyqtSignal +from PyQt6.QtMultimedia import (QVideoFrame, QVideoFrameFormat, QVideoSink) +from PyQt6.QtGui import QImage +from PyQt6.QtCore import QObject, pyqtSignal from electrum.i18n import _ from electrum.logging import get_logger @@ -37,7 +36,7 @@ from electrum.logging import get_logger _logger = get_logger(__name__) -class QrReaderVideoSurface(QAbstractVideoSurface): +class QrReaderVideoSurface(QVideoSink): """ Receives QVideoFrames from QCamera, converts them into a QImage, flips the X and Y axis if necessary and sends them to listeners via the frame_available event. @@ -45,27 +44,28 @@ class QrReaderVideoSurface(QAbstractVideoSurface): def __init__(self, parent: QObject = None): super().__init__(parent) + self.videoFrameChanged.connect(self._on_new_frame) - def present(self, frame: QVideoFrame) -> bool: + def _on_new_frame(self, frame: QVideoFrame) -> None: if not frame.isValid(): - return False + return - image_format = QVideoFrame.imageFormatFromPixelFormat(frame.pixelFormat()) - if image_format == QVideoFrame.Format_Invalid: + image_format = QVideoFrameFormat.imageFormatFromPixelFormat(frame.pixelFormat()) + if image_format == QVideoFrameFormat.PixelFormat.Format_Invalid: _logger.info(_('QR code scanner for video frame with invalid pixel format')) - return False + return - if not frame.map(QAbstractVideoBuffer.ReadOnly): + if not frame.map(QVideoFrame.MapMode.ReadOnly): _logger.info(_('QR code scanner failed to map video frame')) - return False + return try: - img = QImage(int(frame.bits()), frame.width(), frame.height(), image_format) + img = frame.toImage() # Check whether we need to flip the image on any axis - surface_format = self.surfaceFormat() + surface_format = frame.surfaceFormat() flip_x = surface_format.isMirrored() - flip_y = surface_format.scanLineDirection() == QVideoSurfaceFormat.BottomToTop + flip_y = surface_format.scanLineDirection() == QVideoFrameFormat.Direction.BottomToTop # Mirror the image if needed if flip_x or flip_y: @@ -78,14 +78,4 @@ class QrReaderVideoSurface(QAbstractVideoSurface): self.frame_available.emit(img) - return True - - def supportedPixelFormats(self, handle_type: QAbstractVideoBuffer.HandleType) -> List[QVideoFrame.PixelFormat]: - if handle_type == QAbstractVideoBuffer.NoHandle: - # We support all pixel formats that can be understood by QImage directly - return [QVideoFrame.Format_ARGB32, QVideoFrame.Format_ARGB32_Premultiplied, - QVideoFrame.Format_RGB32, QVideoFrame.Format_RGB24, QVideoFrame.Format_RGB565, - QVideoFrame.Format_RGB555, QVideoFrame.Format_ARGB8565_Premultiplied] - return [] - frame_available = pyqtSignal(QImage) diff --git a/electrum/gui/qt/qrreader/qtmultimedia/video_widget.py b/electrum/gui/qt/qrreader/qtmultimedia/video_widget.py index b668f1601..123a37da2 100644 --- a/electrum/gui/qt/qrreader/qtmultimedia/video_widget.py +++ b/electrum/gui/qt/qrreader/qtmultimedia/video_widget.py @@ -23,8 +23,8 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from PyQt5.QtWidgets import QWidget -from PyQt5.QtGui import QPixmap, QPainter, QPaintEvent +from PyQt6.QtWidgets import QWidget +from PyQt6.QtGui import QPixmap, QPainter, QPaintEvent class QrReaderVideoWidget(QWidget): @@ -44,7 +44,7 @@ class QrReaderVideoWidget(QWidget): return painter = QPainter(self) if self.USE_BILINEAR_FILTER: - painter.setRenderHint(QPainter.SmoothPixmapTransform) + painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform) painter.drawPixmap(self.rect(), self.pixmap, self.pixmap.rect()) def setPixmap(self, pixmap: QPixmap): diff --git a/electrum/gui/qt/qrtextedit.py b/electrum/gui/qt/qrtextedit.py index 5866d761c..c8172f7bb 100644 --- a/electrum/gui/qt/qrtextedit.py +++ b/electrum/gui/qt/qrtextedit.py @@ -20,7 +20,7 @@ class ShowQRTextEdit(ButtonsTextEdit): def contextMenuEvent(self, e): m = self.createStandardContextMenu() m.addAction(read_QIcon(get_iconname_qrcode()), _("Show as QR code"), self.on_qr_show_btn) - m.exec_(e.globalPos()) + m.exec(e.globalPos()) class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin): @@ -73,7 +73,7 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin): m.addAction(read_QIcon(get_iconname_camera()), _("Read QR code with camera"), self.on_qr_from_camera_input_btn) m.addAction(read_QIcon("picture_in_picture.png"), _("Read QR code from screen"), self.on_qr_from_screenshot_input_btn) m.addAction(read_QIcon("file.png"), _("Read file"), self.on_input_file) - m.exec_(e.globalPos()) + m.exec(e.globalPos()) class ScanShowQRTextEdit(ButtonsTextEdit, MessageBoxMixin): @@ -92,4 +92,4 @@ class ScanShowQRTextEdit(ButtonsTextEdit, MessageBoxMixin): m.addAction(read_QIcon(get_iconname_camera()), _("Read QR code from camera"), self.on_qr_from_camera_input_btn) m.addAction(read_QIcon("picture_in_picture.png"), _("Read QR code from screen"), self.on_qr_from_screenshot_input_btn) m.addAction(read_QIcon(get_iconname_qrcode()), _("Show as QR code"), self.on_qr_show_btn) - m.exec_(e.globalPos()) + m.exec(e.globalPos()) diff --git a/electrum/gui/qt/qrwindow.py b/electrum/gui/qt/qrwindow.py index d70d694f5..497368b59 100644 --- a/electrum/gui/qt/qrwindow.py +++ b/electrum/gui/qt/qrwindow.py @@ -23,8 +23,8 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QHBoxLayout, QWidget +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QHBoxLayout, QWidget from .qrcodewidget import QRCodeWidget @@ -38,7 +38,7 @@ class QR_Window(QWidget): self.main_window = win self.setWindowTitle('Electrum - '+_('Payment Request')) self.setMinimumSize(800, 800) - self.setFocusPolicy(Qt.NoFocus) + self.setFocusPolicy(Qt.FocusPolicy.NoFocus) main_box = QHBoxLayout() self.qrw = QRCodeWidget() main_box.addWidget(self.qrw, 1) diff --git a/electrum/gui/qt/rate_limiter.py b/electrum/gui/qt/rate_limiter.py index acc2856f6..f108feaf2 100644 --- a/electrum/gui/qt/rate_limiter.py +++ b/electrum/gui/qt/rate_limiter.py @@ -7,7 +7,7 @@ import threading import time import weakref -from PyQt5.QtCore import QObject, QTimer +from PyQt6.QtCore import QObject, QTimer from electrum.logging import Logger, get_logger diff --git a/electrum/gui/qt/rbf_dialog.py b/electrum/gui/qt/rbf_dialog.py index abb6fd385..4e4333abf 100644 --- a/electrum/gui/qt/rbf_dialog.py +++ b/electrum/gui/qt/rbf_dialog.py @@ -4,8 +4,8 @@ from typing import TYPE_CHECKING -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import (QCheckBox, QLabel, QVBoxLayout, QGridLayout, QWidget, +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import (QCheckBox, QLabel, QVBoxLayout, QGridLayout, QWidget, QPushButton, QHBoxLayout, QComboBox) from .amountedit import FeerateEdit @@ -59,9 +59,9 @@ class _BaseRBFDialog(TxEditor): self.method_combo.addItems([strat.text() for strat in self._strategies]) self.method_combo.setCurrentIndex(def_strat_idx) self.method_combo.currentIndexChanged.connect(self.trigger_update) - self.method_combo.setFocusPolicy(Qt.NoFocus) + self.method_combo.setFocusPolicy(Qt.FocusPolicy.NoFocus) old_size_label = TxSizeLabel() - old_size_label.setAlignment(Qt.AlignCenter) + old_size_label.setAlignment(Qt.AlignmentFlag.AlignCenter) old_size_label.setAmount(self.old_tx_size) old_size_label.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet()) current_fee_hbox = QHBoxLayout() @@ -85,7 +85,7 @@ class _BaseRBFDialog(TxEditor): return grid def run(self) -> None: - if not self.exec_(): + if not self.exec(): return if self.is_preview: self.main_window.show_transaction(self.tx) diff --git a/electrum/gui/qt/rebalance_dialog.py b/electrum/gui/qt/rebalance_dialog.py index 281b9491c..eaf88fde2 100644 --- a/electrum/gui/qt/rebalance_dialog.py +++ b/electrum/gui/qt/rebalance_dialog.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING -from PyQt5.QtCore import pyqtSignal -from PyQt5.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton +from PyQt6.QtCore import pyqtSignal +from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton from electrum.i18n import _ from electrum.lnchannel import Channel @@ -68,7 +68,7 @@ class RebalanceDialog(WindowModalDialog): self.ok_button.setEnabled(b) def run(self): - if not self.exec_(): + if not self.exec(): return amount_msat = self.amount_e.get_amount() * 1000 coro = self.wallet.lnworker.rebalance_channels(self.chan1, self.chan2, amount_msat=amount_msat) diff --git a/electrum/gui/qt/receive_tab.py b/electrum/gui/qt/receive_tab.py index cda97d188..8dc198c0b 100644 --- a/electrum/gui/qt/receive_tab.py +++ b/electrum/gui/qt/receive_tab.py @@ -4,9 +4,9 @@ from typing import Optional, TYPE_CHECKING -from PyQt5.QtGui import QFont, QCursor -from PyQt5.QtCore import Qt, QSize -from PyQt5.QtWidgets import (QComboBox, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QTextEdit, +from PyQt6.QtGui import QFont, QCursor, QMouseEvent +from PyQt6.QtCore import Qt, QSize +from PyQt6.QtWidgets import (QComboBox, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QTextEdit, QHBoxLayout, QPushButton, QWidget, QSizePolicy, QFrame) from electrum.bitcoin import is_address @@ -61,7 +61,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger): self.fiat_receive_e = AmountEdit(self.fx.get_currency if self.fx else '') if not self.fx or not self.fx.is_enabled(): self.fiat_receive_e.setVisible(False) - grid.addWidget(self.fiat_receive_e, 1, 2, Qt.AlignLeft) + grid.addWidget(self.fiat_receive_e, 1, 2, Qt.AlignmentFlag.AlignLeft) self.window.connect_fields(self.receive_amount_e, self.fiat_receive_e) @@ -83,8 +83,8 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger): self.receive_e = QTextEdit() self.receive_e.setFont(QFont(MONOSPACE_FONT)) self.receive_e.setReadOnly(True) - self.receive_e.setContextMenuPolicy(Qt.NoContextMenu) - self.receive_e.setTextInteractionFlags(Qt.NoTextInteraction) + self.receive_e.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) + self.receive_e.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction) self.receive_e.textChanged.connect(self.update_receive_widgets) self.receive_qr = QRCodeWidget(manual_size=True) @@ -120,7 +120,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger): self.receive_widget = ReceiveWidget( self, self.receive_e, self.receive_qr, self.receive_help_widget) - receive_widget_sp = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) + receive_widget_sp = QSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding) receive_widget_sp.setRetainSizeWhenHidden(True) self.receive_widget.setSizePolicy(receive_widget_sp) self.receive_widget.setVisible(False) @@ -230,8 +230,8 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger): self.window.do_copy(text, title=title) self.update_receive_qr_window() - def do_copy(self, e): - if e.button() != Qt.LeftButton: + def do_copy(self, e: 'QMouseEvent'): + if e.button() != Qt.MouseButton.LeftButton: return text, data, help_text, title = self.get_tab_data() self.window.do_copy(text, title=title) @@ -379,11 +379,11 @@ class ReceiveWidget(QWidget): for w in [textedit, qr]: w.mousePressEvent = receive_tab.do_copy - w.setCursor(QCursor(Qt.PointingHandCursor)) + w.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) - textedit.setFocusPolicy(Qt.NoFocus) + textedit.setFocusPolicy(Qt.FocusPolicy.NoFocus) if isinstance(help_widget, QLabel): - help_widget.setFrameStyle(QFrame.StyledPanel) + help_widget.setFrameStyle(QFrame.Shape.StyledPanel) help_widget.setStyleSheet("QLabel {border:1px solid gray; border-radius:2px; }") hbox = QHBoxLayout() @@ -422,5 +422,5 @@ class ReceiveWidget(QWidget): class FramedWidget(QFrame): def __init__(self): QFrame.__init__(self) - self.setFrameStyle(QFrame.StyledPanel) + self.setFrameStyle(QFrame.Shape.StyledPanel) self.setStyleSheet("FramedWidget {border:1px solid gray; border-radius:2px; }") diff --git a/electrum/gui/qt/request_list.py b/electrum/gui/qt/request_list.py index 55514915b..52c2290d2 100644 --- a/electrum/gui/qt/request_list.py +++ b/electrum/gui/qt/request_list.py @@ -26,9 +26,9 @@ import enum from typing import Optional, TYPE_CHECKING -from PyQt5.QtGui import QStandardItemModel, QStandardItem -from PyQt5.QtWidgets import QMenu, QAbstractItemView -from PyQt5.QtCore import Qt, QItemSelectionModel, QModelIndex +from PyQt6.QtGui import QStandardItemModel, QStandardItem +from PyQt6.QtWidgets import QMenu, QAbstractItemView +from PyQt6.QtCore import Qt, QItemSelectionModel, QModelIndex from electrum.i18n import _ from electrum.util import format_time @@ -43,9 +43,9 @@ if TYPE_CHECKING: from .receive_tab import ReceiveTab -ROLE_REQUEST_TYPE = Qt.UserRole -ROLE_KEY = Qt.UserRole + 1 -ROLE_SORT_ORDER = Qt.UserRole + 2 +ROLE_REQUEST_TYPE = Qt.ItemDataRole.UserRole +ROLE_KEY = Qt.ItemDataRole.UserRole + 1 +ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 2 class RequestList(MyTreeView): @@ -86,14 +86,15 @@ class RequestList(MyTreeView): self.setModel(self.proxy) self.setSortingEnabled(True) self.selectionModel().currentRowChanged.connect(self.item_changed) - self.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) def set_current_key(self, key): for i in range(self.model().rowCount()): item = self.model().index(i, self.Columns.DATE) row_key = item.data(ROLE_KEY) if key == row_key: - self.selectionModel().setCurrentIndex(item, QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows) + self.selectionModel().setCurrentIndex( + item, QItemSelectionModel.SelectionFlag.SelectCurrent | QItemSelectionModel.SelectionFlag.Rows) break def get_current_key(self): @@ -164,7 +165,7 @@ class RequestList(MyTreeView): self.filter() self.proxy.setDynamicSortFilter(True) # sort requests by date - self.sortByColumn(self.Columns.DATE, Qt.DescendingOrder) + self.sortByColumn(self.Columns.DATE, Qt.SortOrder.DescendingOrder) self.hide_if_empty() if current_key is not None: self.set_current_key(current_key) @@ -183,7 +184,7 @@ class RequestList(MyTreeView): keys = [item.data(ROLE_KEY) for item in items] menu = QMenu(self) menu.addAction(_("Delete requests"), lambda: self.delete_requests(keys)) - menu.exec_(self.viewport().mapToGlobal(position)) + menu.exec(self.viewport().mapToGlobal(position)) return idx = self.indexAt(position) # TODO use siblingAtColumn when min Qt version is >=5.11 @@ -207,7 +208,7 @@ class RequestList(MyTreeView): # menu.addAction(_("View in web browser"), lambda: webopen(req['view_url'])) menu.addAction(_("Delete"), lambda: self.delete_requests([key])) run_hook('receive_list_menu', self.main_window, menu, key) - menu.exec_(self.viewport().mapToGlobal(position)) + menu.exec(self.viewport().mapToGlobal(position)) def delete_requests(self, keys): self.wallet.delete_requests(keys) diff --git a/electrum/gui/qt/seed_dialog.py b/electrum/gui/qt/seed_dialog.py index e97881b1f..620910db9 100644 --- a/electrum/gui/qt/seed_dialog.py +++ b/electrum/gui/qt/seed_dialog.py @@ -25,9 +25,9 @@ from typing import TYPE_CHECKING -from PyQt5.QtCore import Qt, pyqtSignal -from PyQt5.QtGui import QPixmap -from PyQt5.QtWidgets import (QVBoxLayout, QCheckBox, QHBoxLayout, QLineEdit, +from PyQt6.QtCore import Qt, pyqtSignal +from PyQt6.QtGui import QPixmap +from PyQt6.QtWidgets import (QVBoxLayout, QCheckBox, QHBoxLayout, QLineEdit, QLabel, QCompleter, QDialog, QStyledItemDelegate, QScrollArea, QWidget, QPushButton) @@ -124,7 +124,7 @@ class SeedLayout(QVBoxLayout): vbox.addWidget(seed_type_choice) vbox.addLayout(Buttons(OkButton(dialog))) - if not dialog.exec_(): + if not dialog.exec(): return None self.is_ext = cb_ext.isChecked() if 'ext' in self.options else False self.seed_type = seed_type_choice.selected_key if len(seed_types) >= 2 else 'electrum' @@ -172,7 +172,7 @@ class SeedLayout(QVBoxLayout): if icon: logo = QLabel() logo.setPixmap(QPixmap(icon_path("seed.png")) - .scaledToWidth(64, mode=Qt.SmoothTransformation)) + .scaledToWidth(64, mode=Qt.TransformationMode.SmoothTransformation)) logo.setMaximumWidth(60) hbox.addWidget(logo) hbox.addWidget(self.seed_e) diff --git a/electrum/gui/qt/send_tab.py b/electrum/gui/qt/send_tab.py index 8b59c3ed3..9827153ca 100644 --- a/electrum/gui/qt/send_tab.py +++ b/electrum/gui/qt/send_tab.py @@ -4,10 +4,11 @@ from decimal import Decimal from typing import Optional, TYPE_CHECKING, Sequence, List, Callable, Union, Mapping -from PyQt5.QtCore import pyqtSignal, QPoint, QSize, Qt -from PyQt5.QtWidgets import (QLabel, QVBoxLayout, QGridLayout, QHBoxLayout, + +from PyQt6.QtCore import pyqtSignal, QPoint, QSize, Qt +from PyQt6.QtWidgets import (QLabel, QVBoxLayout, QGridLayout, QHBoxLayout, QWidget, QToolTip, QPushButton, QApplication) -from PyQt5.QtGui import QMovie, QColor +from PyQt6.QtGui import QMovie, QColor from electrum.i18n import _ from electrum.logging import Logger @@ -131,7 +132,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger): self.paste_button.setIcon(read_QIcon('copy.png')) self.paste_button.setToolTip(_('Paste invoice from clipboard')) self.paste_button.setMaximumWidth(35) - self.paste_button.setFocusPolicy(Qt.NoFocus) + self.paste_button.setFocusPolicy(Qt.FocusPolicy.NoFocus) grid.addWidget(self.paste_button, 0, 5) self.spinner = QMovie(icon_path('spinner.gif')) @@ -141,7 +142,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger): self.spinner_l.setMargin(5) self.spinner_l.setVisible(False) self.spinner_l.setMovie(self.spinner) - grid.addWidget(self.spinner_l, 0, 1, 1, 4, Qt.AlignRight) + grid.addWidget(self.spinner_l, 0, 1, 1, 4, Qt.AlignmentFlag.AlignRight) self.save_button = EnterButton(_("Save"), self.do_save_invoice) self.save_button.setEnabled(False) diff --git a/electrum/gui/qt/settings_dialog.py b/electrum/gui/qt/settings_dialog.py index ed67edab9..46cd209c8 100644 --- a/electrum/gui/qt/settings_dialog.py +++ b/electrum/gui/qt/settings_dialog.py @@ -26,8 +26,8 @@ import ast from typing import Optional, TYPE_CHECKING -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import (QComboBox, QTabWidget, QDialog, +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import (QComboBox, QTabWidget, QDialog, QSpinBox, QFileDialog, QCheckBox, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QPushButton, QWidget, QHBoxLayout, QSlider) @@ -122,7 +122,7 @@ class SettingsDialog(QDialog, QtEventListener): _("Without this option, Electrum will need to sync with the Lightning network on every start."), _("This may impact the reliability of your payments."), ])): - trampoline_cb.setCheckState(Qt.Checked) + trampoline_cb.setCheckState(Qt.CheckState.Checked) return self.config.LIGHTNING_USE_GOSSIP = not use_trampoline if not use_trampoline: @@ -167,7 +167,7 @@ class SettingsDialog(QDialog, QtEventListener): pos = lnfee_slider.sliderPosition() fee_val = lnfee_map[pos] self.config.LIGHTNING_PAYMENT_FEE_MAX_MILLIONTHS = fee_val - lnfee_slider = QSlider(Qt.Horizontal) + lnfee_slider = QSlider(Qt.Orientation.Horizontal) lnfee_slider.setRange(0, len(lnfee_map)-1) lnfee_slider.setTracking(True) try: @@ -195,7 +195,7 @@ class SettingsDialog(QDialog, QtEventListener): msat_cb = checkbox_from_configvar(self.config.cv.BTC_AMOUNTS_PREC_POST_SAT) msat_cb.setChecked(self.config.BTC_AMOUNTS_PREC_POST_SAT > 0) def on_msat_checked(v): - prec = 3 if v == Qt.Checked else 0 + prec = 3 if v == Qt.CheckState.Checked else 0 if self.config.amt_precision_post_satoshi != prec: self.config.amt_precision_post_satoshi = prec self.config.BTC_AMOUNTS_PREC_POST_SAT = prec @@ -225,7 +225,7 @@ class SettingsDialog(QDialog, QtEventListener): thousandsep_cb = checkbox_from_configvar(self.config.cv.BTC_AMOUNTS_ADD_THOUSANDS_SEP) thousandsep_cb.setChecked(self.config.BTC_AMOUNTS_ADD_THOUSANDS_SEP) def on_set_thousandsep(v): - checked = v == Qt.Checked + checked = v == Qt.CheckState.Checked if self.config.amt_add_thousands_sep != checked: self.config.amt_add_thousands_sep = checked self.config.BTC_AMOUNTS_ADD_THOUSANDS_SEP = checked @@ -259,13 +259,13 @@ class SettingsDialog(QDialog, QtEventListener): updatecheck_cb = checkbox_from_configvar(self.config.cv.AUTOMATIC_CENTRALIZED_UPDATE_CHECKS) updatecheck_cb.setChecked(self.config.AUTOMATIC_CENTRALIZED_UPDATE_CHECKS) def on_set_updatecheck(v): - self.config.AUTOMATIC_CENTRALIZED_UPDATE_CHECKS = (v == Qt.Checked) + self.config.AUTOMATIC_CENTRALIZED_UPDATE_CHECKS = (v == Qt.CheckState.Checked) updatecheck_cb.stateChanged.connect(on_set_updatecheck) filelogging_cb = checkbox_from_configvar(self.config.cv.WRITE_LOGS_TO_DISK) filelogging_cb.setChecked(self.config.WRITE_LOGS_TO_DISK) def on_set_filelogging(v): - self.config.WRITE_LOGS_TO_DISK = (v == Qt.Checked) + self.config.WRITE_LOGS_TO_DISK = (v == Qt.CheckState.Checked) self.need_restart = True filelogging_cb.stateChanged.connect(on_set_filelogging) diff --git a/electrum/gui/qt/stylesheet_patcher.py b/electrum/gui/qt/stylesheet_patcher.py index 55bcf9c63..88045e466 100644 --- a/electrum/gui/qt/stylesheet_patcher.py +++ b/electrum/gui/qt/stylesheet_patcher.py @@ -4,7 +4,7 @@ It reads the current stylesheet, appends our modifications and sets the new styl import sys -from PyQt5 import QtWidgets +from PyQt6 import QtWidgets CUSTOM_PATCH_FOR_DARK_THEME = ''' diff --git a/electrum/gui/qt/swap_dialog.py b/electrum/gui/qt/swap_dialog.py index 08edcd6f2..eda17adcf 100644 --- a/electrum/gui/qt/swap_dialog.py +++ b/electrum/gui/qt/swap_dialog.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING, Optional, Union -from PyQt5.QtCore import pyqtSignal -from PyQt5.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton +from PyQt6.QtCore import pyqtSignal +from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton from electrum.i18n import _ from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates @@ -244,7 +244,7 @@ class SwapDialog(WindowModalDialog, QtEventListener): def run(self): """Can raise InvalidSwapParameters.""" - if not self.exec_(): + if not self.exec(): return if self.is_reverse: lightning_amount = self.send_amount_e.get_amount() diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py index 97f7d8a05..58652161b 100644 --- a/electrum/gui/qt/transaction_dialog.py +++ b/electrum/gui/qt/transaction_dialog.py @@ -34,10 +34,10 @@ from typing import TYPE_CHECKING, Callable, Optional, List, Union, Tuple, Mappin from functools import partial from decimal import Decimal -from PyQt5.QtCore import QSize, Qt, QUrl, QPoint, pyqtSignal -from PyQt5.QtGui import QTextCharFormat, QBrush, QFont, QPixmap, QCursor -from PyQt5.QtWidgets import (QDialog, QLabel, QPushButton, QHBoxLayout, QVBoxLayout, QWidget, QGridLayout, - QTextEdit, QFrame, QAction, QToolButton, QMenu, QCheckBox, QTextBrowser, QToolTip, +from PyQt6.QtCore import QSize, Qt, QUrl, QPoint, pyqtSignal +from PyQt6.QtGui import QTextCharFormat, QBrush, QFont, QPixmap, QTextCursor, QAction +from PyQt6.QtWidgets import (QDialog, QLabel, QPushButton, QHBoxLayout, QVBoxLayout, QWidget, QGridLayout, + QTextEdit, QFrame, QToolButton, QMenu, QCheckBox, QTextBrowser, QToolTip, QApplication, QSizePolicy) import qrcode from qrcode import exceptions @@ -95,7 +95,7 @@ class QTextBrowserWithDefaultSize(QTextBrowser): self._width = width self._height = height QTextBrowser.__init__(self) - self.setLineWrapMode(QTextBrowser.NoWrap) + self.setLineWrapMode(QTextBrowser.LineWrapMode.NoWrap) def sizeHint(self): return QSize(self._width, self._height) @@ -113,8 +113,8 @@ class TxInOutWidget(QWidget): self.inputs_textedit.setOpenLinks(False) # disable automatic link opening self.inputs_textedit.anchorClicked.connect(self._open_internal_link) # send links to our handler self.inputs_textedit.setTextInteractionFlags( - self.inputs_textedit.textInteractionFlags() | Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) - self.inputs_textedit.setContextMenuPolicy(Qt.CustomContextMenu) + self.inputs_textedit.textInteractionFlags() | Qt.TextInteractionFlag.LinksAccessibleByMouse | Qt.TextInteractionFlag.LinksAccessibleByKeyboard) + self.inputs_textedit.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.inputs_textedit.customContextMenuRequested.connect(self.on_context_menu_for_inputs) self.sighash_label = QLabel() @@ -123,7 +123,7 @@ class TxInOutWidget(QWidget): self.inputs_warning_icon = QLabel() pixmap = QPixmap(icon_path("warning")) pixmap_size = round(2 * char_width_in_lineedit()) - pixmap = pixmap.scaled(pixmap_size, pixmap_size, Qt.KeepAspectRatio, Qt.SmoothTransformation) + pixmap = pixmap.scaled(pixmap_size, pixmap_size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) self.inputs_warning_icon.setPixmap(pixmap) self.inputs_warning_icon.setVisible(False) @@ -147,8 +147,8 @@ class TxInOutWidget(QWidget): self.outputs_textedit.setOpenLinks(False) # disable automatic link opening self.outputs_textedit.anchorClicked.connect(self._open_internal_link) # send links to our handler self.outputs_textedit.setTextInteractionFlags( - self.outputs_textedit.textInteractionFlags() | Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) - self.outputs_textedit.setContextMenuPolicy(Qt.CustomContextMenu) + self.outputs_textedit.textInteractionFlags() | Qt.TextInteractionFlag.LinksAccessibleByMouse | Qt.TextInteractionFlag.LinksAccessibleByKeyboard) + self.outputs_textedit.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.outputs_textedit.customContextMenuRequested.connect(self.on_context_menu_for_outputs) outheader_hbox = QHBoxLayout() @@ -166,7 +166,7 @@ class TxInOutWidget(QWidget): vbox.addLayout(outheader_hbox) vbox.addWidget(self.outputs_textedit) self.setLayout(vbox) - self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) def update(self, tx: Optional[Transaction]): self.tx = tx @@ -183,7 +183,7 @@ class TxInOutWidget(QWidget): lnk = QTextCharFormat() lnk.setToolTip(_('Click to open, right-click for menu')) lnk.setAnchor(True) - lnk.setUnderlineStyle(QTextCharFormat.SingleUnderline) + lnk.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SingleUnderline) tf_used_recv, tf_used_change, tf_used_2fa, tf_used_swap = False, False, False, False def addr_text_format(addr: str) -> QTextCharFormat: nonlocal tf_used_recv, tf_used_change, tf_used_2fa, tf_used_swap @@ -198,7 +198,7 @@ class TxInOutWidget(QWidget): fmt.setAnchorHref(addr) fmt.setToolTip(_('Click to open, right-click for menu')) fmt.setAnchor(True) - fmt.setUnderlineStyle(QTextCharFormat.SingleUnderline) + fmt.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SingleUnderline) return fmt elif sm and sm.is_lockup_address_for_a_swap(addr) or addr == DummyAddress.SWAP: tf_used_swap = True @@ -210,7 +210,7 @@ class TxInOutWidget(QWidget): def insert_tx_io( *, - cursor: QCursor, + cursor: QTextCursor, txio_idx: int, is_coinbase: bool, tcf_shortid: QTextCharFormat = None, @@ -305,7 +305,7 @@ class TxInOutWidget(QWidget): of the bare form "txid" and/or "address" -- used by the clickable links in the inputs/outputs QTextBrowsers""" if isinstance(target, QUrl): - target = target.toString(QUrl.None_) + target = target.toString(QUrl.UrlFormattingOption.None_) assert target if bitcoin.is_address(target): # target was an address, open address dialog @@ -323,7 +323,7 @@ class TxInOutWidget(QWidget): name = charFormat.anchorNames() and charFormat.anchorNames()[0] if not name: menu = i_text.createStandardContextMenu() - menu.exec_(global_pos) + menu.exec(global_pos) return menu = QMenu() @@ -360,7 +360,7 @@ class TxInOutWidget(QWidget): menu.addSeparator() std_menu = i_text.createStandardContextMenu() menu.addActions(std_menu.actions()) - menu.exec_(global_pos) + menu.exec(global_pos) def on_context_menu_for_outputs(self, pos: QPoint): o_text = self.outputs_textedit @@ -371,7 +371,7 @@ class TxInOutWidget(QWidget): name = charFormat.anchorNames() and charFormat.anchorNames()[0] if not name: menu = o_text.createStandardContextMenu() - menu.exec_(global_pos) + menu.exec(global_pos) return menu = QMenu() @@ -405,7 +405,7 @@ class TxInOutWidget(QWidget): menu.addSeparator() std_menu = o_text.createStandardContextMenu() menu.addActions(std_menu.actions()) - menu.exec_(global_pos) + menu.exec(global_pos) def show_transaction( @@ -530,7 +530,7 @@ class TxDialog(QDialog, MessageBoxMixin): self.export_actions_button = QToolButton() self.export_actions_button.setText(_("Share")) self.export_actions_button.setMenu(export_actions_menu) - self.export_actions_button.setPopupMode(QToolButton.InstantPopup) + self.export_actions_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) partial_tx_actions_menu = QMenu() ptx_merge_sigs_action = QAction(_("Merge signatures from"), self) @@ -542,7 +542,7 @@ class TxDialog(QDialog, MessageBoxMixin): self.partial_tx_actions_button = QToolButton() self.partial_tx_actions_button.setText(_("Combine")) self.partial_tx_actions_button.setMenu(partial_tx_actions_menu) - self.partial_tx_actions_button.setPopupMode(QToolButton.InstantPopup) + self.partial_tx_actions_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) self.psbt_only_widgets.append(self.partial_tx_actions_button) # Action buttons @@ -559,7 +559,7 @@ class TxDialog(QDialog, MessageBoxMixin): self._fetch_txin_data_fut = None # type: Optional[concurrent.futures.Future] self._fetch_txin_data_progress = None # type: Optional[TxinDataFetchProgress] - self.throttled_update_sig.connect(self._throttled_update, Qt.QueuedConnection) + self.throttled_update_sig.connect(self._throttled_update, Qt.ConnectionType.QueuedConnection) self.set_tx(tx) self.update() @@ -954,7 +954,7 @@ class TxDialog(QDialog, MessageBoxMixin): hbox_stats.setContentsMargins(0, 0, 0, 0) hbox_stats_w = QWidget() hbox_stats_w.setLayout(hbox_stats) - hbox_stats_w.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) + hbox_stats_w.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum) # left column vbox_left = QVBoxLayout() @@ -973,7 +973,7 @@ class TxDialog(QDialog, MessageBoxMixin): self.fee_warning_icon = QLabel() pixmap = QPixmap(icon_path("warning")) pixmap_size = round(2 * char_width_in_lineedit()) - pixmap = pixmap.scaled(pixmap_size, pixmap_size, Qt.KeepAspectRatio, Qt.SmoothTransformation) + pixmap = pixmap.scaled(pixmap_size, pixmap_size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) self.fee_warning_icon.setPixmap(pixmap) self.fee_warning_icon.setVisible(False) fee_hbox.addWidget(self.fee_warning_icon) @@ -1041,7 +1041,7 @@ class TxDialog(QDialog, MessageBoxMixin): class TxDetailLabel(QLabel): def __init__(self, *, word_wrap=None): super().__init__() - self.setTextInteractionFlags(Qt.TextSelectableByMouse) + self.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) if word_wrap is not None: self.setWordWrap(word_wrap) diff --git a/electrum/gui/qt/update_checker.py b/electrum/gui/qt/update_checker.py index daf775417..1baaa00af 100644 --- a/electrum/gui/qt/update_checker.py +++ b/electrum/gui/qt/update_checker.py @@ -5,8 +5,8 @@ import asyncio import base64 -from PyQt5.QtCore import Qt, QThread, pyqtSignal -from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QLabel, QProgressBar, +from PyQt6.QtCore import Qt, QThread, pyqtSignal +from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QLabel, QProgressBar, QHBoxLayout, QPushButton, QDialog) from electrum import version @@ -38,7 +38,7 @@ class UpdateCheck(QDialog, Logger): self.content.addWidget(self.heading_label) self.detail_label = QLabel() - self.detail_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse) + self.detail_label.setTextInteractionFlags(Qt.TextInteractionFlag.LinksAccessibleByMouse) self.detail_label.setOpenExternalLinks(True) self.content.addWidget(self.detail_label) diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 2d524aa6a..4817855be 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -9,11 +9,11 @@ import webbrowser from functools import partial, lru_cache, wraps from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, List, Any, Sequence, Tuple) -from PyQt5 import QtCore -from PyQt5.QtGui import (QFont, QColor, QCursor, QPixmap, QImage, +from PyQt6 import QtCore +from PyQt6.QtGui import (QFont, QColor, QCursor, QPixmap, QImage, QPalette, QIcon, QFontMetrics, QPainter, QContextMenuEvent) -from PyQt5.QtCore import (Qt, pyqtSignal, QCoreApplication, QThread, QSize, QRect, QPoint, QObject) -from PyQt5.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, QVBoxLayout, QLineEdit, +from PyQt6.QtCore import (Qt, pyqtSignal, QCoreApplication, QThread, QSize, QRect, QPoint, QObject) +from PyQt6.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, QVBoxLayout, QLineEdit, QStyle, QDialog, QGroupBox, QButtonGroup, QRadioButton, QFileDialog, QWidget, QToolButton, QPlainTextEdit, QApplication, QToolTip, QGraphicsEffect, QGraphicsScene, QGraphicsPixmapItem, QLayoutItem, QLayout, QMenu, @@ -77,7 +77,7 @@ class EnterButton(QPushButton): self._orig_text = text def keyPressEvent(self, e): - if e.key() in [Qt.Key_Return, Qt.Key_Enter]: + if e.key() in [Qt.Key.Key_Return, Qt.Key.Key_Enter]: self.func() def restore_original_text(self): @@ -106,14 +106,14 @@ class WWLabel(QLabel): def __init__ (self, text="", parent=None): QLabel.__init__(self, text, parent) self.setWordWrap(True) - self.setTextInteractionFlags(Qt.TextSelectableByMouse) + self.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) class AmountLabel(QLabel): def __init__(self, *args, **kwargs): QLabel.__init__(self, *args, **kwargs) self.setFont(QFont(MONOSPACE_FONT)) - self.setTextInteractionFlags(Qt.TextSelectableByMouse) + self.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) class HelpMixin: @@ -123,12 +123,12 @@ class HelpMixin: self._help_title = help_title or _('Help') if isinstance(self, QLabel): self.setTextInteractionFlags( - (self.textInteractionFlags() | Qt.TextSelectableByMouse) - & ~Qt.TextSelectableByKeyboard) + (self.textInteractionFlags() | Qt.TextInteractionFlag.TextSelectableByMouse) + & ~Qt.TextInteractionFlag.TextSelectableByKeyboard) def show_help(self): custom_message_box( - icon=QMessageBox.Information, + icon=QMessageBox.Icon.Information, parent=self, title=self._help_title, text=self.help_text, @@ -154,13 +154,13 @@ class HelpLabel(HelpMixin, QLabel): def enterEvent(self, event): self.font.setUnderline(True) self.setFont(self.font) - self.app.setOverrideCursor(QCursor(Qt.PointingHandCursor)) + self.app.setOverrideCursor(QCursor(Qt.CursorShape.PointingHandCursor)) return QLabel.enterEvent(self, event) def leaveEvent(self, event): self.font.setUnderline(False) self.setFont(self.font) - self.app.setOverrideCursor(QCursor(Qt.ArrowCursor)) + self.app.setOverrideCursor(QCursor(Qt.CursorShape.ArrowCursor)) return QLabel.leaveEvent(self, event) @@ -169,7 +169,7 @@ class HelpButton(HelpMixin, QToolButton): QToolButton.__init__(self) HelpMixin.__init__(self, text) self.setText('?') - self.setFocusPolicy(Qt.NoFocus) + self.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.setFixedWidth(round(2.2 * char_width_in_lineedit())) self.clicked.connect(self.show_help) @@ -178,7 +178,7 @@ class InfoButton(HelpMixin, QPushButton): def __init__(self, text: str): QPushButton.__init__(self, _('Info')) HelpMixin.__init__(self, text, help_title=_('Info')) - self.setFocusPolicy(Qt.NoFocus) + self.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.setFixedWidth(6 * char_width_in_lineedit()) self.clicked.connect(self.show_help) @@ -245,8 +245,8 @@ class MessageBoxMixin(object): return self.top_level_window_recurse(test_func) def question(self, msg, parent=None, title=None, icon=None, **kwargs) -> bool: - Yes, No = QMessageBox.Yes, QMessageBox.No - return Yes == self.msg_box(icon=icon or QMessageBox.Question, + Yes, No = QMessageBox.StandardButton.Yes, QMessageBox.StandardButton.No + return Yes == self.msg_box(icon=icon or QMessageBox.Icon.Question, parent=parent, title=title or '', text=msg, @@ -255,23 +255,23 @@ class MessageBoxMixin(object): **kwargs) def show_warning(self, msg, parent=None, title=None, **kwargs): - return self.msg_box(QMessageBox.Warning, parent, + return self.msg_box(QMessageBox.Icon.Warning, parent, title or _('Warning'), msg, **kwargs) def show_error(self, msg, parent=None, **kwargs): - return self.msg_box(QMessageBox.Warning, parent, + return self.msg_box(QMessageBox.Icon.Warning, parent, _('Error'), msg, **kwargs) def show_critical(self, msg, parent=None, title=None, **kwargs): - return self.msg_box(QMessageBox.Critical, parent, + return self.msg_box(QMessageBox.Icon.Critical, parent, title or _('Critical Error'), msg, **kwargs) def show_message(self, msg, parent=None, title=None, **kwargs): - return self.msg_box(QMessageBox.Information, parent, + return self.msg_box(QMessageBox.Icon.Information, parent, title or _('Information'), msg, **kwargs) - def msg_box(self, icon, parent, title, text, *, buttons=QMessageBox.Ok, - defaultButton=QMessageBox.NoButton, rich_text=False, + def msg_box(self, icon, parent, title, text, *, buttons=QMessageBox.StandardButton.Ok, + defaultButton=QMessageBox.StandardButton.NoButton, rich_text=False, checkbox=None): parent = parent or self.top_level_window() return custom_message_box(icon=icon, @@ -299,34 +299,34 @@ class MessageBoxMixin(object): cancel_button = CancelButton(dialog) vbox.addLayout(Buttons(cancel_button, OkButton(dialog))) cancel_button.setFocus() - if not dialog.exec_(): + if not dialog.exec(): return None return choice_widget.selected_key -def custom_message_box(*, icon, parent, title, text, buttons=QMessageBox.Ok, - defaultButton=QMessageBox.NoButton, rich_text=False, +def custom_message_box(*, icon, parent, title, text, buttons=QMessageBox.StandardButton.Ok, + defaultButton=QMessageBox.StandardButton.NoButton, rich_text=False, checkbox=None): if type(icon) is QPixmap: - d = QMessageBox(QMessageBox.Information, title, str(text), buttons, parent) + d = QMessageBox(QMessageBox.Icon.Information, title, str(text), buttons, parent) d.setIconPixmap(icon) else: d = QMessageBox(icon, title, str(text), buttons, parent) - d.setWindowModality(Qt.WindowModal) + d.setWindowModality(Qt.WindowModality.WindowModal) d.setDefaultButton(defaultButton) if rich_text: - d.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.LinksAccessibleByMouse) + d.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse | Qt.TextInteractionFlag.LinksAccessibleByMouse) # set AutoText instead of RichText # AutoText lets Qt figure out whether to render as rich text. # e.g. if text is actually plain text and uses "\n" newlines; # and we set RichText here, newlines would be swallowed - d.setTextFormat(Qt.AutoText) + d.setTextFormat(Qt.TextFormat.AutoText) else: - d.setTextInteractionFlags(Qt.TextSelectableByMouse) - d.setTextFormat(Qt.PlainText) + d.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + d.setTextFormat(Qt.TextFormat.PlainText) if checkbox is not None: d.setCheckBox(checkbox) - return d.exec_() + return d.exec() class WindowModalDialog(QDialog, MessageBoxMixin): @@ -334,7 +334,7 @@ class WindowModalDialog(QDialog, MessageBoxMixin): daemon model as other wallet windows can still be accessed.''' def __init__(self, parent, title=None): QDialog.__init__(self, parent) - self.setWindowModality(Qt.WindowModal) + self.setWindowModality(Qt.WindowModality.WindowModal) if title: self.setWindowTitle(title) @@ -410,7 +410,7 @@ def line_dialog(parent, title, label, ok_label, default=None): txt.setText(default) l.addWidget(txt) l.addLayout(Buttons(CancelButton(dialog), OkButton(dialog, ok_label))) - if dialog.exec_(): + if dialog.exec(): return txt.text() @@ -438,7 +438,7 @@ def text_dialog( txt.setText(default) l.addWidget(txt) l.addLayout(Buttons(CancelButton(dialog), OkButton(dialog, ok_label))) - if dialog.exec_(): + if dialog.exec(): return txt.toPlainText() @@ -564,7 +564,8 @@ class VLine(QFrame): """Vertical line separator""" def __init__(self): super(VLine, self).__init__() - self.setFrameShape(self.VLine | self.Sunken) + self.setFrameShape(QFrame.Shape.VLine) + self.setFrameShadow(QFrame.Shadow.Sunken) self.setLineWidth(1) @@ -658,7 +659,7 @@ def editor_contextMenuEvent(self, p: 'PayToEdit', e: 'QContextMenuEvent') -> Non m.addAction(read_QIcon(get_iconname_camera()), _("Read QR code with camera"), p.on_qr_from_camera_input_btn) m.addAction(read_QIcon("picture_in_picture.png"), _("Read QR code from screen"), p.on_qr_from_screenshot_input_btn) m.addAction(read_QIcon("file.png"), _("Read file"), p.on_input_file) - m.exec_(e.globalPos()) + m.exec(e.globalPos()) class GenericInputHandler: @@ -821,7 +822,7 @@ class OverlayControlMixin(GenericInputHandler): self._updateOverlayPos() def _updateOverlayPos(self): - frame_width = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth) + frame_width = self.style().pixelMetric(QStyle.PixelMetric.PM_DefaultFrameWidth) overlay_size = self.overlay_widget.sizeHint() x = self.rect().right() - frame_width - overlay_size.width() y = self.rect().bottom() - overlay_size.height() @@ -833,7 +834,7 @@ class OverlayControlMixin(GenericInputHandler): middle = True y = (y / 2) + frame_width if middle else y - frame_width if hasattr(self, 'verticalScrollBar') and self.verticalScrollBar().isVisible(): - scrollbar_width = self.style().pixelMetric(QStyle.PM_ScrollBarExtent) + scrollbar_width = self.style().pixelMetric(QStyle.PixelMetric.PM_ScrollBarExtent) x -= scrollbar_width self.overlay_widget.move(int(x), int(y)) @@ -845,7 +846,7 @@ class OverlayControlMixin(GenericInputHandler): button = QPushButton(self.overlay_widget) button.setToolTip(tooltip) button.setIcon(read_QIcon(icon_name)) - button.setCursor(QCursor(Qt.PointingHandCursor)) + button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) button.clicked.connect(on_click) self.addWidget(button) return button @@ -886,7 +887,7 @@ class OverlayControlMixin(GenericInputHandler): parent=self, title=title, config=config, - ).exec_() + ).exec() self.addButton(get_iconname_qrcode(), qr_show, _("Show as QR code")) # side-effect: we export this method: @@ -1007,7 +1008,7 @@ class ButtonsTextEdit(OverlayControlMixin, QPlainTextEdit): class PasswordLineEdit(QLineEdit): def __init__(self, *args, **kwargs): QLineEdit.__init__(self, *args, **kwargs) - self.setEchoMode(QLineEdit.Password) + self.setEchoMode(QLineEdit.EchoMode.Password) def clear(self): # Try to actually overwrite the memory. @@ -1118,7 +1119,7 @@ class ColorScheme: @staticmethod def has_dark_background(widget): - brightness = sum(widget.palette().color(QPalette.Background).getRgb()[0:3]) + brightness = sum(widget.palette().color(QPalette.ColorRole.Window).getRgb()[0:3]) return brightness < (255*3/2) @staticmethod @@ -1148,7 +1149,7 @@ class AcceptFileDragDrop: def dragMoveEvent(self, event): if self.validateEvent(event): - event.setDropAction(Qt.CopyAction) + event.setDropAction(Qt.DropAction.CopyAction) def dropEvent(self, event): if self.validateEvent(event): @@ -1222,14 +1223,14 @@ def getSaveFileName( path = os.path.join(directory, filename) file_dialog = QFileDialog(parent, title, path, filter) - file_dialog.setAcceptMode(QFileDialog.AcceptSave) + file_dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave) if default_extension: # note: on MacOS, the selected filter's first extension seems to have priority over this... file_dialog.setDefaultSuffix(default_extension) if default_filter: assert default_filter in filter, f"default_filter={default_filter!r} does not appear in filter={filter!r}" file_dialog.selectNameFilter(default_filter) - if file_dialog.exec() != QDialog.Accepted: + if file_dialog.exec() != QDialog.DialogCode.Accepted: return None selected_path = file_dialog.selectedFiles()[0] @@ -1262,7 +1263,7 @@ class IconLabel(QWidget): self.setLayout(layout) self.icon = QLabel() self.label = QLabel(text) - self.label.setTextInteractionFlags(Qt.TextSelectableByMouse) + self.label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) layout.addWidget(self.label) layout.addSpacing(self.HorizontalSpacing) layout.addWidget(self.icon) @@ -1350,16 +1351,16 @@ class FixedAspectRatioLayout(QLayout): free_space = contents.size() - item_rect.size() for item in self.items: - if free_space.width() > 0 and not item.alignment() & Qt.AlignLeft: - if item.alignment() & Qt.AlignRight: + if free_space.width() > 0 and not item.alignment() & Qt.AlignmentFlag.AlignLeft: + if item.alignment() & Qt.AlignmentFlag.AlignRight: item_rect.moveRight(contents.width() + content_margins.right()) else: item_rect.moveLeft(content_margins.left() + (free_space.width() // 2)) else: item_rect.moveLeft(content_margins.left()) - if free_space.height() > 0 and not item.alignment() & Qt.AlignTop: - if item.alignment() & Qt.AlignBottom: + if free_space.height() > 0 and not item.alignment() & Qt.AlignmentFlag.AlignTop: + if item.alignment() & Qt.AlignmentFlag.AlignBottom: item_rect.moveBottom(contents.height() + content_margins.bottom()) else: item_rect.moveTop(content_margins.top() + (free_space.height() // 2)) @@ -1380,8 +1381,8 @@ class FixedAspectRatioLayout(QLayout): result = result.expandedTo(item.minimumSize()) return self._get_contents_margins_size() + result - def expandingDirections(self) -> Qt.Orientations: - return Qt.Horizontal | Qt.Vertical + def expandingDirections(self) -> Qt.Orientation: + return Qt.Orientation.Horizontal | Qt.Orientation.Vertical def QColorLerp(a: QColor, b: QColor, t: float): @@ -1414,8 +1415,8 @@ class ImageGraphicsEffect(QObject): def apply(self, image: QImage): assert image, 'image must be set' - result = QImage(image.size(), QImage.Format_ARGB32) - result.fill(Qt.transparent) + result = QImage(image.size(), QImage.Format.Format_ARGB32) + result.fill(Qt.GlobalColor.transparent) painter = QPainter(result) self.graphics_item.setPixmap(QPixmap.fromImage(image)) self.graphics_scene.render(painter) @@ -1433,7 +1434,8 @@ class QtEventListener(EventListener): def unregister_callbacks(self): try: self.qt_callback_signal.disconnect() - except RuntimeError: # wrapped Qt object might be deleted + except (RuntimeError, TypeError): # wrapped Qt object might be deleted + # "TypeError: disconnect() failed between 'qt_callback_signal' and all its connections" pass EventListener.unregister_callbacks(self) @@ -1462,4 +1464,4 @@ if __name__ == "__main__": app = QApplication([]) t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done")) t.start() - app.exec_() + app.exec() diff --git a/electrum/gui/qt/utxo_dialog.py b/electrum/gui/qt/utxo_dialog.py index e94eae3db..91443efc1 100644 --- a/electrum/gui/qt/utxo_dialog.py +++ b/electrum/gui/qt/utxo_dialog.py @@ -26,9 +26,9 @@ from typing import TYPE_CHECKING import copy -from PyQt5.QtCore import Qt, QUrl -from PyQt5.QtGui import QTextCharFormat, QFont -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QLabel, QTextBrowser +from PyQt6.QtCore import Qt, QUrl +from PyQt6.QtGui import QTextCharFormat, QFont +from PyQt6.QtWidgets import QVBoxLayout, QHBoxLayout, QLabel, QTextBrowser from electrum.i18n import _ @@ -57,7 +57,7 @@ class UTXODialog(WindowModalDialog): self.parents_list.anchorClicked.connect(self.open_tx) # send links to our handler self.parents_list.setFont(QFont(MONOSPACE_FONT)) self.parents_list.setReadOnly(True) - self.parents_list.setTextInteractionFlags(self.parents_list.textInteractionFlags() | Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) + self.parents_list.setTextInteractionFlags(self.parents_list.textInteractionFlags() | Qt.TextInteractionFlag.LinksAccessibleByMouse | Qt.TextInteractionFlag.LinksAccessibleByKeyboard) self.txo_color_parent = TxOutputColoring( legend=_("Direct parent"), color=ColorScheme.BLUE, tooltip=_("Direct parent")) self.txo_color_uncle = TxOutputColoring( @@ -123,7 +123,7 @@ class UTXODialog(WindowModalDialog): lnk.setAnchorHref(_txid) #lnk.setAnchorNames([a_name]) lnk.setAnchor(True) - lnk.setUnderlineStyle(QTextCharFormat.SingleUnderline) + lnk.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SingleUnderline) cursor.insertText(key, lnk) cursor.insertText(" ", ext) cursor.insertText(label, ext) @@ -149,7 +149,7 @@ class UTXODialog(WindowModalDialog): def open_tx(self, txid): if isinstance(txid, QUrl): - txid = txid.toString(QUrl.None_) + txid = txid.toString(QUrl.UrlFormattingOption.None_) tx = self.wallet.adb.get_transaction(txid) if not tx: return diff --git a/electrum/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py index 188cf8ce1..d01325a68 100644 --- a/electrum/gui/qt/utxo_list.py +++ b/electrum/gui/qt/utxo_list.py @@ -27,9 +27,9 @@ from typing import Optional, List, Dict, Sequence, Set, TYPE_CHECKING import enum import copy -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont -from PyQt5.QtWidgets import QAbstractItemView, QMenu, QLabel, QHBoxLayout +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QStandardItemModel, QStandardItem, QFont +from PyQt6.QtWidgets import QAbstractItemView, QMenu, QLabel, QHBoxLayout from electrum.i18n import _ from electrum.bitcoin import is_address @@ -67,8 +67,8 @@ class UTXOList(MyTreeView): filter_columns = [Columns.ADDRESS, Columns.LABEL, Columns.OUTPOINT] stretch_column = Columns.LABEL - ROLE_PREVOUT_STR = Qt.UserRole + 1000 - ROLE_SORT_ORDER = Qt.UserRole + 1001 + ROLE_PREVOUT_STR = Qt.ItemDataRole.UserRole + 1000 + ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 1001 key_role = ROLE_PREVOUT_STR def __init__(self, main_window: 'ElectrumWindow'): @@ -83,7 +83,7 @@ class UTXOList(MyTreeView): self.proxy = MySortModel(self, sort_role=self.ROLE_SORT_ORDER) self.proxy.setSourceModel(self.std_model) self.setModel(self.proxy) - self.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) self.setSortingEnabled(True) def create_toolbar(self, config): @@ -124,7 +124,7 @@ class UTXOList(MyTreeView): self.refresh_row(name, idx) self.filter() self.proxy.setDynamicSortFilter(True) - self.sortByColumn(self.Columns.OUTPOINT, Qt.DescendingOrder) + self.sortByColumn(self.Columns.OUTPOINT, Qt.SortOrder.DescendingOrder) self.update_coincontrol_bar() self.num_coins_label.setText(_('{} unspent transaction outputs').format(len(utxos))) @@ -359,7 +359,7 @@ class UTXOList(MyTreeView): act = menu_freeze.addAction(_("Unfreeze Addresses"), lambda: self.main_window.set_frozen_state_of_addresses(addrs, False)) act.setToolTip(MSG_FREEZE_ADDRESS) - menu.exec_(self.viewport().mapToGlobal(position)) + menu.exec(self.viewport().mapToGlobal(position)) def get_filter_data_from_coordinate(self, row, col): if col == self.Columns.OUTPOINT: diff --git a/electrum/gui/qt/wallet_info_dialog.py b/electrum/gui/qt/wallet_info_dialog.py index 2247cf374..11ecdd86f 100644 --- a/electrum/gui/qt/wallet_info_dialog.py +++ b/electrum/gui/qt/wallet_info_dialog.py @@ -5,8 +5,8 @@ import os from typing import TYPE_CHECKING -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import (QLabel, QVBoxLayout, QGridLayout, +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import (QLabel, QVBoxLayout, QGridLayout, QHBoxLayout, QPushButton, QWidget, QStackedWidget) from electrum.plugin import run_hook @@ -140,7 +140,7 @@ class WalletInfoDialog(WindowModalDialog): der_path_hbox.setContentsMargins(0, 0, 0, 0) der_path_hbox.addWidget(WWLabel(_("Derivation path") + ':')) der_path_text = WWLabel(ks.get_derivation_prefix() or _("unknown")) - der_path_text.setTextInteractionFlags(Qt.TextSelectableByMouse) + der_path_text.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) der_path_hbox.addWidget(der_path_text) der_path_hbox.addStretch() ks_vbox.addLayout(der_path_hbox) @@ -149,7 +149,7 @@ class WalletInfoDialog(WindowModalDialog): bip32fp_hbox.setContentsMargins(0, 0, 0, 0) bip32fp_hbox.addWidget(QLabel("BIP32 root fingerprint:")) bip32fp_text = WWLabel(ks.get_root_fingerprint() or _("unknown")) - bip32fp_text.setTextInteractionFlags(Qt.TextSelectableByMouse) + bip32fp_text.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) bip32fp_hbox.addWidget(bip32fp_text) bip32fp_hbox.addStretch() ks_vbox.addLayout(bip32fp_hbox) diff --git a/electrum/gui/qt/watchtower_dialog.py b/electrum/gui/qt/watchtower_dialog.py index 79db87b90..eb8ccc95c 100644 --- a/electrum/gui/qt/watchtower_dialog.py +++ b/electrum/gui/qt/watchtower_dialog.py @@ -25,9 +25,9 @@ import enum -from PyQt5.QtGui import QStandardItemModel, QStandardItem -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QPushButton, QLabel) +from PyQt6.QtGui import QStandardItemModel, QStandardItem +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QPushButton, QLabel) from electrum.i18n import _ from .util import Buttons diff --git a/electrum/gui/qt/wizard/server_connect.py b/electrum/gui/qt/wizard/server_connect.py index a86c6b327..b4489d7e0 100644 --- a/electrum/gui/qt/wizard/server_connect.py +++ b/electrum/gui/qt/wizard/server_connect.py @@ -1,8 +1,8 @@ from typing import TYPE_CHECKING -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPixmap -from PyQt5.QtWidgets import QCheckBox, QLabel, QHBoxLayout, QVBoxLayout, QWidget +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QPixmap +from PyQt6.QtWidgets import QCheckBox, QLabel, QHBoxLayout, QVBoxLayout, QWidget from electrum.i18n import _ from electrum.wizard import ServerConnectWizard @@ -70,8 +70,8 @@ class WCWelcome(WizardComponent): self.layout().addLayout(hbox_img) self.layout().addSpacing(50) - self.layout().addWidget(self.use_advanced_w, False, Qt.AlignHCenter) - self.layout().addWidget(options_w, False, Qt.AlignHCenter) + self.layout().addWidget(self.use_advanced_w, False, Qt.AlignmentFlag.AlignHCenter) + self.layout().addWidget(options_w, False, Qt.AlignmentFlag.AlignHCenter) self._valid = True def on_advanced_changed(self): diff --git a/electrum/gui/qt/wizard/wallet.py b/electrum/gui/qt/wizard/wallet.py index f39e96959..4a2596e62 100644 --- a/electrum/gui/qt/wizard/wallet.py +++ b/electrum/gui/qt/wizard/wallet.py @@ -5,9 +5,9 @@ import threading from typing import TYPE_CHECKING, Optional -from PyQt5.QtCore import Qt, QTimer, QRect, pyqtSignal -from PyQt5.QtGui import QPen, QPainter, QPalette, QPixmap -from PyQt5.QtWidgets import (QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget, +from PyQt6.QtCore import Qt, QTimer, QRect, pyqtSignal +from PyQt6.QtGui import QPen, QPainter, QPalette, QPixmap +from PyQt6.QtWidgets import (QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget, QFileDialog, QSlider, QGridLayout, QDialog, QApplication) from electrum.bip32 import is_bip32_derivation, BIP32Node, normalize_bip32_derivation, xpub_type @@ -203,7 +203,7 @@ class QENewWalletWizard(NewWalletWizard, QEAbstractWizard, MessageBoxMixin): vbox = QVBoxLayout() vbox.addSpacing(100) label.setMinimumWidth(300) - label.setAlignment(Qt.AlignCenter) + label.setAlignment(Qt.AlignmentFlag.AlignCenter) vbox.addWidget(label) vbox.addSpacing(100) dialog.setLayout(vbox) @@ -281,10 +281,10 @@ class WCWalletName(WalletWizardComponent, Logger): self.layout().addSpacing(50) vbox_create_new = QVBoxLayout() - vbox_create_new.addWidget(QLabel(_('Alternatively') + ':'), alignment=Qt.AlignLeft) + vbox_create_new.addWidget(QLabel(_('Alternatively') + ':'), alignment=Qt.AlignmentFlag.AlignLeft) button_create_new = QPushButton(_('Create New Wallet')) button_create_new.setMinimumWidth(120) - vbox_create_new.addWidget(button_create_new, alignment=Qt.AlignLeft) + vbox_create_new.addWidget(button_create_new, alignment=Qt.AlignmentFlag.AlignLeft) widget_create_new = QWidget() widget_create_new.setLayout(vbox_create_new) vbox_create_new.setContentsMargins(0, 0, 0, 0) @@ -718,7 +718,7 @@ class WCScriptAndDerivation(WalletWizardComponent, Logger): self.derivation_path_edit.setText(account["derivation_path"]) button.clicked.connect(lambda: Bip39RecoveryDialog(self, get_account_xpub, on_account_select)) - self.layout().addWidget(button, alignment=Qt.AlignLeft) + self.layout().addWidget(button, alignment=Qt.AlignmentFlag.AlignLeft) self.layout().addWidget(QLabel(_("Or"))) def on_choice_click(index): @@ -893,14 +893,14 @@ class WCMultisig(WalletWizardComponent): m_label = QLabel() n_label = QLabel() - m_edit = QSlider(Qt.Horizontal, self) + m_edit = QSlider(Qt.Orientation.Horizontal, self) m_edit.setMinimum(1) m_edit.setMaximum(2) m_edit.setValue(2) m_edit.valueChanged.connect(on_m) on_m(m_edit.value()) - n_edit = QSlider(Qt.Horizontal, self) + n_edit = QSlider(Qt.Orientation.Horizontal, self) n_edit.setMinimum(2) n_edit.setMaximum(15) n_edit.setValue(2) @@ -940,7 +940,7 @@ class WCImport(WalletWizardComponent): label = WWLabel(message) label.setMinimumWidth(400) header_layout.addWidget(label) - header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight) + header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignmentFlag.AlignRight) # TODO: KeysLayout assumes too much in parent, refactor KeysLayout # for now, fake parent.next_button.setEnabled @@ -1046,17 +1046,17 @@ class CosignWidget(QWidget): self.update() def paintEvent(self, event): - bgcolor = self.palette().color(QPalette.Background) - pen = QPen(bgcolor, 7, Qt.SolidLine) + bgcolor = self.palette().color(QPalette.ColorRole.Window) + pen = QPen(bgcolor, 7, Qt.PenStyle.SolidLine) qp = QPainter() qp.begin(self) qp.setPen(pen) - qp.setRenderHint(QPainter.Antialiasing) - qp.setBrush(Qt.gray) + qp.setRenderHint(QPainter.RenderHint.Antialiasing) + qp.setBrush(Qt.GlobalColor.gray) for i in range(self.n): alpha = int(16 * 360 * i/self.n) alpha2 = int(16 * 360 * 1/self.n) - qp.setBrush(Qt.green if i < self.m else Qt.gray) + qp.setBrush(Qt.GlobalColor.green if i < self.m else Qt.GlobalColor.gray) qp.drawPie(self.R, alpha, alpha2) qp.end() @@ -1256,10 +1256,10 @@ class WCHWUnlock(WalletWizardComponent, Logger): self.password = None ok_icon = QLabel() - ok_icon.setPixmap(QPixmap(icon_path('confirmed.png')).scaledToWidth(48, mode=Qt.SmoothTransformation)) - ok_icon.setAlignment(Qt.AlignCenter) + ok_icon.setPixmap(QPixmap(icon_path('confirmed.png')).scaledToWidth(48, mode=Qt.TransformationMode.SmoothTransformation)) + ok_icon.setAlignment(Qt.AlignmentFlag.AlignCenter) self.ok_l = WWLabel(_('Hardware successfully unlocked')) - self.ok_l.setAlignment(Qt.AlignCenter) + self.ok_l.setAlignment(Qt.AlignmentFlag.AlignCenter) self.layout().addStretch(1) self.layout().addWidget(ok_icon) self.layout().addWidget(self.ok_l) @@ -1335,10 +1335,10 @@ class WCHWXPub(WalletWizardComponent, Logger): self.soft_device_id = None ok_icon = QLabel() - ok_icon.setPixmap(QPixmap(icon_path('confirmed.png')).scaledToWidth(48, mode=Qt.SmoothTransformation)) - ok_icon.setAlignment(Qt.AlignCenter) + ok_icon.setPixmap(QPixmap(icon_path('confirmed.png')).scaledToWidth(48, mode=Qt.TransformationMode.SmoothTransformation)) + ok_icon.setAlignment(Qt.AlignmentFlag.AlignCenter) self.ok_l = WWLabel(_('Hardware keystore added to wallet')) - self.ok_l.setAlignment(Qt.AlignCenter) + self.ok_l.setAlignment(Qt.AlignmentFlag.AlignCenter) self.layout().addStretch(1) self.layout().addWidget(ok_icon) self.layout().addWidget(self.ok_l) @@ -1423,10 +1423,10 @@ class WCHWUninitialized(WalletWizardComponent): cosigner_data = self.wizard.current_cosigner(self.wizard_data) _name, _info = cosigner_data['hardware_device'] w_icon = QLabel() - w_icon.setPixmap(QPixmap(icon_path('warning.png')).scaledToWidth(48, mode=Qt.SmoothTransformation)) - w_icon.setAlignment(Qt.AlignCenter) + w_icon.setPixmap(QPixmap(icon_path('warning.png')).scaledToWidth(48, mode=Qt.TransformationMode.SmoothTransformation)) + w_icon.setAlignment(Qt.AlignmentFlag.AlignCenter) label = WWLabel(_('This {} is not initialized. Use manufacturer tooling to initialize the device.').format(_info.model_name)) - label.setAlignment(Qt.AlignCenter) + label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.layout().addStretch(1) self.layout().addWidget(w_icon) self.layout().addWidget(label) diff --git a/electrum/gui/qt/wizard/wizard.py b/electrum/gui/qt/wizard/wizard.py index a8a19bc6a..b397e11ac 100644 --- a/electrum/gui/qt/wizard/wizard.py +++ b/electrum/gui/qt/wizard/wizard.py @@ -3,9 +3,9 @@ import threading from abc import abstractmethod from typing import TYPE_CHECKING -from PyQt5.QtCore import Qt, QTimer, pyqtSignal, pyqtSlot, QSize, QMetaObject -from PyQt5.QtGui import QPixmap -from PyQt5.QtWidgets import (QDialog, QPushButton, QWidget, QLabel, QVBoxLayout, QScrollArea, +from PyQt6.QtCore import Qt, QTimer, pyqtSignal, pyqtSlot, QSize, QMetaObject +from PyQt6.QtGui import QPixmap +from PyQt6.QtWidgets import (QDialog, QPushButton, QWidget, QLabel, QVBoxLayout, QScrollArea, QHBoxLayout, QLayout) from electrum.i18n import _ @@ -61,7 +61,7 @@ class QEAbstractWizard(QDialog, MessageBoxMixin): please_wait_layout = QVBoxLayout() please_wait_layout.addStretch(1) self.please_wait_l = QLabel(_("Please wait...")) - self.please_wait_l.setAlignment(Qt.AlignCenter) + self.please_wait_l.setAlignment(Qt.AlignmentFlag.AlignCenter) please_wait_layout.addWidget(self.please_wait_l) please_wait_layout.addStretch(1) self.please_wait = QWidget() @@ -71,11 +71,11 @@ class QEAbstractWizard(QDialog, MessageBoxMixin): error_layout = QVBoxLayout() error_layout.addStretch(1) error_icon = QLabel() - error_icon.setPixmap(QPixmap(icon_path('warning.png')).scaledToWidth(48, mode=Qt.SmoothTransformation)) - error_icon.setAlignment(Qt.AlignCenter) + error_icon.setPixmap(QPixmap(icon_path('warning.png')).scaledToWidth(48, mode=Qt.TransformationMode.SmoothTransformation)) + error_icon.setAlignment(Qt.AlignmentFlag.AlignCenter) error_layout.addWidget(error_icon) self.error_msg = WWLabel() - self.error_msg.setAlignment(Qt.AlignCenter) + self.error_msg.setAlignment(Qt.AlignmentFlag.AlignCenter) error_layout.addWidget(self.error_msg) error_layout.addStretch(1) self.error = QWidget() @@ -92,9 +92,9 @@ class QEAbstractWizard(QDialog, MessageBoxMixin): scroll_widget = QWidget() scroll_widget.setLayout(inner_vbox) scroll = QScrollArea() - scroll.setFocusPolicy(Qt.NoFocus) + scroll.setFocusPolicy(Qt.FocusPolicy.NoFocus) scroll.setWidget(scroll_widget) - scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) scroll.setWidgetResizable(True) icon_vbox = QVBoxLayout() icon_vbox.addWidget(self.logo) @@ -115,7 +115,7 @@ class QEAbstractWizard(QDialog, MessageBoxMixin): self.show() self.raise_() - QMetaObject.invokeMethod(self, 'strt', Qt.QueuedConnection) # call strt after subclass constructor(s) + QMetaObject.invokeMethod(self, 'strt', Qt.ConnectionType.QueuedConnection) # call strt after subclass constructor(s) def sizeHint(self) -> QSize: return QSize(600, 400) @@ -169,7 +169,7 @@ class QEAbstractWizard(QDialog, MessageBoxMixin): def set_icon(self, filename): prior_filename, self.icon_filename = self.icon_filename, filename self.logo.setPixmap(QPixmap(icon_path(filename)) - .scaledToWidth(60, mode=Qt.SmoothTransformation)) + .scaledToWidth(60, mode=Qt.TransformationMode.SmoothTransformation)) return prior_filename def can_go_back(self) -> bool: diff --git a/electrum/plugins/audio_modem/qt.py b/electrum/plugins/audio_modem/qt.py index 3f46e67ea..f830b9e5f 100644 --- a/electrum/plugins/audio_modem/qt.py +++ b/electrum/plugins/audio_modem/qt.py @@ -6,7 +6,7 @@ import sys import platform from typing import TYPE_CHECKING -from PyQt5.QtWidgets import (QComboBox, QGridLayout, QLabel, QPushButton) +from PyQt6.QtWidgets import (QComboBox, QGridLayout, QLabel, QPushButton) from electrum.plugin import BasePlugin, hook from electrum.gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog, read_QIcon @@ -72,7 +72,7 @@ class Plugin(BasePlugin): ok_button.clicked.connect(d.accept) layout.addWidget(ok_button, 1, 1) - return bool(d.exec_()) + return bool(d.exec()) @hook def transaction_dialog(self, dialog: 'TxDialog'): diff --git a/electrum/plugins/bitbox02/qt.py b/electrum/plugins/bitbox02/qt.py index bfde53251..88261ad5f 100644 --- a/electrum/plugins/bitbox02/qt.py +++ b/electrum/plugins/bitbox02/qt.py @@ -2,8 +2,8 @@ import threading from functools import partial from typing import TYPE_CHECKING -from PyQt5.QtCore import Qt, QMetaObject, Q_RETURN_ARG, pyqtSlot, pyqtSignal -from PyQt5.QtWidgets import QLabel, QVBoxLayout, QLineEdit, QHBoxLayout +from PyQt6.QtCore import Qt, QMetaObject, Q_RETURN_ARG, pyqtSlot, pyqtSignal +from PyQt6.QtWidgets import QLabel, QVBoxLayout, QLineEdit, QHBoxLayout from electrum.i18n import _ from electrum.plugin import hook @@ -83,7 +83,7 @@ class BitBox02_Handler(QtHandlerBase): super(BitBox02_Handler, self).__init__(win, "BitBox02") def name_multisig_account(self): - return QMetaObject.invokeMethod(self, "_name_multisig_account", Qt.BlockingQueuedConnection, Q_RETURN_ARG(str)) + return QMetaObject.invokeMethod(self, "_name_multisig_account", Qt.ConnectionType.BlockingQueuedConnection, Q_RETURN_ARG(str)) @pyqtSlot(result=str) def _name_multisig_account(self): @@ -109,7 +109,7 @@ class BitBox02_Handler(QtHandlerBase): vbox.addLayout(he) vbox.addLayout(hlb) dialog.setLayout(vbox) - dialog.exec_() + dialog.exec() return name.text().strip() diff --git a/electrum/plugins/coldcard/qt.py b/electrum/plugins/coldcard/qt.py index bbdaee213..b92310b07 100644 --- a/electrum/plugins/coldcard/qt.py +++ b/electrum/plugins/coldcard/qt.py @@ -1,8 +1,8 @@ from functools import partial from typing import TYPE_CHECKING -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QPushButton, QLabel, QVBoxLayout, QWidget, QGridLayout +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QPushButton, QLabel, QVBoxLayout, QWidget, QGridLayout from electrum.gui.qt.util import WindowModalDialog, CloseButton, getOpenFileName, getSaveFileName from electrum.gui.qt.main_window import ElectrumWindow @@ -81,7 +81,7 @@ class Plugin(ColdcardPlugin, QtPluginBase): def show_settings_dialog(self, window, keystore): # When they click on the icon for CC we come here. # - doesn't matter if device not connected, continue - CKCCSettingsDialog(window, self, keystore).exec_() + CKCCSettingsDialog(window, self, keystore).exec() @hook def init_wallet_wizard(self, wizard: 'QENewWalletWizard'): @@ -143,9 +143,9 @@ class CKCCSettingsDialog(WindowModalDialog): Coldcard Wallet
from Coinkite Inc.
coldcardwallet.com''') - title.setTextInteractionFlags(Qt.LinksAccessibleByMouse) + title.setTextInteractionFlags(Qt.TextInteractionFlag.LinksAccessibleByMouse) - grid.addWidget(title, 0,0, 1,2, Qt.AlignHCenter) + grid.addWidget(title, 0,0, 1,2, Qt.AlignmentFlag.AlignHCenter) y = 3 rows = [ @@ -158,10 +158,10 @@ class CKCCSettingsDialog(WindowModalDialog): for row_num, (member_name, label) in enumerate(rows): # XXX we know xfp already, even if not connected widget = QLabel('000000000000') - widget.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.TextSelectableByKeyboard) + widget.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse | Qt.TextInteractionFlag.TextSelectableByKeyboard) - grid.addWidget(QLabel(label), y, 0, 1,1, Qt.AlignRight) - grid.addWidget(widget, y, 1, 1, 1, Qt.AlignLeft) + grid.addWidget(QLabel(label), y, 0, 1,1, Qt.AlignmentFlag.AlignRight) + grid.addWidget(widget, y, 1, 1, 1, Qt.AlignmentFlag.AlignLeft) setattr(self, member_name, widget) y += 1 body_layout.addLayout(grid) diff --git a/electrum/plugins/cosigner_pool/qt.py b/electrum/plugins/cosigner_pool/qt.py index 634f84466..b95945abe 100644 --- a/electrum/plugins/cosigner_pool/qt.py +++ b/electrum/plugins/cosigner_pool/qt.py @@ -28,8 +28,8 @@ from xmlrpc.client import ServerProxy, Transport from typing import TYPE_CHECKING, Union, List, Tuple, Dict import ssl -from PyQt5.QtCore import QObject, pyqtSignal -from PyQt5.QtWidgets import QPushButton +from PyQt6.QtCore import QObject, pyqtSignal +from PyQt6.QtWidgets import QPushButton import certifi from electrum import util, keystore, ecc, crypto diff --git a/electrum/plugins/digitalbitbox/qt.py b/electrum/plugins/digitalbitbox/qt.py index 38826297c..10ee5a621 100644 --- a/electrum/plugins/digitalbitbox/qt.py +++ b/electrum/plugins/digitalbitbox/qt.py @@ -2,7 +2,7 @@ import threading from functools import partial from typing import TYPE_CHECKING -from PyQt5.QtCore import pyqtSignal +from PyQt6.QtCore import pyqtSignal from electrum.i18n import _ from electrum.plugin import hook diff --git a/electrum/plugins/hw_wallet/qt.py b/electrum/plugins/hw_wallet/qt.py index 9656019a0..c75d1be04 100644 --- a/electrum/plugins/hw_wallet/qt.py +++ b/electrum/plugins/hw_wallet/qt.py @@ -28,8 +28,8 @@ import threading from functools import partial from typing import TYPE_CHECKING, Union, Optional, Sequence, Tuple -from PyQt5.QtCore import QObject, pyqtSignal, Qt -from PyQt5.QtWidgets import QVBoxLayout, QLineEdit, QHBoxLayout, QLabel +from PyQt6.QtCore import QObject, pyqtSignal, Qt +from PyQt6.QtWidgets import QVBoxLayout, QLineEdit, QHBoxLayout, QLabel from electrum.gui.qt.password_dialog import PasswordLayout, PW_PASSPHRASE from electrum.gui.qt.util import (read_QIcon, WWLabel, OkButton, WindowModalDialog, @@ -142,7 +142,7 @@ class QtHandlerBase(HardwareHandlerBase, QObject, Logger): vbox.addLayout(playout.layout()) vbox.addLayout(Buttons(CancelButton(d), OK_button)) d.setLayout(vbox) - passphrase = playout.new_password() if d.exec_() else None + passphrase = playout.new_password() if d.exec() else None else: pw = PasswordLineEdit() pw.setMinimumWidth(200) @@ -151,7 +151,7 @@ class QtHandlerBase(HardwareHandlerBase, QObject, Logger): vbox.addWidget(pw) vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) d.setLayout(vbox) - passphrase = pw.text() if d.exec_() else None + passphrase = pw.text() if d.exec() else None self.passphrase = passphrase self.done.set() @@ -164,7 +164,7 @@ class QtHandlerBase(HardwareHandlerBase, QObject, Logger): text.returnPressed.connect(dialog.accept) hbox.addWidget(text) hbox.addStretch(1) - dialog.exec_() # Firmware cannot handle cancellation + dialog.exec() # Firmware cannot handle cancellation self.word = text.text() self.done.set() @@ -176,7 +176,7 @@ class QtHandlerBase(HardwareHandlerBase, QObject, Logger): title = _('Please check your {} device').format(self.device) self.dialog = dialog = WindowModalDialog(self.top_level_window(), title) label = QLabel(msg) - label.setTextInteractionFlags(Qt.TextSelectableByMouse) + label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) vbox = QVBoxLayout(dialog) vbox.addWidget(label) if on_cancel: diff --git a/electrum/plugins/jade/qt.py b/electrum/plugins/jade/qt.py index 96a5c92d7..73d5b8de8 100644 --- a/electrum/plugins/jade/qt.py +++ b/electrum/plugins/jade/qt.py @@ -1,7 +1,7 @@ from functools import partial from typing import TYPE_CHECKING -from PyQt5.QtCore import pyqtSignal +from PyQt6.QtCore import pyqtSignal from electrum.i18n import _ from electrum.plugin import hook diff --git a/electrum/plugins/keepkey/qt.py b/electrum/plugins/keepkey/qt.py index 9c53499d5..e162ee138 100644 --- a/electrum/plugins/keepkey/qt.py +++ b/electrum/plugins/keepkey/qt.py @@ -2,9 +2,9 @@ import threading from functools import partial from typing import TYPE_CHECKING -from PyQt5.QtCore import Qt, QEventLoop, pyqtSignal, QRegExp -from PyQt5.QtGui import QRegExpValidator -from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton, +from PyQt6.QtCore import Qt, QEventLoop, pyqtSignal, QRegularExpression +from PyQt6.QtGui import QRegularExpressionValidator +from PyQt6.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton, QHBoxLayout, QButtonGroup, QGroupBox, QDialog, QTextEdit, QLineEdit, QRadioButton, QCheckBox, QWidget, QMessageBox, QSlider, QTabWidget) @@ -88,7 +88,7 @@ class CharacterDialog(WindowModalDialog): self.finished_button = QPushButton(_("Seed Entered")) self.cancel_button = QPushButton(_("Cancel")) self.finished_button.clicked.connect(partial(self.process_key, - Qt.Key_Return)) + Qt.Key.Key_Return)) self.cancel_button.clicked.connect(self.rejected) buttons = Buttons(self.finished_button, self.cancel_button) vbox.addSpacing(40) @@ -118,9 +118,9 @@ class CharacterDialog(WindowModalDialog): def process_key(self, key): self.data = None - if key == Qt.Key_Return and self.finished_button.isEnabled(): + if key == Qt.Key.Key_Return and self.finished_button.isEnabled(): self.data = {'done': True} - elif key == Qt.Key_Backspace and (self.word_pos or self.character_pos): + elif key == Qt.Key.Key_Backspace and (self.word_pos or self.character_pos): self.data = {'delete': True} elif self.is_valid_alpha_space(key): self.data = {'character': chr(key).lower()} @@ -136,7 +136,7 @@ class CharacterDialog(WindowModalDialog): self.word_pos = word_pos self.character_pos = character_pos self.refresh() - if self.loop.exec_(): + if self.loop.exec(): self.data = None # User cancelled @@ -183,7 +183,7 @@ class QtHandler(QtHandlerBase): vbox.addWidget(matrix) vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog))) dialog.setLayout(vbox) - dialog.exec_() + dialog.exec() self.response = str(matrix.get_value()) self.done.set() @@ -217,7 +217,7 @@ class QtPlugin(QtPluginBase): return device_id def show_dialog(device_id): if device_id: - SettingsDialog(window, self, keystore, device_id).exec_() + SettingsDialog(window, self, keystore, device_id).exec() keystore.thread.add(connect, on_success=show_dialog) @@ -276,7 +276,7 @@ class KeepkeyInitLayout(QVBoxLayout): self.addWidget(QLabel(msg)) self.addWidget(self.text_e) self.pin = QLineEdit() - self.pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}'))) + self.pin.setValidator(QRegularExpressionValidator(QRegularExpression('[1-9]{0,9}'))) self.pin.setMaximumWidth(100) hbox_pin = QHBoxLayout() hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):"))) @@ -439,7 +439,7 @@ class SettingsDialog(WindowModalDialog): msg = _("Are you SURE you want to wipe the device?\n" "Your wallet still has bitcoins in it!") if not self.question(msg, title=title, - icon=QMessageBox.Critical): + icon=QMessageBox.Icon.Critical): return invoke_client('wipe_device', unpair_after=True) @@ -521,11 +521,11 @@ class SettingsDialog(WindowModalDialog): # Settings tab - Session Timeout timeout_label = QLabel(_("Session Timeout")) timeout_minutes = QLabel() - timeout_slider = QSlider(Qt.Horizontal) + timeout_slider = QSlider(Qt.Orientation.Horizontal) timeout_slider.setRange(1, 60) timeout_slider.setSingleStep(1) timeout_slider.setTickInterval(5) - timeout_slider.setTickPosition(QSlider.TicksBelow) + timeout_slider.setTickPosition(QSlider.TickPosition.TicksBelow) timeout_slider.setTracking(True) timeout_msg = QLabel( _("Clear the session after the specified period " diff --git a/electrum/plugins/labels/qt.py b/electrum/plugins/labels/qt.py index c1e98b116..be867e838 100644 --- a/electrum/plugins/labels/qt.py +++ b/electrum/plugins/labels/qt.py @@ -3,8 +3,8 @@ import traceback import sys from typing import TYPE_CHECKING -from PyQt5.QtCore import QObject, pyqtSignal -from PyQt5.QtWidgets import (QHBoxLayout, QLabel, QVBoxLayout) +from PyQt6.QtCore import QObject, pyqtSignal +from PyQt6.QtWidgets import (QHBoxLayout, QLabel, QVBoxLayout) from electrum.plugin import hook from electrum.i18n import _ @@ -60,7 +60,7 @@ class Plugin(LabelsPlugin): vbox.addLayout(hbox) vbox.addSpacing(20) vbox.addLayout(Buttons(OkButton(d))) - return bool(d.exec_()) + return bool(d.exec()) def on_pulled(self, wallet): self.obj.labels_changed_signal.emit(wallet) diff --git a/electrum/plugins/ledger/auth2fa.py b/electrum/plugins/ledger/auth2fa.py index aa7a07a19..af6ccdf26 100644 --- a/electrum/plugins/ledger/auth2fa.py +++ b/electrum/plugins/ledger/auth2fa.py @@ -1,7 +1,7 @@ import copy from typing import TYPE_CHECKING -from PyQt5.QtWidgets import (QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel, +from PyQt6.QtWidgets import (QDialog, QLineEdit, QTextEdit, QVBoxLayout, QLabel, QWidget, QHBoxLayout, QComboBox) from btchip.btchip import BTChipException diff --git a/electrum/plugins/ledger/qt.py b/electrum/plugins/ledger/qt.py index b1014f77c..4df2f6f35 100644 --- a/electrum/plugins/ledger/qt.py +++ b/electrum/plugins/ledger/qt.py @@ -1,8 +1,8 @@ from functools import partial from typing import TYPE_CHECKING -from PyQt5.QtCore import pyqtSignal -from PyQt5.QtWidgets import QInputDialog, QLineEdit +from PyQt6.QtCore import pyqtSignal +from PyQt6.QtWidgets import QInputDialog, QLineEdit from electrum.i18n import _ from electrum.plugin import hook @@ -77,7 +77,7 @@ class Ledger_Handler(QtHandlerBase): self.message_dialog(repr(e)) return dialog = LedgerAuthDialog(self, data, client=client) - dialog.exec_() + dialog.exec() self.word = dialog.pin self.done.set() diff --git a/electrum/plugins/revealer/qt.py b/electrum/plugins/revealer/qt.py index e7ca53bec..3e82b07b2 100644 --- a/electrum/plugins/revealer/qt.py +++ b/electrum/plugins/revealer/qt.py @@ -22,11 +22,11 @@ import sys from typing import TYPE_CHECKING import qrcode -from PyQt5.QtPrintSupport import QPrinter -from PyQt5.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize -from PyQt5.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont, - QColor, QDesktopServices, qRgba, QPainterPath) -from PyQt5.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel, +from PyQt6.QtPrintSupport import QPrinter +from PyQt6.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize, QMarginsF +from PyQt6.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont, + QColor, QDesktopServices, qRgba, QPainterPath, QPageSize) +from PyQt6.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QLineEdit) from electrum.plugin import hook @@ -128,7 +128,7 @@ class Plugin(RevealerPlugin): logo_label.setPixmap(QPixmap(icon_path('revealer.png'))) # Align the logo label to the top left. - logo_label.setAlignment(Qt.AlignLeft) + logo_label.setAlignment(Qt.AlignmentFlag.AlignLeft) # Create a VBox layout for the main contents of the dialog. vbox_layout = QVBoxLayout() @@ -143,8 +143,8 @@ class Plugin(RevealerPlugin): instructions_label = QLabel(_("Click the button above or type an existing revealer code in the box below.")) # Allow users to select text in the labels. - create_or_load_noise_file_label.setTextInteractionFlags(Qt.TextSelectableByMouse) - instructions_label.setTextInteractionFlags(Qt.TextSelectableByMouse) + create_or_load_noise_file_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + instructions_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) # Create the buttons. create_button = QPushButton(_("Create a new Revealer noise file")) @@ -187,7 +187,7 @@ class Plugin(RevealerPlugin): # Populate the VBox layout. vbox_layout.addWidget(create_or_load_noise_file_label) - vbox_layout.addWidget(create_button, alignment=Qt.AlignCenter) + vbox_layout.addWidget(create_button, alignment=Qt.AlignmentFlag.AlignCenter) vbox_layout.addWidget(instructions_label) vbox_layout.addWidget(self.noise_scan_qr_textedit) vbox_layout.addLayout(Buttons(self.next_button)) @@ -196,7 +196,7 @@ class Plugin(RevealerPlugin): hbox_layout.addStretch(1) vbox_layout.addStretch(1) - return bool(self.d.exec_()) + return bool(self.d.exec()) def get_noise(self): # Get the text from the scan QR text edit. @@ -270,7 +270,7 @@ class Plugin(RevealerPlugin): textCursor = self.custom_secret_scan_qr_textedit.textCursor() # Move the cursor position to the end (setting the text above automatically moves the cursor to the beginning, which is undesirable) - textCursor.movePosition(textCursor.End) + textCursor.movePosition(textCursor.MoveOperation.End) # Set the text cursor with the corrected position. self.custom_secret_scan_qr_textedit.setTextCursor(textCursor) @@ -311,7 +311,7 @@ class Plugin(RevealerPlugin): logo_label.setPixmap(QPixmap(icon_path('revealer.png'))) # Align the logo label to the top left. - logo_label.setAlignment(Qt.AlignLeft) + logo_label.setAlignment(Qt.AlignmentFlag.AlignLeft) # Create a VBox layout for the main contents of the dialog. vbox_layout = QVBoxLayout() @@ -331,14 +331,13 @@ class Plugin(RevealerPlugin): one_time_pad_warning_label = QLabel("" + _("Warning ") + ": " + _("each Revealer is a one-time-pad, use it for a single secret.")) # Allow users to select text in the labels. - ready_to_encrypt_label.setTextInteractionFlags(Qt.TextSelectableByMouse) - instructions_label.setTextInteractionFlags(Qt.TextSelectableByMouse) - self.custom_secret_character_count_label.setTextInteractionFlags(Qt.TextSelectableByMouse) - self.custom_secret_maximum_characters_warning_label - one_time_pad_warning_label.setTextInteractionFlags(Qt.TextSelectableByMouse) + ready_to_encrypt_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + instructions_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + self.custom_secret_character_count_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + one_time_pad_warning_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) # Align the custom secret character count label to the right. - self.custom_secret_character_count_label.setAlignment(Qt.AlignRight) + self.custom_secret_character_count_label.setAlignment(Qt.AlignmentFlag.AlignRight) # Initially hide the custom secret character count label. self.custom_secret_maximum_characters_warning_label.setVisible(False) @@ -376,12 +375,12 @@ class Plugin(RevealerPlugin): # Populate the VBox layout. vbox_layout.addWidget(ready_to_encrypt_label) - vbox_layout.addWidget(encrypt_seed_button, alignment=Qt.AlignCenter) + vbox_layout.addWidget(encrypt_seed_button, alignment=Qt.AlignmentFlag.AlignCenter) vbox_layout.addWidget(instructions_label) vbox_layout.addWidget(self.custom_secret_scan_qr_textedit) vbox_layout.addWidget(self.custom_secret_character_count_label) vbox_layout.addWidget(self.custom_secret_maximum_characters_warning_label) - vbox_layout.addWidget(self.encrypt_custom_secret_button, alignment=Qt.AlignCenter) + vbox_layout.addWidget(self.encrypt_custom_secret_button, alignment=Qt.AlignmentFlag.AlignCenter) vbox_layout.addSpacing(40) vbox_layout.addWidget(one_time_pad_warning_label) vbox_layout.addLayout(Buttons(CloseButton(d))) @@ -390,7 +389,7 @@ class Plugin(RevealerPlugin): hbox_layout.addStretch(1) vbox_layout.addStretch(1) - return bool(d.exec_()) + return bool(d.exec()) def update_wallet_name(self, name): self.wallet_name = str(name) @@ -412,9 +411,9 @@ class Plugin(RevealerPlugin): else: txt = self.txt.upper() - img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono) - bitmap = QBitmap.fromImage(img, Qt.MonoOnly) - bitmap.fill(Qt.white) + img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format.Format_Mono) + bitmap = QBitmap.fromImage(img, Qt.ImageConversionFlag.MonoOnly) + bitmap.fill(Qt.GlobalColor.white) painter = QPainter() painter.begin(bitmap) if len(txt) < 102 : @@ -430,8 +429,8 @@ class Plugin(RevealerPlugin): max_lines = 9 max_words = int(max_letters/4) - font = QFont('Source Sans 3', fontsize, QFont.Bold) - font.setLetterSpacing(QFont.PercentageSpacing, 100) + font = QFont('Source Sans 3', fontsize, QFont.Weight.Bold) + font.setLetterSpacing(QFont.SpacingType.PercentageSpacing, 100) font.setPixelSize(fontsize) painter.setFont(font) seed_array = txt.split(' ') @@ -442,7 +441,7 @@ class Plugin(RevealerPlugin): while len(' '.join(map(str, temp_seed))) > max_letters: nwords = nwords - 1 temp_seed = seed_array[:nwords] - painter.drawText(QRect(0, linespace*n, self.SIZE[0], self.SIZE[1]), Qt.AlignHCenter, ' '.join(map(str, temp_seed))) + painter.drawText(QRect(0, linespace*n, self.SIZE[0], self.SIZE[1]), Qt.AlignmentFlag.AlignHCenter, ' '.join(map(str, temp_seed))) del seed_array[:nwords] painter.end() @@ -458,7 +457,7 @@ class Plugin(RevealerPlugin): self.versioned_seed = self.gen_random_versioned_seed() assert self.versioned_seed w, h = self.SIZE - rawnoise = QImage(w, h, QImage.Format_Mono) + rawnoise = QImage(w, h, QImage.Format.Format_Mono) noise_map = self.get_noise_map(self.versioned_seed) for (x,y), pixel in noise_map.items(): @@ -471,7 +470,7 @@ class Plugin(RevealerPlugin): def make_calnoise(self): random.seed(self.calibration_noise) w, h = self.SIZE - rawnoise = QImage(w, h, QImage.Format_Mono) + rawnoise = QImage(w, h, QImage.Format.Format_Mono) for x in range(w): for y in range(h): rawnoise.setPixel(x,y,random.randint(0, 1)) @@ -481,7 +480,7 @@ class Plugin(RevealerPlugin): revealer = self.pixelcode_2x2(self.rawnoise) revealer.invertPixels() revealer = QBitmap.fromImage(revealer) - revealer = revealer.scaled(self.f_size, Qt.KeepAspectRatio) + revealer = revealer.scaled(self.f_size, Qt.AspectRatioMode.KeepAspectRatio) revealer = self.overlay_marks(revealer) self.filename_prefix = 'revealer_' @@ -489,7 +488,7 @@ class Plugin(RevealerPlugin): self.toPdf(QImage(revealer)) def make_cypherseed(self, img, rawnoise, calibration=False, is_seed = True): - img = img.convertToFormat(QImage.Format_Mono) + img = img.convertToFormat(QImage.Format.Format_Mono) p = QPainter() p.begin(img) p.setCompositionMode(26) #xor @@ -497,7 +496,7 @@ class Plugin(RevealerPlugin): p.end() cypherseed = self.pixelcode_2x2(img) cypherseed = QBitmap.fromImage(cypherseed) - cypherseed = cypherseed.scaled(self.f_size, Qt.KeepAspectRatio) + cypherseed = cypherseed.scaled(self.f_size, Qt.AspectRatioMode.KeepAspectRatio) cypherseed = self.overlay_marks(cypherseed, True, calibration) if not is_seed: @@ -517,9 +516,9 @@ class Plugin(RevealerPlugin): return cypherseed def calibration(self): - img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono) - bitmap = QBitmap.fromImage(img, Qt.MonoOnly) - bitmap.fill(Qt.black) + img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format.Format_Mono) + bitmap = QBitmap.fromImage(img, Qt.ImageConversionFlag.MonoOnly) + bitmap.fill(Qt.GlobalColor.black) self.make_calnoise() img = self.overlay_marks(self.calnoise.scaledToHeight(self.f_size.height()), False, True) self.calibration_pdf(img) @@ -528,11 +527,11 @@ class Plugin(RevealerPlugin): def toPdf(self, image): printer = QPrinter() - printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter) + printer.setPageSize(QPageSize(QSizeF(210, 297), QPrinter.Unit.Millimeter)) printer.setResolution(600) - printer.setOutputFormat(QPrinter.PdfFormat) + printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat) printer.setOutputFileName(self.get_path_to_revealer_file('.pdf')) - printer.setPageMargins(0,0,0,0,6) + printer.setPageMargins(QMarginsF(0, 0, 0, 0), QPrinter.Unit.DevicePixel) painter = QPainter() painter.begin(printer) @@ -547,28 +546,28 @@ class Plugin(RevealerPlugin): painter.drawImage(553,533, image) wpath = QPainterPath() wpath.addRoundedRect(QRectF(553,533, size_h, size_v), 19, 19) - painter.setPen(QPen(Qt.black, 1)) + painter.setPen(QPen(Qt.GlobalColor.black, 1)) painter.drawPath(wpath) painter.end() def calibration_pdf(self, image): printer = QPrinter() - printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter) + printer.setPageSize(QPageSize(QSizeF(210, 297), QPrinter.Unit.Millimeter)) printer.setResolution(600) - printer.setOutputFormat(QPrinter.PdfFormat) + printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat) printer.setOutputFileName(self.get_path_to_calibration_file()) - printer.setPageMargins(0,0,0,0,6) + printer.setPageMargins(QMarginsF(0, 0, 0, 0), QPrinter.Unit.DevicePixel) painter = QPainter() painter.begin(printer) painter.drawImage(553,533, image) - font = QFont('Source Sans 3', 10, QFont.Bold) + font = QFont('Source Sans 3', 10, QFont.Weight.Bold) painter.setFont(font) painter.drawText(254,277, _("Calibration sheet")) - font = QFont('Source Sans 3', 7, QFont.Bold) + font = QFont('Source Sans 3', 7, QFont.Weight.Bold) painter.setFont(font) painter.drawText(600,2077, _("Instructions:")) - font = QFont("", 7, QFont.Normal) + font = QFont("", 7, QFont.Weight.Normal) painter.setFont(font) painter.drawText(700, 2177, _("1. Place this paper on a flat and well illuminated surface.")) painter.drawText(700, 2277, _("2. Align your Revealer borderlines to the dashed lines on the top and left.")) @@ -578,7 +577,7 @@ class Plugin(RevealerPlugin): painter.end() def pixelcode_2x2(self, img): - result = QImage(img.width()*2, img.height()*2, QImage.Format_ARGB32) + result = QImage(img.width()*2, img.height()*2, QImage.Format.Format_ARGB32) white = qRgba(255,255,255,0) black = qRgba(0,0,0,255) @@ -600,8 +599,8 @@ class Plugin(RevealerPlugin): return result def overlay_marks(self, img, is_cseed=False, calibration_sheet=False): - border_color = Qt.white - base_img = QImage(self.f_size.width(),self.f_size.height(), QImage.Format_ARGB32) + border_color = Qt.GlobalColor.white + base_img = QImage(self.f_size.width(),self.f_size.height(), QImage.Format.Format_ARGB32) base_img.fill(border_color) img = QImage(img) @@ -618,7 +617,7 @@ class Plugin(RevealerPlugin): img) #frame around image - pen = QPen(Qt.black, 2) + pen = QPen(Qt.GlobalColor.black, 2) painter.setPen(pen) #horz @@ -635,8 +634,8 @@ class Plugin(RevealerPlugin): (total_distance_h)+(border_thick/2), base_img.width()-((total_distance_h)*2)-((border_thick)-1), (base_img.height()-((total_distance_h))*2)-((border_thick)-1))) - pen = QPen(Qt.black, border_thick) - pen.setJoinStyle (Qt.MiterJoin) + pen = QPen(Qt.GlobalColor.black, border_thick) + pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin) painter.setPen(pen) painter.drawPath(Rpath) @@ -644,11 +643,11 @@ class Plugin(RevealerPlugin): Bpath = QPainterPath() Bpath.addRect(QRectF((total_distance_h), (total_distance_h), base_img.width()-((total_distance_h)*2), (base_img.height()-((total_distance_h))*2))) - pen = QPen(Qt.black, 1) + pen = QPen(Qt.GlobalColor.black, 1) painter.setPen(pen) painter.drawPath(Bpath) - pen = QPen(Qt.black, 1) + pen = QPen(Qt.GlobalColor.black, 1) painter.setPen(pen) painter.drawLine(0, base_img.height()//2, total_distance_h, base_img.height()//2) painter.drawLine(base_img.width()//2, 0, base_img.width()//2, total_distance_h) @@ -658,29 +657,29 @@ class Plugin(RevealerPlugin): #print code f_size = 37 - font = QFont("DejaVu Sans Mono", f_size-11, QFont.Bold) + font = QFont("DejaVu Sans Mono", f_size-11, QFont.Weight.Bold) font.setPixelSize(35) painter.setFont(font) if not calibration_sheet: if is_cseed: #its a secret - painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine)) + painter.setPen(QPen(Qt.GlobalColor.black, 1, Qt.PenStyle.DashDotDotLine)) painter.drawLine(0, dist_v, base_img.width(), dist_v) painter.drawLine(dist_h, 0, dist_h, base_img.height()) painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v)) painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) painter.drawImage(((total_distance_h))+11, ((total_distance_h))+11, - QImage(icon_path('electrumb.png')).scaledToWidth(round(2.1*total_distance_h), Qt.SmoothTransformation)) + QImage(icon_path('electrumb.png')).scaledToWidth(round(2.1*total_distance_h), Qt.TransformationMode.SmoothTransformation)) - painter.setPen(QPen(Qt.white, border_thick*8)) + painter.setPen(QPen(Qt.GlobalColor.white, border_thick*8)) painter.drawLine(int(base_img.width()-total_distance_h-(border_thick*8)/2-(border_thick/2)-2), int(base_img.height()-total_distance_h-((border_thick*8)/2)-(border_thick/2)-2), int(base_img.width()-total_distance_h-(border_thick*8)/2-(border_thick/2)-2 - 77), int(base_img.height()-total_distance_h-((border_thick*8)/2)-(border_thick/2)-2)) painter.setPen(QColor(0,0,0,255)) painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick - 11, - base_img.height()-total_distance_h - border_thick), Qt.AlignRight, + base_img.height()-total_distance_h - border_thick), Qt.AlignmentFlag.AlignRight, self.versioned_seed.version + '_'+self.versioned_seed.checksum) painter.end() @@ -692,16 +691,16 @@ class Plugin(RevealerPlugin): painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v)) painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) - painter.setPen(QPen(Qt.black, 2)) + painter.setPen(QPen(Qt.GlobalColor.black, 2)) painter.drawLine(0, dist_v, base_img.width(), dist_v) painter.drawLine(dist_h, 0, dist_h, base_img.height()) painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v)) painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) logo = QImage(icon_path('revealer_c.png')).scaledToWidth(round(1.3*(total_distance_h))) - painter.drawImage(int(total_distance_h+border_thick), int(total_distance_h+border_thick), logo, Qt.SmoothTransformation) + painter.drawImage(int(total_distance_h+border_thick), int(total_distance_h+border_thick), logo, Qt.TransformationMode.SmoothTransformation) #frame around logo - painter.setPen(QPen(Qt.black, border_thick)) + painter.setPen(QPen(Qt.GlobalColor.black, border_thick)) painter.drawLine(int(total_distance_h+border_thick), int(total_distance_h+logo.height()+3*(border_thick/2)), int(total_distance_h+logo.width()+border_thick), int(total_distance_h+logo.height()+3*(border_thick/2))) painter.drawLine(int(logo.width()+total_distance_h+3*(border_thick/2)), int(total_distance_h+(border_thick)), @@ -720,7 +719,7 @@ class Plugin(RevealerPlugin): int(base_img.width()//2 + (total_distance_h/2)-border_thick-(border_thick*8)//2-qr_size), int((base_img.height()-((total_distance_h)))-(border_thick/2)-2)) - painter.setPen(QPen(Qt.white, border_thick * 8)) + painter.setPen(QPen(Qt.GlobalColor.white, border_thick * 8)) painter.drawLine( int(base_img.width() - ((total_distance_h)) - (border_thick * 8) / 2 - (border_thick / 2) - 2), int((base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2), @@ -732,9 +731,9 @@ class Plugin(RevealerPlugin): int(base_img.height()-107), int(base_img.width()-total_distance_h - border_thick -93), int(base_img.height()-total_distance_h - border_thick)), - Qt.AlignLeft, self.versioned_seed.get_ui_string_version_plus_seed()) + Qt.AlignmentFlag.AlignLeft, self.versioned_seed.get_ui_string_version_plus_seed()) painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick -3 -qr_size, - base_img.height()-total_distance_h - border_thick), Qt.AlignRight, self.versioned_seed.checksum) + base_img.height()-total_distance_h - border_thick), Qt.AlignmentFlag.AlignRight, self.versioned_seed.checksum) # draw qr code qr_qt = self.paintQR(self.versioned_seed.get_ui_string_version_plus_seed() @@ -743,7 +742,7 @@ class Plugin(RevealerPlugin): base_img.height()-65-qr_size, qr_size, qr_size) painter.drawImage(target, qr_qt) - painter.setPen(QPen(Qt.black, 4)) + painter.setPen(QPen(Qt.GlobalColor.black, 4)) painter.drawLine( int(base_img.width()-65-qr_size), int(base_img.height()-65-qr_size), @@ -761,23 +760,23 @@ class Plugin(RevealerPlugin): else: # calibration only painter.end() cal_img = QImage(self.f_size.width() + 100, self.f_size.height() + 100, - QImage.Format_ARGB32) - cal_img.fill(Qt.white) + QImage.Format.Format_ARGB32) + cal_img.fill(Qt.GlobalColor.white) cal_painter = QPainter() cal_painter.begin(cal_img) cal_painter.drawImage(0,0, base_img) #black lines in the middle of border top left only - cal_painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine)) + cal_painter.setPen(QPen(Qt.GlobalColor.black, 1, Qt.PenStyle.DashDotDotLine)) cal_painter.drawLine(0, dist_v, base_img.width(), dist_v) cal_painter.drawLine(dist_h, 0, dist_h, base_img.height()) - pen = QPen(Qt.black, 2, Qt.DashDotDotLine) + pen = QPen(Qt.GlobalColor.black, 2, Qt.PenStyle.DashDotDotLine) cal_painter.setPen(pen) n=15 - cal_painter.setFont(QFont("DejaVu Sans Mono", 21, QFont.Bold)) + cal_painter.setFont(QFont("DejaVu Sans Mono", 21, QFont.Weight.Bold)) for x in range(-n,n): #lines on bottom (vertical calibration) cal_painter.drawLine(int((((base_img.width())/(n*2)) *(x))+ (base_img.width()//2)-13), @@ -818,8 +817,8 @@ class Plugin(RevealerPlugin): qr.add_data(data) matrix = qr.get_matrix() k = len(matrix) - border_color = Qt.white - base_img = QImage(k * 5, k * 5, QImage.Format_ARGB32) + border_color = Qt.GlobalColor.white + base_img = QImage(k * 5, k * 5, QImage.Format.Format_ARGB32) base_img.fill(border_color) qrpainter = QPainter() qrpainter.begin(base_img) @@ -827,8 +826,8 @@ class Plugin(RevealerPlugin): size = k * boxsize left = (base_img.width() - size)//2 top = (base_img.height() - size)//2 - qrpainter.setBrush(Qt.black) - qrpainter.setPen(Qt.black) + qrpainter.setBrush(Qt.GlobalColor.black) + qrpainter.setPen(Qt.GlobalColor.black) for r in range(k): for c in range(k): @@ -869,7 +868,7 @@ class Plugin(RevealerPlugin): vbox.addSpacing(13) vbox.addLayout(Buttons(CloseButton(d), OkButton(d))) - if not d.exec_(): + if not d.exec(): return self.calibration_h = int(Decimal(horizontal.text())) diff --git a/electrum/plugins/safe_t/qt.py b/electrum/plugins/safe_t/qt.py index 77463d5d4..34860da7d 100644 --- a/electrum/plugins/safe_t/qt.py +++ b/electrum/plugins/safe_t/qt.py @@ -2,9 +2,9 @@ import threading from functools import partial from typing import TYPE_CHECKING -from PyQt5.QtCore import Qt, pyqtSignal, QRegExp -from PyQt5.QtGui import QRegExpValidator -from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton, +from PyQt6.QtCore import Qt, pyqtSignal, QRegularExpression +from PyQt6.QtGui import QRegularExpressionValidator +from PyQt6.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton, QHBoxLayout, QButtonGroup, QGroupBox, QTextEdit, QLineEdit, QRadioButton, QCheckBox, QWidget, QMessageBox, QFileDialog, QSlider, QTabWidget) @@ -66,7 +66,7 @@ class QtHandler(QtHandlerBase): vbox.addWidget(matrix) vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog))) dialog.setLayout(vbox) - dialog.exec_() + dialog.exec() self.response = str(matrix.get_value()) self.done.set() @@ -94,7 +94,7 @@ class QtPlugin(QtPluginBase): return device_id def show_dialog(device_id): if device_id: - SettingsDialog(window, self, keystore, device_id).exec_() + SettingsDialog(window, self, keystore, device_id).exec() keystore.thread.add(connect, on_success=show_dialog) @@ -152,7 +152,7 @@ class SafeTInitLayout(QVBoxLayout): self.addWidget(QLabel(msg)) self.addWidget(self.text_e) self.pin = QLineEdit() - self.pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}'))) + self.pin.setValidator(QRegularExpressionValidator(QRegularExpression('[1-9]{0,9}'))) self.pin.setMaximumWidth(100) hbox_pin = QHBoxLayout() hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):"))) @@ -351,7 +351,7 @@ class SettingsDialog(WindowModalDialog): msg = _("Are you SURE you want to wipe the device?\n" "Your wallet still has bitcoins in it!") if not self.question(msg, title=title, - icon=QMessageBox.Critical): + icon=QMessageBox.Icon.Critical): return invoke_client('wipe_device', unpair_after=True) @@ -453,11 +453,11 @@ class SettingsDialog(WindowModalDialog): # Settings tab - Session Timeout timeout_label = QLabel(_("Session Timeout")) timeout_minutes = QLabel() - timeout_slider = QSlider(Qt.Horizontal) + timeout_slider = QSlider(Qt.Orientation.Horizontal) timeout_slider.setRange(1, 60) timeout_slider.setSingleStep(1) timeout_slider.setTickInterval(5) - timeout_slider.setTickPosition(QSlider.TicksBelow) + timeout_slider.setTickPosition(QSlider.TickPosition.TicksBelow) timeout_slider.setTracking(True) timeout_msg = QLabel( _("Clear the session after the specified period " diff --git a/electrum/plugins/trezor/qt.py b/electrum/plugins/trezor/qt.py index 80adffd0b..9dba1f5c5 100644 --- a/electrum/plugins/trezor/qt.py +++ b/electrum/plugins/trezor/qt.py @@ -2,8 +2,8 @@ from functools import partial import threading from typing import TYPE_CHECKING -from PyQt5.QtCore import Qt, QEventLoop, pyqtSignal -from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton, +from PyQt6.QtCore import Qt, QEventLoop, pyqtSignal +from PyQt6.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton, QHBoxLayout, QButtonGroup, QGroupBox, QDialog, QLineEdit, QRadioButton, QCheckBox, QWidget, QMessageBox, QSlider, QTabWidget) @@ -74,9 +74,9 @@ class MatrixDialog(WindowModalDialog): vbox.addLayout(grid) self.backspace_button = QPushButton("<=") - self.backspace_button.clicked.connect(partial(self.process_key, Qt.Key_Backspace)) + self.backspace_button.clicked.connect(partial(self.process_key, Qt.Key.Key_Backspace)) self.cancel_button = QPushButton(_("Cancel")) - self.cancel_button.clicked.connect(partial(self.process_key, Qt.Key_Escape)) + self.cancel_button.clicked.connect(partial(self.process_key, Qt.Key.Key_Escape)) buttons = Buttons(self.backspace_button, self.cancel_button) vbox.addSpacing(40) vbox.addLayout(buttons) @@ -92,9 +92,9 @@ class MatrixDialog(WindowModalDialog): def process_key(self, key): self.data = None - if key == Qt.Key_Backspace: + if key == Qt.Key.Key_Backspace: self.data = '\010' - elif key == Qt.Key_Escape: + elif key == Qt.Key.Key_Escape: self.data = 'x' elif self.is_valid(key): self.char_buttons[key - ord('1')].setFocus() @@ -110,7 +110,7 @@ class MatrixDialog(WindowModalDialog): def get_matrix(self, num): self.num = num self.refresh() - self.loop.exec_() + self.loop.exec() class QtHandler(QtHandlerBase): @@ -161,7 +161,7 @@ class QtHandler(QtHandlerBase): vbox.addWidget(matrix) vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog))) dialog.setLayout(vbox) - dialog.exec_() + dialog.exec() self.response = str(matrix.get_value()) self.done.set() @@ -232,7 +232,7 @@ class QtHandler(QtHandlerBase): OnDevice_button.clicked.connect(on_device_clicked) OnDevice_button.clicked.connect(d.accept) - d.exec_() + d.exec() self.done.set() @@ -259,7 +259,7 @@ class QtPlugin(QtPluginBase): return device_id def show_dialog(device_id): if device_id: - SettingsDialog(window, self, keystore, device_id).exec_() + SettingsDialog(window, self, keystore, device_id).exec() keystore.thread.add(connect, on_success=show_dialog) @@ -619,7 +619,7 @@ class SettingsDialog(WindowModalDialog): msg = _("Are you SURE you want to wipe the device?\n" "Your wallet still has bitcoins in it!") if not self.question(msg, title=title, - icon=QMessageBox.Critical): + icon=QMessageBox.Icon.Critical): return invoke_client('wipe_device', unpair_after=True) @@ -721,11 +721,11 @@ class SettingsDialog(WindowModalDialog): # Settings tab - Session Timeout timeout_label = QLabel(_("Session Timeout")) timeout_minutes = QLabel() - timeout_slider = QSlider(Qt.Horizontal) + timeout_slider = QSlider(Qt.Orientation.Horizontal) timeout_slider.setRange(1, 60) timeout_slider.setSingleStep(1) timeout_slider.setTickInterval(5) - timeout_slider.setTickPosition(QSlider.TicksBelow) + timeout_slider.setTickPosition(QSlider.TickPosition.TicksBelow) timeout_slider.setTracking(True) timeout_msg = QLabel( _("Clear the session after the specified period " diff --git a/electrum/plugins/trustedcoin/qt.py b/electrum/plugins/trustedcoin/qt.py index 7a223b241..154fa6b38 100644 --- a/electrum/plugins/trustedcoin/qt.py +++ b/electrum/plugins/trustedcoin/qt.py @@ -28,9 +28,9 @@ import threading import os from typing import TYPE_CHECKING -from PyQt5.QtGui import QPixmap, QMovie, QColor -from PyQt5.QtCore import QObject, pyqtSignal, QSize, Qt -from PyQt5.QtWidgets import (QTextEdit, QVBoxLayout, QLabel, QGridLayout, QHBoxLayout, +from PyQt6.QtGui import QPixmap, QMovie, QColor +from PyQt6.QtCore import QObject, pyqtSignal, QSize, Qt +from PyQt6.QtWidgets import (QTextEdit, QVBoxLayout, QLabel, QGridLayout, QHBoxLayout, QRadioButton, QCheckBox, QLineEdit, QPushButton, QWidget) from electrum.i18n import _ @@ -128,7 +128,7 @@ class Plugin(TrustedCoinPlugin): label.setWordWrap(1) vbox.addWidget(label) vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) - if not d.exec_(): + if not d.exec(): return return pw.get_amount() @@ -221,7 +221,7 @@ class Plugin(TrustedCoinPlugin): n = wallet.billing_info.get('tx_remaining', 0) grid.addWidget(QLabel(_("Your wallet has {} prepaid transactions.").format(n)), i, 0) vbox.addLayout(Buttons(CloseButton(d))) - d.exec_() + d.exec() @hook def init_wallet_wizard(self, wizard: 'QENewWalletWizard'): @@ -436,7 +436,7 @@ class WCShowConfirmOTP(WizardComponent): self.spinner_l.setMovie(self.spinner) self.otp_status_l = QLabel() - self.otp_status_l.setAlignment(Qt.AlignHCenter) + self.otp_status_l.setAlignment(Qt.AlignmentFlag.AlignHCenter) self.otp_status_l.setVisible(False) self.resetlabel = WWLabel(_('If you have lost your OTP secret, click the button below to request a new secret from the server.')) @@ -593,7 +593,7 @@ class WCContinueOnline(WizardComponent): self.cb_online.stateChanged.connect(self.on_updated) # self.cb_online.setToolTip(_("Check this box to request a new secret. You will need to retype your seed.")) self.layout().addWidget(self.cb_online) - self.layout().setAlignment(self.cb_online, Qt.AlignHCenter) + self.layout().setAlignment(self.cb_online, Qt.AlignmentFlag.AlignHCenter) self.layout().addStretch(1) self._valid = True