Browse Source

qt desktop gui: upgrade qt5->qt6

closes https://github.com/spesmilo/electrum/issues/8007
master
SomberNight 1 year ago
parent
commit
cfe8502f96
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 1
      contrib/build-linux/appimage/Dockerfile
  2. 28
      contrib/build-linux/appimage/make_appimage.sh
  3. 2
      contrib/build-wine/build-electrum-git.sh
  4. 22
      contrib/build-wine/deterministic.spec
  5. 70
      contrib/deterministic-build/requirements-binaries-mac.txt
  6. 70
      contrib/deterministic-build/requirements-binaries.txt
  7. 20
      contrib/osx/README_macos.md
  8. 4
      contrib/osx/make_osx.sh
  9. 26
      contrib/osx/osx.spec
  10. 2
      contrib/requirements/requirements-binaries-mac.txt
  11. 2
      contrib/requirements/requirements-binaries.txt
  12. 2
      electrum-env
  13. 5
      electrum/_vendor/pyperclip/README.md
  14. 20
      electrum/_vendor/pyperclip/__init__.py
  15. 2
      electrum/gui/common_qt/__init__.py
  16. 2
      electrum/gui/default_lang.py
  17. 50
      electrum/gui/qt/__init__.py
  18. 2
      electrum/gui/qt/address_dialog.py
  19. 20
      electrum/gui/qt/address_list.py
  20. 12
      electrum/gui/qt/amountedit.py
  21. 58
      electrum/gui/qt/balance_dialog.py
  22. 6
      electrum/gui/qt/bip39_recovery_dialog.py
  23. 12
      electrum/gui/qt/channel_details.py
  24. 24
      electrum/gui/qt/channels_list.py
  25. 27
      electrum/gui/qt/completion_text_edit.py
  26. 26
      electrum/gui/qt/confirm_tx_dialog.py
  27. 47
      electrum/gui/qt/console.py
  28. 14
      electrum/gui/qt/contact_list.py
  29. 2
      electrum/gui/qt/custom_model.py
  30. 8
      electrum/gui/qt/exception_window.py
  31. 8
      electrum/gui/qt/fee_slider.py
  32. 68
      electrum/gui/qt/history_list.py
  33. 28
      electrum/gui/qt/invoice_list.py
  34. 2
      electrum/gui/qt/lightning_dialog.py
  35. 4
      electrum/gui/qt/lightning_tx_dialog.py
  36. 10
      electrum/gui/qt/locktimeedit.py
  37. 88
      electrum/gui/qt/main_window.py
  38. 41
      electrum/gui/qt/my_treeview.py
  39. 22
      electrum/gui/qt/network_dialog.py
  40. 4
      electrum/gui/qt/new_channel_dialog.py
  41. 20
      electrum/gui/qt/password_dialog.py
  42. 12
      electrum/gui/qt/paytoedit.py
  43. 2
      electrum/gui/qt/plugins_dialog.py
  44. 10
      electrum/gui/qt/qrcodewidget.py
  45. 18
      electrum/gui/qt/qrreader/__init__.py
  46. 10
      electrum/gui/qt/qrreader/qtmultimedia/__init__.py
  47. 200
      electrum/gui/qt/qrreader/qtmultimedia/camera_dialog.py
  48. 10
      electrum/gui/qt/qrreader/qtmultimedia/crop_blur_effect.py
  49. 8
      electrum/gui/qt/qrreader/qtmultimedia/validator.py
  50. 20
      electrum/gui/qt/qrreader/qtmultimedia/video_overlay.py
  51. 42
      electrum/gui/qt/qrreader/qtmultimedia/video_surface.py
  52. 6
      electrum/gui/qt/qrreader/qtmultimedia/video_widget.py
  53. 6
      electrum/gui/qt/qrtextedit.py
  54. 6
      electrum/gui/qt/qrwindow.py
  55. 2
      electrum/gui/qt/rate_limiter.py
  56. 10
      electrum/gui/qt/rbf_dialog.py
  57. 6
      electrum/gui/qt/rebalance_dialog.py
  58. 26
      electrum/gui/qt/receive_tab.py
  59. 23
      electrum/gui/qt/request_list.py
  60. 10
      electrum/gui/qt/seed_dialog.py
  61. 11
      electrum/gui/qt/send_tab.py
  62. 16
      electrum/gui/qt/settings_dialog.py
  63. 2
      electrum/gui/qt/stylesheet_patcher.py
  64. 6
      electrum/gui/qt/swap_dialog.py
  65. 50
      electrum/gui/qt/transaction_dialog.py
  66. 6
      electrum/gui/qt/update_checker.py
  67. 116
      electrum/gui/qt/util.py
  68. 12
      electrum/gui/qt/utxo_dialog.py
  69. 16
      electrum/gui/qt/utxo_list.py
  70. 8
      electrum/gui/qt/wallet_info_dialog.py
  71. 6
      electrum/gui/qt/watchtower_dialog.py
  72. 10
      electrum/gui/qt/wizard/server_connect.py
  73. 48
      electrum/gui/qt/wizard/wallet.py
  74. 22
      electrum/gui/qt/wizard/wizard.py
  75. 4
      electrum/plugins/audio_modem/qt.py
  76. 8
      electrum/plugins/bitbox02/qt.py
  77. 16
      electrum/plugins/coldcard/qt.py
  78. 4
      electrum/plugins/cosigner_pool/qt.py
  79. 2
      electrum/plugins/digitalbitbox/qt.py
  80. 12
      electrum/plugins/hw_wallet/qt.py
  81. 2
      electrum/plugins/jade/qt.py
  82. 26
      electrum/plugins/keepkey/qt.py
  83. 6
      electrum/plugins/labels/qt.py
  84. 2
      electrum/plugins/ledger/auth2fa.py
  85. 6
      electrum/plugins/ledger/qt.py
  86. 149
      electrum/plugins/revealer/qt.py
  87. 18
      electrum/plugins/safe_t/qt.py
  88. 26
      electrum/plugins/trezor/qt.py
  89. 14
      electrum/plugins/trustedcoin/qt.py

1
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 \

28
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

2
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 \

22
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:

70
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

70
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

20
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
```

4
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"

26
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(

2
contrib/requirements/requirements-binaries-mac.txt

@ -1,2 +1,2 @@
PyQt5>=5.15.2
PyQt6
cryptography>=2.6

2
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]

2
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.:

5
electrum/_vendor/pyperclip/README.md vendored

@ -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

20
electrum/_vendor/pyperclip/__init__.py vendored

@ -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:

2
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=}")

2
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":

50
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

2
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 _

20
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):

12
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:

58
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()

6
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

12
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):

24
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):

27
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()

26
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)

47
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()

14
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)

2
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:

8
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))

8
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

68
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))

28
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:

2
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 _

4
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

10
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:

88
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:

41
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

22
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()

4
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,

20
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:

12
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:

2
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

10
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()

18
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:

10
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 <derago@gmail.com>
# 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}

200
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 <derago@gmail.com>
# 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

10
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

8
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

20
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)

42
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 <derago@gmail.com>
# 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)

6
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):

6
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())

6
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)

2
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

10
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)

6
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)

26
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; }")

23
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)

10
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)

11
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)

16
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)

2
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 = '''

6
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()

50
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)

6
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)

116
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()

12
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

16
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:

8
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)

6
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

10
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):

48
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)

22
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:

4
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'):

8
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()

16
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):
<span style="font-size: x-large">Coldcard Wallet</span>
<br><span style="font-size: medium">from Coinkite Inc.</span>
<br><a href="https://coldcardwallet.com">coldcardwallet.com</a>''')
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('<tt>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)

4
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

2
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

12
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:

2
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

26
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 "

6
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)

2
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

6
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()

149
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("<b>" + _("Warning ") + "</b>: " + _("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()))

18
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 "

26
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 "

14
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

Loading…
Cancel
Save