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-util0 \
#libxcb-util1 \ #libxcb-util1 \
libxcb-render-util0 \ libxcb-render-util0 \
libxcb-cursor0 \
libx11-xcb1 \ libx11-xcb1 \
libc6-dev \ libc6-dev \
libc6 \ libc6 \

28
contrib/build-linux/appimage/make_appimage.sh

@ -150,7 +150,7 @@ info "Installing build dependencies."
# note: re pip installing from PyPI, # note: re pip installing from PyPI,
# we prefer compiling C extensions ourselves, instead of using binary wheels, # we prefer compiling C extensions ourselves, instead of using binary wheels,
# hence "--no-binary :all:" flags. However, we specifically allow # 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 # - 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", # - 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 # 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." info "installing electrum and its dependencies."
"$python" -m pip install --no-build-isolation --no-dependencies --no-binary :all: --no-warn-script-location \ "$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" --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" --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 \ "$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" --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/{opt,pip,setuptools,wheel}
rm -rf "$PYDIR"/site-packages/Cryptodome/SelfTest rm -rf "$PYDIR"/site-packages/Cryptodome/SelfTest
rm -rf "$PYDIR"/site-packages/{psutil,qrcode,websocket}/tests 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 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/PyQt6/Qt6/translations/qt${component}_*
rm -rf "$PYDIR"/site-packages/PyQt5/Qt5/resources/qt${component}_* rm -rf "$PYDIR"/site-packages/PyQt6/Qt6/resources/qt${component}_*
done done
rm -rf "$PYDIR"/site-packages/PyQt5/Qt5/{qml,libexec} rm -rf "$PYDIR"/site-packages/PyQt6/Qt6/{qml,libexec}
rm -rf "$PYDIR"/site-packages/PyQt5/{pyrcc*.so,pylupdate*.so,uic} rm -rf "$PYDIR"/site-packages/PyQt6/{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} 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 ; do 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/PyQt5/Qt5/lib/libQt5${component}* rm -rf "$PYDIR"/site-packages/PyQt6/Qt6/lib/libQt6${component}*
rm -rf "$PYDIR"/site-packages/PyQt5/Qt${component}* rm -rf "$PYDIR"/site-packages/PyQt6/Qt${component}*
rm -rf "$PYDIR"/site-packages/PyQt6/bindings/Qt${component}*
done 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 # these are deleted as they were not deterministic; and are not needed anyway
find "$APPDIR" -path '*/__pycache__*' -delete 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..." info "Installing dependencies specific to binaries..."
# TODO tighten "--no-binary :all:" (but we don't have a C compiler...) # 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 \ $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 --cache-dir "$WINE_PIP_CACHE_DIR" -r "$CONTRIB"/deterministic-build/requirements-binaries.txt
info "Installing hardware wallet requirements..." info "Installing hardware wallet requirements..."
$WINE_PYTHON -m pip install --no-build-isolation --no-dependencies --no-warn-script-location \ $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 = [] binaries = []
# Workaround for "Retro Look": # 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: # add libsecp256k1, libusb, etc:
binaries += [(f"{PROJECT_ROOT}/{PYPKG}/*.dll", '.')] binaries += [(f"{PROJECT_ROOT}/{PYPKG}/*.dll", '.')]
@ -69,8 +69,19 @@ for d in a.datas:
break break
# Strip out parts of Qt that we never use. Reduces binary size by tens of MBs. see #4815 # 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', qt_bins2remove=(
'qt5location', 'qt5test', 'qt5xml', r'pyqt5\qt\qml\qtquick') 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) print("Removing Qt binaries:", *qt_bins2remove)
for x in a.binaries.copy(): for x in a.binaries.copy():
for r in qt_bins2remove: for r in qt_bins2remove:
@ -78,7 +89,10 @@ for x in a.binaries.copy():
a.binaries.remove(x) a.binaries.remove(x)
print('----> Removed x =', 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) print("Removing Qt datas:", *qt_data2remove)
for x in a.datas.copy(): for x in a.datas.copy():
for r in qt_data2remove: for r in qt_data2remove:

70
contrib/deterministic-build/requirements-binaries-mac.txt

@ -90,40 +90,42 @@ pip==22.3.1 \
pycparser==2.21 \ pycparser==2.21 \
--hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
--hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206
PyQt5==5.15.10 \ PyQt6==6.7.1 \
--hash=sha256:501355f327e9a2c38db0428e1a236d25ebcb99304cd6e668c05d1188d514adec \ --hash=sha256:0adb7914c732ad1dee46d9cec838a98cb2b11bc38cc3b7b36fbd8701ae64bf47 \
--hash=sha256:862cea3be95b4b0a2b9678003b3a18edf7bd5eafd673860f58820f246d4bf616 \ --hash=sha256:2d771fa0981514cb1ee937633dfa64f14caa902707d9afffab66677f3a73e3da \
--hash=sha256:93288d62ebd47b1933d80c27f5d43c7c435307b84d480af689cef2474e87e4c8 \ --hash=sha256:3672a82ccd3a62e99ab200a13903421e2928e399fda25ced98d140313ad59cb9 \
--hash=sha256:b89478d16d4118664ff58ed609e0a804d002703c9420118de7e4e70fa1cb5486 \ --hash=sha256:7f397f4b38b23b5588eb2c0933510deb953d96b1f0323a916c4839c2a66ccccc \
--hash=sha256:d46b7804b1b10a4ff91753f8113e5b5580d2b4462f3226288e2d84497334898a \ --hash=sha256:c2f202b7941aa74e5c7e1463a6f27d9131dbc1e6cabe85571d7364f5b3de7397 \
--hash=sha256:ff99b4f91aa8eb60510d5889faad07116d3340041916e46c07d519f7cad344e1 --hash=sha256:f053378e3aef6248fa612c8afddda17f942fb63f9fe8a9aeb2a6b6b4cbb0eba9 \
PyQt5-Qt5==5.15.2 \ --hash=sha256:fa3954698233fe286a8afc477b84d8517f0788eb46b74da69d3ccc0170d3714c
--hash=sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a \ PyQt6-Qt6==6.7.2 \
--hash=sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962 \ --hash=sha256:05f2c7d195d316d9e678a92ecac0252a24ed175bd2444cc6077441807d756580 \
--hash=sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154 \ --hash=sha256:065415589219a2f364aba29d6a98920bb32810286301acbfa157e522d30369e3 \
--hash=sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327 --hash=sha256:7f817efa86a0e8eda9152c85b73405463fbf3266299090f32bbb2266da540ead \
PyQt5-sip==12.13.0 \ --hash=sha256:b2d7e5ddb1b9764cd60f1d730fa7bf7a1f0f61b2630967c81761d3d0a5a8a2e0 \
--hash=sha256:0f85fb633a522f04e48008de49dce1ff1d947011b48885b8428838973fbca412 \ --hash=sha256:fc93945eaef4536d68bd53566535efcbe78a7c05c2a533790a8fd022bac8bfaa
--hash=sha256:108a15f603e1886988c4b0d9d41cb74c9f9815bf05cefc843d559e8c298a10ce \ PyQt6-sip==13.8.0 \
--hash=sha256:1c8371682f77852256f1f2d38c41e2e684029f43330f0635870895ab01c02f6c \ --hash=sha256:056af69d1d8d28d5968066ec5da908afd82fc0be07b67cf2b84b9f02228416ce \
--hash=sha256:205cd449d08a2b024a468fb6100cd7ed03e946b4f49706f508944006f955ae1a \ --hash=sha256:08dd81037a2864982ece2bf9891f3bf4558e247034e112993ea1a3fe239458cb \
--hash=sha256:29fa9cc964517c9fc3f94f072b9a2aeef4e7a2eda1879cb835d9e06971161cdf \ --hash=sha256:2559afa68825d08de09d71c42f3b6ad839dcc30f91e7c6d0785e07830d5541a5 \
--hash=sha256:3188a06956aef86f604fb0d14421a110fad70d2a9e943dbacbfc3303f651dade \ --hash=sha256:2f74cf3d6d9cab5152bd9f49d570b2dfb87553ebb5c4919abfde27f5b9fd69d4 \
--hash=sha256:3a4498f3b1b15f43f5d12963accdce0fd652b0bcaae6baf8008663365827444c \ --hash=sha256:33d9b399fc9c9dc99496266842b0fb2735d924604774e97cf9b555667cc0fc59 \
--hash=sha256:5338773bbaedaa4f16a73c142fb23cc18c327be6c338813af70260b756c7bc92 \ --hash=sha256:6bce6bc5870d9e87efe5338b1ee4a7b9d7d26cdd16a79a5757d80b6f25e71edc \
--hash=sha256:6e4ac714252370ca037c7d609da92388057165edd4f94e63354f6d65c3ed9d53 \ --hash=sha256:755beb5d271d081e56618fb30342cdd901464f721450495cb7cb0212764da89e \
--hash=sha256:773731b1b5ab1a7cf5621249f2379c95e3d2905e9bd96ff3611b119586daa876 \ --hash=sha256:7a0bbc0918eab5b6351735d40cf22cbfa5aa2476b55e0d5fe881aeed7d871c29 \
--hash=sha256:7f321daf84b9c9dbca61b80e1ef37bdaffc0e93312edae2cd7da25b953971d91 \ --hash=sha256:7f84c472afdc7d316ff683f63129350d645ef82d9b3fd75a609b08472d1f7291 \
--hash=sha256:7fe3375b508c5bc657d73b9896bba8a768791f1f426c68053311b046bcebdddf \ --hash=sha256:835ed22eab977f75fd77e60d4ff308a1fa794b1d0c04849311f36d2a080cdf3b \
--hash=sha256:96414c93f3d33963887cf562d50d88b955121fbfd73f937c8eca46643e77bf61 \ --hash=sha256:9ea9223c94906efd68148f12ae45b51a21d67e86704225ddc92bce9c54e4d93c \
--hash=sha256:9a8cdd6cb66adcbe5c941723ed1544eba05cf19b6c961851b58ccdae1c894afb \ --hash=sha256:a5c086b7c9c7996ea9b7522646cc24eebbf3591ec9dd38f65c0a3fdb0dbeaac7 \
--hash=sha256:9b984c2620a7a7eaf049221b09ae50a345317add2624c706c7d2e9e6632a9587 \ --hash=sha256:b1bf29e95f10a8a00819dac804ca7e5eba5fc1769adcd74c837c11477bf81954 \
--hash=sha256:a7e3623b2c743753625c4650ec7696362a37fb36433b61824cf257f6d3d43cca \ --hash=sha256:b203b6fbae4a8f2d27f35b7df46200057033d9ecd9134bcf30e3eab66d43572c \
--hash=sha256:bbc7cd498bf19e0862097be1ad2243e824dea56726f00c11cff1b547c2d31d01 \ --hash=sha256:beaddc1ec96b342f4e239702f91802706a80cb403166c2da318cec4ad8b790cb \
--hash=sha256:d5032da3fff62da055104926ffe76fd6044c1221f8ad35bb60804bcb422fe866 \ --hash=sha256:cd81144b0770084e8005d3a121c9382e6f9bc8d0bb320dd618718ffe5090e0e6 \
--hash=sha256:db228cd737f5cbfc66a3c3e50042140cb80b30b52edc5756dbbaa2346ec73137 \ --hash=sha256:cedd554c643e54c4c2e12b5874781a87441a1b405acf3650a4a2e1df42aae231 \
--hash=sha256:ec60162e034c42fb99859206d62b83b74f987d58937b3a82bdc07b5c3d190dec \ --hash=sha256:d8b22a6850917c68ce83fc152a8b606ecb2efaaeed35be53110468885d6cdd9d \
--hash=sha256:fb4a5271fa3f6bc2feb303269a837a95a6d8dd16be553aa40e530de7fb81bfdf --hash=sha256:dd168667addf01f8a4b0fa7755323e43e4cd12ca4bade558c61f713a5d48ba1a \
--hash=sha256:f57275b5af774529f9838adcfb58869ba3ebdaf805daea113bb0697a96a3f3cb \
--hash=sha256:fbb249b82c53180f1420571ece5dc24fea1188ba435923edd055599dffe7abfb
setuptools==65.5.1 \ setuptools==65.5.1 \
--hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \ --hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \
--hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f --hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f

70
contrib/deterministic-build/requirements-binaries.txt

@ -90,40 +90,42 @@ pip==22.3.1 \
pycparser==2.21 \ pycparser==2.21 \
--hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
--hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206
PyQt5==5.15.10 \ PyQt6==6.7.1 \
--hash=sha256:501355f327e9a2c38db0428e1a236d25ebcb99304cd6e668c05d1188d514adec \ --hash=sha256:0adb7914c732ad1dee46d9cec838a98cb2b11bc38cc3b7b36fbd8701ae64bf47 \
--hash=sha256:862cea3be95b4b0a2b9678003b3a18edf7bd5eafd673860f58820f246d4bf616 \ --hash=sha256:2d771fa0981514cb1ee937633dfa64f14caa902707d9afffab66677f3a73e3da \
--hash=sha256:93288d62ebd47b1933d80c27f5d43c7c435307b84d480af689cef2474e87e4c8 \ --hash=sha256:3672a82ccd3a62e99ab200a13903421e2928e399fda25ced98d140313ad59cb9 \
--hash=sha256:b89478d16d4118664ff58ed609e0a804d002703c9420118de7e4e70fa1cb5486 \ --hash=sha256:7f397f4b38b23b5588eb2c0933510deb953d96b1f0323a916c4839c2a66ccccc \
--hash=sha256:d46b7804b1b10a4ff91753f8113e5b5580d2b4462f3226288e2d84497334898a \ --hash=sha256:c2f202b7941aa74e5c7e1463a6f27d9131dbc1e6cabe85571d7364f5b3de7397 \
--hash=sha256:ff99b4f91aa8eb60510d5889faad07116d3340041916e46c07d519f7cad344e1 --hash=sha256:f053378e3aef6248fa612c8afddda17f942fb63f9fe8a9aeb2a6b6b4cbb0eba9 \
PyQt5-Qt5==5.15.2 \ --hash=sha256:fa3954698233fe286a8afc477b84d8517f0788eb46b74da69d3ccc0170d3714c
--hash=sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a \ PyQt6-Qt6==6.7.2 \
--hash=sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962 \ --hash=sha256:05f2c7d195d316d9e678a92ecac0252a24ed175bd2444cc6077441807d756580 \
--hash=sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154 \ --hash=sha256:065415589219a2f364aba29d6a98920bb32810286301acbfa157e522d30369e3 \
--hash=sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327 --hash=sha256:7f817efa86a0e8eda9152c85b73405463fbf3266299090f32bbb2266da540ead \
PyQt5-sip==12.13.0 \ --hash=sha256:b2d7e5ddb1b9764cd60f1d730fa7bf7a1f0f61b2630967c81761d3d0a5a8a2e0 \
--hash=sha256:0f85fb633a522f04e48008de49dce1ff1d947011b48885b8428838973fbca412 \ --hash=sha256:fc93945eaef4536d68bd53566535efcbe78a7c05c2a533790a8fd022bac8bfaa
--hash=sha256:108a15f603e1886988c4b0d9d41cb74c9f9815bf05cefc843d559e8c298a10ce \ PyQt6-sip==13.8.0 \
--hash=sha256:1c8371682f77852256f1f2d38c41e2e684029f43330f0635870895ab01c02f6c \ --hash=sha256:056af69d1d8d28d5968066ec5da908afd82fc0be07b67cf2b84b9f02228416ce \
--hash=sha256:205cd449d08a2b024a468fb6100cd7ed03e946b4f49706f508944006f955ae1a \ --hash=sha256:08dd81037a2864982ece2bf9891f3bf4558e247034e112993ea1a3fe239458cb \
--hash=sha256:29fa9cc964517c9fc3f94f072b9a2aeef4e7a2eda1879cb835d9e06971161cdf \ --hash=sha256:2559afa68825d08de09d71c42f3b6ad839dcc30f91e7c6d0785e07830d5541a5 \
--hash=sha256:3188a06956aef86f604fb0d14421a110fad70d2a9e943dbacbfc3303f651dade \ --hash=sha256:2f74cf3d6d9cab5152bd9f49d570b2dfb87553ebb5c4919abfde27f5b9fd69d4 \
--hash=sha256:3a4498f3b1b15f43f5d12963accdce0fd652b0bcaae6baf8008663365827444c \ --hash=sha256:33d9b399fc9c9dc99496266842b0fb2735d924604774e97cf9b555667cc0fc59 \
--hash=sha256:5338773bbaedaa4f16a73c142fb23cc18c327be6c338813af70260b756c7bc92 \ --hash=sha256:6bce6bc5870d9e87efe5338b1ee4a7b9d7d26cdd16a79a5757d80b6f25e71edc \
--hash=sha256:6e4ac714252370ca037c7d609da92388057165edd4f94e63354f6d65c3ed9d53 \ --hash=sha256:755beb5d271d081e56618fb30342cdd901464f721450495cb7cb0212764da89e \
--hash=sha256:773731b1b5ab1a7cf5621249f2379c95e3d2905e9bd96ff3611b119586daa876 \ --hash=sha256:7a0bbc0918eab5b6351735d40cf22cbfa5aa2476b55e0d5fe881aeed7d871c29 \
--hash=sha256:7f321daf84b9c9dbca61b80e1ef37bdaffc0e93312edae2cd7da25b953971d91 \ --hash=sha256:7f84c472afdc7d316ff683f63129350d645ef82d9b3fd75a609b08472d1f7291 \
--hash=sha256:7fe3375b508c5bc657d73b9896bba8a768791f1f426c68053311b046bcebdddf \ --hash=sha256:835ed22eab977f75fd77e60d4ff308a1fa794b1d0c04849311f36d2a080cdf3b \
--hash=sha256:96414c93f3d33963887cf562d50d88b955121fbfd73f937c8eca46643e77bf61 \ --hash=sha256:9ea9223c94906efd68148f12ae45b51a21d67e86704225ddc92bce9c54e4d93c \
--hash=sha256:9a8cdd6cb66adcbe5c941723ed1544eba05cf19b6c961851b58ccdae1c894afb \ --hash=sha256:a5c086b7c9c7996ea9b7522646cc24eebbf3591ec9dd38f65c0a3fdb0dbeaac7 \
--hash=sha256:9b984c2620a7a7eaf049221b09ae50a345317add2624c706c7d2e9e6632a9587 \ --hash=sha256:b1bf29e95f10a8a00819dac804ca7e5eba5fc1769adcd74c837c11477bf81954 \
--hash=sha256:a7e3623b2c743753625c4650ec7696362a37fb36433b61824cf257f6d3d43cca \ --hash=sha256:b203b6fbae4a8f2d27f35b7df46200057033d9ecd9134bcf30e3eab66d43572c \
--hash=sha256:bbc7cd498bf19e0862097be1ad2243e824dea56726f00c11cff1b547c2d31d01 \ --hash=sha256:beaddc1ec96b342f4e239702f91802706a80cb403166c2da318cec4ad8b790cb \
--hash=sha256:d5032da3fff62da055104926ffe76fd6044c1221f8ad35bb60804bcb422fe866 \ --hash=sha256:cd81144b0770084e8005d3a121c9382e6f9bc8d0bb320dd618718ffe5090e0e6 \
--hash=sha256:db228cd737f5cbfc66a3c3e50042140cb80b30b52edc5756dbbaa2346ec73137 \ --hash=sha256:cedd554c643e54c4c2e12b5874781a87441a1b405acf3650a4a2e1df42aae231 \
--hash=sha256:ec60162e034c42fb99859206d62b83b74f987d58937b3a82bdc07b5c3d190dec \ --hash=sha256:d8b22a6850917c68ce83fc152a8b606ecb2efaaeed35be53110468885d6cdd9d \
--hash=sha256:fb4a5271fa3f6bc2feb303269a837a95a6d8dd16be553aa40e530de7fb81bfdf --hash=sha256:dd168667addf01f8a4b0fa7755323e43e4cd12ca4bade558c61f713a5d48ba1a \
--hash=sha256:f57275b5af774529f9838adcfb58869ba3ebdaf805daea113bb0697a96a3f3cb \
--hash=sha256:fbb249b82c53180f1420571ece5dc24fea1188ba435923edd055599dffe7abfb
setuptools==65.5.1 \ setuptools==65.5.1 \
--hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \ --hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \
--hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f --hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f

20
contrib/osx/README_macos.md

@ -17,7 +17,7 @@ $ git submodule update --init
Run install (this should install most dependencies): 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 ### 2. Install libsecp256k1
@ -26,23 +26,7 @@ $ brew install autoconf automake libtool coreutils
$ contrib/make_libsecp256k1.sh $ contrib/make_libsecp256k1.sh
``` ```
### 3. Install PyQt5 ### 3. Run electrum:
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:
``` ```
$ ./run_electrum $ ./run_electrum
``` ```

4
contrib/osx/make_osx.sh

@ -71,7 +71,7 @@ info "Installing build dependencies"
# note: re pip installing from PyPI, # note: re pip installing from PyPI,
# we prefer compiling C extensions ourselves, instead of using binary wheels, # we prefer compiling C extensions ourselves, instead of using binary wheels,
# hence "--no-binary :all:" flags. However, we specifically allow # 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 # - 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", # - 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 # 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" || fail "Could not install hardware wallet requirements"
info "Installing dependencies specific to binaries..." 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 \ --no-warn-script-location \
-Ir ./contrib/deterministic-build/requirements-binaries-mac.txt \ -Ir ./contrib/deterministic-build/requirements-binaries-mac.txt \
|| fail "Could not install dependencies specific to binaries" || fail "Could not install dependencies specific to binaries"

26
contrib/osx/osx.spec

@ -25,7 +25,7 @@ hiddenimports += collect_submodules(f"{PYPKG}.plugins")
binaries = [] binaries = []
# Workaround for "Retro Look": # 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: # add libsecp256k1, libusb, etc:
binaries += [(f"{PROJECT_ROOT}/{PYPKG}/*.dylib", ".")] binaries += [(f"{PROJECT_ROOT}/{PYPKG}/*.dylib", ".")]
@ -72,7 +72,19 @@ for d in a.datas:
break break
# Strip out parts of Qt that we never use. Reduces binary size by tens of MBs. see #4815 # 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) print("Removing Qt binaries:", *qt_bins2remove)
for x in a.binaries.copy(): for x in a.binaries.copy():
for r in qt_bins2remove: for r in qt_bins2remove:
@ -80,6 +92,16 @@ for x in a.binaries.copy():
a.binaries.remove(x) a.binaries.remove(x)
print('----> Removed x =', 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) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE( exe = EXE(

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

@ -1,2 +1,2 @@
PyQt5>=5.15.2 PyQt6
cryptography>=2.6 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, # we need at least cryptography>=2.1 for electrum.crypto,
# and at least cryptography>=2.6 for dnspython[DNSSEC] # 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 # If 'env' already exists, it is activated and Electrum is started
# without any installations. Additionally, the PYTHONPATH environment # without any installations. Additionally, the PYTHONPATH environment
# variable is set so that system packages such as e.g. apt installed # 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. # By default, only pure python dependencies are installed.
# If you would like more extras to be installed, do e.g.: # 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. 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. It is used by the "text" GUI.
At revision https://github.com/asweigart/pyperclip/blob/781603ea491eefce3b58f4f203bf748dbf9ff003/src/pyperclip/__init__.py At revision https://github.com/asweigart/pyperclip/blob/781603ea491eefce3b58f4f203bf748dbf9ff003/src/pyperclip/__init__.py
(version 1.8.2) (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 from qtpy.QtWidgets import QApplication
except: except:
try: try:
from PyQt5.QtWidgets import QApplication from PyQt6.QtWidgets import QApplication
except: except:
from PyQt4.QtGui import QApplication try:
from PyQt5.QtWidgets import QApplication
except:
from PyQt4.QtGui import QApplication
app = QApplication.instance() app = QApplication.instance()
if app is None: if app is None:
@ -530,7 +533,7 @@ def determine_clipboard():
accordingly. accordingly.
''' '''
global Foundation, AppKit, gtk, qtpy, PyQt4, PyQt5 global Foundation, AppKit, gtk, qtpy, PyQt4, PyQt5, PyQt6
# Setup for the CYGWIN platform: # 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' 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: except ImportError:
# If qtpy isn't installed, fall back on importing PyQt4. # If qtpy isn't installed, fall back on importing PyQt4.
try: try:
import PyQt5 # check if PyQt5 is installed import PyQt6 # check if PyQt6 is installed
except ImportError: except ImportError:
try: try:
import PyQt4 # check if PyQt4 is installed import PyQt5 # check if PyQt5 is installed
except ImportError: 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: else:
return init_qt_clipboard() return init_qt_clipboard()
else: 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) _GUI_QT_VERSION = getattr(sys, '_GUI_QT_VERSION', None)
if _GUI_QT_VERSION is None: if _GUI_QT_VERSION is None:
# used by pyinstaller when building (analysis phase) # used by pyinstaller when building (analysis phase)
_GUI_QT_VERSION = 5 _GUI_QT_VERSION = 6
if _GUI_QT_VERSION in (5, 6): if _GUI_QT_VERSION in (5, 6):
return _GUI_QT_VERSION return _GUI_QT_VERSION
raise Exception(f"unexpected {_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: def get_default_language(*, gui_name: Optional[str] = None) -> str:
if gui_name == "qt": if gui_name == "qt":
from PyQt5.QtCore import QLocale from PyQt6.QtCore import QLocale
name = QLocale.system().name() name = QLocale.system().name()
return name if name in languages else "en_UK" return name if name in languages else "en_UK"
elif gui_name == "qml": elif gui_name == "qml":

50
electrum/gui/qt/__init__.py

@ -30,25 +30,25 @@ import threading
from typing import Optional, TYPE_CHECKING, List, Sequence from typing import Optional, TYPE_CHECKING, List, Sequence
try: try:
import PyQt5 import PyQt6
import PyQt5.QtGui import PyQt6.QtGui
except Exception as e: except Exception as e:
from electrum import GuiImportError from electrum import GuiImportError
raise GuiImportError( raise GuiImportError(
"Error: Could not import PyQt5. On Linux systems, " "Error: Could not import PyQt6. On Linux systems, "
"you may try 'sudo apt-get install python3-pyqt5'") from e "you may try 'sudo apt-get install python3-pyqt6'") from e
from PyQt5.QtGui import QGuiApplication from PyQt6.QtGui import QGuiApplication
from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QWidget, QMenu, QMessageBox from PyQt6.QtWidgets import QApplication, QSystemTrayIcon, QWidget, QMenu, QMessageBox, QDialog
from PyQt5.QtCore import QObject, pyqtSignal, QTimer, Qt from PyQt6.QtCore import QObject, pyqtSignal, QTimer, Qt
import PyQt5.QtCore as QtCore import PyQt6.QtCore as QtCore
sys._GUI_QT_VERSION = 5 # used by gui/common_qt sys._GUI_QT_VERSION = 6 # used by gui/common_qt
try: try:
# Preload QtMultimedia at app start, if available. # Preload QtMultimedia at app start, if available.
# We use QtMultimedia on some platforms for camera-handling, and # 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) # lazy-loading it later led to some crashes. Maybe due to bugs in PyQt. (see #7725)
from PyQt5.QtMultimedia import QCameraInfo; del QCameraInfo from PyQt6.QtMultimedia import QMediaDevices; del QMediaDevices
except ImportError as e: except ImportError as e:
pass # failure is ok; it is an optional dependency. pass # failure is ok; it is an optional dependency.
@ -86,7 +86,7 @@ class OpenFileEventFilter(QObject):
super(OpenFileEventFilter, self).__init__() super(OpenFileEventFilter, self).__init__()
def eventFilter(self, obj, event): def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.FileOpen: if event.type() == QtCore.QEvent.Type.FileOpen:
if len(self.windows) >= 1: if len(self.windows) >= 1:
self.windows[0].set_payment_identifier(event.url().toString()) self.windows[0].set_payment_identifier(event.url().toString())
return True return True
@ -142,10 +142,10 @@ class ElectrumGui(BaseElectrumGui, Logger):
self._num_wizards_in_progress = 0 self._num_wizards_in_progress = 0
self._num_wizards_lock = threading.Lock() self._num_wizards_lock = threading.Lock()
self.dark_icon = self.config.GUI_QT_DARK_TRAY_ICON self.dark_icon = self.config.GUI_QT_DARK_TRAY_ICON
self.tray = None self.tray = None # type: Optional[QSystemTrayIcon]
self._init_tray() self._init_tray()
self.app.new_window_signal.connect(self.start_new_window) 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 # maybe set dark theme
self._default_qtstylesheet = self.app.styleSheet() self._default_qtstylesheet = self.app.styleSheet()
self.reload_app_stylesheet() self.reload_app_stylesheet()
@ -228,7 +228,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
self.tray.setIcon(self.tray_icon()) self.tray.setIcon(self.tray_icon())
def tray_activated(self, reason): def tray_activated(self, reason):
if reason == QSystemTrayIcon.DoubleClick: if reason == QSystemTrayIcon.ActivationReason.DoubleClick:
if all([w.is_hidden() for w in self.windows]): if all([w.is_hidden() for w in self.windows]):
for w in self.windows: for w in self.windows:
w.bring_to_top() w.bring_to_top()
@ -261,7 +261,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
self.timer.stop() self.timer.stop()
self.timer = None self.timer = None
# clipboard persistence. see http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17328.html # 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) self.app.sendEvent(self.app.clipboard(), event)
if self.tray: if self.tray:
self.tray.hide() self.tray.hide()
@ -359,7 +359,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
except Exception as e: except Exception as e:
self.logger.exception('') self.logger.exception('')
err_text = str(e) if isinstance(e, WalletFileException) else repr(e) 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, parent=None,
title=_('Error'), title=_('Error'),
text=_('Cannot load wallet') + ' (1):\n' + err_text) text=_('Cannot load wallet') + ' (1):\n' + err_text)
@ -384,7 +384,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
except Exception as e: except Exception as e:
self.logger.exception('') self.logger.exception('')
err_text = str(e) if isinstance(e, WalletFileException) else repr(e) 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, parent=None,
title=_('Error'), title=_('Error'),
text=_('Cannot load wallet') + '(2) :\n' + err_text) 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 self.start_new_window(path, uri=None, force_wizard=True)
return return
window.bring_to_top() 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() window.activateWindow()
if uri: if uri:
window.show_send_tab() window.show_send_tab()
@ -418,7 +418,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
result = wizard.exec() result = wizard.exec()
# TODO: use dialog.open() instead to avoid new event loop spawn? # TODO: use dialog.open() instead to avoid new event loop spawn?
self.logger.info(f'wizard dialog exec result={result}') 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') self.logger.info('wizard dialog cancelled by user')
return return
@ -466,7 +466,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
wizard = QENewWalletWizard(self.config, self.app, self.plugins, self.daemon, path, wizard = QENewWalletWizard(self.config, self.app, self.plugins, self.daemon, path,
start_viewstate=WizardViewState('trustedcoin_tos', data, {})) start_viewstate=WizardViewState('trustedcoin_tos', data, {}))
result = wizard.exec() result = wizard.exec()
if result == QENewWalletWizard.Rejected: if result == QDialog.DialogCode.Rejected:
self.logger.info('wizard dialog cancelled by user') self.logger.info('wizard dialog cancelled by user')
return return
db.put('x3', wizard.get_wizard_data()['x3']) 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(): if not self.config.cv.NETWORK_AUTO_CONNECT.is_set():
dialog = QEServerConnectWizard(self.config, self.app, self.plugins, self.daemon) dialog = QEServerConnectWizard(self.config, self.app, self.plugins, self.daemon)
result = dialog.exec() result = dialog.exec()
if result == QEServerConnectWizard.Rejected: if result == QDialog.DialogCode.Rejected:
self.logger.info('network wizard dialog cancelled by user') self.logger.info('network wizard dialog cancelled by user')
raise UserCancelled() raise UserCancelled()
@ -530,7 +530,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
# We will shutdown when the user closes that window, via lastWindowClosed signal. # We will shutdown when the user closes that window, via lastWindowClosed signal.
# main loop # main loop
self.logger.info("starting Qt 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 # on some platforms the exec_ call may not return, so use _cleanup_before_exit
def stop(self): def stop(self):
@ -543,6 +543,6 @@ class ElectrumGui(BaseElectrumGui, Logger):
"qt.version": QtCore.QT_VERSION_STR, "qt.version": QtCore.QT_VERSION_STR,
"pyqt.version": QtCore.PYQT_VERSION_STR, "pyqt.version": QtCore.PYQT_VERSION_STR,
} }
if hasattr(PyQt5, "__path__"): if hasattr(PyQt6, "__path__"):
ret["pyqt.path"] = ", ".join(PyQt5.__path__ or []) ret["pyqt.path"] = ", ".join(PyQt6.__path__ or [])
return ret return ret

2
electrum/gui/qt/address_dialog.py

@ -25,7 +25,7 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtWidgets import QVBoxLayout, QLabel from PyQt6.QtWidgets import QVBoxLayout, QLabel
from electrum.i18n import _ from electrum.i18n import _

20
electrum/gui/qt/address_list.py

@ -27,9 +27,9 @@ import enum
from enum import IntEnum from enum import IntEnum
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, QPersistentModelIndex, QModelIndex from PyQt6.QtCore import Qt, QPersistentModelIndex, QModelIndex
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont from PyQt6.QtGui import QStandardItemModel, QStandardItem, QFont
from PyQt5.QtWidgets import QAbstractItemView, QComboBox, QLabel, QMenu from PyQt6.QtWidgets import QAbstractItemView, QComboBox, QLabel, QMenu
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import block_explorer_URL, profiler 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] filter_columns = [Columns.TYPE, Columns.ADDRESS, Columns.LABEL, Columns.COIN_BALANCE]
ROLE_SORT_ORDER = Qt.UserRole + 1000 ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 1000
ROLE_ADDRESS_STR = Qt.UserRole + 1001 ROLE_ADDRESS_STR = Qt.ItemDataRole.UserRole + 1001
key_role = ROLE_ADDRESS_STR key_role = ROLE_ADDRESS_STR
def __init__(self, main_window: 'ElectrumWindow'): def __init__(self, main_window: 'ElectrumWindow'):
@ -99,7 +99,7 @@ class AddressList(MyTreeView):
editable_columns=[self.Columns.LABEL], editable_columns=[self.Columns.LABEL],
) )
self.wallet = self.main_window.wallet self.wallet = self.main_window.wallet
self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
self.setSortingEnabled(True) self.setSortingEnabled(True)
self.show_change = AddressTypeFilter.ALL # type: AddressTypeFilter self.show_change = AddressTypeFilter.ALL # type: AddressTypeFilter
self.show_used = AddressUsageStateFilter.ALL # type: AddressUsageStateFilter self.show_used = AddressUsageStateFilter.ALL # type: AddressUsageStateFilter
@ -116,7 +116,7 @@ class AddressList(MyTreeView):
self.proxy.setSourceModel(self.std_model) self.proxy.setSourceModel(self.std_model)
self.setModel(self.proxy) self.setModel(self.proxy)
self.update() self.update()
self.sortByColumn(self.Columns.TYPE, Qt.AscendingOrder) self.sortByColumn(self.Columns.TYPE, Qt.SortOrder.AscendingOrder)
if self.config: if self.config:
self.configvar_show_toolbar = self.config.cv.GUI_QT_ADDRESSES_TAB_SHOW_TOOLBAR 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] address_item = [QStandardItem(e) for e in labels]
# align text and set fonts # align text and set fonts
for i, item in enumerate(address_item): 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): if i not in (self.Columns.TYPE, self.Columns.LABEL):
item.setFont(QFont(MONOSPACE_FONT)) item.setFont(QFont(MONOSPACE_FONT))
self.set_editability(address_item) 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 # setup column 0
if self.wallet.is_change(address): if self.wallet.is_change(address):
address_item[self.Columns.TYPE].setText(_('change')) 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)) menu.addAction(_("Add to coin control"), lambda: self.main_window.utxo_list.add_to_coincontrol(coins))
run_hook('receive_menu', menu, addrs, self.wallet) 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: def place_text_on_clipboard(self, text: str, *, title: str = None) -> None:
if is_address(text): if is_address(text):

12
electrum/gui/qt/amountedit.py

@ -3,9 +3,9 @@
from decimal import Decimal from decimal import Decimal
from typing import Union from typing import Union
from PyQt5.QtCore import pyqtSignal, Qt, QSize from PyQt6.QtCore import pyqtSignal, Qt, QSize
from PyQt5.QtGui import QPalette, QPainter from PyQt6.QtGui import QPalette, QPainter
from PyQt5.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame, QSizePolicy) from PyQt6.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame, QSizePolicy)
from .util import char_width_in_lineedit, ColorScheme from .util import char_width_in_lineedit, ColorScheme
@ -32,7 +32,7 @@ class SizedFreezableLineEdit(FreezableLineEdit):
def __init__(self, *, width: int, parent=None): def __init__(self, *, width: int, parent=None):
super().__init__(parent) super().__init__(parent)
self._width = width self._width = width
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
self.setMaximumWidth(width) self.setMaximumWidth(width)
def sizeHint(self) -> QSize: def sizeHint(self) -> QSize:
@ -88,11 +88,11 @@ class AmountEdit(SizedFreezableLineEdit):
if self.base_unit: if self.base_unit:
panel = QStyleOptionFrame() panel = QStyleOptionFrame()
self.initStyleOption(panel) 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) textRect.adjust(2, 0, -10, 0)
painter = QPainter(self) painter = QPainter(self)
painter.setPen(ColorScheme.GRAY.as_color()) 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]: def _get_amount_from_text(self, text: str) -> Union[None, Decimal, int]:
try: try:

58
electrum/gui/qt/balance_dialog.py

@ -25,11 +25,11 @@
from typing import TYPE_CHECKING 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, QLabel, QCompleter, QDialog, QStyledItemDelegate,
QScrollArea, QWidget, QPushButton, QGridLayout, QToolButton) QScrollArea, QWidget, QPushButton, QGridLayout, QToolButton)
from PyQt5.QtCore import QRect, QEventLoop, Qt, pyqtSignal from PyQt6.QtCore import QRect, QEventLoop, Qt, pyqtSignal
from PyQt5.QtGui import QPalette, QPen, QPainter, QPixmap from PyQt6.QtGui import QPalette, QPen, QPainter, QPixmap
from electrum.i18n import _ from electrum.i18n import _
@ -45,23 +45,22 @@ if TYPE_CHECKING:
# show lightning funds that are not usable # show lightning funds that are not usable
# pie chart mouse interactive, to prepare a swap # pie chart mouse interactive, to prepare a swap
COLOR_CONFIRMED = Qt.green COLOR_CONFIRMED = Qt.GlobalColor.green
COLOR_UNCONFIRMED = Qt.red COLOR_UNCONFIRMED = Qt.GlobalColor.red
COLOR_UNMATURED = Qt.magenta COLOR_UNMATURED = Qt.GlobalColor.magenta
COLOR_FROZEN = ColorScheme.BLUE.as_color(True) COLOR_FROZEN = ColorScheme.BLUE.as_color(True)
COLOR_LIGHTNING = Qt.yellow COLOR_LIGHTNING = Qt.GlobalColor.yellow
COLOR_FROZEN_LIGHTNING = Qt.cyan COLOR_FROZEN_LIGHTNING = Qt.GlobalColor.cyan
class PieChartObject: class PieChartObject:
def paintEvent(self, event): def paintEvent(self, event):
bgcolor = self.palette().color(QPalette.Background) pen = QPen(Qt.GlobalColor.gray, 1, Qt.PenStyle.SolidLine)
pen = QPen(Qt.gray, 1, Qt.SolidLine)
qp = QPainter() qp = QPainter()
qp.begin(self) qp.begin(self)
qp.setPen(pen) qp.setPen(pen)
qp.setRenderHint(QPainter.Antialiasing) qp.setRenderHint(QPainter.RenderHint.Antialiasing)
qp.setBrush(Qt.gray) qp.setBrush(Qt.GlobalColor.gray)
total = sum([x[2] for x in self._list]) total = sum([x[2] for x in self._list])
if total == 0: if total == 0:
return return
@ -140,12 +139,11 @@ class LegendWidget(QWidget):
self.setMaximumHeight(self.size) self.setMaximumHeight(self.size)
def paintEvent(self, event): def paintEvent(self, event):
bgcolor = self.palette().color(QPalette.Background) pen = QPen(Qt.GlobalColor.gray, 1, Qt.PenStyle.SolidLine)
pen = QPen(Qt.gray, 1, Qt.SolidLine)
qp = QPainter() qp = QPainter()
qp.begin(self) qp.begin(self)
qp.setPen(pen) qp.setPen(pen)
qp.setRenderHint(QPainter.Antialiasing) qp.setRenderHint(QPainter.RenderHint.Antialiasing)
qp.setBrush(self.color) qp.setBrush(self.color)
qp.drawRect(self.R) qp.drawRect(self.R)
qp.end() qp.end()
@ -192,39 +190,39 @@ class BalanceDialog(WindowModalDialog):
vbox.addWidget(piechart) vbox.addWidget(piechart)
grid = QGridLayout() grid = QGridLayout()
#grid.addWidget(QLabel(_("Onchain") + ':'), 0, 1) #grid.addWidget(QLabel(_("Onchain") + ':'), 0, 1)
#grid.addWidget(QLabel(onchain_str), 0, 2, alignment=Qt.AlignRight) #grid.addWidget(QLabel(onchain_str), 0, 2, alignment=Qt.AlignmentFlag.AlignRight)
#grid.addWidget(QLabel(onchain_fiat_str), 0, 3, alignment=Qt.AlignRight) #grid.addWidget(QLabel(onchain_fiat_str), 0, 3, alignment=Qt.AlignmentFlag.AlignRight)
if frozen: if frozen:
grid.addWidget(LegendWidget(COLOR_FROZEN), 0, 0) grid.addWidget(LegendWidget(COLOR_FROZEN), 0, 0)
grid.addWidget(QLabel(_("Frozen") + ':'), 0, 1) grid.addWidget(QLabel(_("Frozen") + ':'), 0, 1)
grid.addWidget(AmountLabel(frozen_str), 0, 2, alignment=Qt.AlignRight) grid.addWidget(AmountLabel(frozen_str), 0, 2, alignment=Qt.AlignmentFlag.AlignRight)
grid.addWidget(AmountLabel(frozen_fiat_str), 0, 3, alignment=Qt.AlignRight) grid.addWidget(AmountLabel(frozen_fiat_str), 0, 3, alignment=Qt.AlignmentFlag.AlignRight)
if unconfirmed: if unconfirmed:
grid.addWidget(LegendWidget(COLOR_UNCONFIRMED), 2, 0) grid.addWidget(LegendWidget(COLOR_UNCONFIRMED), 2, 0)
grid.addWidget(QLabel(_("Unconfirmed") + ':'), 2, 1) grid.addWidget(QLabel(_("Unconfirmed") + ':'), 2, 1)
grid.addWidget(AmountLabel(unconfirmed_str), 2, 2, alignment=Qt.AlignRight) grid.addWidget(AmountLabel(unconfirmed_str), 2, 2, alignment=Qt.AlignmentFlag.AlignRight)
grid.addWidget(AmountLabel(unconfirmed_fiat_str), 2, 3, alignment=Qt.AlignRight) grid.addWidget(AmountLabel(unconfirmed_fiat_str), 2, 3, alignment=Qt.AlignmentFlag.AlignRight)
if unmatured: if unmatured:
grid.addWidget(LegendWidget(COLOR_UNMATURED), 3, 0) grid.addWidget(LegendWidget(COLOR_UNMATURED), 3, 0)
grid.addWidget(QLabel(_("Unmatured") + ':'), 3, 1) grid.addWidget(QLabel(_("Unmatured") + ':'), 3, 1)
grid.addWidget(AmountLabel(unmatured_str), 3, 2, alignment=Qt.AlignRight) grid.addWidget(AmountLabel(unmatured_str), 3, 2, alignment=Qt.AlignmentFlag.AlignRight)
grid.addWidget(AmountLabel(unmatured_fiat_str), 3, 3, alignment=Qt.AlignRight) grid.addWidget(AmountLabel(unmatured_fiat_str), 3, 3, alignment=Qt.AlignmentFlag.AlignRight)
if confirmed: if confirmed:
grid.addWidget(LegendWidget(COLOR_CONFIRMED), 1, 0) grid.addWidget(LegendWidget(COLOR_CONFIRMED), 1, 0)
grid.addWidget(QLabel(_("On-chain") + ':'), 1, 1) grid.addWidget(QLabel(_("On-chain") + ':'), 1, 1)
grid.addWidget(AmountLabel(confirmed_str), 1, 2, alignment=Qt.AlignRight) grid.addWidget(AmountLabel(confirmed_str), 1, 2, alignment=Qt.AlignmentFlag.AlignRight)
grid.addWidget(AmountLabel(confirmed_fiat_str), 1, 3, alignment=Qt.AlignRight) grid.addWidget(AmountLabel(confirmed_fiat_str), 1, 3, alignment=Qt.AlignmentFlag.AlignRight)
if lightning: if lightning:
grid.addWidget(LegendWidget(COLOR_LIGHTNING), 4, 0) grid.addWidget(LegendWidget(COLOR_LIGHTNING), 4, 0)
grid.addWidget(QLabel(_("Lightning") + ':'), 4, 1) grid.addWidget(QLabel(_("Lightning") + ':'), 4, 1)
grid.addWidget(AmountLabel(lightning_str), 4, 2, alignment=Qt.AlignRight) grid.addWidget(AmountLabel(lightning_str), 4, 2, alignment=Qt.AlignmentFlag.AlignRight)
grid.addWidget(AmountLabel(lightning_fiat_str), 4, 3, alignment=Qt.AlignRight) grid.addWidget(AmountLabel(lightning_fiat_str), 4, 3, alignment=Qt.AlignmentFlag.AlignRight)
if f_lightning: if f_lightning:
grid.addWidget(LegendWidget(COLOR_FROZEN_LIGHTNING), 5, 0) grid.addWidget(LegendWidget(COLOR_FROZEN_LIGHTNING), 5, 0)
grid.addWidget(QLabel(_("Lightning (frozen)") + ':'), 5, 1) grid.addWidget(QLabel(_("Lightning (frozen)") + ':'), 5, 1)
grid.addWidget(AmountLabel(f_lightning_str), 5, 2, 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.AlignRight) grid.addWidget(AmountLabel(f_lightning_fiat_str), 5, 3, alignment=Qt.AlignmentFlag.AlignRight)
vbox.addLayout(grid) vbox.addLayout(grid)
vbox.addStretch(1) vbox.addStretch(1)
@ -234,4 +232,4 @@ class BalanceDialog(WindowModalDialog):
self.setLayout(vbox) self.setLayout(vbox)
def run(self): def run(self):
self.exec_() self.exec()

6
electrum/gui/qt/bip39_recovery_dialog.py

@ -5,8 +5,8 @@
import asyncio import asyncio
import concurrent.futures import concurrent.futures
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QGridLayout, QLabel, QListWidget, QListWidgetItem from PyQt6.QtWidgets import QWidget, QVBoxLayout, QGridLayout, QLabel, QListWidget, QListWidgetItem
from electrum.i18n import _ from electrum.i18n import _
from electrum.network import Network from electrum.network import Network
@ -22,7 +22,7 @@ _logger = get_logger(__name__)
class Bip39RecoveryDialog(WindowModalDialog): class Bip39RecoveryDialog(WindowModalDialog):
ROLE_ACCOUNT = Qt.UserRole ROLE_ACCOUNT = Qt.ItemDataRole.UserRole
def __init__(self, parent: QWidget, get_account_xpub, on_account_select): def __init__(self, parent: QWidget, get_account_xpub, on_account_select):
self.get_account_xpub = get_account_xpub self.get_account_xpub = get_account_xpub

12
electrum/gui/qt/channel_details.py

@ -1,9 +1,9 @@
from typing import TYPE_CHECKING, Sequence from typing import TYPE_CHECKING, Sequence
import PyQt5.QtGui as QtGui import PyQt6.QtGui as QtGui
import PyQt5.QtWidgets as QtWidgets import PyQt6.QtWidgets as QtWidgets
import PyQt5.QtCore as QtCore import PyQt6.QtCore as QtCore
from PyQt5.QtWidgets import QLabel, QLineEdit, QHBoxLayout, QGridLayout from PyQt6.QtWidgets import QLabel, QLineEdit, QHBoxLayout, QGridLayout
from electrum.util import EventListener, ShortID from electrum.util import EventListener, ShortID
from electrum.i18n import _ from electrum.i18n import _
@ -28,7 +28,7 @@ class HTLCItem(QtGui.QStandardItem):
class SelectableLabel(QtWidgets.QLabel): class SelectableLabel(QtWidgets.QLabel):
def __init__(self, text=''): def __init__(self, text=''):
super().__init__(text) super().__init__(text)
self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) self.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextSelectableByMouse)
class LinkedLabel(QtWidgets.QLabel): class LinkedLabel(QtWidgets.QLabel):
def __init__(self, text, on_clicked): def __init__(self, text, on_clicked):
@ -269,7 +269,7 @@ class ChannelDetailsDialog(QtWidgets.QDialog, MessageBoxMixin, QtEventListener):
for htlc_with_status in plist: for htlc_with_status in plist:
htlc_list.append(htlc_with_status) htlc_list.append(htlc_with_status)
w.setModel(self.make_model(htlc_list)) w.setModel(self.make_model(htlc_list))
w.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) w.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
return w return w
def closeEvent(self, event): 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 typing import Sequence, Optional, Dict, TYPE_CHECKING
from abc import abstractmethod, ABC from abc import abstractmethod, ABC
from PyQt5 import QtCore, QtGui from PyQt6 import QtCore, QtGui
from PyQt5.QtCore import Qt, QRect, QSize from PyQt6.QtCore import Qt, QRect, QSize
from PyQt5.QtWidgets import (QMenu, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout, QLineEdit, from PyQt6.QtWidgets import (QMenu, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout, QLineEdit,
QPushButton, QAbstractItemView, QComboBox, QCheckBox, QPushButton, QAbstractItemView, QComboBox, QCheckBox,
QToolTip) 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.util import NotEnoughFunds, NoDynamicFeeEstimates
from electrum.i18n import _ from electrum.i18n import _
@ -29,7 +29,7 @@ if TYPE_CHECKING:
from .main_window import ElectrumWindow from .main_window import ElectrumWindow
ROLE_CHANNEL_ID = Qt.UserRole ROLE_CHANNEL_ID = Qt.ItemDataRole.UserRole
class ChannelsList(MyTreeView): class ChannelsList(MyTreeView):
@ -73,7 +73,7 @@ class ChannelsList(MyTreeView):
stretch_column=self.Columns.NODE_ALIAS, stretch_column=self.Columns.NODE_ALIAS,
) )
self.setModel(QtGui.QStandardItemModel(self)) self.setModel(QtGui.QStandardItemModel(self))
self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
self.gossip_db_loaded.connect(self.on_gossip_db) self.gossip_db_loaded.connect(self.on_gossip_db)
self.update_rows.connect(self.do_update_rows) self.update_rows.connect(self.do_update_rows)
self.update_single_row.connect(self.do_update_single_row) 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 menu.setSeparatorsCollapsible(True) # consecutive separators are merged together
selected = self.selected_in_column(self.Columns.NODE_ALIAS) selected = self.selected_in_column(self.Columns.NODE_ALIAS)
if not selected: if not selected:
menu.exec_(self.viewport().mapToGlobal(position)) menu.exec(self.viewport().mapToGlobal(position))
return return
if len(selected) == 2: if len(selected) == 2:
chan1, chan2 = self.get_rebalance_pair() chan1, chan2 = self.get_rebalance_pair()
if chan1 and chan2: if chan1 and chan2:
menu.addAction(_("Rebalance channels"), lambda: self.main_window.rebalance_dialog(chan1, 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 return
elif len(selected) > 2: elif len(selected) > 2:
return return
@ -283,7 +283,7 @@ class ChannelsList(MyTreeView):
menu.addAction(_("Delete"), lambda: self.remove_channel_backup(channel_id)) menu.addAction(_("Delete"), lambda: self.remove_channel_backup(channel_id))
else: else:
menu.addAction(_("Delete"), lambda: self.remove_channel(channel_id)) 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) @QtCore.pyqtSlot(Abstract_Wallet, AbstractChannel)
def do_update_single_row(self, wallet: Abstract_Wallet, chan: 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: if item.data(ROLE_CHANNEL_ID) != chan.channel_id:
continue continue
for column, v in self.format_fields(chan).items(): 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] items = [self.model().item(row, column) for column in self.Columns]
self._update_chan_frozen_bg(chan=chan, items=items) self._update_chan_frozen_bg(chan=chan, items=items)
if wallet.lnworker: if wallet.lnworker:
@ -331,7 +331,7 @@ class ChannelsList(MyTreeView):
self.model().insertRow(0, items) self.model().insertRow(0, items)
# FIXME sorting by SHORT_CHANID should treat values as tuple, not as string ( 50x1x1 > 8x1x1 ) # 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]): def _update_chan_frozen_bg(self, *, chan: AbstractChannel, items: Sequence[QStandardItem]):
assert self._default_item_bg_brush is not None assert self._default_item_bg_brush is not None
@ -389,7 +389,7 @@ class ChannelsList(MyTreeView):
h.addWidget(QLabel(capacity), 2, 1) h.addWidget(QLabel(capacity), 2, 1)
vbox.addLayout(h) vbox.addLayout(h)
vbox.addLayout(Buttons(OkButton(d))) vbox.addLayout(Buttons(OkButton(d)))
d.exec_() d.exec()
def set_visibility_of_columns(self): def set_visibility_of_columns(self):
def set_visible(col: int, b: bool): 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 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from PyQt5.QtGui import QTextCursor from PyQt6.QtGui import QTextCursor
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtWidgets import QCompleter, QPlainTextEdit, QApplication from PyQt6.QtWidgets import QCompleter, QPlainTextEdit, QApplication
from .util import ButtonsTextEdit from .util import ButtonsTextEdit
@ -35,7 +35,7 @@ class CompletionTextEdit(ButtonsTextEdit):
def __init__(self): def __init__(self):
ButtonsTextEdit.__init__(self) ButtonsTextEdit.__init__(self)
self.completer = None self.completer = None
self.moveCursor(QTextCursor.End) self.moveCursor(QTextCursor.MoveOperation.End)
self.disable_suggestions() self.disable_suggestions()
def set_completer(self, completer): def set_completer(self, completer):
@ -44,7 +44,7 @@ class CompletionTextEdit(ButtonsTextEdit):
def initialize_completer(self): def initialize_completer(self):
self.completer.setWidget(self) self.completer.setWidget(self)
self.completer.setCompletionMode(QCompleter.PopupCompletion) self.completer.setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
self.completer.activated.connect(self.insert_completion) self.completer.activated.connect(self.insert_completion)
self.enable_suggestions() self.enable_suggestions()
@ -53,8 +53,8 @@ class CompletionTextEdit(ButtonsTextEdit):
return return
text_cursor = self.textCursor() text_cursor = self.textCursor()
extra = len(completion) - len(self.completer.completionPrefix()) extra = len(completion) - len(self.completer.completionPrefix())
text_cursor.movePosition(QTextCursor.Left) text_cursor.movePosition(QTextCursor.MoveOperation.Left)
text_cursor.movePosition(QTextCursor.EndOfWord) text_cursor.movePosition(QTextCursor.MoveOperation.EndOfWord)
if extra == 0: if extra == 0:
text_cursor.insertText(" ") text_cursor.insertText(" ")
else: else:
@ -63,7 +63,7 @@ class CompletionTextEdit(ButtonsTextEdit):
def text_under_cursor(self): def text_under_cursor(self):
tc = self.textCursor() tc = self.textCursor()
tc.select(QTextCursor.WordUnderCursor) tc.select(QTextCursor.SelectionType.WordUnderCursor)
return tc.selectedText() return tc.selectedText()
def enable_suggestions(self): def enable_suggestions(self):
@ -84,7 +84,8 @@ class CompletionTextEdit(ButtonsTextEdit):
if self.isReadOnly(): # if field became read-only *after* keyPress, exit now if self.isReadOnly(): # if field became read-only *after* keyPress, exit now
return 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()): if self.completer is None or (ctrlOrShift and not e.text()):
return return
@ -92,7 +93,7 @@ class CompletionTextEdit(ButtonsTextEdit):
return return
eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=" eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="
hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift hasModifier = (e.modifiers() != Qt.KeyboardModifier.NoModifier) and not ctrlOrShift
completionPrefix = self.text_under_cursor() completionPrefix = self.text_under_cursor()
if hasModifier or not e.text() or len(completionPrefix) < 1 or eow.find(e.text()[-1]) >= 0: 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): def is_special_key(self, e):
if self.completer and self.completer.popup().isVisible(): 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 return True
if e.key() == Qt.Key_Tab: if e.key() == Qt.Key.Key_Tab:
return True return True
return False return False
@ -121,4 +122,4 @@ if __name__ == "__main__":
te = CompletionTextEdit() te = CompletionTextEdit()
te.set_completer(completer) te.set_completer(completer)
te.show() 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 functools import partial
from typing import TYPE_CHECKING, Optional, Union, Callable from typing import TYPE_CHECKING, Optional, Union, Callable
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtGui import QIcon 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.i18n import _
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates
@ -151,18 +151,18 @@ class TxEditor(WindowModalDialog):
def create_fee_controls(self): def create_fee_controls(self):
self.fee_label = QLabel('') self.fee_label = QLabel('')
self.fee_label.setTextInteractionFlags(Qt.TextSelectableByMouse) self.fee_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
self.size_label = TxSizeLabel() 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.setAmount(0)
self.size_label.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet()) self.size_label.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
self.feerate_label = QLabel('') 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 = 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.setAmount(0)
self.fiat_fee_label.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet()) self.fiat_fee_label.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
@ -185,7 +185,7 @@ class TxEditor(WindowModalDialog):
self.fee_target = QLabel('') self.fee_target = QLabel('')
self.fee_slider = FeeSlider(self, self.config, self.fee_slider_callback) self.fee_slider = FeeSlider(self, self.config, self.fee_slider_callback)
self.fee_combo = FeeComboBox(self.fee_slider) self.fee_combo = FeeComboBox(self.fee_slider)
self.fee_combo.setFocusPolicy(Qt.NoFocus) self.fee_combo.setFocusPolicy(Qt.FocusPolicy.NoFocus)
def feerounding_onclick(): def feerounding_onclick():
text = (self.feerounding_text() + '\n\n' + text = (self.feerounding_text() + '\n\n' +
@ -417,8 +417,8 @@ class TxEditor(WindowModalDialog):
self.pref_button = QToolButton() self.pref_button = QToolButton()
self.pref_button.setIcon(read_QIcon("preferences.png")) self.pref_button.setIcon(read_QIcon("preferences.png"))
self.pref_button.setMenu(self.pref_menu) self.pref_button.setMenu(self.pref_menu)
self.pref_button.setPopupMode(QToolButton.InstantPopup) self.pref_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
self.pref_button.setFocusPolicy(Qt.NoFocus) self.pref_button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
hbox = QHBoxLayout() hbox = QHBoxLayout()
hbox.addWidget(QLabel(text)) hbox.addWidget(QLabel(text))
hbox.addStretch() hbox.addStretch()
@ -504,7 +504,7 @@ class TxEditor(WindowModalDialog):
w.setVisible(b) w.setVisible(b)
def run(self): def run(self):
cancelled = not self.exec_() cancelled = not self.exec()
self.stop_editor_updates() self.stop_editor_updates()
self.deleteLater() # see #3956 self.deleteLater() # see #3956
return self.tx if not cancelled else None return self.tx if not cancelled else None
@ -681,7 +681,7 @@ class ConfirmTxDialog(TxEditor):
msg = (_('The amount to be received by the recipient.') + ' ' msg = (_('The amount to be received by the recipient.') + ' '
+ _('Fees are paid by the sender.')) + _('Fees are paid by the sender.'))
self.amount_label = QLabel('') 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(HelpLabel(_("Amount to be sent") + ": ", msg), 0, 0)
grid.addWidget(self.amount_label, 0, 1) 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 = QLabel(_("Additional fees") + ": ")
self.extra_fee_label.setVisible(False) self.extra_fee_label.setVisible(False)
self.extra_fee_value = QLabel('') 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) self.extra_fee_value.setVisible(False)
grid.addWidget(self.extra_fee_label, 5, 0) grid.addWidget(self.extra_fee_label, 5, 0)
grid.addWidget(self.extra_fee_value, 5, 1) grid.addWidget(self.extra_fee_value, 5, 1)

47
electrum/gui/qt/console.py

@ -6,9 +6,10 @@ import os
import re import re
import traceback import traceback
from PyQt5 import QtCore from PyQt6 import QtCore
from PyQt5 import QtGui from PyQt6.QtCore import Qt
from PyQt5 import QtWidgets from PyQt6 import QtGui
from PyQt6 import QtWidgets
from electrum import util from electrum import util
from electrum.i18n import _ from electrum.i18n import _
@ -36,7 +37,7 @@ class OverlayLabel(QtWidgets.QLabel):
self.setGeometry(0, 0, self.width(), self.height()) self.setGeometry(0, 0, self.width(), self.height())
self.setStyleSheet(self.STYLESHEET) self.setStyleSheet(self.STYLESHEET)
self.setMargin(0) self.setMargin(0)
parent.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) parent.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setWordWrap(True) self.setWordWrap(True)
def mousePressEvent(self, e): def mousePressEvent(self, e):
@ -56,9 +57,9 @@ class Console(QtWidgets.QPlainTextEdit):
self.construct = [] self.construct = []
self.setGeometry(50, 75, 600, 400) self.setGeometry(50, 75, 600, 400)
self.setWordWrapMode(QtGui.QTextOption.WrapAnywhere) self.setWordWrapMode(QtGui.QTextOption.WrapMode.WrapAnywhere)
self.setUndoRedoEnabled(False) 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.newPrompt("") # make sure there is always a prompt, even before first server.banner
self.updateNamespace({'run':self.run_script}) self.updateNamespace({'run':self.run_script})
@ -114,7 +115,7 @@ class Console(QtWidgets.QPlainTextEdit):
self.completions_visible = False self.completions_visible = False
self.appendPlainText(prompt) self.appendPlainText(prompt)
self.moveCursor(QtGui.QTextCursor.End) self.moveCursor(QtGui.QTextCursor.MoveOperation.End)
def getCommand(self, *, strip=True): def getCommand(self, *, strip=True):
doc = self.document() doc = self.document()
@ -130,13 +131,13 @@ class Console(QtWidgets.QPlainTextEdit):
doc = self.document() doc = self.document()
curr_line = doc.findBlockByLineNumber(doc.lineCount() - 1).text() 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)): 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().removeSelectedText()
self.textCursor().insertText(command) self.textCursor().insertText(command)
self.moveCursor(QtGui.QTextCursor.End) self.moveCursor(QtGui.QTextCursor.MoveOperation.End)
def show_completions(self, completions): def show_completions(self, completions):
if self.completions_visible: if self.completions_visible:
@ -152,7 +153,7 @@ class Console(QtWidgets.QPlainTextEdit):
c.insertText(t) c.insertText(t)
self.completions_end = c.position() self.completions_end = c.position()
self.moveCursor(QtGui.QTextCursor.End) self.moveCursor(QtGui.QTextCursor.MoveOperation.End)
self.completions_visible = True self.completions_visible = True
def hide_completions(self): def hide_completions(self):
@ -163,7 +164,7 @@ class Console(QtWidgets.QPlainTextEdit):
l = self.completions_end - self.completions_pos l = self.completions_end - self.completions_pos
for x in range(l): c.deleteChar() for x in range(l): c.deleteChar()
self.moveCursor(QtGui.QTextCursor.End) self.moveCursor(QtGui.QTextCursor.MoveOperation.End)
self.completions_visible = False self.completions_visible = False
def getConstruct(self, command): def getConstruct(self, command):
@ -211,9 +212,9 @@ class Console(QtWidgets.QPlainTextEdit):
return c.position() - c.block().position() - len(sys.ps1) return c.position() - c.block().position() - len(sys.ps1)
def setCursorPosition(self, position): def setCursorPosition(self, position):
self.moveCursor(QtGui.QTextCursor.StartOfLine) self.moveCursor(QtGui.QTextCursor.MoveOperation.StartOfLine)
for i in range(len(sys.ps1) + position): for i in range(len(sys.ps1) + position):
self.moveCursor(QtGui.QTextCursor.Right) self.moveCursor(QtGui.QTextCursor.MoveOperation.Right)
def run_command(self): def run_command(self):
command = self.getCommand() command = self.getCommand()
@ -279,32 +280,32 @@ class Console(QtWidgets.QPlainTextEdit):
sys.stdout = tmp_stdout sys.stdout = tmp_stdout
def keyPressEvent(self, event): def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Tab: if event.key() == Qt.Key.Key_Tab:
self.completions() self.completions()
return return
self.hide_completions() 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() self.run_command()
return return
if event.key() == QtCore.Qt.Key_Home: if event.key() == Qt.Key.Key_Home:
self.setCursorPosition(0) self.setCursorPosition(0)
return return
if event.key() == QtCore.Qt.Key_PageUp: if event.key() == Qt.Key.Key_PageUp:
return 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: if self.getCursorPosition() == 0:
return return
elif event.key() == QtCore.Qt.Key_Up: elif event.key() == Qt.Key.Key_Up:
self.setCommand(self.getPrevHistoryEntry()) self.setCommand(self.getPrevHistoryEntry())
return return
elif event.key() == QtCore.Qt.Key_Down: elif event.key() == Qt.Key.Key_Down:
self.setCommand(self.getNextHistoryEntry()) self.setCommand(self.getNextHistoryEntry())
return 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() 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(): if not self.textCursor().selectedText():
self.keyboard_interrupt() self.keyboard_interrupt()

14
electrum/gui/qt/contact_list.py

@ -26,9 +26,9 @@
import enum import enum
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtGui import QStandardItemModel, QStandardItem from PyQt6.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtCore import Qt, QPersistentModelIndex, QModelIndex from PyQt6.QtCore import Qt, QPersistentModelIndex, QModelIndex
from PyQt5.QtWidgets import (QAbstractItemView, QMenu) from PyQt6.QtWidgets import (QAbstractItemView, QMenu)
from electrum.i18n import _ from electrum.i18n import _
from electrum.bitcoin import is_address from electrum.bitcoin import is_address
@ -54,7 +54,7 @@ class ContactList(MyTreeView):
} }
filter_columns = [Columns.NAME, Columns.ADDRESS] filter_columns = [Columns.NAME, Columns.ADDRESS]
ROLE_CONTACT_KEY = Qt.UserRole + 1000 ROLE_CONTACT_KEY = Qt.ItemDataRole.UserRole + 1000
key_role = ROLE_CONTACT_KEY key_role = ROLE_CONTACT_KEY
def __init__(self, main_window: 'ElectrumWindow'): def __init__(self, main_window: 'ElectrumWindow'):
@ -64,7 +64,7 @@ class ContactList(MyTreeView):
editable_columns=[self.Columns.NAME], editable_columns=[self.Columns.NAME],
) )
self.setModel(QStandardItemModel(self)) self.setModel(QStandardItemModel(self))
self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
self.setSortingEnabled(True) self.setSortingEnabled(True)
self.std_model = self.model() self.std_model = self.model()
self.update() self.update()
@ -100,7 +100,7 @@ class ContactList(MyTreeView):
menu.addAction(_("View on block explorer"), lambda: [webopen(u) for u in URLs]) menu.addAction(_("View on block explorer"), lambda: [webopen(u) for u in URLs])
run_hook('create_contact_menu', menu, selected_keys) run_hook('create_contact_menu', menu, selected_keys)
menu.exec_(self.viewport().mapToGlobal(position)) menu.exec(self.viewport().mapToGlobal(position))
def update(self): def update(self):
if self.maybe_defer_update(): if self.maybe_defer_update():
@ -125,7 +125,7 @@ class ContactList(MyTreeView):
set_current = QPersistentModelIndex(idx) set_current = QPersistentModelIndex(idx)
self.set_current_idx(set_current) self.set_current_idx(set_current)
# FIXME refresh loses sort order; so set "default" here: # 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() self.filter()
run_hook('update_contacts_tab', self) run_hook('update_contacts_tab', self)

2
electrum/gui/qt/custom_model.py

@ -1,7 +1,7 @@
# loosely based on # loosely based on
# http://trevorius.com/scrapbook/uncategorized/pyqt-custom-abstractitemmodel/ # http://trevorius.com/scrapbook/uncategorized/pyqt-custom-abstractitemmodel/
from PyQt5 import QtCore, QtWidgets from PyQt6 import QtCore, QtWidgets
class CustomNode: class CustomNode:

8
electrum/gui/qt/exception_window.py

@ -25,9 +25,9 @@ import sys
import html import html
from typing import TYPE_CHECKING, Optional, Set from typing import TYPE_CHECKING, Optional, Set
from PyQt5.QtCore import QObject from PyQt6.QtCore import QObject
import PyQt5.QtCore as QtCore import PyQt6.QtCore as QtCore
from PyQt5.QtWidgets import (QWidget, QLabel, QPushButton, QTextEdit, from PyQt6.QtWidgets import (QWidget, QLabel, QPushButton, QTextEdit,
QMessageBox, QHBoxLayout, QVBoxLayout) QMessageBox, QHBoxLayout, QVBoxLayout)
from electrum.i18n import _ from electrum.i18n import _
@ -67,7 +67,7 @@ class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin, Logger):
collapse_info = QPushButton(_("Show report contents")) collapse_info = QPushButton(_("Show report contents"))
collapse_info.clicked.connect( collapse_info.clicked.connect(
lambda: self.msg_box(QMessageBox.NoIcon, lambda: self.msg_box(QMessageBox.Icon.NoIcon,
self, _("Report contents"), self.get_report_string(), self, _("Report contents"), self.get_report_string(),
rich_text=True)) rich_text=True))

8
electrum/gui/qt/fee_slider.py

@ -1,8 +1,8 @@
import threading import threading
from PyQt5.QtGui import QCursor from PyQt6.QtGui import QCursor
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtWidgets import QSlider, QToolTip, QComboBox from PyQt6.QtWidgets import QSlider, QToolTip, QComboBox
from electrum.i18n import _ from electrum.i18n import _
@ -31,7 +31,7 @@ class FeeComboBox(QComboBox):
class FeeSlider(QSlider): class FeeSlider(QSlider):
def __init__(self, window, config, callback): def __init__(self, window, config, callback):
QSlider.__init__(self, Qt.Horizontal) QSlider.__init__(self, Qt.Orientation.Horizontal)
self.config = config self.config = config
self.window = window self.window = window
self.callback = callback self.callback = callback

68
electrum/gui/qt/history_list.py

@ -33,10 +33,10 @@ import threading
import enum import enum
from decimal import Decimal from decimal import Decimal
from PyQt5.QtGui import QFont, QBrush, QColor from PyQt6.QtGui import QFont, QBrush, QColor
from PyQt5.QtCore import (Qt, QPersistentModelIndex, QModelIndex, QAbstractItemModel, from PyQt6.QtCore import (Qt, QPersistentModelIndex, QModelIndex, QAbstractItemModel,
QSortFilterProxyModel, QVariant, QItemSelectionModel, QDate, QPoint) 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, QPushButton, QComboBox, QVBoxLayout, QCalendarWidget,
QGridLayout) QGridLayout)
@ -77,7 +77,7 @@ TX_ICONS = [
] ]
ROLE_SORT_ORDER = Qt.UserRole + 1000 ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 1000
class HistorySortModel(QSortFilterProxyModel): class HistorySortModel(QSortFilterProxyModel):
@ -155,11 +155,11 @@ class HistoryNode(CustomNode):
return QVariant(d[col]) return QVariant(d[col])
if role == MyTreeView.ROLE_EDIT_KEY: if role == MyTreeView.ROLE_EDIT_KEY:
return QVariant(get_item_key(tx_item)) return QVariant(get_item_key(tx_item))
if role not in (Qt.DisplayRole, Qt.EditRole, MyTreeView.ROLE_CLIPBOARD_DATA): if role not in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole, MyTreeView.ROLE_CLIPBOARD_DATA):
if col == HistoryColumns.STATUS and role == Qt.DecorationRole: if col == HistoryColumns.STATUS and role == Qt.ItemDataRole.DecorationRole:
icon = "lightning" if is_lightning else TX_ICONS[status] icon = "lightning" if is_lightning else TX_ICONS[status]
return QVariant(read_QIcon(icon)) 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: if is_lightning:
msg = 'lightning transaction' msg = 'lightning transaction'
else: # on-chain else: # on-chain
@ -171,19 +171,19 @@ class HistoryNode(CustomNode):
else: else:
msg = str(conf) + _(" confirmation" + ("s" if conf != 1 else "")) msg = str(conf) + _(" confirmation" + ("s" if conf != 1 else ""))
return QVariant(msg) return QVariant(msg)
elif col > HistoryColumns.DESCRIPTION and role == Qt.TextAlignmentRole: elif col > HistoryColumns.DESCRIPTION and role == Qt.ItemDataRole.TextAlignmentRole:
return QVariant(int(Qt.AlignRight | Qt.AlignVCenter)) return QVariant(int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter))
elif col > HistoryColumns.DESCRIPTION and role == Qt.FontRole: elif col > HistoryColumns.DESCRIPTION and role == Qt.ItemDataRole.FontRole:
monospace_font = QFont(MONOSPACE_FONT) monospace_font = QFont(MONOSPACE_FONT)
return QVariant(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): # and self.parent.wallet.invoices.paid.get(tx_hash):
# return QVariant(read_QIcon("seal")) # return QVariant(read_QIcon("seal"))
elif col in (HistoryColumns.DESCRIPTION, HistoryColumns.AMOUNT) \ 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")) red_brush = QBrush(QColor("#BC1E1E"))
return QVariant(red_brush) 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: and not tx_item.get('fiat_default') and tx_item.get('fiat_value') is not None:
blue_brush = QBrush(QColor("#1E1EFF")) blue_brush = QBrush(QColor("#1E1EFF"))
return QVariant(blue_brush) return QVariant(blue_brush)
@ -247,7 +247,7 @@ class HistoryModel(CustomModel, Logger):
tx_item = index.internalPointer().get_data() tx_item = index.internalPointer().get_data()
tx_item['label'] = self.window.wallet.get_label_for_txid(get_item_key(tx_item)) tx_item['label'] = self.window.wallet.get_label_for_txid(get_item_key(tx_item))
topLeft = bottomRight = self.createIndex(index.row(), HistoryColumns.DESCRIPTION) 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() self.window.utxo_list.update()
def get_domain(self): def get_domain(self):
@ -319,7 +319,9 @@ class HistoryModel(CustomModel, Logger):
self.endInsertRows() self.endInsertRows()
if selected_row: 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() self.view.filter()
# update time filter # update time filter
if not self.view.years and self.transactions: 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( 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_hash=txid, amount_sat=value, fx=self.window.fx, tx_fee=fee.value if fee else None)
tx_item.update(fiat_fields) 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): def update_tx_mined_status(self, tx_hash: str, tx_mined_info: TxMinedInfo):
try: try:
@ -392,8 +394,8 @@ class HistoryModel(CustomModel, Logger):
self.update_tx_mined_status(tx_hash, tx_mined_info) self.update_tx_mined_status(tx_hash, tx_mined_info)
def headerData(self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole): def headerData(self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole):
assert orientation == Qt.Horizontal assert orientation == Qt.Orientation.Horizontal
if role != Qt.DisplayRole: if role != Qt.ItemDataRole.DisplayRole:
return None return None
fx = self.window.fx fx = self.window.fx
fiat_title = 'n/a fiat value' fiat_title = 'n/a fiat value'
@ -415,11 +417,11 @@ class HistoryModel(CustomModel, Logger):
HistoryColumns.SHORT_ID: 'Short ID', HistoryColumns.SHORT_ID: 'Short ID',
}[section] }[section]
def flags(self, idx: QModelIndex) -> int: def flags(self, idx: QModelIndex) -> Qt.ItemFlag:
extra_flags = Qt.NoItemFlags # type: Qt.ItemFlag extra_flags = Qt.ItemFlag.NoItemFlags # type: Qt.ItemFlag
if idx.column() in self.view.editable_columns: if idx.column() in self.view.editable_columns:
extra_flags |= Qt.ItemIsEditable extra_flags |= Qt.ItemFlag.ItemIsEditable
return super().flags(idx) | int(extra_flags) return super().flags(idx) | extra_flags
@staticmethod @staticmethod
def _tx_mined_info_from_tx_item(tx_item: Dict[str, Any]) -> TxMinedInfo: 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.addItems([_('All'), _('Custom')])
self.period_combo.activated.connect(self.on_combo) self.period_combo.activated.connect(self.on_combo)
self.wallet = self.main_window.wallet # type: Abstract_Wallet 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.setRootIsDecorated(True)
self.header().setStretchLastSection(False) self.header().setStretchLastSection(False)
for col in HistoryColumns: 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) self.header().setSectionResizeMode(col, sm)
if self.config: if self.config:
self.configvar_show_toolbar = self.config.cv.GUI_QT_HISTORY_TAB_SHOW_TOOLBAR 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.addWidget(cal)
vbox.addLayout(Buttons(OkButton(d), CancelButton(d))) vbox.addLayout(Buttons(OkButton(d), CancelButton(d)))
d.setLayout(vbox) d.setLayout(vbox)
if d.exec_(): if d.exec():
if d.date is None: if d.date is None:
return None return None
date = d.date.toPyDate() date = d.date.toPyDate()
@ -658,7 +660,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
vbox.addLayout(grid2) vbox.addLayout(grid2)
vbox.addLayout(Buttons(CloseButton(d))) vbox.addLayout(Buttons(CloseButton(d)))
d.setLayout(vbox) d.setLayout(vbox)
d.exec_() d.exec()
def plot_history_dialog(self): def plot_history_dialog(self):
try: try:
@ -711,11 +713,11 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
for column in HistoryColumns: for column in HistoryColumns:
if self.isColumnHidden(column): if self.isColumnHidden(column):
continue 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) idx2 = idx.sibling(idx.row(), column)
clipboard_data = self.hm.data(idx2, self.ROLE_CLIPBOARD_DATA).value() clipboard_data = self.hm.data(idx2, self.ROLE_CLIPBOARD_DATA).value()
if clipboard_data is None: 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( cc.addAction(
column_title, column_title,
lambda text=clipboard_data, title=column_title: lambda text=clipboard_data, title=column_title:
@ -739,7 +741,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
log = self.wallet.lnworker.logs.get(key) log = self.wallet.lnworker.logs.get(key)
if log: if log:
menu.addAction(_("View log"), lambda: self.main_window.send_tab.invoice_list.show_log(key, 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 return
tx_hash = tx_item['txid'] tx_hash = tx_item['txid']
tx = self.wallet.adb.get_transaction(tx_hash) tx = self.wallet.adb.get_transaction(tx_hash)
@ -757,7 +759,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
menu_edit = menu.addMenu(_("Edit")) menu_edit = menu.addMenu(_("Edit"))
for c in self.editable_columns: for c in self.editable_columns:
if self.isColumnHidden(c): continue 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 # TODO use siblingAtColumn when min Qt version is >=5.11
persistent = QPersistentModelIndex(org_idx.sibling(org_idx.row(), c)) persistent = QPersistentModelIndex(org_idx.sibling(org_idx.row(), c))
menu_edit.addAction(_("{}").format(label), lambda p=persistent: self.edit(QModelIndex(p))) 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)) menu_invs.addAction(_("View invoice"), lambda inv=inv: self.main_window.show_onchain_invoice(inv))
if tx_URL: if tx_URL:
menu.addAction(_("View on block explorer"), lambda: webopen(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): def remove_local_tx(self, tx_hash: str):
num_child_txs = len(self.wallet.adb.get_depending_transactions(tx_hash)) num_child_txs = len(self.wallet.adb.get_depending_transactions(tx_hash))
@ -821,7 +823,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
vbox.addLayout(hbox) vbox.addLayout(hbox)
#run_hook('export_history_dialog', self, hbox) #run_hook('export_history_dialog', self, hbox)
self.update() self.update()
if not d.exec_(): if not d.exec():
return return
filename = filename_e.text() filename = filename_e.text()
if not filename: if not filename:
@ -867,7 +869,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
f.write(json_encode(txns)) f.write(json_encode(txns))
def get_text_from_coordinate(self, row, col): 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): def get_role_data_from_coordinate(self, row, col, *, role):
idx = self.model().mapToSource(self.model().index(row, col)) idx = self.model().mapToSource(self.model().index(row, col))

28
electrum/gui/qt/invoice_list.py

@ -26,10 +26,10 @@
import enum import enum
from typing import Sequence, TYPE_CHECKING from typing import Sequence, TYPE_CHECKING
from PyQt5.QtCore import Qt, QItemSelectionModel from PyQt6.QtCore import Qt, QItemSelectionModel
from PyQt5.QtGui import QStandardItemModel, QStandardItem from PyQt6.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QAbstractItemView from PyQt6.QtWidgets import QAbstractItemView
from PyQt5.QtWidgets import QMenu, QVBoxLayout, QTreeWidget, QTreeWidgetItem, QHeaderView from PyQt6.QtWidgets import QMenu, QVBoxLayout, QTreeWidget, QTreeWidgetItem, QHeaderView
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import format_time from electrum.util import format_time
@ -47,9 +47,9 @@ if TYPE_CHECKING:
from .send_tab import SendTab from .send_tab import SendTab
ROLE_REQUEST_TYPE = Qt.UserRole ROLE_REQUEST_TYPE = Qt.ItemDataRole.UserRole
ROLE_REQUEST_ID = Qt.UserRole + 1 ROLE_REQUEST_ID = Qt.ItemDataRole.UserRole + 1
ROLE_SORT_ORDER = Qt.UserRole + 2 ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 2
class InvoiceList(MyTreeView): class InvoiceList(MyTreeView):
@ -82,7 +82,7 @@ class InvoiceList(MyTreeView):
self.proxy.setSourceModel(self.std_model) self.proxy.setSourceModel(self.std_model)
self.setModel(self.proxy) self.setModel(self.proxy)
self.setSortingEnabled(True) self.setSortingEnabled(True)
self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
def on_double_click(self, idx): def on_double_click(self, idx):
key = idx.sibling(idx.row(), self.Columns.DATE).data(ROLE_REQUEST_ID) key = idx.sibling(idx.row(), self.Columns.DATE).data(ROLE_REQUEST_ID)
@ -139,7 +139,7 @@ class InvoiceList(MyTreeView):
self.filter() self.filter()
self.proxy.setDynamicSortFilter(True) self.proxy.setDynamicSortFilter(True)
# sort requests by date # sort requests by date
self.sortByColumn(self.Columns.DATE, Qt.DescendingOrder) self.sortByColumn(self.Columns.DATE, Qt.SortOrder.DescendingOrder)
self.hide_if_empty() self.hide_if_empty()
def show_invoice(self, key): def show_invoice(self, key):
@ -165,7 +165,7 @@ class InvoiceList(MyTreeView):
if can_batch_pay: if can_batch_pay:
menu.addAction(_("Batch pay invoices") + "...", lambda: self.send_tab.pay_multiple_invoices(invoices)) menu.addAction(_("Batch pay invoices") + "...", lambda: self.send_tab.pay_multiple_invoices(invoices))
menu.addAction(_("Delete invoices"), lambda: self.delete_invoices(keys)) menu.addAction(_("Delete invoices"), lambda: self.delete_invoices(keys))
menu.exec_(self.viewport().mapToGlobal(position)) menu.exec(self.viewport().mapToGlobal(position))
return return
idx = self.indexAt(position) idx = self.indexAt(position)
item = self.item_from_index(idx) item = self.item_from_index(idx)
@ -193,7 +193,7 @@ class InvoiceList(MyTreeView):
if log: if log:
menu.addAction(_("View log"), lambda: self.show_log(key, log)) menu.addAction(_("View log"), lambda: self.show_log(key, log))
menu.addAction(_("Delete"), lambda: self.delete_invoices([key])) 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]): def show_log(self, key, log: Sequence[HtlcLog]):
d = WindowModalDialog(self, _("Payment log")) d = WindowModalDialog(self, _("Payment log"))
@ -201,15 +201,15 @@ class InvoiceList(MyTreeView):
vbox = QVBoxLayout(d) vbox = QVBoxLayout(d)
log_w = QTreeWidget() log_w = QTreeWidget()
log_w.setHeaderLabels([_('Hops'), _('Channel ID'), _('Message')]) log_w.setHeaderLabels([_('Hops'), _('Channel ID'), _('Message')])
log_w.header().setSectionResizeMode(2, QHeaderView.Stretch) log_w.header().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
log_w.header().setSectionResizeMode(1, QHeaderView.ResizeToContents) log_w.header().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
for payment_attempt_log in log: for payment_attempt_log in log:
route_str, chan_str, message = payment_attempt_log.formatted_tuple() route_str, chan_str, message = payment_attempt_log.formatted_tuple()
x = QTreeWidgetItem([route_str, chan_str, message]) x = QTreeWidgetItem([route_str, chan_str, message])
log_w.addTopLevelItem(x) log_w.addTopLevelItem(x)
vbox.addWidget(log_w) vbox.addWidget(log_w)
vbox.addLayout(Buttons(CloseButton(d))) vbox.addLayout(Buttons(CloseButton(d)))
d.exec_() d.exec()
def delete_invoices(self, keys): def delete_invoices(self, keys):
for key in keys: for key in keys:

2
electrum/gui/qt/lightning_dialog.py

@ -25,7 +25,7 @@
from typing import TYPE_CHECKING 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 _ 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 from decimal import Decimal
import datetime import datetime
from PyQt5.QtGui import QFont from PyQt6.QtGui import QFont
from PyQt5.QtWidgets import QVBoxLayout, QLabel, QGridLayout from PyQt6.QtWidgets import QVBoxLayout, QLabel, QGridLayout
from electrum.i18n import _ from electrum.i18n import _
from electrum.lnworker import PaymentDirection from electrum.lnworker import PaymentDirection

10
electrum/gui/qt/locktimeedit.py

@ -6,9 +6,9 @@ import time
from datetime import datetime from datetime import datetime
from typing import Optional, Any from typing import Optional, Any
from PyQt5.QtCore import Qt, QDateTime, pyqtSignal from PyQt6.QtCore import Qt, QDateTime, pyqtSignal
from PyQt5.QtGui import QPalette, QPainter from PyQt6.QtGui import QPalette, QPainter
from PyQt5.QtWidgets import (QWidget, QLineEdit, QStyle, QStyleOptionFrame, QComboBox, from PyQt6.QtWidgets import (QWidget, QLineEdit, QStyle, QStyleOptionFrame, QComboBox,
QHBoxLayout, QDateTimeEdit) QHBoxLayout, QDateTimeEdit)
from electrum.i18n import _ from electrum.i18n import _
@ -145,11 +145,11 @@ class LockTimeHeightEdit(LockTimeRawEdit):
super().paintEvent(event) super().paintEvent(event)
panel = QStyleOptionFrame() panel = QStyleOptionFrame()
self.initStyleOption(panel) 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) textRect.adjust(2, 0, -10, 0)
painter = QPainter(self) painter = QPainter(self)
painter.setPen(ColorScheme.GRAY.as_color()) 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: 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 from typing import Optional, TYPE_CHECKING, Sequence, Union, Dict, Mapping
import concurrent.futures import concurrent.futures
from PyQt5.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont, QFontMetrics from PyQt6.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont, QFontMetrics, QAction, QShortcut
from PyQt5.QtCore import Qt, QRect, QStringListModel, QSize, pyqtSignal from PyQt6.QtCore import Qt, QRect, QStringListModel, QSize, pyqtSignal
from PyQt5.QtWidgets import (QMessageBox, QSystemTrayIcon, QTabWidget, from PyQt6.QtWidgets import (QMessageBox, QSystemTrayIcon, QTabWidget,
QMenuBar, QFileDialog, QCheckBox, QLabel, QMenuBar, QFileDialog, QCheckBox, QLabel,
QVBoxLayout, QGridLayout, QLineEdit, QVBoxLayout, QGridLayout, QLineEdit,
QHBoxLayout, QPushButton, QScrollArea, QTextEdit, QHBoxLayout, QPushButton, QScrollArea, QTextEdit,
QShortcut, QMainWindow, QInputDialog, QMainWindow, QInputDialog,
QWidget, QSizePolicy, QStatusBar, QToolTip, QWidget, QSizePolicy, QStatusBar, QToolTip,
QMenu, QAction, QToolButton) QMenu, QToolButton)
import electrum import electrum
from electrum.gui import messages from electrum.gui import messages
@ -113,21 +113,21 @@ class StatusBarButton(QToolButton):
self.setText('') self.setText('')
self.setIcon(icon) self.setIcon(icon)
self.setToolTip(tooltip) self.setToolTip(tooltip)
self.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
self.setAutoRaise(True) self.setAutoRaise(True)
size = max(25, round(0.9 * sb_height)) size = max(25, round(0.9 * sb_height))
self.setMaximumWidth(size) self.setMaximumWidth(size)
self.clicked.connect(self.onPress) self.clicked.connect(self.onPress)
self.func = func self.func = func
self.setIconSize(QSize(size, size)) self.setIconSize(QSize(size, size))
self.setCursor(QCursor(Qt.PointingHandCursor)) self.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
def onPress(self, checked=False): def onPress(self, checked=False):
'''Drops the unwanted PyQt5 "checked" argument''' '''Drops the unwanted PyQt "checked" argument'''
self.func() self.func()
def keyPressEvent(self, e): 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() 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.console_tab, read_QIcon("tab_console.png"), _("Con&sole"))
add_optional_tab(tabs, self.notes_tab, read_QIcon("pen.png"), _("&Notes")) 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() central_widget = QScrollArea()
vbox = QVBoxLayout(central_widget) vbox = QVBoxLayout(central_widget)
@ -596,7 +596,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
cb_checked = False cb_checked = False
def on_cb(x): def on_cb(x):
nonlocal cb_checked nonlocal cb_checked
cb_checked = x == Qt.Checked cb_checked = x == Qt.CheckState.Checked
cb.stateChanged.connect(on_cb) cb.stateChanged.connect(on_cb)
self.show_warning(msg, title=_('Testnet'), checkbox=cb) self.show_warning(msg, title=_('Testnet'), checkbox=cb)
if cb_checked: if cb_checked:
@ -641,7 +641,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
vbox.addLayout(grid) vbox.addLayout(grid)
vbox.addWidget(WWLabel(msg)) vbox.addWidget(WWLabel(msg))
vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
if not d.exec_(): if not d.exec():
return False return False
backup_dir = self.config.get_backup_dir() backup_dir = self.config.get_backup_dir()
if backup_dir is None: if backup_dir is None:
@ -700,9 +700,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
file_menu = menubar.addMenu(_("&File")) file_menu = menubar.addMenu(_("&File"))
self.recently_visited_menu = file_menu.addMenu(_("&Recently open")) self.recently_visited_menu = file_menu.addMenu(_("&Recently open"))
file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open) file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.StandardKey.Open)
file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New) file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.StandardKey.New)
file_menu.addAction(_("&Save backup"), self.backup_wallet).setShortcut(QKeySequence.SaveAs) file_menu.addAction(_("&Save backup"), self.backup_wallet).setShortcut(QKeySequence.StandardKey.SaveAs)
file_menu.addAction(_("Delete"), self.remove_wallet) file_menu.addAction(_("Delete"), self.remove_wallet)
file_menu.addSeparator() file_menu.addSeparator()
file_menu.addAction(_("&Quit"), self.close) 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, # preferences_action will get picked up based on name (and put into a standardized location,
# and given a standard reserved hotkey) # and given a standard reserved hotkey)
# Hence, this menu item will be at a "uniform location re macOS processes" # 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" # Add another preferences item, to also have a "uniform location for Electrum between different OSes"
tools_menu.addAction(_("Electrum preferences"), self.settings_dialog) 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(_("&Check for updates"), self.show_update_check)
help_menu.addAction(_("&Official website"), lambda: webopen("https://electrum.org")) help_menu.addAction(_("&Official website"), lambda: webopen("https://electrum.org"))
help_menu.addSeparator() 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: if not constants.net.TESTNET:
help_menu.addAction(_("&Bitcoin Paper"), self.show_bitcoin_paper) help_menu.addAction(_("&Bitcoin Paper"), self.show_bitcoin_paper)
help_menu.addAction(_("&Report Bug"), self.show_report_bug) help_menu.addAction(_("&Report Bug"), self.show_report_bug)
@ -861,11 +861,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
def notify(self, message): def notify(self, message):
if self.tray: if self.tray:
try: self.tray.showMessage("Electrum", message, read_QIcon("electrum_dark_icon"), 20000)
# 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)
def timer_actions(self): def timer_actions(self):
# refresh invoices and requests because they show ETA # 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): def show_address(self, addr: str, *, parent: QWidget = None):
from . import address_dialog from . import address_dialog
d = address_dialog.AddressDialog(self, addr, parent=parent) d = address_dialog.AddressDialog(self, addr, parent=parent)
d.exec_() d.exec()
def show_utxo(self, utxo): def show_utxo(self, utxo):
from . import utxo_dialog from . import utxo_dialog
d = utxo_dialog.UTXODialog(self, utxo) d = utxo_dialog.UTXODialog(self, utxo)
d.exec_() d.exec()
def show_channel_details(self, chan): def show_channel_details(self, chan):
from .channel_details import ChannelDetailsDialog from .channel_details import ChannelDetailsDialog
@ -1508,7 +1504,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
buttons = Buttons(CloseButton(d)) buttons = Buttons(CloseButton(d))
vbox.addLayout(grid) vbox.addLayout(grid)
vbox.addLayout(buttons) vbox.addLayout(buttons)
d.exec_() d.exec()
def show_lightning_invoice(self, invoice: Invoice): def show_lightning_invoice(self, invoice: Invoice):
from electrum.util import format_short_id from electrum.util import format_short_id
@ -1554,7 +1550,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
grid.addWidget(routing_e, 9, 1) grid.addWidget(routing_e, 9, 1)
vbox.addLayout(grid) vbox.addLayout(grid)
vbox.addLayout(Buttons(CloseButton(d),)) vbox.addLayout(Buttons(CloseButton(d),))
d.exec_() d.exec()
def create_console_tab(self): def create_console_tab(self):
from .console import Console from .console import Console
@ -1563,10 +1559,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
return console return console
def create_notes_tab(self): def create_notes_tab(self):
from PyQt5 import QtGui, QtWidgets from PyQt6 import QtGui, QtWidgets
notes_tab = QtWidgets.QPlainTextEdit() notes_tab = QtWidgets.QPlainTextEdit()
notes_tab.setWordWrapMode(QtGui.QTextOption.WrapAnywhere) notes_tab.setWordWrapMode(QtGui.QTextOption.WrapMode.WrapAnywhere)
notes_tab.setFont(QtGui.QFont(MONOSPACE_FONT, 10, QtGui.QFont.Normal)) notes_tab.setFont(QtGui.QFont(MONOSPACE_FONT, 10, QtGui.QFont.Weight.Normal))
notes_tab.setPlainText(self.wallet.db.get('notes_text', '')) notes_tab.setPlainText(self.wallet.db.get('notes_text', ''))
notes_tab.is_shown_cv = self.config.cv.GUI_QT_SHOW_TAB_NOTES notes_tab.is_shown_cv = self.config.cv.GUI_QT_SHOW_TAB_NOTES
notes_tab.textChanged.connect(self.maybe_save_notes_text) 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 = QPushButton("")
self.update_check_button.setFlat(True) 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.setIcon(read_QIcon("update.png"))
self.update_check_button.hide() self.update_check_button.hide()
sb.addPermanentWidget(self.update_check_button) sb.addPermanentWidget(self.update_check_button)
@ -1694,8 +1690,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
+ ColorScheme.GREEN.as_stylesheet(True)) + ColorScheme.GREEN.as_stylesheet(True))
self.coincontrol_label = QLabel() self.coincontrol_label = QLabel()
self.coincontrol_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.coincontrol_label.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
self.coincontrol_label.setTextInteractionFlags(Qt.TextSelectableByMouse) self.coincontrol_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
sb.addWidget(self.coincontrol_label) sb.addWidget(self.coincontrol_label)
clear_cc_button = EnterButton(_('Reset'), lambda: self.utxo_list.clear_coincontrol()) 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) grid.addWidget(line2, 2, 1)
vbox.addLayout(grid) vbox.addLayout(grid)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
if d.exec_(): if d.exec():
self.set_contact(line2.text(), line1.text()) self.set_contact(line2.text(), line1.text())
def init_lightning_dialog(self, dialog): def init_lightning_dialog(self, dialog):
@ -1857,7 +1853,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
def show_wallet_info(self): def show_wallet_info(self):
from .wallet_info_dialog import WalletInfoDialog from .wallet_info_dialog import WalletInfoDialog
d = WalletInfoDialog(self, window=self) d = WalletInfoDialog(self, window=self)
d.exec_() d.exec()
def remove_wallet(self): def remove_wallet(self):
if self.question('\n'.join([ if self.question('\n'.join([
@ -1891,7 +1887,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
return return
from .seed_dialog import SeedDialog from .seed_dialog import SeedDialog
d = SeedDialog(self, seed, passphrase, config=self.config) d = SeedDialog(self, seed, passphrase, config=self.config)
d.exec_() d.exec()
def show_qrcode(self, data, title=None, parent=None, *, def show_qrcode(self, data, title=None, parent=None, *,
help_text=None, show_copy_text_btn=False): 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, show_copy_text_btn=show_copy_text_btn,
config=self.config, config=self.config,
) )
d.exec_() d.exec()
@protected @protected
def show_private_key(self, address, password): def show_private_key(self, address, password):
@ -1931,7 +1927,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
vbox.addWidget(keys_e) vbox.addWidget(keys_e)
vbox.addLayout(Buttons(CloseButton(d))) vbox.addLayout(Buttons(CloseButton(d)))
d.setLayout(vbox) d.setLayout(vbox)
d.exec_() d.exec()
msg_sign = _("Signing with an address actually means signing with the corresponding " msg_sign = _("Signing with an address actually means signing with the corresponding "
"private key, and verifying with the corresponding public key. The " "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) b.clicked.connect(d.accept)
hbox.addWidget(b) hbox.addWidget(b)
layout.addLayout(hbox, 4, 1) layout.addLayout(hbox, 4, 1)
d.exec_() d.exec()
@protected @protected
def do_decrypt(self, message_e, pubkey_e, encrypted_e, password): def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
@ -2092,7 +2088,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
hbox.addWidget(b) hbox.addWidget(b)
layout.addLayout(hbox, 4, 1) layout.addLayout(hbox, 4, 1)
d.exec_() d.exec()
def password_dialog(self, msg=None, parent=None): def password_dialog(self, msg=None, parent=None):
from .password_dialog import PasswordDialog from .password_dialog import PasswordDialog
@ -2303,7 +2299,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
d.finished.connect(on_dialog_closed) d.finished.connect(on_dialog_closed)
threading.Thread(target=privkeys_thread).start() threading.Thread(target=privkeys_thread).start()
if not d.exec_(): if not d.exec():
done = True done = True
return return
@ -2373,7 +2369,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
vbox = QVBoxLayout(d) vbox = QVBoxLayout(d)
hbox_top = QHBoxLayout() hbox_top = QHBoxLayout()
hbox_top.addWidget(QLabel(_("Enter private keys to sweep coins from:"))) 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) vbox.addLayout(hbox_top)
keys_e = ScanQRTextEdit(allow_multi=True, config=self.config) keys_e = ScanQRTextEdit(allow_multi=True, config=self.config)
keys_e.setTabChangesFocus(True) 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_edit)
address_e.textChanged.connect(on_address) address_e.textChanged.connect(on_address)
on_address(str(address_e.text())) on_address(str(address_e.text()))
if not d.exec_(): if not d.exec():
return return
# user pressed "sweep" # user pressed "sweep"
addr = get_address() addr = get_address()
@ -2480,7 +2476,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
title = _('Import private keys') title = _('Import private keys')
header_layout = QHBoxLayout() header_layout = QHBoxLayout()
header_layout.addWidget(QLabel(_("Enter private keys")+':')) 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)) self._do_import(title, header_layout, lambda x: self.wallet.import_private_keys(x, password))
def refresh_amount_edits(self): def refresh_amount_edits(self):
@ -2502,7 +2498,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
def settings_dialog(self): def settings_dialog(self):
from .settings_dialog import SettingsDialog from .settings_dialog import SettingsDialog
d = SettingsDialog(self, self.config) d = SettingsDialog(self, self.config)
d.exec_() d.exec()
if self.fx: if self.fx:
self.fx.trigger_update() self.fx.trigger_update()
run_hook('close_settings_dialog') run_hook('close_settings_dialog')
@ -2547,7 +2543,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
def plugins_dialog(self): def plugins_dialog(self):
from .plugins_dialog import PluginsDialog from .plugins_dialog import PluginsDialog
d = PluginsDialog(self) d = PluginsDialog(self)
d.exec_() d.exec()
def cpfp_dialog(self, parent_tx: Transaction) -> None: def cpfp_dialog(self, parent_tx: Transaction) -> None:
new_tx = self.wallet.cpfp(parent_tx, 0) new_tx = self.wallet.cpfp(parent_tx, 0)
@ -2624,7 +2620,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
grid.addWidget(combined_feerate, 6, 1) grid.addWidget(combined_feerate, 6, 1)
vbox.addLayout(grid) vbox.addLayout(grid)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
if not d.exec_(): if not d.exec():
return return
fee = fee_e.get_amount() fee = fee_e.get_amount()
if fee is None: 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, from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, Union, List, Dict, Any,
Sequence, Iterable, Tuple, Type) Sequence, Iterable, Tuple, Type)
from PyQt5 import QtWidgets, QtCore from PyQt6 import QtWidgets, QtCore
from PyQt5.QtGui import (QFont, QColor, QCursor, QPixmap, QStandardItem, QImage, from PyQt6.QtGui import (QFont, QColor, QCursor, QPixmap, QStandardItem, QImage, QStandardItemModel,
QPalette, QIcon, QFontMetrics, QShowEvent, QPainter, QHelpEvent, QMouseEvent) QPalette, QIcon, QFontMetrics, QShowEvent, QPainter, QHelpEvent, QMouseEvent, QAction)
from PyQt5.QtCore import (Qt, QPersistentModelIndex, QModelIndex, pyqtSignal, from PyQt6.QtCore import (Qt, QPersistentModelIndex, QModelIndex, pyqtSignal,
QCoreApplication, QItemSelectionModel, QThread, QCoreApplication, QItemSelectionModel, QThread,
QSortFilterProxyModel, QSize, QLocale, QAbstractItemModel, QSortFilterProxyModel, QSize, QLocale, QAbstractItemModel,
QEvent, QRect, QPoint, QObject) QEvent, QRect, QPoint, QObject)
from PyQt5.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, from PyQt6.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout,
QAbstractItemView, QVBoxLayout, QLineEdit, QAbstractItemView, QVBoxLayout, QLineEdit,
QStyle, QDialog, QGroupBox, QButtonGroup, QRadioButton, QStyle, QDialog, QGroupBox, QButtonGroup, QRadioButton,
QFileDialog, QWidget, QToolButton, QTreeView, QPlainTextEdit, QFileDialog, QWidget, QToolButton, QTreeView, QPlainTextEdit,
QHeaderView, QApplication, QToolTip, QTreeWidget, QStyledItemDelegate, QHeaderView, QApplication, QToolTip, QTreeWidget, QStyledItemDelegate,
QMenu, QStyleOptionViewItem, QLayout, QLayoutItem, QAbstractButton, QMenu, QStyleOptionViewItem, QLayout, QLayoutItem, QAbstractButton,
QGraphicsEffect, QGraphicsScene, QGraphicsPixmapItem, QSizePolicy, QAction) QGraphicsEffect, QGraphicsScene, QGraphicsPixmapItem, QSizePolicy)
from electrum.i18n import _, languages from electrum.i18n import _, languages
from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, resource_path 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 = QToolButton()
toolbar_button.setIcon(read_QIcon("preferences.png")) toolbar_button.setIcon(read_QIcon("preferences.png"))
toolbar_button.setMenu(menu) toolbar_button.setMenu(menu)
toolbar_button.setPopupMode(QToolButton.InstantPopup) toolbar_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
toolbar_button.setFocusPolicy(Qt.NoFocus) toolbar_button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
toolbar = QHBoxLayout() toolbar = QHBoxLayout()
toolbar.addWidget(QLabel(title)) toolbar.addWidget(QLabel(title))
toolbar.addStretch() toolbar.addStretch()
@ -133,8 +133,9 @@ class MySortModel(QSortFilterProxyModel):
self._sort_role = sort_role self._sort_role = sort_role
def lessThan(self, source_left: QModelIndex, source_right: QModelIndex): def lessThan(self, source_left: QModelIndex, source_right: QModelIndex):
item1 = self.sourceModel().itemFromIndex(source_left) parent_model = self.sourceModel() # type: QStandardItemModel
item2 = self.sourceModel().itemFromIndex(source_right) item1 = parent_model.itemFromIndex(source_left)
item2 = parent_model.itemFromIndex(source_right)
data1 = item1.data(self._sort_role) data1 = item1.data(self._sort_role)
data2 = item2.data(self._sort_role) data2 = item2.data(self._sort_role)
if data1 is not None and data2 is not None: if data1 is not None and data2 is not None:
@ -186,7 +187,7 @@ class ElectrumItemDelegate(QStyledItemDelegate):
if custom_data is None: if custom_data is None:
return super().helpEvent(evt, view, option, idx) return super().helpEvent(evt, view, option, idx)
else: else:
if evt.type() == QEvent.ToolTip: if evt.type() == QEvent.Type.ToolTip:
if custom_data.show_tooltip(evt): if custom_data.show_tooltip(evt):
return True return True
return super().helpEvent(evt, view, option, idx) return super().helpEvent(evt, view, option, idx)
@ -201,10 +202,10 @@ class ElectrumItemDelegate(QStyledItemDelegate):
class MyTreeView(QTreeView): class MyTreeView(QTreeView):
ROLE_CLIPBOARD_DATA = Qt.UserRole + 100 ROLE_CLIPBOARD_DATA = Qt.ItemDataRole.UserRole + 100
ROLE_CUSTOM_PAINT = Qt.UserRole + 101 ROLE_CUSTOM_PAINT = Qt.ItemDataRole.UserRole + 101
ROLE_EDIT_KEY = Qt.UserRole + 102 ROLE_EDIT_KEY = Qt.ItemDataRole.UserRole + 102
ROLE_FILTER_DATA = Qt.UserRole + 103 ROLE_FILTER_DATA = Qt.ItemDataRole.UserRole + 103
filter_columns: Iterable[int] filter_columns: Iterable[int]
@ -229,7 +230,7 @@ class MyTreeView(QTreeView):
self.main_window = main_window self.main_window = main_window
self.config = self.main_window.config if self.main_window else None self.config = self.main_window.config if self.main_window else None
self.stretch_column = stretch_column self.stretch_column = stretch_column
self.setContextMenuPolicy(Qt.CustomContextMenu) self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self.create_menu) self.customContextMenuRequested.connect(self.create_menu)
self.setUniformRowHeights(True) self.setUniformRowHeights(True)
@ -294,7 +295,7 @@ class MyTreeView(QTreeView):
if set_current: if set_current:
assert isinstance(set_current, QPersistentModelIndex) assert isinstance(set_current, QPersistentModelIndex)
assert set_current.isValid() 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]]): 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) # 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.original_model().setHorizontalHeaderLabels(col_names)
self.header().setStretchLastSection(False) self.header().setStretchLastSection(False)
for col_idx in headers: 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) self.header().setSectionResizeMode(col_idx, sm)
def keyPressEvent(self, event): def keyPressEvent(self, event):
if self.itemDelegate().opened: if self.itemDelegate().opened:
return 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()) self.on_activated(self.selectionModel().currentIndex())
return return
super().keyPressEvent(event) super().keyPressEvent(event)
@ -333,7 +334,7 @@ class MyTreeView(QTreeView):
pt.setX(50) pt.setX(50)
self.customContextMenuRequested.emit(pt) 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: this is to prevent:
edit: editing failed edit: editing failed

22
electrum/gui/qt/network_dialog.py

@ -29,11 +29,11 @@ from enum import IntEnum
from typing import Tuple, TYPE_CHECKING from typing import Tuple, TYPE_CHECKING
import threading import threading
from PyQt5.QtCore import Qt, pyqtSignal, QThread from PyQt6.QtCore import Qt, pyqtSignal, QThread
from PyQt5.QtWidgets import (QTreeWidget, QTreeWidgetItem, QMenu, QGridLayout, QComboBox, from PyQt6.QtWidgets import (QTreeWidget, QTreeWidgetItem, QMenu, QGridLayout, QComboBox,
QLineEdit, QDialog, QVBoxLayout, QHeaderView, QCheckBox, QLineEdit, QDialog, QVBoxLayout, QHeaderView, QCheckBox,
QTabWidget, QWidget, QLabel) QTabWidget, QWidget, QLabel)
from PyQt5.QtGui import QIntValidator from PyQt6.QtGui import QIntValidator
from electrum.i18n import _ from electrum.i18n import _
from electrum import constants, blockchain, util from electrum import constants, blockchain, util
@ -86,9 +86,9 @@ class NetworkDialog(QDialog, QtEventListener):
class NodesListWidget(QTreeWidget): class NodesListWidget(QTreeWidget):
"""List of connected servers.""" """List of connected servers."""
SERVER_ADDR_ROLE = Qt.UserRole + 100 SERVER_ADDR_ROLE = Qt.ItemDataRole.UserRole + 100
CHAIN_ID_ROLE = Qt.UserRole + 101 CHAIN_ID_ROLE = Qt.ItemDataRole.UserRole + 101
ITEMTYPE_ROLE = Qt.UserRole + 102 ITEMTYPE_ROLE = Qt.ItemDataRole.UserRole + 102
class ItemType(IntEnum): class ItemType(IntEnum):
CHAIN = 0 CHAIN = 0
@ -103,7 +103,7 @@ class NodesListWidget(QTreeWidget):
def __init__(self, *, network: Network): def __init__(self, *, network: Network):
QTreeWidget.__init__(self) QTreeWidget.__init__(self)
self.setHeaderLabels([_('Server'), _('Height')]) self.setHeaderLabels([_('Server'), _('Height')])
self.setContextMenuPolicy(Qt.CustomContextMenu) self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self.create_menu) self.customContextMenuRequested.connect(self.create_menu)
self.network = network self.network = network
@ -137,10 +137,10 @@ class NodesListWidget(QTreeWidget):
menu.addAction(_("Follow this branch"), do_follow_chain) menu.addAction(_("Follow this branch"), do_follow_chain)
else: else:
return return
menu.exec_(self.viewport().mapToGlobal(position)) menu.exec(self.viewport().mapToGlobal(position))
def keyPressEvent(self, event): 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()) self.on_activated(self.currentItem(), self.currentColumn())
else: else:
QTreeWidget.keyPressEvent(self, event) QTreeWidget.keyPressEvent(self, event)
@ -220,8 +220,8 @@ class NodesListWidget(QTreeWidget):
# headers # headers
h = self.header() h = self.header()
h.setStretchLastSection(False) h.setStretchLastSection(False)
h.setSectionResizeMode(0, QHeaderView.Stretch) h.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
h.setSectionResizeMode(1, QHeaderView.ResizeToContents) h.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
super().update() super().update()

4
electrum/gui/qt/new_channel_dialog.py

@ -1,6 +1,6 @@
from typing import TYPE_CHECKING, Optional 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.i18n import _
from electrum.transaction import PartialTxOutput, PartialTransaction from electrum.transaction import PartialTxOutput, PartialTransaction
@ -134,7 +134,7 @@ class NewChannelDialog(WindowModalDialog):
self.amount_e.setAmount(amount) self.amount_e.setAmount(amount)
def run(self): def run(self):
if not self.exec_(): if not self.exec():
return return
if self.max_button.isChecked() and self.amount_e.get_amount() < self.config.LIGHTNING_MAX_FUNDING_SAT: 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, # 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 import math
from functools import partial from functools import partial
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtGui import QPixmap from PyQt6.QtGui import QPixmap
from PyQt5.QtWidgets import QLineEdit, QLabel, QGridLayout, QVBoxLayout, QCheckBox from PyQt6.QtWidgets import QLineEdit, QLabel, QGridLayout, QVBoxLayout, QCheckBox
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugin import run_hook from electrum.plugin import run_hook
@ -93,7 +93,7 @@ class PasswordLayout(object):
logo_grid.setColumnStretch(1,1) logo_grid.setColumnStretch(1,1)
logo = QLabel() logo = QLabel()
logo.setAlignment(Qt.AlignCenter) logo.setAlignment(Qt.AlignmentFlag.AlignCenter)
logo_grid.addWidget(logo, 0, 0) logo_grid.addWidget(logo, 0, 0)
logo_grid.addWidget(label, 0, 1, 1, 2) logo_grid.addWidget(label, 0, 1, 1, 2)
@ -108,7 +108,7 @@ class PasswordLayout(object):
else: else:
lockfile = "unlock.png" lockfile = "unlock.png"
logo.setPixmap(QPixmap(icon_path(lockfile)) 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(QLabel(msgs[0]), 1, 0)
grid.addWidget(self.new_pw, 1, 1) grid.addWidget(self.new_pw, 1, 1)
@ -196,7 +196,7 @@ class PasswordLayoutForHW(object):
logo_grid.setColumnStretch(1,1) logo_grid.setColumnStretch(1,1)
logo = QLabel() logo = QLabel()
logo.setAlignment(Qt.AlignCenter) logo.setAlignment(Qt.AlignmentFlag.AlignCenter)
logo_grid.addWidget(logo, 0, 0) logo_grid.addWidget(logo, 0, 0)
logo_grid.addWidget(label, 0, 1, 1, 2) logo_grid.addWidget(label, 0, 1, 1, 2)
@ -207,7 +207,7 @@ class PasswordLayoutForHW(object):
else: else:
lockfile = "unlock.png" lockfile = "unlock.png"
logo.setPixmap(QPixmap(icon_path(lockfile)) logo.setPixmap(QPixmap(icon_path(lockfile))
.scaledToWidth(36, mode=Qt.SmoothTransformation)) .scaledToWidth(36, mode=Qt.TransformationMode.SmoothTransformation))
vbox.addLayout(grid) vbox.addLayout(grid)
@ -268,7 +268,7 @@ class ChangePasswordDialogForSW(ChangePasswordDialogBase):
def run(self): def run(self):
try: try:
if not self.exec_(): if not self.exec():
return False, None, None, None return False, None, None, None
return True, self.playout.old_password(), self.playout.new_password(), self.playout.encrypt_cb.isChecked() return True, self.playout.old_password(), self.playout.new_password(), self.playout.encrypt_cb.isChecked()
finally: finally:
@ -290,7 +290,7 @@ class ChangePasswordDialogForHW(ChangePasswordDialogBase):
self.playout = PasswordLayoutForHW(msg) self.playout = PasswordLayoutForHW(msg)
def run(self): def run(self):
if not self.exec_(): if not self.exec():
return False, None return False, None
return True, self.playout.encrypt_cb.isChecked() return True, self.playout.encrypt_cb.isChecked()
@ -314,7 +314,7 @@ class PasswordDialog(WindowModalDialog):
def run(self): def run(self):
try: try:
if not self.exec_(): if not self.exec():
return return
return self.pw.text() return self.pw.text()
finally: finally:

12
electrum/gui/qt/paytoedit.py

@ -26,10 +26,10 @@
from functools import partial from functools import partial
from typing import Optional, TYPE_CHECKING from typing import Optional, TYPE_CHECKING
from PyQt5.QtCore import Qt, QTimer, QSize from PyQt6.QtCore import Qt, QTimer, QSize
from PyQt5.QtCore import QObject, pyqtSignal from PyQt6.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QFontMetrics, QFont from PyQt6.QtGui import QFontMetrics, QFont
from PyQt5.QtWidgets import QApplication, QTextEdit, QWidget, QLineEdit, QStackedLayout, QSizePolicy from PyQt6.QtWidgets import QApplication, QTextEdit, QWidget, QLineEdit, QStackedLayout, QSizePolicy
from electrum.payment_identifier import PaymentIdentifier from electrum.payment_identifier import PaymentIdentifier
from electrum.logging import Logger from electrum.logging import Logger
@ -85,7 +85,7 @@ class ResizingTextEdit(QTextEdit):
h = min(max(h, self.heightMin), self.heightMax) h = min(max(h, self.heightMin), self.heightMax)
self.setMinimumHeight(int(h)) self.setMinimumHeight(int(h))
self.setMaximumHeight(int(h)) self.setMaximumHeight(int(h))
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.verticalScrollBar().setHidden(docHeight + self.verticalMargins < self.heightMax) self.verticalScrollBar().setHidden(docHeight + self.verticalMargins < self.heightMax)
self.setLineWrapMode(QTextEdit.LineWrapMode.WidgetWidth) self.setLineWrapMode(QTextEdit.LineWrapMode.WidgetWidth)
self.resized.emit() self.resized.emit()
@ -199,7 +199,7 @@ class PayToEdit(QWidget, Logger, GenericInputHandler):
self.line_edit.setText(text) self.line_edit.setText(text)
self.text_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: if self.multiline:
self.text_edit.setFocus(reason) self.text_edit.setFocus(reason)
else: else:

2
electrum/gui/qt/plugins_dialog.py

@ -1,7 +1,7 @@
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from functools import partial 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.i18n import _
from electrum.gui import messages from electrum.gui import messages

10
electrum/gui/qt/qrcodewidget.py

@ -3,10 +3,10 @@ from typing import Optional
import qrcode import qrcode
import qrcode.exceptions import qrcode.exceptions
from PyQt5.QtGui import QColor, QPen from PyQt6.QtGui import QColor, QPen
import PyQt5.QtGui as QtGui import PyQt6.QtGui as QtGui
from PyQt5.QtCore import Qt, QRect from PyQt6.QtCore import Qt, QRect
from PyQt5.QtWidgets import ( from PyQt6.QtWidgets import (
QApplication, QVBoxLayout, QTextEdit, QHBoxLayout, QPushButton, QWidget, QApplication, QVBoxLayout, QTextEdit, QHBoxLayout, QPushButton, QWidget,
QFileDialog, QFileDialog,
) )
@ -63,7 +63,7 @@ class QRCodeWidget(QWidget):
grey = QColor(196, 196, 196, 255) grey = QColor(196, 196, 196, 255)
white = QColor(255, 255, 255, 255) white = QColor(255, 255, 255, 255)
black_pen = QPen(black) if self.isEnabled() else QPen(grey) 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: if not self.qr:
qp = QtGui.QPainter() qp = QtGui.QPainter()

18
electrum/gui/qt/qrreader/__init__.py

@ -11,7 +11,7 @@
# to access the camera # to access the camera
# - zbar fails to access the camera on macOS # - zbar fails to access the camera on macOS
# - qtmultimedia seems to support more cameras on Windows than zbar # - 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" # in particular, on debian, you need both "python3-pyqt5" and "python3-pyqt5.qtmultimedia"
# - older versions of qtmultimedia don't seem to work reliably # - older versions of qtmultimedia don't seem to work reliably
# #
@ -24,8 +24,8 @@
import sys import sys
from typing import Callable, Optional, TYPE_CHECKING, Mapping, Sequence from typing import Callable, Optional, TYPE_CHECKING, Mapping, Sequence
from PyQt5.QtWidgets import QMessageBox, QWidget from PyQt6.QtWidgets import QMessageBox, QWidget
from PyQt5.QtGui import QImage from PyQt6.QtGui import QImage
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import UserFacingException from electrum.util import UserFacingException
@ -59,12 +59,13 @@ def scan_qrcode(
def scan_qr_from_image(image: QImage) -> Sequence[QrCodeResult]: def scan_qr_from_image(image: QImage) -> Sequence[QrCodeResult]:
"""Might raise exception: MissingQrDetectionLib.""" """Might raise exception: MissingQrDetectionLib."""
qr_reader = get_qr_reader() 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( 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.bytesPerLine(),
image_y800.width(), image_y800.width(),
image_y800.height() image_y800.height(),
) )
return res return res
@ -124,10 +125,11 @@ def _scan_qrcode_using_qtmultimedia(
try: try:
from .qtmultimedia import QrReaderCameraDialog, CameraError from .qtmultimedia import QrReaderCameraDialog, CameraError
except ImportError as e: except ImportError as e:
icon = QMessageBox.Warning icon = QMessageBox.Icon.Warning
title = _("QR Reader Error") title = _("QR Reader Error")
message = _("QR reader failed to load. This may happen if " 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): if isinstance(parent, MessageBoxMixin):
parent.msg_box(title=title, text=message, icon=icon, parent=None) parent.msg_box(title=title, text=message, icon=icon, parent=None)
else: else:

10
electrum/gui/qt/qrreader/qtmultimedia/__init__.py

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# #
# Electron Cash - lightweight Bitcoin client
# Copyright (C) 2019 Axel Gembe <derago@gmail.com> # Copyright (C) 2019 Axel Gembe <derago@gmail.com>
# Copyright (c) 2024 The Electrum developers
# #
# Permission is hereby granted, free of charge, to any person # Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files # obtaining a copy of this software and associated documentation files
@ -26,7 +26,7 @@
from typing import Mapping from typing import Mapping
from .camera_dialog import (QrReaderCameraDialog, CameraError, NoCamerasFound, from .camera_dialog import (QrReaderCameraDialog, CameraError, NoCamerasFound,
NoCameraResolutionsFound) get_camera_path)
from .validator import (QrReaderValidatorResult, AbstractQrReaderValidator, from .validator import (QrReaderValidatorResult, AbstractQrReaderValidator,
QrReaderValidatorCounting, QrReaderValidatorColorizing, QrReaderValidatorCounting, QrReaderValidatorColorizing,
QrReaderValidatorStrong, QrReaderValidatorCounted) QrReaderValidatorStrong, QrReaderValidatorCounted)
@ -34,6 +34,6 @@ from .validator import (QrReaderValidatorResult, AbstractQrReaderValidator,
def find_system_cameras() -> Mapping[str, str]: def find_system_cameras() -> Mapping[str, str]:
"""Returns a camera_description -> camera_path map.""" """Returns a camera_description -> camera_path map."""
from PyQt5.QtMultimedia import QCameraInfo from PyQt6.QtMultimedia import QMediaDevices
system_cameras = QCameraInfo.availableCameras() system_cameras = QMediaDevices.videoInputs()
return {cam.description(): cam.deviceName() for cam in system_cameras} 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 #!/usr/bin/env python3
# #
# Electron Cash - lightweight Bitcoin client
# Copyright (C) 2019 Axel Gembe <derago@gmail.com> # Copyright (C) 2019 Axel Gembe <derago@gmail.com>
# Copyright (c) 2024 The Electrum developers
# #
# Permission is hereby granted, free of charge, to any person # Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files # obtaining a copy of this software and associated documentation files
@ -29,10 +29,10 @@ import sys
import os import os
from typing import List, Optional from typing import List, Optional
from PyQt5.QtMultimedia import QCameraInfo, QCamera, QCameraViewfinderSettings from PyQt6.QtMultimedia import QMediaDevices, QCamera, QMediaCaptureSession, QCameraDevice
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QCheckBox, QPushButton, QLabel, QWidget from PyQt6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QCheckBox, QPushButton, QLabel, QWidget
from PyQt5.QtGui import QImage, QPixmap from PyQt6.QtGui import QImage, QPixmap
from PyQt5.QtCore import QSize, QRect, Qt, pyqtSignal, PYQT_VERSION from PyQt6.QtCore import QSize, QRect, Qt, pyqtSignal, PYQT_VERSION
from electrum.simple_config import SimpleConfig from electrum.simple_config import SimpleConfig
from electrum.i18n import _ from electrum.i18n import _
@ -55,8 +55,10 @@ class NoCamerasFound(CameraError):
''' Raised by start_scan if no usable cameras were found. Interested ''' Raised by start_scan if no usable cameras were found. Interested
code can catch this specific exception.''' 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): class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog):
""" """
@ -84,6 +86,7 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog):
self.qr_frame_counter: int = 0 self.qr_frame_counter: int = 0
self.last_qr_scan_ts: float = 0.0 self.last_qr_scan_ts: float = 0.0
self.camera: QCamera = None self.camera: QCamera = None
self.media_capture_session: QMediaCaptureSession = None
self._error_message: str = None self._error_message: str = None
self._ok_done: bool = False self._ok_done: bool = False
self.camera_sc_conn = None self.camera_sc_conn = None
@ -96,10 +99,10 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog):
# Set up the window, add the maximize button # Set up the window, add the maximize button
flags = self.windowFlags() flags = self.windowFlags()
flags = flags | Qt.WindowMaximizeButtonHint flags = flags | Qt.WindowType.WindowMaximizeButtonHint
self.setWindowFlags(flags) self.setWindowFlags(flags)
self.setWindowTitle(_("Scan QR Code")) 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 # Create video widget and fixed aspect ratio layout to contain it
self.video_widget = QrReaderVideoWidget() self.video_widget = QrReaderVideoWidget()
@ -114,12 +117,6 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog):
vbox.setContentsMargins(0, 0, 0, 0) vbox.setContentsMargins(0, 0, 0, 0)
vbox.addLayout(self.video_layout) 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 # Create a layout for the controls
controls_layout = QHBoxLayout() controls_layout = QHBoxLayout()
controls_layout.addStretch(2) 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 -- # 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 # and we do it from within callback functions. If you don't use
# queued connections here, bad things can happen. # queued connections here, bad things can happen.
self.finished.connect(self._boilerplate_cleanup, Qt.QueuedConnection) self.finished.connect(self._boilerplate_cleanup, Qt.ConnectionType.QueuedConnection)
self.finished.connect(self._on_finished, Qt.QueuedConnection) self.finished.connect(self._on_finished, Qt.ConnectionType.QueuedConnection)
def _on_flip_x_changed(self, _state: int): def _on_flip_x_changed(self, _state: int):
self.config.QR_READER_FLIP_X = self.flip_x.isChecked() 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 @staticmethod
def _get_crop(resolution: QSize, scan_size: int) -> QRect: 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 scan_pos_y = (resolution.height() - scan_size) // 2
return QRect(scan_pos_x, scan_pos_y, scan_size, scan_size) 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 = ''): def start_scan(self, device: str = ''):
""" """
Scans a QR code from the given camera device. Scans a QR code from the given camera device.
@ -237,17 +175,17 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog):
device_info = None device_info = None
for camera in QCameraInfo.availableCameras(): for camera in QMediaDevices.videoInputs():
if camera.deviceName() == device: if get_camera_path(camera) == device:
device_info = camera device_info = camera
break break
if not device_info: if not device_info:
self.logger.info('Failed to open selected camera, trying to use default camera') 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(): 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._init_stats()
self.qrreader_res = [] self.qrreader_res = []
@ -259,29 +197,14 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog):
self.logger.info("Warning: start_scan already called for this instance.") self.logger.info("Warning: start_scan already called for this instance.")
self.camera = QCamera(device_info) self.camera = QCamera(device_info)
self.camera.setViewfinder(self.video_surface) self.camera.start()
self.camera.setCaptureMode(QCamera.CaptureViewfinder) self.camera.errorOccurred.connect(self._on_camera_error) # log the errors we get, if any, for debugging
# this operates on camera from within the signal handler, so should be a queued connection self.media_capture_session = QMediaCaptureSession()
self.camera_sc_conn = self.camera.statusChanged.connect(self._on_camera_status_changed, Qt.QueuedConnection) self.media_capture_session.setCamera(self.camera)
self.camera.error.connect(self._on_camera_error) # log the errors we get, if any, for debugging self.media_capture_session.setVideoSink(self.video_surface)
# Camera needs to be loaded to query resolutions, this tries to open the camera
self.camera.load() self.open()
_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'))
def _set_resolution(self, resolution: QSize): def _set_resolution(self, resolution: QSize):
self.resolution = resolution self.resolution = resolution
@ -297,50 +220,8 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog):
# Set up the crop blur effect # Set up the crop blur effect
self.crop_blur_effect.setCrop(self.qr_crop) self.crop_blur_effect.setCrop(self.qr_crop)
def _on_camera_status_changed(self, status: QCamera.Status): def _on_camera_error(self, error: QCamera.Error, error_str: str):
if self._ok_done: self.logger.info(f"QCamera error: {error}. {error_str}")
# 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 accept(self): def accept(self):
self._ok_done = True # immediately blocks further processing self._ok_done = True # immediately blocks further processing
@ -357,15 +238,11 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog):
def _close_camera(self): def _close_camera(self):
if self.camera: if self.camera:
self.camera.setViewfinder(None) self.camera.stop()
if self.camera_sc_conn:
self.camera.statusChanged.disconnect(self.camera_sc_conn)
self.camera_sc_conn = None
self.camera.unload()
self.camera = None self.camera = None
def _on_finished(self, code): 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 and self.validator_res.accepted
and self.validator_res.simple_result) and self.validator_res.simple_result)
or '' ) or '' )
@ -374,7 +251,7 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog):
self.logger.info(f'closed {res}') 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): def _on_frame_available(self, frame: QImage):
if self._ok_done: if self._ok_done:
@ -382,12 +259,7 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog):
self.frame_id += 1 self.frame_id += 1
if frame.size() != self.resolution: self._set_resolution(frame.size())
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())
flip_x = self.flip_x.isChecked() flip_x = self.flip_x.isChecked()
@ -400,14 +272,16 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog):
# Convert to Y800 / GREY FourCC (single 8-bit channel) # Convert to Y800 / GREY FourCC (single 8-bit channel)
# This creates a copy, so we don't need to keep the frame around anymore # 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 # Read the QR codes from the frame
self.qrreader_res = self.qrreader.read_qr_code( 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.bytesPerLine(),
frame_y800.width(), 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 # 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 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from PyQt5.QtWidgets import QGraphicsBlurEffect, QGraphicsEffect from PyQt6.QtWidgets import QGraphicsBlurEffect, QGraphicsEffect
from PyQt5.QtGui import QPainter, QTransform, QRegion from PyQt6.QtGui import QPainter, QTransform, QRegion
from PyQt5.QtCore import QObject, QRect, QPoint, Qt from PyQt6.QtCore import QObject, QRect, QPoint, Qt
class QrReaderCropBlurEffect(QGraphicsBlurEffect): class QrReaderCropBlurEffect(QGraphicsBlurEffect):
@ -56,7 +56,7 @@ class QrReaderCropBlurEffect(QGraphicsBlurEffect):
# Fill with black and set opacity so that the blurred region is drawn darker # Fill with black and set opacity so that the blurred region is drawn darker
if self.BLUR_DARKEN > 0.0: 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) painter.setOpacity(1 - self.BLUR_DARKEN)
# Draw the blur effect # Draw the blur effect
@ -67,7 +67,7 @@ class QrReaderCropBlurEffect(QGraphicsBlurEffect):
painter.setOpacity(1.0) painter.setOpacity(1.0)
# Get the source pixmap # 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()) painter.setWorldTransform(QTransform())
# Get the source by adding the offset to the crop location # 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 typing import List, Dict, Callable, Any
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from PyQt5.QtGui import QColor from PyQt6.QtGui import QColor
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from electrum.i18n import _ from electrum.i18n import _
from electrum.qrreader import QrCodeResult from electrum.qrreader import QrCodeResult
@ -101,8 +101,8 @@ class QrReaderValidatorColorizing(QrReaderValidatorCounting):
based on the counts maintained by `QrReaderValidatorCounting`. based on the counts maintained by `QrReaderValidatorCounting`.
""" """
WEAK_COLOR: QColor = QColor(Qt.red) WEAK_COLOR: QColor = QColor(Qt.GlobalColor.red)
STRONG_COLOR: QColor = QColor(Qt.green) STRONG_COLOR: QColor = QColor(Qt.GlobalColor.green)
strong_count: int = 10 strong_count: int = 10

20
electrum/gui/qt/qrreader/qtmultimedia/video_overlay.py

@ -25,9 +25,9 @@
from typing import List from typing import List
from PyQt5.QtWidgets import QWidget from PyQt6.QtWidgets import QWidget
from PyQt5.QtGui import QPainter, QPaintEvent, QPen, QPainterPath, QColor, QTransform from PyQt6.QtGui import QPainter, QPaintEvent, QPen, QPainterPath, QColor, QTransform
from PyQt5.QtCore import QPoint, QSize, QRect, QRectF, Qt from PyQt6.QtCore import QPoint, QSize, QRect, QRectF, Qt
from electrum.qrreader import QrCodeResult from electrum.qrreader import QrCodeResult
@ -53,16 +53,16 @@ class QrReaderVideoOverlay(QWidget):
self.resolution = None self.resolution = None
self.qr_outline_pen = QPen() 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.setWidth(3)
self.qr_outline_pen.setStyle(Qt.DotLine) self.qr_outline_pen.setStyle(Qt.PenStyle.DotLine)
self.text_pen = QPen() 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 = QPen()
self.bg_rect_pen.setColor(Qt.black) self.bg_rect_pen.setColor(Qt.GlobalColor.black)
self.bg_rect_pen.setStyle(Qt.DotLine) self.bg_rect_pen.setStyle(Qt.PenStyle.DotLine)
self.bg_rect_fill = QColor(255, 255, 255, int(255 * self.BG_RECT_OPACITY)) self.bg_rect_fill = QColor(255, 255, 255, int(255 * self.BG_RECT_OPACITY))
def set_results(self, results: List[QrCodeResult], flip_x: bool, def set_results(self, results: List[QrCodeResult], flip_x: bool,
@ -102,7 +102,7 @@ class QrReaderVideoOverlay(QWidget):
return QPoint(point[0], point[1]) return QPoint(point[0], point[1])
# Starting from here we care about AA # Starting from here we care about AA
painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.RenderHint.Antialiasing)
# Draw all the QR code results # Draw all the QR code results
for res in self.results: for res in self.results:
@ -148,7 +148,7 @@ class QrReaderVideoOverlay(QWidget):
bg_rect = QRect(bg_rect_pos, bg_rect_size) bg_rect = QRect(bg_rect_pos, bg_rect_size)
bg_rect_path = QPainterPath() bg_rect_path = QPainterPath()
radius = self.BG_RECT_CORNER_RADIUS 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.setPen(self.bg_rect_pen)
painter.fillPath(bg_rect_path, self.bg_rect_fill) painter.fillPath(bg_rect_path, self.bg_rect_fill)
painter.drawPath(bg_rect_path) painter.drawPath(bg_rect_path)

42
electrum/gui/qt/qrreader/qtmultimedia/video_surface.py

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# #
# Electron Cash - lightweight Bitcoin client
# Copyright (C) 2019 Axel Gembe <derago@gmail.com> # Copyright (C) 2019 Axel Gembe <derago@gmail.com>
# Copyright (c) 2024 The Electrum developers
# #
# Permission is hereby granted, free of charge, to any person # Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files # obtaining a copy of this software and associated documentation files
@ -25,10 +25,9 @@
from typing import List from typing import List
from PyQt5.QtMultimedia import (QVideoFrame, QAbstractVideoBuffer, QAbstractVideoSurface, from PyQt6.QtMultimedia import (QVideoFrame, QVideoFrameFormat, QVideoSink)
QVideoSurfaceFormat) from PyQt6.QtGui import QImage
from PyQt5.QtGui import QImage from PyQt6.QtCore import QObject, pyqtSignal
from PyQt5.QtCore import QObject, pyqtSignal
from electrum.i18n import _ from electrum.i18n import _
from electrum.logging import get_logger from electrum.logging import get_logger
@ -37,7 +36,7 @@ from electrum.logging import get_logger
_logger = get_logger(__name__) _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 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. necessary and sends them to listeners via the frame_available event.
@ -45,27 +44,28 @@ class QrReaderVideoSurface(QAbstractVideoSurface):
def __init__(self, parent: QObject = None): def __init__(self, parent: QObject = None):
super().__init__(parent) 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(): if not frame.isValid():
return False return
image_format = QVideoFrame.imageFormatFromPixelFormat(frame.pixelFormat()) image_format = QVideoFrameFormat.imageFormatFromPixelFormat(frame.pixelFormat())
if image_format == QVideoFrame.Format_Invalid: if image_format == QVideoFrameFormat.PixelFormat.Format_Invalid:
_logger.info(_('QR code scanner for video frame with invalid pixel format')) _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')) _logger.info(_('QR code scanner failed to map video frame'))
return False return
try: 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 # 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_x = surface_format.isMirrored()
flip_y = surface_format.scanLineDirection() == QVideoSurfaceFormat.BottomToTop flip_y = surface_format.scanLineDirection() == QVideoFrameFormat.Direction.BottomToTop
# Mirror the image if needed # Mirror the image if needed
if flip_x or flip_y: if flip_x or flip_y:
@ -78,14 +78,4 @@ class QrReaderVideoSurface(QAbstractVideoSurface):
self.frame_available.emit(img) 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) 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 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from PyQt5.QtWidgets import QWidget from PyQt6.QtWidgets import QWidget
from PyQt5.QtGui import QPixmap, QPainter, QPaintEvent from PyQt6.QtGui import QPixmap, QPainter, QPaintEvent
class QrReaderVideoWidget(QWidget): class QrReaderVideoWidget(QWidget):
@ -44,7 +44,7 @@ class QrReaderVideoWidget(QWidget):
return return
painter = QPainter(self) painter = QPainter(self)
if self.USE_BILINEAR_FILTER: if self.USE_BILINEAR_FILTER:
painter.setRenderHint(QPainter.SmoothPixmapTransform) painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
painter.drawPixmap(self.rect(), self.pixmap, self.pixmap.rect()) painter.drawPixmap(self.rect(), self.pixmap, self.pixmap.rect())
def setPixmap(self, pixmap: QPixmap): def setPixmap(self, pixmap: QPixmap):

6
electrum/gui/qt/qrtextedit.py

@ -20,7 +20,7 @@ class ShowQRTextEdit(ButtonsTextEdit):
def contextMenuEvent(self, e): def contextMenuEvent(self, e):
m = self.createStandardContextMenu() m = self.createStandardContextMenu()
m.addAction(read_QIcon(get_iconname_qrcode()), _("Show as QR code"), self.on_qr_show_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())
class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin): 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(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("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.addAction(read_QIcon("file.png"), _("Read file"), self.on_input_file)
m.exec_(e.globalPos()) m.exec(e.globalPos())
class ScanShowQRTextEdit(ButtonsTextEdit, MessageBoxMixin): 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(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("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.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 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtWidgets import QHBoxLayout, QWidget from PyQt6.QtWidgets import QHBoxLayout, QWidget
from .qrcodewidget import QRCodeWidget from .qrcodewidget import QRCodeWidget
@ -38,7 +38,7 @@ class QR_Window(QWidget):
self.main_window = win self.main_window = win
self.setWindowTitle('Electrum - '+_('Payment Request')) self.setWindowTitle('Electrum - '+_('Payment Request'))
self.setMinimumSize(800, 800) self.setMinimumSize(800, 800)
self.setFocusPolicy(Qt.NoFocus) self.setFocusPolicy(Qt.FocusPolicy.NoFocus)
main_box = QHBoxLayout() main_box = QHBoxLayout()
self.qrw = QRCodeWidget() self.qrw = QRCodeWidget()
main_box.addWidget(self.qrw, 1) main_box.addWidget(self.qrw, 1)

2
electrum/gui/qt/rate_limiter.py

@ -7,7 +7,7 @@ import threading
import time import time
import weakref import weakref
from PyQt5.QtCore import QObject, QTimer from PyQt6.QtCore import QObject, QTimer
from electrum.logging import Logger, get_logger from electrum.logging import Logger, get_logger

10
electrum/gui/qt/rbf_dialog.py

@ -4,8 +4,8 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtWidgets import (QCheckBox, QLabel, QVBoxLayout, QGridLayout, QWidget, from PyQt6.QtWidgets import (QCheckBox, QLabel, QVBoxLayout, QGridLayout, QWidget,
QPushButton, QHBoxLayout, QComboBox) QPushButton, QHBoxLayout, QComboBox)
from .amountedit import FeerateEdit 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.addItems([strat.text() for strat in self._strategies])
self.method_combo.setCurrentIndex(def_strat_idx) self.method_combo.setCurrentIndex(def_strat_idx)
self.method_combo.currentIndexChanged.connect(self.trigger_update) 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 = 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.setAmount(self.old_tx_size)
old_size_label.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet()) old_size_label.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
current_fee_hbox = QHBoxLayout() current_fee_hbox = QHBoxLayout()
@ -85,7 +85,7 @@ class _BaseRBFDialog(TxEditor):
return grid return grid
def run(self) -> None: def run(self) -> None:
if not self.exec_(): if not self.exec():
return return
if self.is_preview: if self.is_preview:
self.main_window.show_transaction(self.tx) self.main_window.show_transaction(self.tx)

6
electrum/gui/qt/rebalance_dialog.py

@ -1,7 +1,7 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import pyqtSignal from PyQt6.QtCore import pyqtSignal
from PyQt5.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton
from electrum.i18n import _ from electrum.i18n import _
from electrum.lnchannel import Channel from electrum.lnchannel import Channel
@ -68,7 +68,7 @@ class RebalanceDialog(WindowModalDialog):
self.ok_button.setEnabled(b) self.ok_button.setEnabled(b)
def run(self): def run(self):
if not self.exec_(): if not self.exec():
return return
amount_msat = self.amount_e.get_amount() * 1000 amount_msat = self.amount_e.get_amount() * 1000
coro = self.wallet.lnworker.rebalance_channels(self.chan1, self.chan2, amount_msat=amount_msat) 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 typing import Optional, TYPE_CHECKING
from PyQt5.QtGui import QFont, QCursor from PyQt6.QtGui import QFont, QCursor, QMouseEvent
from PyQt5.QtCore import Qt, QSize from PyQt6.QtCore import Qt, QSize
from PyQt5.QtWidgets import (QComboBox, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QTextEdit, from PyQt6.QtWidgets import (QComboBox, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QTextEdit,
QHBoxLayout, QPushButton, QWidget, QSizePolicy, QFrame) QHBoxLayout, QPushButton, QWidget, QSizePolicy, QFrame)
from electrum.bitcoin import is_address 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 '') self.fiat_receive_e = AmountEdit(self.fx.get_currency if self.fx else '')
if not self.fx or not self.fx.is_enabled(): if not self.fx or not self.fx.is_enabled():
self.fiat_receive_e.setVisible(False) 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) 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 = QTextEdit()
self.receive_e.setFont(QFont(MONOSPACE_FONT)) self.receive_e.setFont(QFont(MONOSPACE_FONT))
self.receive_e.setReadOnly(True) self.receive_e.setReadOnly(True)
self.receive_e.setContextMenuPolicy(Qt.NoContextMenu) self.receive_e.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)
self.receive_e.setTextInteractionFlags(Qt.NoTextInteraction) self.receive_e.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction)
self.receive_e.textChanged.connect(self.update_receive_widgets) self.receive_e.textChanged.connect(self.update_receive_widgets)
self.receive_qr = QRCodeWidget(manual_size=True) self.receive_qr = QRCodeWidget(manual_size=True)
@ -120,7 +120,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
self.receive_widget = ReceiveWidget( self.receive_widget = ReceiveWidget(
self, self.receive_e, self.receive_qr, self.receive_help_widget) 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) receive_widget_sp.setRetainSizeWhenHidden(True)
self.receive_widget.setSizePolicy(receive_widget_sp) self.receive_widget.setSizePolicy(receive_widget_sp)
self.receive_widget.setVisible(False) self.receive_widget.setVisible(False)
@ -230,8 +230,8 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
self.window.do_copy(text, title=title) self.window.do_copy(text, title=title)
self.update_receive_qr_window() self.update_receive_qr_window()
def do_copy(self, e): def do_copy(self, e: 'QMouseEvent'):
if e.button() != Qt.LeftButton: if e.button() != Qt.MouseButton.LeftButton:
return return
text, data, help_text, title = self.get_tab_data() text, data, help_text, title = self.get_tab_data()
self.window.do_copy(text, title=title) self.window.do_copy(text, title=title)
@ -379,11 +379,11 @@ class ReceiveWidget(QWidget):
for w in [textedit, qr]: for w in [textedit, qr]:
w.mousePressEvent = receive_tab.do_copy 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): 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; }") help_widget.setStyleSheet("QLabel {border:1px solid gray; border-radius:2px; }")
hbox = QHBoxLayout() hbox = QHBoxLayout()
@ -422,5 +422,5 @@ class ReceiveWidget(QWidget):
class FramedWidget(QFrame): class FramedWidget(QFrame):
def __init__(self): def __init__(self):
QFrame.__init__(self) QFrame.__init__(self)
self.setFrameStyle(QFrame.StyledPanel) self.setFrameStyle(QFrame.Shape.StyledPanel)
self.setStyleSheet("FramedWidget {border:1px solid gray; border-radius:2px; }") self.setStyleSheet("FramedWidget {border:1px solid gray; border-radius:2px; }")

23
electrum/gui/qt/request_list.py

@ -26,9 +26,9 @@
import enum import enum
from typing import Optional, TYPE_CHECKING from typing import Optional, TYPE_CHECKING
from PyQt5.QtGui import QStandardItemModel, QStandardItem from PyQt6.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QMenu, QAbstractItemView from PyQt6.QtWidgets import QMenu, QAbstractItemView
from PyQt5.QtCore import Qt, QItemSelectionModel, QModelIndex from PyQt6.QtCore import Qt, QItemSelectionModel, QModelIndex
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import format_time from electrum.util import format_time
@ -43,9 +43,9 @@ if TYPE_CHECKING:
from .receive_tab import ReceiveTab from .receive_tab import ReceiveTab
ROLE_REQUEST_TYPE = Qt.UserRole ROLE_REQUEST_TYPE = Qt.ItemDataRole.UserRole
ROLE_KEY = Qt.UserRole + 1 ROLE_KEY = Qt.ItemDataRole.UserRole + 1
ROLE_SORT_ORDER = Qt.UserRole + 2 ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 2
class RequestList(MyTreeView): class RequestList(MyTreeView):
@ -86,14 +86,15 @@ class RequestList(MyTreeView):
self.setModel(self.proxy) self.setModel(self.proxy)
self.setSortingEnabled(True) self.setSortingEnabled(True)
self.selectionModel().currentRowChanged.connect(self.item_changed) self.selectionModel().currentRowChanged.connect(self.item_changed)
self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
def set_current_key(self, key): def set_current_key(self, key):
for i in range(self.model().rowCount()): for i in range(self.model().rowCount()):
item = self.model().index(i, self.Columns.DATE) item = self.model().index(i, self.Columns.DATE)
row_key = item.data(ROLE_KEY) row_key = item.data(ROLE_KEY)
if key == row_key: if key == row_key:
self.selectionModel().setCurrentIndex(item, QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows) self.selectionModel().setCurrentIndex(
item, QItemSelectionModel.SelectionFlag.SelectCurrent | QItemSelectionModel.SelectionFlag.Rows)
break break
def get_current_key(self): def get_current_key(self):
@ -164,7 +165,7 @@ class RequestList(MyTreeView):
self.filter() self.filter()
self.proxy.setDynamicSortFilter(True) self.proxy.setDynamicSortFilter(True)
# sort requests by date # sort requests by date
self.sortByColumn(self.Columns.DATE, Qt.DescendingOrder) self.sortByColumn(self.Columns.DATE, Qt.SortOrder.DescendingOrder)
self.hide_if_empty() self.hide_if_empty()
if current_key is not None: if current_key is not None:
self.set_current_key(current_key) self.set_current_key(current_key)
@ -183,7 +184,7 @@ class RequestList(MyTreeView):
keys = [item.data(ROLE_KEY) for item in items] keys = [item.data(ROLE_KEY) for item in items]
menu = QMenu(self) menu = QMenu(self)
menu.addAction(_("Delete requests"), lambda: self.delete_requests(keys)) menu.addAction(_("Delete requests"), lambda: self.delete_requests(keys))
menu.exec_(self.viewport().mapToGlobal(position)) menu.exec(self.viewport().mapToGlobal(position))
return return
idx = self.indexAt(position) idx = self.indexAt(position)
# TODO use siblingAtColumn when min Qt version is >=5.11 # 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(_("View in web browser"), lambda: webopen(req['view_url']))
menu.addAction(_("Delete"), lambda: self.delete_requests([key])) menu.addAction(_("Delete"), lambda: self.delete_requests([key]))
run_hook('receive_list_menu', self.main_window, menu, 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): def delete_requests(self, keys):
self.wallet.delete_requests(keys) self.wallet.delete_requests(keys)

10
electrum/gui/qt/seed_dialog.py

@ -25,9 +25,9 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, pyqtSignal from PyQt6.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QPixmap from PyQt6.QtGui import QPixmap
from PyQt5.QtWidgets import (QVBoxLayout, QCheckBox, QHBoxLayout, QLineEdit, from PyQt6.QtWidgets import (QVBoxLayout, QCheckBox, QHBoxLayout, QLineEdit,
QLabel, QCompleter, QDialog, QStyledItemDelegate, QLabel, QCompleter, QDialog, QStyledItemDelegate,
QScrollArea, QWidget, QPushButton) QScrollArea, QWidget, QPushButton)
@ -124,7 +124,7 @@ class SeedLayout(QVBoxLayout):
vbox.addWidget(seed_type_choice) vbox.addWidget(seed_type_choice)
vbox.addLayout(Buttons(OkButton(dialog))) vbox.addLayout(Buttons(OkButton(dialog)))
if not dialog.exec_(): if not dialog.exec():
return None return None
self.is_ext = cb_ext.isChecked() if 'ext' in self.options else False 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' self.seed_type = seed_type_choice.selected_key if len(seed_types) >= 2 else 'electrum'
@ -172,7 +172,7 @@ class SeedLayout(QVBoxLayout):
if icon: if icon:
logo = QLabel() logo = QLabel()
logo.setPixmap(QPixmap(icon_path("seed.png")) logo.setPixmap(QPixmap(icon_path("seed.png"))
.scaledToWidth(64, mode=Qt.SmoothTransformation)) .scaledToWidth(64, mode=Qt.TransformationMode.SmoothTransformation))
logo.setMaximumWidth(60) logo.setMaximumWidth(60)
hbox.addWidget(logo) hbox.addWidget(logo)
hbox.addWidget(self.seed_e) hbox.addWidget(self.seed_e)

11
electrum/gui/qt/send_tab.py

@ -4,10 +4,11 @@
from decimal import Decimal from decimal import Decimal
from typing import Optional, TYPE_CHECKING, Sequence, List, Callable, Union, Mapping 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) QWidget, QToolTip, QPushButton, QApplication)
from PyQt5.QtGui import QMovie, QColor from PyQt6.QtGui import QMovie, QColor
from electrum.i18n import _ from electrum.i18n import _
from electrum.logging import Logger 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.setIcon(read_QIcon('copy.png'))
self.paste_button.setToolTip(_('Paste invoice from clipboard')) self.paste_button.setToolTip(_('Paste invoice from clipboard'))
self.paste_button.setMaximumWidth(35) 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) grid.addWidget(self.paste_button, 0, 5)
self.spinner = QMovie(icon_path('spinner.gif')) self.spinner = QMovie(icon_path('spinner.gif'))
@ -141,7 +142,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
self.spinner_l.setMargin(5) self.spinner_l.setMargin(5)
self.spinner_l.setVisible(False) self.spinner_l.setVisible(False)
self.spinner_l.setMovie(self.spinner) 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 = EnterButton(_("Save"), self.do_save_invoice)
self.save_button.setEnabled(False) self.save_button.setEnabled(False)

16
electrum/gui/qt/settings_dialog.py

@ -26,8 +26,8 @@
import ast import ast
from typing import Optional, TYPE_CHECKING from typing import Optional, TYPE_CHECKING
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtWidgets import (QComboBox, QTabWidget, QDialog, from PyQt6.QtWidgets import (QComboBox, QTabWidget, QDialog,
QSpinBox, QFileDialog, QCheckBox, QLabel, QSpinBox, QFileDialog, QCheckBox, QLabel,
QVBoxLayout, QGridLayout, QLineEdit, QVBoxLayout, QGridLayout, QLineEdit,
QPushButton, QWidget, QHBoxLayout, QSlider) 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."), _("Without this option, Electrum will need to sync with the Lightning network on every start."),
_("This may impact the reliability of your payments."), _("This may impact the reliability of your payments."),
])): ])):
trampoline_cb.setCheckState(Qt.Checked) trampoline_cb.setCheckState(Qt.CheckState.Checked)
return return
self.config.LIGHTNING_USE_GOSSIP = not use_trampoline self.config.LIGHTNING_USE_GOSSIP = not use_trampoline
if not use_trampoline: if not use_trampoline:
@ -167,7 +167,7 @@ class SettingsDialog(QDialog, QtEventListener):
pos = lnfee_slider.sliderPosition() pos = lnfee_slider.sliderPosition()
fee_val = lnfee_map[pos] fee_val = lnfee_map[pos]
self.config.LIGHTNING_PAYMENT_FEE_MAX_MILLIONTHS = fee_val 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.setRange(0, len(lnfee_map)-1)
lnfee_slider.setTracking(True) lnfee_slider.setTracking(True)
try: try:
@ -195,7 +195,7 @@ class SettingsDialog(QDialog, QtEventListener):
msat_cb = checkbox_from_configvar(self.config.cv.BTC_AMOUNTS_PREC_POST_SAT) msat_cb = checkbox_from_configvar(self.config.cv.BTC_AMOUNTS_PREC_POST_SAT)
msat_cb.setChecked(self.config.BTC_AMOUNTS_PREC_POST_SAT > 0) msat_cb.setChecked(self.config.BTC_AMOUNTS_PREC_POST_SAT > 0)
def on_msat_checked(v): 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: if self.config.amt_precision_post_satoshi != prec:
self.config.amt_precision_post_satoshi = prec self.config.amt_precision_post_satoshi = prec
self.config.BTC_AMOUNTS_PREC_POST_SAT = 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 = checkbox_from_configvar(self.config.cv.BTC_AMOUNTS_ADD_THOUSANDS_SEP)
thousandsep_cb.setChecked(self.config.BTC_AMOUNTS_ADD_THOUSANDS_SEP) thousandsep_cb.setChecked(self.config.BTC_AMOUNTS_ADD_THOUSANDS_SEP)
def on_set_thousandsep(v): def on_set_thousandsep(v):
checked = v == Qt.Checked checked = v == Qt.CheckState.Checked
if self.config.amt_add_thousands_sep != checked: if self.config.amt_add_thousands_sep != checked:
self.config.amt_add_thousands_sep = checked self.config.amt_add_thousands_sep = checked
self.config.BTC_AMOUNTS_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 = checkbox_from_configvar(self.config.cv.AUTOMATIC_CENTRALIZED_UPDATE_CHECKS)
updatecheck_cb.setChecked(self.config.AUTOMATIC_CENTRALIZED_UPDATE_CHECKS) updatecheck_cb.setChecked(self.config.AUTOMATIC_CENTRALIZED_UPDATE_CHECKS)
def on_set_updatecheck(v): 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) updatecheck_cb.stateChanged.connect(on_set_updatecheck)
filelogging_cb = checkbox_from_configvar(self.config.cv.WRITE_LOGS_TO_DISK) filelogging_cb = checkbox_from_configvar(self.config.cv.WRITE_LOGS_TO_DISK)
filelogging_cb.setChecked(self.config.WRITE_LOGS_TO_DISK) filelogging_cb.setChecked(self.config.WRITE_LOGS_TO_DISK)
def on_set_filelogging(v): 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 self.need_restart = True
filelogging_cb.stateChanged.connect(on_set_filelogging) 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 import sys
from PyQt5 import QtWidgets from PyQt6 import QtWidgets
CUSTOM_PATCH_FOR_DARK_THEME = ''' CUSTOM_PATCH_FOR_DARK_THEME = '''

6
electrum/gui/qt/swap_dialog.py

@ -1,7 +1,7 @@
from typing import TYPE_CHECKING, Optional, Union from typing import TYPE_CHECKING, Optional, Union
from PyQt5.QtCore import pyqtSignal from PyQt6.QtCore import pyqtSignal
from PyQt5.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates
@ -244,7 +244,7 @@ class SwapDialog(WindowModalDialog, QtEventListener):
def run(self): def run(self):
"""Can raise InvalidSwapParameters.""" """Can raise InvalidSwapParameters."""
if not self.exec_(): if not self.exec():
return return
if self.is_reverse: if self.is_reverse:
lightning_amount = self.send_amount_e.get_amount() 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 functools import partial
from decimal import Decimal from decimal import Decimal
from PyQt5.QtCore import QSize, Qt, QUrl, QPoint, pyqtSignal from PyQt6.QtCore import QSize, Qt, QUrl, QPoint, pyqtSignal
from PyQt5.QtGui import QTextCharFormat, QBrush, QFont, QPixmap, QCursor from PyQt6.QtGui import QTextCharFormat, QBrush, QFont, QPixmap, QTextCursor, QAction
from PyQt5.QtWidgets import (QDialog, QLabel, QPushButton, QHBoxLayout, QVBoxLayout, QWidget, QGridLayout, from PyQt6.QtWidgets import (QDialog, QLabel, QPushButton, QHBoxLayout, QVBoxLayout, QWidget, QGridLayout,
QTextEdit, QFrame, QAction, QToolButton, QMenu, QCheckBox, QTextBrowser, QToolTip, QTextEdit, QFrame, QToolButton, QMenu, QCheckBox, QTextBrowser, QToolTip,
QApplication, QSizePolicy) QApplication, QSizePolicy)
import qrcode import qrcode
from qrcode import exceptions from qrcode import exceptions
@ -95,7 +95,7 @@ class QTextBrowserWithDefaultSize(QTextBrowser):
self._width = width self._width = width
self._height = height self._height = height
QTextBrowser.__init__(self) QTextBrowser.__init__(self)
self.setLineWrapMode(QTextBrowser.NoWrap) self.setLineWrapMode(QTextBrowser.LineWrapMode.NoWrap)
def sizeHint(self): def sizeHint(self):
return QSize(self._width, self._height) 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.setOpenLinks(False) # disable automatic link opening
self.inputs_textedit.anchorClicked.connect(self._open_internal_link) # send links to our handler self.inputs_textedit.anchorClicked.connect(self._open_internal_link) # send links to our handler
self.inputs_textedit.setTextInteractionFlags( self.inputs_textedit.setTextInteractionFlags(
self.inputs_textedit.textInteractionFlags() | Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) self.inputs_textedit.textInteractionFlags() | Qt.TextInteractionFlag.LinksAccessibleByMouse | Qt.TextInteractionFlag.LinksAccessibleByKeyboard)
self.inputs_textedit.setContextMenuPolicy(Qt.CustomContextMenu) self.inputs_textedit.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.inputs_textedit.customContextMenuRequested.connect(self.on_context_menu_for_inputs) self.inputs_textedit.customContextMenuRequested.connect(self.on_context_menu_for_inputs)
self.sighash_label = QLabel() self.sighash_label = QLabel()
@ -123,7 +123,7 @@ class TxInOutWidget(QWidget):
self.inputs_warning_icon = QLabel() self.inputs_warning_icon = QLabel()
pixmap = QPixmap(icon_path("warning")) pixmap = QPixmap(icon_path("warning"))
pixmap_size = round(2 * char_width_in_lineedit()) 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.setPixmap(pixmap)
self.inputs_warning_icon.setVisible(False) 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.setOpenLinks(False) # disable automatic link opening
self.outputs_textedit.anchorClicked.connect(self._open_internal_link) # send links to our handler self.outputs_textedit.anchorClicked.connect(self._open_internal_link) # send links to our handler
self.outputs_textedit.setTextInteractionFlags( self.outputs_textedit.setTextInteractionFlags(
self.outputs_textedit.textInteractionFlags() | Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) self.outputs_textedit.textInteractionFlags() | Qt.TextInteractionFlag.LinksAccessibleByMouse | Qt.TextInteractionFlag.LinksAccessibleByKeyboard)
self.outputs_textedit.setContextMenuPolicy(Qt.CustomContextMenu) self.outputs_textedit.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.outputs_textedit.customContextMenuRequested.connect(self.on_context_menu_for_outputs) self.outputs_textedit.customContextMenuRequested.connect(self.on_context_menu_for_outputs)
outheader_hbox = QHBoxLayout() outheader_hbox = QHBoxLayout()
@ -166,7 +166,7 @@ class TxInOutWidget(QWidget):
vbox.addLayout(outheader_hbox) vbox.addLayout(outheader_hbox)
vbox.addWidget(self.outputs_textedit) vbox.addWidget(self.outputs_textedit)
self.setLayout(vbox) self.setLayout(vbox)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
def update(self, tx: Optional[Transaction]): def update(self, tx: Optional[Transaction]):
self.tx = tx self.tx = tx
@ -183,7 +183,7 @@ class TxInOutWidget(QWidget):
lnk = QTextCharFormat() lnk = QTextCharFormat()
lnk.setToolTip(_('Click to open, right-click for menu')) lnk.setToolTip(_('Click to open, right-click for menu'))
lnk.setAnchor(True) 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 tf_used_recv, tf_used_change, tf_used_2fa, tf_used_swap = False, False, False, False
def addr_text_format(addr: str) -> QTextCharFormat: def addr_text_format(addr: str) -> QTextCharFormat:
nonlocal tf_used_recv, tf_used_change, tf_used_2fa, tf_used_swap nonlocal tf_used_recv, tf_used_change, tf_used_2fa, tf_used_swap
@ -198,7 +198,7 @@ class TxInOutWidget(QWidget):
fmt.setAnchorHref(addr) fmt.setAnchorHref(addr)
fmt.setToolTip(_('Click to open, right-click for menu')) fmt.setToolTip(_('Click to open, right-click for menu'))
fmt.setAnchor(True) fmt.setAnchor(True)
fmt.setUnderlineStyle(QTextCharFormat.SingleUnderline) fmt.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SingleUnderline)
return fmt return fmt
elif sm and sm.is_lockup_address_for_a_swap(addr) or addr == DummyAddress.SWAP: elif sm and sm.is_lockup_address_for_a_swap(addr) or addr == DummyAddress.SWAP:
tf_used_swap = True tf_used_swap = True
@ -210,7 +210,7 @@ class TxInOutWidget(QWidget):
def insert_tx_io( def insert_tx_io(
*, *,
cursor: QCursor, cursor: QTextCursor,
txio_idx: int, txio_idx: int,
is_coinbase: bool, is_coinbase: bool,
tcf_shortid: QTextCharFormat = None, tcf_shortid: QTextCharFormat = None,
@ -305,7 +305,7 @@ class TxInOutWidget(QWidget):
of the bare form "txid" and/or "address" -- used by the clickable of the bare form "txid" and/or "address" -- used by the clickable
links in the inputs/outputs QTextBrowsers""" links in the inputs/outputs QTextBrowsers"""
if isinstance(target, QUrl): if isinstance(target, QUrl):
target = target.toString(QUrl.None_) target = target.toString(QUrl.UrlFormattingOption.None_)
assert target assert target
if bitcoin.is_address(target): if bitcoin.is_address(target):
# target was an address, open address dialog # target was an address, open address dialog
@ -323,7 +323,7 @@ class TxInOutWidget(QWidget):
name = charFormat.anchorNames() and charFormat.anchorNames()[0] name = charFormat.anchorNames() and charFormat.anchorNames()[0]
if not name: if not name:
menu = i_text.createStandardContextMenu() menu = i_text.createStandardContextMenu()
menu.exec_(global_pos) menu.exec(global_pos)
return return
menu = QMenu() menu = QMenu()
@ -360,7 +360,7 @@ class TxInOutWidget(QWidget):
menu.addSeparator() menu.addSeparator()
std_menu = i_text.createStandardContextMenu() std_menu = i_text.createStandardContextMenu()
menu.addActions(std_menu.actions()) menu.addActions(std_menu.actions())
menu.exec_(global_pos) menu.exec(global_pos)
def on_context_menu_for_outputs(self, pos: QPoint): def on_context_menu_for_outputs(self, pos: QPoint):
o_text = self.outputs_textedit o_text = self.outputs_textedit
@ -371,7 +371,7 @@ class TxInOutWidget(QWidget):
name = charFormat.anchorNames() and charFormat.anchorNames()[0] name = charFormat.anchorNames() and charFormat.anchorNames()[0]
if not name: if not name:
menu = o_text.createStandardContextMenu() menu = o_text.createStandardContextMenu()
menu.exec_(global_pos) menu.exec(global_pos)
return return
menu = QMenu() menu = QMenu()
@ -405,7 +405,7 @@ class TxInOutWidget(QWidget):
menu.addSeparator() menu.addSeparator()
std_menu = o_text.createStandardContextMenu() std_menu = o_text.createStandardContextMenu()
menu.addActions(std_menu.actions()) menu.addActions(std_menu.actions())
menu.exec_(global_pos) menu.exec(global_pos)
def show_transaction( def show_transaction(
@ -530,7 +530,7 @@ class TxDialog(QDialog, MessageBoxMixin):
self.export_actions_button = QToolButton() self.export_actions_button = QToolButton()
self.export_actions_button.setText(_("Share")) self.export_actions_button.setText(_("Share"))
self.export_actions_button.setMenu(export_actions_menu) 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() partial_tx_actions_menu = QMenu()
ptx_merge_sigs_action = QAction(_("Merge signatures from"), self) 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 = QToolButton()
self.partial_tx_actions_button.setText(_("Combine")) self.partial_tx_actions_button.setText(_("Combine"))
self.partial_tx_actions_button.setMenu(partial_tx_actions_menu) 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) self.psbt_only_widgets.append(self.partial_tx_actions_button)
# Action buttons # 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_fut = None # type: Optional[concurrent.futures.Future]
self._fetch_txin_data_progress = None # type: Optional[TxinDataFetchProgress] 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.set_tx(tx)
self.update() self.update()
@ -954,7 +954,7 @@ class TxDialog(QDialog, MessageBoxMixin):
hbox_stats.setContentsMargins(0, 0, 0, 0) hbox_stats.setContentsMargins(0, 0, 0, 0)
hbox_stats_w = QWidget() hbox_stats_w = QWidget()
hbox_stats_w.setLayout(hbox_stats) 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 # left column
vbox_left = QVBoxLayout() vbox_left = QVBoxLayout()
@ -973,7 +973,7 @@ class TxDialog(QDialog, MessageBoxMixin):
self.fee_warning_icon = QLabel() self.fee_warning_icon = QLabel()
pixmap = QPixmap(icon_path("warning")) pixmap = QPixmap(icon_path("warning"))
pixmap_size = round(2 * char_width_in_lineedit()) 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.setPixmap(pixmap)
self.fee_warning_icon.setVisible(False) self.fee_warning_icon.setVisible(False)
fee_hbox.addWidget(self.fee_warning_icon) fee_hbox.addWidget(self.fee_warning_icon)
@ -1041,7 +1041,7 @@ class TxDialog(QDialog, MessageBoxMixin):
class TxDetailLabel(QLabel): class TxDetailLabel(QLabel):
def __init__(self, *, word_wrap=None): def __init__(self, *, word_wrap=None):
super().__init__() super().__init__()
self.setTextInteractionFlags(Qt.TextSelectableByMouse) self.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
if word_wrap is not None: if word_wrap is not None:
self.setWordWrap(word_wrap) self.setWordWrap(word_wrap)

6
electrum/gui/qt/update_checker.py

@ -5,8 +5,8 @@
import asyncio import asyncio
import base64 import base64
from PyQt5.QtCore import Qt, QThread, pyqtSignal from PyQt6.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QLabel, QProgressBar, from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QLabel, QProgressBar,
QHBoxLayout, QPushButton, QDialog) QHBoxLayout, QPushButton, QDialog)
from electrum import version from electrum import version
@ -38,7 +38,7 @@ class UpdateCheck(QDialog, Logger):
self.content.addWidget(self.heading_label) self.content.addWidget(self.heading_label)
self.detail_label = QLabel() self.detail_label = QLabel()
self.detail_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse) self.detail_label.setTextInteractionFlags(Qt.TextInteractionFlag.LinksAccessibleByMouse)
self.detail_label.setOpenExternalLinks(True) self.detail_label.setOpenExternalLinks(True)
self.content.addWidget(self.detail_label) 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 functools import partial, lru_cache, wraps
from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, List, Any, Sequence, Tuple) from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, List, Any, Sequence, Tuple)
from PyQt5 import QtCore from PyQt6 import QtCore
from PyQt5.QtGui import (QFont, QColor, QCursor, QPixmap, QImage, from PyQt6.QtGui import (QFont, QColor, QCursor, QPixmap, QImage,
QPalette, QIcon, QFontMetrics, QPainter, QContextMenuEvent) QPalette, QIcon, QFontMetrics, QPainter, QContextMenuEvent)
from PyQt5.QtCore import (Qt, pyqtSignal, QCoreApplication, QThread, QSize, QRect, QPoint, QObject) from PyQt6.QtCore import (Qt, pyqtSignal, QCoreApplication, QThread, QSize, QRect, QPoint, QObject)
from PyQt5.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, QVBoxLayout, QLineEdit, from PyQt6.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, QVBoxLayout, QLineEdit,
QStyle, QDialog, QGroupBox, QButtonGroup, QRadioButton, QStyle, QDialog, QGroupBox, QButtonGroup, QRadioButton,
QFileDialog, QWidget, QToolButton, QPlainTextEdit, QApplication, QToolTip, QFileDialog, QWidget, QToolButton, QPlainTextEdit, QApplication, QToolTip,
QGraphicsEffect, QGraphicsScene, QGraphicsPixmapItem, QLayoutItem, QLayout, QMenu, QGraphicsEffect, QGraphicsScene, QGraphicsPixmapItem, QLayoutItem, QLayout, QMenu,
@ -77,7 +77,7 @@ class EnterButton(QPushButton):
self._orig_text = text self._orig_text = text
def keyPressEvent(self, e): 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() self.func()
def restore_original_text(self): def restore_original_text(self):
@ -106,14 +106,14 @@ class WWLabel(QLabel):
def __init__ (self, text="", parent=None): def __init__ (self, text="", parent=None):
QLabel.__init__(self, text, parent) QLabel.__init__(self, text, parent)
self.setWordWrap(True) self.setWordWrap(True)
self.setTextInteractionFlags(Qt.TextSelectableByMouse) self.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
class AmountLabel(QLabel): class AmountLabel(QLabel):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
QLabel.__init__(self, *args, **kwargs) QLabel.__init__(self, *args, **kwargs)
self.setFont(QFont(MONOSPACE_FONT)) self.setFont(QFont(MONOSPACE_FONT))
self.setTextInteractionFlags(Qt.TextSelectableByMouse) self.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
class HelpMixin: class HelpMixin:
@ -123,12 +123,12 @@ class HelpMixin:
self._help_title = help_title or _('Help') self._help_title = help_title or _('Help')
if isinstance(self, QLabel): if isinstance(self, QLabel):
self.setTextInteractionFlags( self.setTextInteractionFlags(
(self.textInteractionFlags() | Qt.TextSelectableByMouse) (self.textInteractionFlags() | Qt.TextInteractionFlag.TextSelectableByMouse)
& ~Qt.TextSelectableByKeyboard) & ~Qt.TextInteractionFlag.TextSelectableByKeyboard)
def show_help(self): def show_help(self):
custom_message_box( custom_message_box(
icon=QMessageBox.Information, icon=QMessageBox.Icon.Information,
parent=self, parent=self,
title=self._help_title, title=self._help_title,
text=self.help_text, text=self.help_text,
@ -154,13 +154,13 @@ class HelpLabel(HelpMixin, QLabel):
def enterEvent(self, event): def enterEvent(self, event):
self.font.setUnderline(True) self.font.setUnderline(True)
self.setFont(self.font) self.setFont(self.font)
self.app.setOverrideCursor(QCursor(Qt.PointingHandCursor)) self.app.setOverrideCursor(QCursor(Qt.CursorShape.PointingHandCursor))
return QLabel.enterEvent(self, event) return QLabel.enterEvent(self, event)
def leaveEvent(self, event): def leaveEvent(self, event):
self.font.setUnderline(False) self.font.setUnderline(False)
self.setFont(self.font) self.setFont(self.font)
self.app.setOverrideCursor(QCursor(Qt.ArrowCursor)) self.app.setOverrideCursor(QCursor(Qt.CursorShape.ArrowCursor))
return QLabel.leaveEvent(self, event) return QLabel.leaveEvent(self, event)
@ -169,7 +169,7 @@ class HelpButton(HelpMixin, QToolButton):
QToolButton.__init__(self) QToolButton.__init__(self)
HelpMixin.__init__(self, text) HelpMixin.__init__(self, text)
self.setText('?') self.setText('?')
self.setFocusPolicy(Qt.NoFocus) self.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.setFixedWidth(round(2.2 * char_width_in_lineedit())) self.setFixedWidth(round(2.2 * char_width_in_lineedit()))
self.clicked.connect(self.show_help) self.clicked.connect(self.show_help)
@ -178,7 +178,7 @@ class InfoButton(HelpMixin, QPushButton):
def __init__(self, text: str): def __init__(self, text: str):
QPushButton.__init__(self, _('Info')) QPushButton.__init__(self, _('Info'))
HelpMixin.__init__(self, text, help_title=_('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.setFixedWidth(6 * char_width_in_lineedit())
self.clicked.connect(self.show_help) self.clicked.connect(self.show_help)
@ -245,8 +245,8 @@ class MessageBoxMixin(object):
return self.top_level_window_recurse(test_func) return self.top_level_window_recurse(test_func)
def question(self, msg, parent=None, title=None, icon=None, **kwargs) -> bool: def question(self, msg, parent=None, title=None, icon=None, **kwargs) -> bool:
Yes, No = QMessageBox.Yes, QMessageBox.No Yes, No = QMessageBox.StandardButton.Yes, QMessageBox.StandardButton.No
return Yes == self.msg_box(icon=icon or QMessageBox.Question, return Yes == self.msg_box(icon=icon or QMessageBox.Icon.Question,
parent=parent, parent=parent,
title=title or '', title=title or '',
text=msg, text=msg,
@ -255,23 +255,23 @@ class MessageBoxMixin(object):
**kwargs) **kwargs)
def show_warning(self, msg, parent=None, title=None, **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) title or _('Warning'), msg, **kwargs)
def show_error(self, msg, parent=None, **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) _('Error'), msg, **kwargs)
def show_critical(self, msg, parent=None, title=None, **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) title or _('Critical Error'), msg, **kwargs)
def show_message(self, msg, parent=None, title=None, **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) title or _('Information'), msg, **kwargs)
def msg_box(self, icon, parent, title, text, *, buttons=QMessageBox.Ok, def msg_box(self, icon, parent, title, text, *, buttons=QMessageBox.StandardButton.Ok,
defaultButton=QMessageBox.NoButton, rich_text=False, defaultButton=QMessageBox.StandardButton.NoButton, rich_text=False,
checkbox=None): checkbox=None):
parent = parent or self.top_level_window() parent = parent or self.top_level_window()
return custom_message_box(icon=icon, return custom_message_box(icon=icon,
@ -299,34 +299,34 @@ class MessageBoxMixin(object):
cancel_button = CancelButton(dialog) cancel_button = CancelButton(dialog)
vbox.addLayout(Buttons(cancel_button, OkButton(dialog))) vbox.addLayout(Buttons(cancel_button, OkButton(dialog)))
cancel_button.setFocus() cancel_button.setFocus()
if not dialog.exec_(): if not dialog.exec():
return None return None
return choice_widget.selected_key return choice_widget.selected_key
def custom_message_box(*, icon, parent, title, text, buttons=QMessageBox.Ok, def custom_message_box(*, icon, parent, title, text, buttons=QMessageBox.StandardButton.Ok,
defaultButton=QMessageBox.NoButton, rich_text=False, defaultButton=QMessageBox.StandardButton.NoButton, rich_text=False,
checkbox=None): checkbox=None):
if type(icon) is QPixmap: 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) d.setIconPixmap(icon)
else: else:
d = QMessageBox(icon, title, str(text), buttons, parent) d = QMessageBox(icon, title, str(text), buttons, parent)
d.setWindowModality(Qt.WindowModal) d.setWindowModality(Qt.WindowModality.WindowModal)
d.setDefaultButton(defaultButton) d.setDefaultButton(defaultButton)
if rich_text: if rich_text:
d.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.LinksAccessibleByMouse) d.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse | Qt.TextInteractionFlag.LinksAccessibleByMouse)
# set AutoText instead of RichText # set AutoText instead of RichText
# AutoText lets Qt figure out whether to render as rich text. # AutoText lets Qt figure out whether to render as rich text.
# e.g. if text is actually plain text and uses "\n" newlines; # e.g. if text is actually plain text and uses "\n" newlines;
# and we set RichText here, newlines would be swallowed # and we set RichText here, newlines would be swallowed
d.setTextFormat(Qt.AutoText) d.setTextFormat(Qt.TextFormat.AutoText)
else: else:
d.setTextInteractionFlags(Qt.TextSelectableByMouse) d.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
d.setTextFormat(Qt.PlainText) d.setTextFormat(Qt.TextFormat.PlainText)
if checkbox is not None: if checkbox is not None:
d.setCheckBox(checkbox) d.setCheckBox(checkbox)
return d.exec_() return d.exec()
class WindowModalDialog(QDialog, MessageBoxMixin): class WindowModalDialog(QDialog, MessageBoxMixin):
@ -334,7 +334,7 @@ class WindowModalDialog(QDialog, MessageBoxMixin):
daemon model as other wallet windows can still be accessed.''' daemon model as other wallet windows can still be accessed.'''
def __init__(self, parent, title=None): def __init__(self, parent, title=None):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.setWindowModality(Qt.WindowModal) self.setWindowModality(Qt.WindowModality.WindowModal)
if title: if title:
self.setWindowTitle(title) self.setWindowTitle(title)
@ -410,7 +410,7 @@ def line_dialog(parent, title, label, ok_label, default=None):
txt.setText(default) txt.setText(default)
l.addWidget(txt) l.addWidget(txt)
l.addLayout(Buttons(CancelButton(dialog), OkButton(dialog, ok_label))) l.addLayout(Buttons(CancelButton(dialog), OkButton(dialog, ok_label)))
if dialog.exec_(): if dialog.exec():
return txt.text() return txt.text()
@ -438,7 +438,7 @@ def text_dialog(
txt.setText(default) txt.setText(default)
l.addWidget(txt) l.addWidget(txt)
l.addLayout(Buttons(CancelButton(dialog), OkButton(dialog, ok_label))) l.addLayout(Buttons(CancelButton(dialog), OkButton(dialog, ok_label)))
if dialog.exec_(): if dialog.exec():
return txt.toPlainText() return txt.toPlainText()
@ -564,7 +564,8 @@ class VLine(QFrame):
"""Vertical line separator""" """Vertical line separator"""
def __init__(self): def __init__(self):
super(VLine, self).__init__() super(VLine, self).__init__()
self.setFrameShape(self.VLine | self.Sunken) self.setFrameShape(QFrame.Shape.VLine)
self.setFrameShadow(QFrame.Shadow.Sunken)
self.setLineWidth(1) 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(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("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.addAction(read_QIcon("file.png"), _("Read file"), p.on_input_file)
m.exec_(e.globalPos()) m.exec(e.globalPos())
class GenericInputHandler: class GenericInputHandler:
@ -821,7 +822,7 @@ class OverlayControlMixin(GenericInputHandler):
self._updateOverlayPos() self._updateOverlayPos()
def _updateOverlayPos(self): 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() overlay_size = self.overlay_widget.sizeHint()
x = self.rect().right() - frame_width - overlay_size.width() x = self.rect().right() - frame_width - overlay_size.width()
y = self.rect().bottom() - overlay_size.height() y = self.rect().bottom() - overlay_size.height()
@ -833,7 +834,7 @@ class OverlayControlMixin(GenericInputHandler):
middle = True middle = True
y = (y / 2) + frame_width if middle else y - frame_width y = (y / 2) + frame_width if middle else y - frame_width
if hasattr(self, 'verticalScrollBar') and self.verticalScrollBar().isVisible(): 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 x -= scrollbar_width
self.overlay_widget.move(int(x), int(y)) self.overlay_widget.move(int(x), int(y))
@ -845,7 +846,7 @@ class OverlayControlMixin(GenericInputHandler):
button = QPushButton(self.overlay_widget) button = QPushButton(self.overlay_widget)
button.setToolTip(tooltip) button.setToolTip(tooltip)
button.setIcon(read_QIcon(icon_name)) button.setIcon(read_QIcon(icon_name))
button.setCursor(QCursor(Qt.PointingHandCursor)) button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
button.clicked.connect(on_click) button.clicked.connect(on_click)
self.addWidget(button) self.addWidget(button)
return button return button
@ -886,7 +887,7 @@ class OverlayControlMixin(GenericInputHandler):
parent=self, parent=self,
title=title, title=title,
config=config, config=config,
).exec_() ).exec()
self.addButton(get_iconname_qrcode(), qr_show, _("Show as QR code")) self.addButton(get_iconname_qrcode(), qr_show, _("Show as QR code"))
# side-effect: we export this method: # side-effect: we export this method:
@ -1007,7 +1008,7 @@ class ButtonsTextEdit(OverlayControlMixin, QPlainTextEdit):
class PasswordLineEdit(QLineEdit): class PasswordLineEdit(QLineEdit):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
QLineEdit.__init__(self, *args, **kwargs) QLineEdit.__init__(self, *args, **kwargs)
self.setEchoMode(QLineEdit.Password) self.setEchoMode(QLineEdit.EchoMode.Password)
def clear(self): def clear(self):
# Try to actually overwrite the memory. # Try to actually overwrite the memory.
@ -1118,7 +1119,7 @@ class ColorScheme:
@staticmethod @staticmethod
def has_dark_background(widget): 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) return brightness < (255*3/2)
@staticmethod @staticmethod
@ -1148,7 +1149,7 @@ class AcceptFileDragDrop:
def dragMoveEvent(self, event): def dragMoveEvent(self, event):
if self.validateEvent(event): if self.validateEvent(event):
event.setDropAction(Qt.CopyAction) event.setDropAction(Qt.DropAction.CopyAction)
def dropEvent(self, event): def dropEvent(self, event):
if self.validateEvent(event): if self.validateEvent(event):
@ -1222,14 +1223,14 @@ def getSaveFileName(
path = os.path.join(directory, filename) path = os.path.join(directory, filename)
file_dialog = QFileDialog(parent, title, path, filter) file_dialog = QFileDialog(parent, title, path, filter)
file_dialog.setAcceptMode(QFileDialog.AcceptSave) file_dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
if default_extension: if default_extension:
# note: on MacOS, the selected filter's first extension seems to have priority over this... # note: on MacOS, the selected filter's first extension seems to have priority over this...
file_dialog.setDefaultSuffix(default_extension) file_dialog.setDefaultSuffix(default_extension)
if default_filter: if default_filter:
assert default_filter in filter, f"default_filter={default_filter!r} does not appear in filter={filter!r}" assert default_filter in filter, f"default_filter={default_filter!r} does not appear in filter={filter!r}"
file_dialog.selectNameFilter(default_filter) file_dialog.selectNameFilter(default_filter)
if file_dialog.exec() != QDialog.Accepted: if file_dialog.exec() != QDialog.DialogCode.Accepted:
return None return None
selected_path = file_dialog.selectedFiles()[0] selected_path = file_dialog.selectedFiles()[0]
@ -1262,7 +1263,7 @@ class IconLabel(QWidget):
self.setLayout(layout) self.setLayout(layout)
self.icon = QLabel() self.icon = QLabel()
self.label = QLabel(text) self.label = QLabel(text)
self.label.setTextInteractionFlags(Qt.TextSelectableByMouse) self.label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
layout.addWidget(self.label) layout.addWidget(self.label)
layout.addSpacing(self.HorizontalSpacing) layout.addSpacing(self.HorizontalSpacing)
layout.addWidget(self.icon) layout.addWidget(self.icon)
@ -1350,16 +1351,16 @@ class FixedAspectRatioLayout(QLayout):
free_space = contents.size() - item_rect.size() free_space = contents.size() - item_rect.size()
for item in self.items: for item in self.items:
if free_space.width() > 0 and not item.alignment() & Qt.AlignLeft: if free_space.width() > 0 and not item.alignment() & Qt.AlignmentFlag.AlignLeft:
if item.alignment() & Qt.AlignRight: if item.alignment() & Qt.AlignmentFlag.AlignRight:
item_rect.moveRight(contents.width() + content_margins.right()) item_rect.moveRight(contents.width() + content_margins.right())
else: else:
item_rect.moveLeft(content_margins.left() + (free_space.width() // 2)) item_rect.moveLeft(content_margins.left() + (free_space.width() // 2))
else: else:
item_rect.moveLeft(content_margins.left()) item_rect.moveLeft(content_margins.left())
if free_space.height() > 0 and not item.alignment() & Qt.AlignTop: if free_space.height() > 0 and not item.alignment() & Qt.AlignmentFlag.AlignTop:
if item.alignment() & Qt.AlignBottom: if item.alignment() & Qt.AlignmentFlag.AlignBottom:
item_rect.moveBottom(contents.height() + content_margins.bottom()) item_rect.moveBottom(contents.height() + content_margins.bottom())
else: else:
item_rect.moveTop(content_margins.top() + (free_space.height() // 2)) item_rect.moveTop(content_margins.top() + (free_space.height() // 2))
@ -1380,8 +1381,8 @@ class FixedAspectRatioLayout(QLayout):
result = result.expandedTo(item.minimumSize()) result = result.expandedTo(item.minimumSize())
return self._get_contents_margins_size() + result return self._get_contents_margins_size() + result
def expandingDirections(self) -> Qt.Orientations: def expandingDirections(self) -> Qt.Orientation:
return Qt.Horizontal | Qt.Vertical return Qt.Orientation.Horizontal | Qt.Orientation.Vertical
def QColorLerp(a: QColor, b: QColor, t: float): def QColorLerp(a: QColor, b: QColor, t: float):
@ -1414,8 +1415,8 @@ class ImageGraphicsEffect(QObject):
def apply(self, image: QImage): def apply(self, image: QImage):
assert image, 'image must be set' assert image, 'image must be set'
result = QImage(image.size(), QImage.Format_ARGB32) result = QImage(image.size(), QImage.Format.Format_ARGB32)
result.fill(Qt.transparent) result.fill(Qt.GlobalColor.transparent)
painter = QPainter(result) painter = QPainter(result)
self.graphics_item.setPixmap(QPixmap.fromImage(image)) self.graphics_item.setPixmap(QPixmap.fromImage(image))
self.graphics_scene.render(painter) self.graphics_scene.render(painter)
@ -1433,7 +1434,8 @@ class QtEventListener(EventListener):
def unregister_callbacks(self): def unregister_callbacks(self):
try: try:
self.qt_callback_signal.disconnect() 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 pass
EventListener.unregister_callbacks(self) EventListener.unregister_callbacks(self)
@ -1462,4 +1464,4 @@ if __name__ == "__main__":
app = QApplication([]) app = QApplication([])
t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done")) t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done"))
t.start() t.start()
app.exec_() app.exec()

12
electrum/gui/qt/utxo_dialog.py

@ -26,9 +26,9 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import copy import copy
from PyQt5.QtCore import Qt, QUrl from PyQt6.QtCore import Qt, QUrl
from PyQt5.QtGui import QTextCharFormat, QFont from PyQt6.QtGui import QTextCharFormat, QFont
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QLabel, QTextBrowser from PyQt6.QtWidgets import QVBoxLayout, QHBoxLayout, QLabel, QTextBrowser
from electrum.i18n import _ 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.anchorClicked.connect(self.open_tx) # send links to our handler
self.parents_list.setFont(QFont(MONOSPACE_FONT)) self.parents_list.setFont(QFont(MONOSPACE_FONT))
self.parents_list.setReadOnly(True) 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( self.txo_color_parent = TxOutputColoring(
legend=_("Direct parent"), color=ColorScheme.BLUE, tooltip=_("Direct parent")) legend=_("Direct parent"), color=ColorScheme.BLUE, tooltip=_("Direct parent"))
self.txo_color_uncle = TxOutputColoring( self.txo_color_uncle = TxOutputColoring(
@ -123,7 +123,7 @@ class UTXODialog(WindowModalDialog):
lnk.setAnchorHref(_txid) lnk.setAnchorHref(_txid)
#lnk.setAnchorNames([a_name]) #lnk.setAnchorNames([a_name])
lnk.setAnchor(True) lnk.setAnchor(True)
lnk.setUnderlineStyle(QTextCharFormat.SingleUnderline) lnk.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SingleUnderline)
cursor.insertText(key, lnk) cursor.insertText(key, lnk)
cursor.insertText(" ", ext) cursor.insertText(" ", ext)
cursor.insertText(label, ext) cursor.insertText(label, ext)
@ -149,7 +149,7 @@ class UTXODialog(WindowModalDialog):
def open_tx(self, txid): def open_tx(self, txid):
if isinstance(txid, QUrl): if isinstance(txid, QUrl):
txid = txid.toString(QUrl.None_) txid = txid.toString(QUrl.UrlFormattingOption.None_)
tx = self.wallet.adb.get_transaction(txid) tx = self.wallet.adb.get_transaction(txid)
if not tx: if not tx:
return 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 enum
import copy import copy
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont from PyQt6.QtGui import QStandardItemModel, QStandardItem, QFont
from PyQt5.QtWidgets import QAbstractItemView, QMenu, QLabel, QHBoxLayout from PyQt6.QtWidgets import QAbstractItemView, QMenu, QLabel, QHBoxLayout
from electrum.i18n import _ from electrum.i18n import _
from electrum.bitcoin import is_address from electrum.bitcoin import is_address
@ -67,8 +67,8 @@ class UTXOList(MyTreeView):
filter_columns = [Columns.ADDRESS, Columns.LABEL, Columns.OUTPOINT] filter_columns = [Columns.ADDRESS, Columns.LABEL, Columns.OUTPOINT]
stretch_column = Columns.LABEL stretch_column = Columns.LABEL
ROLE_PREVOUT_STR = Qt.UserRole + 1000 ROLE_PREVOUT_STR = Qt.ItemDataRole.UserRole + 1000
ROLE_SORT_ORDER = Qt.UserRole + 1001 ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 1001
key_role = ROLE_PREVOUT_STR key_role = ROLE_PREVOUT_STR
def __init__(self, main_window: 'ElectrumWindow'): 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 = MySortModel(self, sort_role=self.ROLE_SORT_ORDER)
self.proxy.setSourceModel(self.std_model) self.proxy.setSourceModel(self.std_model)
self.setModel(self.proxy) self.setModel(self.proxy)
self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
self.setSortingEnabled(True) self.setSortingEnabled(True)
def create_toolbar(self, config): def create_toolbar(self, config):
@ -124,7 +124,7 @@ class UTXOList(MyTreeView):
self.refresh_row(name, idx) self.refresh_row(name, idx)
self.filter() self.filter()
self.proxy.setDynamicSortFilter(True) self.proxy.setDynamicSortFilter(True)
self.sortByColumn(self.Columns.OUTPOINT, Qt.DescendingOrder) self.sortByColumn(self.Columns.OUTPOINT, Qt.SortOrder.DescendingOrder)
self.update_coincontrol_bar() self.update_coincontrol_bar()
self.num_coins_label.setText(_('{} unspent transaction outputs').format(len(utxos))) 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 = menu_freeze.addAction(_("Unfreeze Addresses"), lambda: self.main_window.set_frozen_state_of_addresses(addrs, False))
act.setToolTip(MSG_FREEZE_ADDRESS) 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): def get_filter_data_from_coordinate(self, row, col):
if col == self.Columns.OUTPOINT: if col == self.Columns.OUTPOINT:

8
electrum/gui/qt/wallet_info_dialog.py

@ -5,8 +5,8 @@
import os import os
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtWidgets import (QLabel, QVBoxLayout, QGridLayout, from PyQt6.QtWidgets import (QLabel, QVBoxLayout, QGridLayout,
QHBoxLayout, QPushButton, QWidget, QStackedWidget) QHBoxLayout, QPushButton, QWidget, QStackedWidget)
from electrum.plugin import run_hook from electrum.plugin import run_hook
@ -140,7 +140,7 @@ class WalletInfoDialog(WindowModalDialog):
der_path_hbox.setContentsMargins(0, 0, 0, 0) der_path_hbox.setContentsMargins(0, 0, 0, 0)
der_path_hbox.addWidget(WWLabel(_("Derivation path") + ':')) der_path_hbox.addWidget(WWLabel(_("Derivation path") + ':'))
der_path_text = WWLabel(ks.get_derivation_prefix() or _("unknown")) 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.addWidget(der_path_text)
der_path_hbox.addStretch() der_path_hbox.addStretch()
ks_vbox.addLayout(der_path_hbox) ks_vbox.addLayout(der_path_hbox)
@ -149,7 +149,7 @@ class WalletInfoDialog(WindowModalDialog):
bip32fp_hbox.setContentsMargins(0, 0, 0, 0) bip32fp_hbox.setContentsMargins(0, 0, 0, 0)
bip32fp_hbox.addWidget(QLabel("BIP32 root fingerprint:")) bip32fp_hbox.addWidget(QLabel("BIP32 root fingerprint:"))
bip32fp_text = WWLabel(ks.get_root_fingerprint() or _("unknown")) 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.addWidget(bip32fp_text)
bip32fp_hbox.addStretch() bip32fp_hbox.addStretch()
ks_vbox.addLayout(bip32fp_hbox) ks_vbox.addLayout(bip32fp_hbox)

6
electrum/gui/qt/watchtower_dialog.py

@ -25,9 +25,9 @@
import enum import enum
from PyQt5.QtGui import QStandardItemModel, QStandardItem from PyQt6.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QPushButton, QLabel) from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QPushButton, QLabel)
from electrum.i18n import _ from electrum.i18n import _
from .util import Buttons from .util import Buttons

10
electrum/gui/qt/wizard/server_connect.py

@ -1,8 +1,8 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtGui import QPixmap from PyQt6.QtGui import QPixmap
from PyQt5.QtWidgets import QCheckBox, QLabel, QHBoxLayout, QVBoxLayout, QWidget from PyQt6.QtWidgets import QCheckBox, QLabel, QHBoxLayout, QVBoxLayout, QWidget
from electrum.i18n import _ from electrum.i18n import _
from electrum.wizard import ServerConnectWizard from electrum.wizard import ServerConnectWizard
@ -70,8 +70,8 @@ class WCWelcome(WizardComponent):
self.layout().addLayout(hbox_img) self.layout().addLayout(hbox_img)
self.layout().addSpacing(50) self.layout().addSpacing(50)
self.layout().addWidget(self.use_advanced_w, False, Qt.AlignHCenter) self.layout().addWidget(self.use_advanced_w, False, Qt.AlignmentFlag.AlignHCenter)
self.layout().addWidget(options_w, False, Qt.AlignHCenter) self.layout().addWidget(options_w, False, Qt.AlignmentFlag.AlignHCenter)
self._valid = True self._valid = True
def on_advanced_changed(self): 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 typing import TYPE_CHECKING, Optional
from PyQt5.QtCore import Qt, QTimer, QRect, pyqtSignal from PyQt6.QtCore import Qt, QTimer, QRect, pyqtSignal
from PyQt5.QtGui import QPen, QPainter, QPalette, QPixmap from PyQt6.QtGui import QPen, QPainter, QPalette, QPixmap
from PyQt5.QtWidgets import (QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget, from PyQt6.QtWidgets import (QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget,
QFileDialog, QSlider, QGridLayout, QDialog, QApplication) QFileDialog, QSlider, QGridLayout, QDialog, QApplication)
from electrum.bip32 import is_bip32_derivation, BIP32Node, normalize_bip32_derivation, xpub_type 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 = QVBoxLayout()
vbox.addSpacing(100) vbox.addSpacing(100)
label.setMinimumWidth(300) label.setMinimumWidth(300)
label.setAlignment(Qt.AlignCenter) label.setAlignment(Qt.AlignmentFlag.AlignCenter)
vbox.addWidget(label) vbox.addWidget(label)
vbox.addSpacing(100) vbox.addSpacing(100)
dialog.setLayout(vbox) dialog.setLayout(vbox)
@ -281,10 +281,10 @@ class WCWalletName(WalletWizardComponent, Logger):
self.layout().addSpacing(50) self.layout().addSpacing(50)
vbox_create_new = QVBoxLayout() 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 = QPushButton(_('Create New Wallet'))
button_create_new.setMinimumWidth(120) 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 = QWidget()
widget_create_new.setLayout(vbox_create_new) widget_create_new.setLayout(vbox_create_new)
vbox_create_new.setContentsMargins(0, 0, 0, 0) vbox_create_new.setContentsMargins(0, 0, 0, 0)
@ -718,7 +718,7 @@ class WCScriptAndDerivation(WalletWizardComponent, Logger):
self.derivation_path_edit.setText(account["derivation_path"]) self.derivation_path_edit.setText(account["derivation_path"])
button.clicked.connect(lambda: Bip39RecoveryDialog(self, get_account_xpub, on_account_select)) 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"))) self.layout().addWidget(QLabel(_("Or")))
def on_choice_click(index): def on_choice_click(index):
@ -893,14 +893,14 @@ class WCMultisig(WalletWizardComponent):
m_label = QLabel() m_label = QLabel()
n_label = QLabel() n_label = QLabel()
m_edit = QSlider(Qt.Horizontal, self) m_edit = QSlider(Qt.Orientation.Horizontal, self)
m_edit.setMinimum(1) m_edit.setMinimum(1)
m_edit.setMaximum(2) m_edit.setMaximum(2)
m_edit.setValue(2) m_edit.setValue(2)
m_edit.valueChanged.connect(on_m) m_edit.valueChanged.connect(on_m)
on_m(m_edit.value()) on_m(m_edit.value())
n_edit = QSlider(Qt.Horizontal, self) n_edit = QSlider(Qt.Orientation.Horizontal, self)
n_edit.setMinimum(2) n_edit.setMinimum(2)
n_edit.setMaximum(15) n_edit.setMaximum(15)
n_edit.setValue(2) n_edit.setValue(2)
@ -940,7 +940,7 @@ class WCImport(WalletWizardComponent):
label = WWLabel(message) label = WWLabel(message)
label.setMinimumWidth(400) label.setMinimumWidth(400)
header_layout.addWidget(label) 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 # TODO: KeysLayout assumes too much in parent, refactor KeysLayout
# for now, fake parent.next_button.setEnabled # for now, fake parent.next_button.setEnabled
@ -1046,17 +1046,17 @@ class CosignWidget(QWidget):
self.update() self.update()
def paintEvent(self, event): def paintEvent(self, event):
bgcolor = self.palette().color(QPalette.Background) bgcolor = self.palette().color(QPalette.ColorRole.Window)
pen = QPen(bgcolor, 7, Qt.SolidLine) pen = QPen(bgcolor, 7, Qt.PenStyle.SolidLine)
qp = QPainter() qp = QPainter()
qp.begin(self) qp.begin(self)
qp.setPen(pen) qp.setPen(pen)
qp.setRenderHint(QPainter.Antialiasing) qp.setRenderHint(QPainter.RenderHint.Antialiasing)
qp.setBrush(Qt.gray) qp.setBrush(Qt.GlobalColor.gray)
for i in range(self.n): for i in range(self.n):
alpha = int(16 * 360 * i/self.n) alpha = int(16 * 360 * i/self.n)
alpha2 = int(16 * 360 * 1/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.drawPie(self.R, alpha, alpha2)
qp.end() qp.end()
@ -1256,10 +1256,10 @@ class WCHWUnlock(WalletWizardComponent, Logger):
self.password = None self.password = None
ok_icon = QLabel() ok_icon = QLabel()
ok_icon.setPixmap(QPixmap(icon_path('confirmed.png')).scaledToWidth(48, mode=Qt.SmoothTransformation)) ok_icon.setPixmap(QPixmap(icon_path('confirmed.png')).scaledToWidth(48, mode=Qt.TransformationMode.SmoothTransformation))
ok_icon.setAlignment(Qt.AlignCenter) ok_icon.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.ok_l = WWLabel(_('Hardware successfully unlocked')) 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().addStretch(1)
self.layout().addWidget(ok_icon) self.layout().addWidget(ok_icon)
self.layout().addWidget(self.ok_l) self.layout().addWidget(self.ok_l)
@ -1335,10 +1335,10 @@ class WCHWXPub(WalletWizardComponent, Logger):
self.soft_device_id = None self.soft_device_id = None
ok_icon = QLabel() ok_icon = QLabel()
ok_icon.setPixmap(QPixmap(icon_path('confirmed.png')).scaledToWidth(48, mode=Qt.SmoothTransformation)) ok_icon.setPixmap(QPixmap(icon_path('confirmed.png')).scaledToWidth(48, mode=Qt.TransformationMode.SmoothTransformation))
ok_icon.setAlignment(Qt.AlignCenter) ok_icon.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.ok_l = WWLabel(_('Hardware keystore added to wallet')) 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().addStretch(1)
self.layout().addWidget(ok_icon) self.layout().addWidget(ok_icon)
self.layout().addWidget(self.ok_l) self.layout().addWidget(self.ok_l)
@ -1423,10 +1423,10 @@ class WCHWUninitialized(WalletWizardComponent):
cosigner_data = self.wizard.current_cosigner(self.wizard_data) cosigner_data = self.wizard.current_cosigner(self.wizard_data)
_name, _info = cosigner_data['hardware_device'] _name, _info = cosigner_data['hardware_device']
w_icon = QLabel() w_icon = QLabel()
w_icon.setPixmap(QPixmap(icon_path('warning.png')).scaledToWidth(48, mode=Qt.SmoothTransformation)) w_icon.setPixmap(QPixmap(icon_path('warning.png')).scaledToWidth(48, mode=Qt.TransformationMode.SmoothTransformation))
w_icon.setAlignment(Qt.AlignCenter) w_icon.setAlignment(Qt.AlignmentFlag.AlignCenter)
label = WWLabel(_('This {} is not initialized. Use manufacturer tooling to initialize the device.').format(_info.model_name)) 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().addStretch(1)
self.layout().addWidget(w_icon) self.layout().addWidget(w_icon)
self.layout().addWidget(label) self.layout().addWidget(label)

22
electrum/gui/qt/wizard/wizard.py

@ -3,9 +3,9 @@ import threading
from abc import abstractmethod from abc import abstractmethod
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, pyqtSlot, QSize, QMetaObject from PyQt6.QtCore import Qt, QTimer, pyqtSignal, pyqtSlot, QSize, QMetaObject
from PyQt5.QtGui import QPixmap from PyQt6.QtGui import QPixmap
from PyQt5.QtWidgets import (QDialog, QPushButton, QWidget, QLabel, QVBoxLayout, QScrollArea, from PyQt6.QtWidgets import (QDialog, QPushButton, QWidget, QLabel, QVBoxLayout, QScrollArea,
QHBoxLayout, QLayout) QHBoxLayout, QLayout)
from electrum.i18n import _ from electrum.i18n import _
@ -61,7 +61,7 @@ class QEAbstractWizard(QDialog, MessageBoxMixin):
please_wait_layout = QVBoxLayout() please_wait_layout = QVBoxLayout()
please_wait_layout.addStretch(1) please_wait_layout.addStretch(1)
self.please_wait_l = QLabel(_("Please wait...")) 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.addWidget(self.please_wait_l)
please_wait_layout.addStretch(1) please_wait_layout.addStretch(1)
self.please_wait = QWidget() self.please_wait = QWidget()
@ -71,11 +71,11 @@ class QEAbstractWizard(QDialog, MessageBoxMixin):
error_layout = QVBoxLayout() error_layout = QVBoxLayout()
error_layout.addStretch(1) error_layout.addStretch(1)
error_icon = QLabel() error_icon = QLabel()
error_icon.setPixmap(QPixmap(icon_path('warning.png')).scaledToWidth(48, mode=Qt.SmoothTransformation)) error_icon.setPixmap(QPixmap(icon_path('warning.png')).scaledToWidth(48, mode=Qt.TransformationMode.SmoothTransformation))
error_icon.setAlignment(Qt.AlignCenter) error_icon.setAlignment(Qt.AlignmentFlag.AlignCenter)
error_layout.addWidget(error_icon) error_layout.addWidget(error_icon)
self.error_msg = WWLabel() 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.addWidget(self.error_msg)
error_layout.addStretch(1) error_layout.addStretch(1)
self.error = QWidget() self.error = QWidget()
@ -92,9 +92,9 @@ class QEAbstractWizard(QDialog, MessageBoxMixin):
scroll_widget = QWidget() scroll_widget = QWidget()
scroll_widget.setLayout(inner_vbox) scroll_widget.setLayout(inner_vbox)
scroll = QScrollArea() scroll = QScrollArea()
scroll.setFocusPolicy(Qt.NoFocus) scroll.setFocusPolicy(Qt.FocusPolicy.NoFocus)
scroll.setWidget(scroll_widget) scroll.setWidget(scroll_widget)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
scroll.setWidgetResizable(True) scroll.setWidgetResizable(True)
icon_vbox = QVBoxLayout() icon_vbox = QVBoxLayout()
icon_vbox.addWidget(self.logo) icon_vbox.addWidget(self.logo)
@ -115,7 +115,7 @@ class QEAbstractWizard(QDialog, MessageBoxMixin):
self.show() self.show()
self.raise_() 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: def sizeHint(self) -> QSize:
return QSize(600, 400) return QSize(600, 400)
@ -169,7 +169,7 @@ class QEAbstractWizard(QDialog, MessageBoxMixin):
def set_icon(self, filename): def set_icon(self, filename):
prior_filename, self.icon_filename = self.icon_filename, filename prior_filename, self.icon_filename = self.icon_filename, filename
self.logo.setPixmap(QPixmap(icon_path(filename)) self.logo.setPixmap(QPixmap(icon_path(filename))
.scaledToWidth(60, mode=Qt.SmoothTransformation)) .scaledToWidth(60, mode=Qt.TransformationMode.SmoothTransformation))
return prior_filename return prior_filename
def can_go_back(self) -> bool: def can_go_back(self) -> bool:

4
electrum/plugins/audio_modem/qt.py

@ -6,7 +6,7 @@ import sys
import platform import platform
from typing import TYPE_CHECKING 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.plugin import BasePlugin, hook
from electrum.gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog, read_QIcon from electrum.gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog, read_QIcon
@ -72,7 +72,7 @@ class Plugin(BasePlugin):
ok_button.clicked.connect(d.accept) ok_button.clicked.connect(d.accept)
layout.addWidget(ok_button, 1, 1) layout.addWidget(ok_button, 1, 1)
return bool(d.exec_()) return bool(d.exec())
@hook @hook
def transaction_dialog(self, dialog: 'TxDialog'): def transaction_dialog(self, dialog: 'TxDialog'):

8
electrum/plugins/bitbox02/qt.py

@ -2,8 +2,8 @@ import threading
from functools import partial from functools import partial
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, QMetaObject, Q_RETURN_ARG, pyqtSlot, pyqtSignal from PyQt6.QtCore import Qt, QMetaObject, Q_RETURN_ARG, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import QLabel, QVBoxLayout, QLineEdit, QHBoxLayout from PyQt6.QtWidgets import QLabel, QVBoxLayout, QLineEdit, QHBoxLayout
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugin import hook from electrum.plugin import hook
@ -83,7 +83,7 @@ class BitBox02_Handler(QtHandlerBase):
super(BitBox02_Handler, self).__init__(win, "BitBox02") super(BitBox02_Handler, self).__init__(win, "BitBox02")
def name_multisig_account(self): 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) @pyqtSlot(result=str)
def _name_multisig_account(self): def _name_multisig_account(self):
@ -109,7 +109,7 @@ class BitBox02_Handler(QtHandlerBase):
vbox.addLayout(he) vbox.addLayout(he)
vbox.addLayout(hlb) vbox.addLayout(hlb)
dialog.setLayout(vbox) dialog.setLayout(vbox)
dialog.exec_() dialog.exec()
return name.text().strip() return name.text().strip()

16
electrum/plugins/coldcard/qt.py

@ -1,8 +1,8 @@
from functools import partial from functools import partial
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt5.QtWidgets import QPushButton, QLabel, QVBoxLayout, QWidget, QGridLayout from PyQt6.QtWidgets import QPushButton, QLabel, QVBoxLayout, QWidget, QGridLayout
from electrum.gui.qt.util import WindowModalDialog, CloseButton, getOpenFileName, getSaveFileName from electrum.gui.qt.util import WindowModalDialog, CloseButton, getOpenFileName, getSaveFileName
from electrum.gui.qt.main_window import ElectrumWindow from electrum.gui.qt.main_window import ElectrumWindow
@ -81,7 +81,7 @@ class Plugin(ColdcardPlugin, QtPluginBase):
def show_settings_dialog(self, window, keystore): def show_settings_dialog(self, window, keystore):
# When they click on the icon for CC we come here. # When they click on the icon for CC we come here.
# - doesn't matter if device not connected, continue # - doesn't matter if device not connected, continue
CKCCSettingsDialog(window, self, keystore).exec_() CKCCSettingsDialog(window, self, keystore).exec()
@hook @hook
def init_wallet_wizard(self, wizard: 'QENewWalletWizard'): def init_wallet_wizard(self, wizard: 'QENewWalletWizard'):
@ -143,9 +143,9 @@ class CKCCSettingsDialog(WindowModalDialog):
<span style="font-size: x-large">Coldcard Wallet</span> <span style="font-size: x-large">Coldcard Wallet</span>
<br><span style="font-size: medium">from Coinkite Inc.</span> <br><span style="font-size: medium">from Coinkite Inc.</span>
<br><a href="https://coldcardwallet.com">coldcardwallet.com</a>''') <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 y = 3
rows = [ rows = [
@ -158,10 +158,10 @@ class CKCCSettingsDialog(WindowModalDialog):
for row_num, (member_name, label) in enumerate(rows): for row_num, (member_name, label) in enumerate(rows):
# XXX we know xfp already, even if not connected # XXX we know xfp already, even if not connected
widget = QLabel('<tt>000000000000') 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(QLabel(label), y, 0, 1,1, Qt.AlignmentFlag.AlignRight)
grid.addWidget(widget, y, 1, 1, 1, Qt.AlignLeft) grid.addWidget(widget, y, 1, 1, 1, Qt.AlignmentFlag.AlignLeft)
setattr(self, member_name, widget) setattr(self, member_name, widget)
y += 1 y += 1
body_layout.addLayout(grid) 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 from typing import TYPE_CHECKING, Union, List, Tuple, Dict
import ssl import ssl
from PyQt5.QtCore import QObject, pyqtSignal from PyQt6.QtCore import QObject, pyqtSignal
from PyQt5.QtWidgets import QPushButton from PyQt6.QtWidgets import QPushButton
import certifi import certifi
from electrum import util, keystore, ecc, crypto from electrum import util, keystore, ecc, crypto

2
electrum/plugins/digitalbitbox/qt.py

@ -2,7 +2,7 @@ import threading
from functools import partial from functools import partial
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import pyqtSignal from PyQt6.QtCore import pyqtSignal
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugin import hook from electrum.plugin import hook

12
electrum/plugins/hw_wallet/qt.py

@ -28,8 +28,8 @@ import threading
from functools import partial from functools import partial
from typing import TYPE_CHECKING, Union, Optional, Sequence, Tuple from typing import TYPE_CHECKING, Union, Optional, Sequence, Tuple
from PyQt5.QtCore import QObject, pyqtSignal, Qt from PyQt6.QtCore import QObject, pyqtSignal, Qt
from PyQt5.QtWidgets import QVBoxLayout, QLineEdit, QHBoxLayout, QLabel from PyQt6.QtWidgets import QVBoxLayout, QLineEdit, QHBoxLayout, QLabel
from electrum.gui.qt.password_dialog import PasswordLayout, PW_PASSPHRASE from electrum.gui.qt.password_dialog import PasswordLayout, PW_PASSPHRASE
from electrum.gui.qt.util import (read_QIcon, WWLabel, OkButton, WindowModalDialog, 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(playout.layout())
vbox.addLayout(Buttons(CancelButton(d), OK_button)) vbox.addLayout(Buttons(CancelButton(d), OK_button))
d.setLayout(vbox) d.setLayout(vbox)
passphrase = playout.new_password() if d.exec_() else None passphrase = playout.new_password() if d.exec() else None
else: else:
pw = PasswordLineEdit() pw = PasswordLineEdit()
pw.setMinimumWidth(200) pw.setMinimumWidth(200)
@ -151,7 +151,7 @@ class QtHandlerBase(HardwareHandlerBase, QObject, Logger):
vbox.addWidget(pw) vbox.addWidget(pw)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
d.setLayout(vbox) d.setLayout(vbox)
passphrase = pw.text() if d.exec_() else None passphrase = pw.text() if d.exec() else None
self.passphrase = passphrase self.passphrase = passphrase
self.done.set() self.done.set()
@ -164,7 +164,7 @@ class QtHandlerBase(HardwareHandlerBase, QObject, Logger):
text.returnPressed.connect(dialog.accept) text.returnPressed.connect(dialog.accept)
hbox.addWidget(text) hbox.addWidget(text)
hbox.addStretch(1) hbox.addStretch(1)
dialog.exec_() # Firmware cannot handle cancellation dialog.exec() # Firmware cannot handle cancellation
self.word = text.text() self.word = text.text()
self.done.set() self.done.set()
@ -176,7 +176,7 @@ class QtHandlerBase(HardwareHandlerBase, QObject, Logger):
title = _('Please check your {} device').format(self.device) title = _('Please check your {} device').format(self.device)
self.dialog = dialog = WindowModalDialog(self.top_level_window(), title) self.dialog = dialog = WindowModalDialog(self.top_level_window(), title)
label = QLabel(msg) label = QLabel(msg)
label.setTextInteractionFlags(Qt.TextSelectableByMouse) label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
vbox = QVBoxLayout(dialog) vbox = QVBoxLayout(dialog)
vbox.addWidget(label) vbox.addWidget(label)
if on_cancel: if on_cancel:

2
electrum/plugins/jade/qt.py

@ -1,7 +1,7 @@
from functools import partial from functools import partial
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import pyqtSignal from PyQt6.QtCore import pyqtSignal
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugin import hook from electrum.plugin import hook

26
electrum/plugins/keepkey/qt.py

@ -2,9 +2,9 @@ import threading
from functools import partial from functools import partial
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, QEventLoop, pyqtSignal, QRegExp from PyQt6.QtCore import Qt, QEventLoop, pyqtSignal, QRegularExpression
from PyQt5.QtGui import QRegExpValidator from PyQt6.QtGui import QRegularExpressionValidator
from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton, from PyQt6.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton,
QHBoxLayout, QButtonGroup, QGroupBox, QDialog, QHBoxLayout, QButtonGroup, QGroupBox, QDialog,
QTextEdit, QLineEdit, QRadioButton, QCheckBox, QWidget, QTextEdit, QLineEdit, QRadioButton, QCheckBox, QWidget,
QMessageBox, QSlider, QTabWidget) QMessageBox, QSlider, QTabWidget)
@ -88,7 +88,7 @@ class CharacterDialog(WindowModalDialog):
self.finished_button = QPushButton(_("Seed Entered")) self.finished_button = QPushButton(_("Seed Entered"))
self.cancel_button = QPushButton(_("Cancel")) self.cancel_button = QPushButton(_("Cancel"))
self.finished_button.clicked.connect(partial(self.process_key, self.finished_button.clicked.connect(partial(self.process_key,
Qt.Key_Return)) Qt.Key.Key_Return))
self.cancel_button.clicked.connect(self.rejected) self.cancel_button.clicked.connect(self.rejected)
buttons = Buttons(self.finished_button, self.cancel_button) buttons = Buttons(self.finished_button, self.cancel_button)
vbox.addSpacing(40) vbox.addSpacing(40)
@ -118,9 +118,9 @@ class CharacterDialog(WindowModalDialog):
def process_key(self, key): def process_key(self, key):
self.data = None 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} 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} self.data = {'delete': True}
elif self.is_valid_alpha_space(key): elif self.is_valid_alpha_space(key):
self.data = {'character': chr(key).lower()} self.data = {'character': chr(key).lower()}
@ -136,7 +136,7 @@ class CharacterDialog(WindowModalDialog):
self.word_pos = word_pos self.word_pos = word_pos
self.character_pos = character_pos self.character_pos = character_pos
self.refresh() self.refresh()
if self.loop.exec_(): if self.loop.exec():
self.data = None # User cancelled self.data = None # User cancelled
@ -183,7 +183,7 @@ class QtHandler(QtHandlerBase):
vbox.addWidget(matrix) vbox.addWidget(matrix)
vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog))) vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog)))
dialog.setLayout(vbox) dialog.setLayout(vbox)
dialog.exec_() dialog.exec()
self.response = str(matrix.get_value()) self.response = str(matrix.get_value())
self.done.set() self.done.set()
@ -217,7 +217,7 @@ class QtPlugin(QtPluginBase):
return device_id return device_id
def show_dialog(device_id): def show_dialog(device_id):
if 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) keystore.thread.add(connect, on_success=show_dialog)
@ -276,7 +276,7 @@ class KeepkeyInitLayout(QVBoxLayout):
self.addWidget(QLabel(msg)) self.addWidget(QLabel(msg))
self.addWidget(self.text_e) self.addWidget(self.text_e)
self.pin = QLineEdit() 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) self.pin.setMaximumWidth(100)
hbox_pin = QHBoxLayout() hbox_pin = QHBoxLayout()
hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):"))) 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" msg = _("Are you SURE you want to wipe the device?\n"
"Your wallet still has bitcoins in it!") "Your wallet still has bitcoins in it!")
if not self.question(msg, title=title, if not self.question(msg, title=title,
icon=QMessageBox.Critical): icon=QMessageBox.Icon.Critical):
return return
invoke_client('wipe_device', unpair_after=True) invoke_client('wipe_device', unpair_after=True)
@ -521,11 +521,11 @@ class SettingsDialog(WindowModalDialog):
# Settings tab - Session Timeout # Settings tab - Session Timeout
timeout_label = QLabel(_("Session Timeout")) timeout_label = QLabel(_("Session Timeout"))
timeout_minutes = QLabel() timeout_minutes = QLabel()
timeout_slider = QSlider(Qt.Horizontal) timeout_slider = QSlider(Qt.Orientation.Horizontal)
timeout_slider.setRange(1, 60) timeout_slider.setRange(1, 60)
timeout_slider.setSingleStep(1) timeout_slider.setSingleStep(1)
timeout_slider.setTickInterval(5) timeout_slider.setTickInterval(5)
timeout_slider.setTickPosition(QSlider.TicksBelow) timeout_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
timeout_slider.setTracking(True) timeout_slider.setTracking(True)
timeout_msg = QLabel( timeout_msg = QLabel(
_("Clear the session after the specified period " _("Clear the session after the specified period "

6
electrum/plugins/labels/qt.py

@ -3,8 +3,8 @@ import traceback
import sys import sys
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import QObject, pyqtSignal from PyQt6.QtCore import QObject, pyqtSignal
from PyQt5.QtWidgets import (QHBoxLayout, QLabel, QVBoxLayout) from PyQt6.QtWidgets import (QHBoxLayout, QLabel, QVBoxLayout)
from electrum.plugin import hook from electrum.plugin import hook
from electrum.i18n import _ from electrum.i18n import _
@ -60,7 +60,7 @@ class Plugin(LabelsPlugin):
vbox.addLayout(hbox) vbox.addLayout(hbox)
vbox.addSpacing(20) vbox.addSpacing(20)
vbox.addLayout(Buttons(OkButton(d))) vbox.addLayout(Buttons(OkButton(d)))
return bool(d.exec_()) return bool(d.exec())
def on_pulled(self, wallet): def on_pulled(self, wallet):
self.obj.labels_changed_signal.emit(wallet) self.obj.labels_changed_signal.emit(wallet)

2
electrum/plugins/ledger/auth2fa.py

@ -1,7 +1,7 @@
import copy import copy
from typing import TYPE_CHECKING 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) QWidget, QHBoxLayout, QComboBox)
from btchip.btchip import BTChipException from btchip.btchip import BTChipException

6
electrum/plugins/ledger/qt.py

@ -1,8 +1,8 @@
from functools import partial from functools import partial
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import pyqtSignal from PyQt6.QtCore import pyqtSignal
from PyQt5.QtWidgets import QInputDialog, QLineEdit from PyQt6.QtWidgets import QInputDialog, QLineEdit
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugin import hook from electrum.plugin import hook
@ -77,7 +77,7 @@ class Ledger_Handler(QtHandlerBase):
self.message_dialog(repr(e)) self.message_dialog(repr(e))
return return
dialog = LedgerAuthDialog(self, data, client=client) dialog = LedgerAuthDialog(self, data, client=client)
dialog.exec_() dialog.exec()
self.word = dialog.pin self.word = dialog.pin
self.done.set() self.done.set()

149
electrum/plugins/revealer/qt.py

@ -22,11 +22,11 @@ import sys
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import qrcode import qrcode
from PyQt5.QtPrintSupport import QPrinter from PyQt6.QtPrintSupport import QPrinter
from PyQt5.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize from PyQt6.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize, QMarginsF
from PyQt5.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont, from PyQt6.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont,
QColor, QDesktopServices, qRgba, QPainterPath) QColor, QDesktopServices, qRgba, QPainterPath, QPageSize)
from PyQt5.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel, from PyQt6.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QLineEdit) QPushButton, QLineEdit)
from electrum.plugin import hook from electrum.plugin import hook
@ -128,7 +128,7 @@ class Plugin(RevealerPlugin):
logo_label.setPixmap(QPixmap(icon_path('revealer.png'))) logo_label.setPixmap(QPixmap(icon_path('revealer.png')))
# Align the logo label to the top left. # 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. # Create a VBox layout for the main contents of the dialog.
vbox_layout = QVBoxLayout() 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.")) 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. # Allow users to select text in the labels.
create_or_load_noise_file_label.setTextInteractionFlags(Qt.TextSelectableByMouse) create_or_load_noise_file_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
instructions_label.setTextInteractionFlags(Qt.TextSelectableByMouse) instructions_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
# Create the buttons. # Create the buttons.
create_button = QPushButton(_("Create a new Revealer noise file")) create_button = QPushButton(_("Create a new Revealer noise file"))
@ -187,7 +187,7 @@ class Plugin(RevealerPlugin):
# Populate the VBox layout. # Populate the VBox layout.
vbox_layout.addWidget(create_or_load_noise_file_label) 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(instructions_label)
vbox_layout.addWidget(self.noise_scan_qr_textedit) vbox_layout.addWidget(self.noise_scan_qr_textedit)
vbox_layout.addLayout(Buttons(self.next_button)) vbox_layout.addLayout(Buttons(self.next_button))
@ -196,7 +196,7 @@ class Plugin(RevealerPlugin):
hbox_layout.addStretch(1) hbox_layout.addStretch(1)
vbox_layout.addStretch(1) vbox_layout.addStretch(1)
return bool(self.d.exec_()) return bool(self.d.exec())
def get_noise(self): def get_noise(self):
# Get the text from the scan QR text edit. # Get the text from the scan QR text edit.
@ -270,7 +270,7 @@ class Plugin(RevealerPlugin):
textCursor = self.custom_secret_scan_qr_textedit.textCursor() 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) # 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. # Set the text cursor with the corrected position.
self.custom_secret_scan_qr_textedit.setTextCursor(textCursor) self.custom_secret_scan_qr_textedit.setTextCursor(textCursor)
@ -311,7 +311,7 @@ class Plugin(RevealerPlugin):
logo_label.setPixmap(QPixmap(icon_path('revealer.png'))) logo_label.setPixmap(QPixmap(icon_path('revealer.png')))
# Align the logo label to the top left. # 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. # Create a VBox layout for the main contents of the dialog.
vbox_layout = QVBoxLayout() 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.")) 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. # Allow users to select text in the labels.
ready_to_encrypt_label.setTextInteractionFlags(Qt.TextSelectableByMouse) ready_to_encrypt_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
instructions_label.setTextInteractionFlags(Qt.TextSelectableByMouse) instructions_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
self.custom_secret_character_count_label.setTextInteractionFlags(Qt.TextSelectableByMouse) self.custom_secret_character_count_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
self.custom_secret_maximum_characters_warning_label one_time_pad_warning_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
one_time_pad_warning_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
# Align the custom secret character count label to the right. # 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. # Initially hide the custom secret character count label.
self.custom_secret_maximum_characters_warning_label.setVisible(False) self.custom_secret_maximum_characters_warning_label.setVisible(False)
@ -376,12 +375,12 @@ class Plugin(RevealerPlugin):
# Populate the VBox layout. # Populate the VBox layout.
vbox_layout.addWidget(ready_to_encrypt_label) 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(instructions_label)
vbox_layout.addWidget(self.custom_secret_scan_qr_textedit) vbox_layout.addWidget(self.custom_secret_scan_qr_textedit)
vbox_layout.addWidget(self.custom_secret_character_count_label) vbox_layout.addWidget(self.custom_secret_character_count_label)
vbox_layout.addWidget(self.custom_secret_maximum_characters_warning_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.addSpacing(40)
vbox_layout.addWidget(one_time_pad_warning_label) vbox_layout.addWidget(one_time_pad_warning_label)
vbox_layout.addLayout(Buttons(CloseButton(d))) vbox_layout.addLayout(Buttons(CloseButton(d)))
@ -390,7 +389,7 @@ class Plugin(RevealerPlugin):
hbox_layout.addStretch(1) hbox_layout.addStretch(1)
vbox_layout.addStretch(1) vbox_layout.addStretch(1)
return bool(d.exec_()) return bool(d.exec())
def update_wallet_name(self, name): def update_wallet_name(self, name):
self.wallet_name = str(name) self.wallet_name = str(name)
@ -412,9 +411,9 @@ class Plugin(RevealerPlugin):
else: else:
txt = self.txt.upper() txt = self.txt.upper()
img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono) img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format.Format_Mono)
bitmap = QBitmap.fromImage(img, Qt.MonoOnly) bitmap = QBitmap.fromImage(img, Qt.ImageConversionFlag.MonoOnly)
bitmap.fill(Qt.white) bitmap.fill(Qt.GlobalColor.white)
painter = QPainter() painter = QPainter()
painter.begin(bitmap) painter.begin(bitmap)
if len(txt) < 102 : if len(txt) < 102 :
@ -430,8 +429,8 @@ class Plugin(RevealerPlugin):
max_lines = 9 max_lines = 9
max_words = int(max_letters/4) max_words = int(max_letters/4)
font = QFont('Source Sans 3', fontsize, QFont.Bold) font = QFont('Source Sans 3', fontsize, QFont.Weight.Bold)
font.setLetterSpacing(QFont.PercentageSpacing, 100) font.setLetterSpacing(QFont.SpacingType.PercentageSpacing, 100)
font.setPixelSize(fontsize) font.setPixelSize(fontsize)
painter.setFont(font) painter.setFont(font)
seed_array = txt.split(' ') seed_array = txt.split(' ')
@ -442,7 +441,7 @@ class Plugin(RevealerPlugin):
while len(' '.join(map(str, temp_seed))) > max_letters: while len(' '.join(map(str, temp_seed))) > max_letters:
nwords = nwords - 1 nwords = nwords - 1
temp_seed = seed_array[:nwords] 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] del seed_array[:nwords]
painter.end() painter.end()
@ -458,7 +457,7 @@ class Plugin(RevealerPlugin):
self.versioned_seed = self.gen_random_versioned_seed() self.versioned_seed = self.gen_random_versioned_seed()
assert self.versioned_seed assert self.versioned_seed
w, h = self.SIZE 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) noise_map = self.get_noise_map(self.versioned_seed)
for (x,y), pixel in noise_map.items(): for (x,y), pixel in noise_map.items():
@ -471,7 +470,7 @@ class Plugin(RevealerPlugin):
def make_calnoise(self): def make_calnoise(self):
random.seed(self.calibration_noise) random.seed(self.calibration_noise)
w, h = self.SIZE 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 x in range(w):
for y in range(h): for y in range(h):
rawnoise.setPixel(x,y,random.randint(0, 1)) rawnoise.setPixel(x,y,random.randint(0, 1))
@ -481,7 +480,7 @@ class Plugin(RevealerPlugin):
revealer = self.pixelcode_2x2(self.rawnoise) revealer = self.pixelcode_2x2(self.rawnoise)
revealer.invertPixels() revealer.invertPixels()
revealer = QBitmap.fromImage(revealer) 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) revealer = self.overlay_marks(revealer)
self.filename_prefix = 'revealer_' self.filename_prefix = 'revealer_'
@ -489,7 +488,7 @@ class Plugin(RevealerPlugin):
self.toPdf(QImage(revealer)) self.toPdf(QImage(revealer))
def make_cypherseed(self, img, rawnoise, calibration=False, is_seed = True): 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 = QPainter()
p.begin(img) p.begin(img)
p.setCompositionMode(26) #xor p.setCompositionMode(26) #xor
@ -497,7 +496,7 @@ class Plugin(RevealerPlugin):
p.end() p.end()
cypherseed = self.pixelcode_2x2(img) cypherseed = self.pixelcode_2x2(img)
cypherseed = QBitmap.fromImage(cypherseed) 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) cypherseed = self.overlay_marks(cypherseed, True, calibration)
if not is_seed: if not is_seed:
@ -517,9 +516,9 @@ class Plugin(RevealerPlugin):
return cypherseed return cypherseed
def calibration(self): def calibration(self):
img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono) img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format.Format_Mono)
bitmap = QBitmap.fromImage(img, Qt.MonoOnly) bitmap = QBitmap.fromImage(img, Qt.ImageConversionFlag.MonoOnly)
bitmap.fill(Qt.black) bitmap.fill(Qt.GlobalColor.black)
self.make_calnoise() self.make_calnoise()
img = self.overlay_marks(self.calnoise.scaledToHeight(self.f_size.height()), False, True) img = self.overlay_marks(self.calnoise.scaledToHeight(self.f_size.height()), False, True)
self.calibration_pdf(img) self.calibration_pdf(img)
@ -528,11 +527,11 @@ class Plugin(RevealerPlugin):
def toPdf(self, image): def toPdf(self, image):
printer = QPrinter() printer = QPrinter()
printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter) printer.setPageSize(QPageSize(QSizeF(210, 297), QPrinter.Unit.Millimeter))
printer.setResolution(600) printer.setResolution(600)
printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat)
printer.setOutputFileName(self.get_path_to_revealer_file('.pdf')) 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 = QPainter()
painter.begin(printer) painter.begin(printer)
@ -547,28 +546,28 @@ class Plugin(RevealerPlugin):
painter.drawImage(553,533, image) painter.drawImage(553,533, image)
wpath = QPainterPath() wpath = QPainterPath()
wpath.addRoundedRect(QRectF(553,533, size_h, size_v), 19, 19) 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.drawPath(wpath)
painter.end() painter.end()
def calibration_pdf(self, image): def calibration_pdf(self, image):
printer = QPrinter() printer = QPrinter()
printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter) printer.setPageSize(QPageSize(QSizeF(210, 297), QPrinter.Unit.Millimeter))
printer.setResolution(600) printer.setResolution(600)
printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat)
printer.setOutputFileName(self.get_path_to_calibration_file()) 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 = QPainter()
painter.begin(printer) painter.begin(printer)
painter.drawImage(553,533, image) 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.setFont(font)
painter.drawText(254,277, _("Calibration sheet")) 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.setFont(font)
painter.drawText(600,2077, _("Instructions:")) painter.drawText(600,2077, _("Instructions:"))
font = QFont("", 7, QFont.Normal) font = QFont("", 7, QFont.Weight.Normal)
painter.setFont(font) painter.setFont(font)
painter.drawText(700, 2177, _("1. Place this paper on a flat and well illuminated surface.")) 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.")) 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() painter.end()
def pixelcode_2x2(self, img): 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) white = qRgba(255,255,255,0)
black = qRgba(0,0,0,255) black = qRgba(0,0,0,255)
@ -600,8 +599,8 @@ class Plugin(RevealerPlugin):
return result return result
def overlay_marks(self, img, is_cseed=False, calibration_sheet=False): def overlay_marks(self, img, is_cseed=False, calibration_sheet=False):
border_color = Qt.white border_color = Qt.GlobalColor.white
base_img = QImage(self.f_size.width(),self.f_size.height(), QImage.Format_ARGB32) base_img = QImage(self.f_size.width(),self.f_size.height(), QImage.Format.Format_ARGB32)
base_img.fill(border_color) base_img.fill(border_color)
img = QImage(img) img = QImage(img)
@ -618,7 +617,7 @@ class Plugin(RevealerPlugin):
img) img)
#frame around image #frame around image
pen = QPen(Qt.black, 2) pen = QPen(Qt.GlobalColor.black, 2)
painter.setPen(pen) painter.setPen(pen)
#horz #horz
@ -635,8 +634,8 @@ class Plugin(RevealerPlugin):
(total_distance_h)+(border_thick/2), (total_distance_h)+(border_thick/2),
base_img.width()-((total_distance_h)*2)-((border_thick)-1), base_img.width()-((total_distance_h)*2)-((border_thick)-1),
(base_img.height()-((total_distance_h))*2)-((border_thick)-1))) (base_img.height()-((total_distance_h))*2)-((border_thick)-1)))
pen = QPen(Qt.black, border_thick) pen = QPen(Qt.GlobalColor.black, border_thick)
pen.setJoinStyle (Qt.MiterJoin) pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin)
painter.setPen(pen) painter.setPen(pen)
painter.drawPath(Rpath) painter.drawPath(Rpath)
@ -644,11 +643,11 @@ class Plugin(RevealerPlugin):
Bpath = QPainterPath() Bpath = QPainterPath()
Bpath.addRect(QRectF((total_distance_h), (total_distance_h), Bpath.addRect(QRectF((total_distance_h), (total_distance_h),
base_img.width()-((total_distance_h)*2), (base_img.height()-((total_distance_h))*2))) 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.setPen(pen)
painter.drawPath(Bpath) painter.drawPath(Bpath)
pen = QPen(Qt.black, 1) pen = QPen(Qt.GlobalColor.black, 1)
painter.setPen(pen) painter.setPen(pen)
painter.drawLine(0, base_img.height()//2, total_distance_h, base_img.height()//2) 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) painter.drawLine(base_img.width()//2, 0, base_img.width()//2, total_distance_h)
@ -658,29 +657,29 @@ class Plugin(RevealerPlugin):
#print code #print code
f_size = 37 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) font.setPixelSize(35)
painter.setFont(font) painter.setFont(font)
if not calibration_sheet: if not calibration_sheet:
if is_cseed: #its a secret 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(0, dist_v, base_img.width(), dist_v)
painter.drawLine(dist_h, 0, dist_h, base_img.height()) 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(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.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, 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), 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.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.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)) int(base_img.height()-total_distance_h-((border_thick*8)/2)-(border_thick/2)-2))
painter.setPen(QColor(0,0,0,255)) painter.setPen(QColor(0,0,0,255))
painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick - 11, 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) self.versioned_seed.version + '_'+self.versioned_seed.checksum)
painter.end() 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(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.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(0, dist_v, base_img.width(), dist_v)
painter.drawLine(dist_h, 0, dist_h, base_img.height()) 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(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.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))) 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 #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)), 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))) 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)), 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.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)) 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( painter.drawLine(
int(base_img.width() - ((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),
int((base_img.height() - ((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.height()-107),
int(base_img.width()-total_distance_h - border_thick -93), int(base_img.width()-total_distance_h - border_thick -93),
int(base_img.height()-total_distance_h - border_thick)), 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, 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 # draw qr code
qr_qt = self.paintQR(self.versioned_seed.get_ui_string_version_plus_seed() 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, base_img.height()-65-qr_size,
qr_size, qr_size) qr_size, qr_size)
painter.drawImage(target, qr_qt) painter.drawImage(target, qr_qt)
painter.setPen(QPen(Qt.black, 4)) painter.setPen(QPen(Qt.GlobalColor.black, 4))
painter.drawLine( painter.drawLine(
int(base_img.width()-65-qr_size), int(base_img.width()-65-qr_size),
int(base_img.height()-65-qr_size), int(base_img.height()-65-qr_size),
@ -761,23 +760,23 @@ class Plugin(RevealerPlugin):
else: # calibration only else: # calibration only
painter.end() painter.end()
cal_img = QImage(self.f_size.width() + 100, self.f_size.height() + 100, cal_img = QImage(self.f_size.width() + 100, self.f_size.height() + 100,
QImage.Format_ARGB32) QImage.Format.Format_ARGB32)
cal_img.fill(Qt.white) cal_img.fill(Qt.GlobalColor.white)
cal_painter = QPainter() cal_painter = QPainter()
cal_painter.begin(cal_img) cal_painter.begin(cal_img)
cal_painter.drawImage(0,0, base_img) cal_painter.drawImage(0,0, base_img)
#black lines in the middle of border top left only #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(0, dist_v, base_img.width(), dist_v)
cal_painter.drawLine(dist_h, 0, dist_h, base_img.height()) 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) cal_painter.setPen(pen)
n=15 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): for x in range(-n,n):
#lines on bottom (vertical calibration) #lines on bottom (vertical calibration)
cal_painter.drawLine(int((((base_img.width())/(n*2)) *(x))+ (base_img.width()//2)-13), 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) qr.add_data(data)
matrix = qr.get_matrix() matrix = qr.get_matrix()
k = len(matrix) k = len(matrix)
border_color = Qt.white border_color = Qt.GlobalColor.white
base_img = QImage(k * 5, k * 5, QImage.Format_ARGB32) base_img = QImage(k * 5, k * 5, QImage.Format.Format_ARGB32)
base_img.fill(border_color) base_img.fill(border_color)
qrpainter = QPainter() qrpainter = QPainter()
qrpainter.begin(base_img) qrpainter.begin(base_img)
@ -827,8 +826,8 @@ class Plugin(RevealerPlugin):
size = k * boxsize size = k * boxsize
left = (base_img.width() - size)//2 left = (base_img.width() - size)//2
top = (base_img.height() - size)//2 top = (base_img.height() - size)//2
qrpainter.setBrush(Qt.black) qrpainter.setBrush(Qt.GlobalColor.black)
qrpainter.setPen(Qt.black) qrpainter.setPen(Qt.GlobalColor.black)
for r in range(k): for r in range(k):
for c in range(k): for c in range(k):
@ -869,7 +868,7 @@ class Plugin(RevealerPlugin):
vbox.addSpacing(13) vbox.addSpacing(13)
vbox.addLayout(Buttons(CloseButton(d), OkButton(d))) vbox.addLayout(Buttons(CloseButton(d), OkButton(d)))
if not d.exec_(): if not d.exec():
return return
self.calibration_h = int(Decimal(horizontal.text())) 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 functools import partial
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, pyqtSignal, QRegExp from PyQt6.QtCore import Qt, pyqtSignal, QRegularExpression
from PyQt5.QtGui import QRegExpValidator from PyQt6.QtGui import QRegularExpressionValidator
from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton, from PyQt6.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton,
QHBoxLayout, QButtonGroup, QGroupBox, QHBoxLayout, QButtonGroup, QGroupBox,
QTextEdit, QLineEdit, QRadioButton, QCheckBox, QWidget, QTextEdit, QLineEdit, QRadioButton, QCheckBox, QWidget,
QMessageBox, QFileDialog, QSlider, QTabWidget) QMessageBox, QFileDialog, QSlider, QTabWidget)
@ -66,7 +66,7 @@ class QtHandler(QtHandlerBase):
vbox.addWidget(matrix) vbox.addWidget(matrix)
vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog))) vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog)))
dialog.setLayout(vbox) dialog.setLayout(vbox)
dialog.exec_() dialog.exec()
self.response = str(matrix.get_value()) self.response = str(matrix.get_value())
self.done.set() self.done.set()
@ -94,7 +94,7 @@ class QtPlugin(QtPluginBase):
return device_id return device_id
def show_dialog(device_id): def show_dialog(device_id):
if 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) keystore.thread.add(connect, on_success=show_dialog)
@ -152,7 +152,7 @@ class SafeTInitLayout(QVBoxLayout):
self.addWidget(QLabel(msg)) self.addWidget(QLabel(msg))
self.addWidget(self.text_e) self.addWidget(self.text_e)
self.pin = QLineEdit() 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) self.pin.setMaximumWidth(100)
hbox_pin = QHBoxLayout() hbox_pin = QHBoxLayout()
hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):"))) 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" msg = _("Are you SURE you want to wipe the device?\n"
"Your wallet still has bitcoins in it!") "Your wallet still has bitcoins in it!")
if not self.question(msg, title=title, if not self.question(msg, title=title,
icon=QMessageBox.Critical): icon=QMessageBox.Icon.Critical):
return return
invoke_client('wipe_device', unpair_after=True) invoke_client('wipe_device', unpair_after=True)
@ -453,11 +453,11 @@ class SettingsDialog(WindowModalDialog):
# Settings tab - Session Timeout # Settings tab - Session Timeout
timeout_label = QLabel(_("Session Timeout")) timeout_label = QLabel(_("Session Timeout"))
timeout_minutes = QLabel() timeout_minutes = QLabel()
timeout_slider = QSlider(Qt.Horizontal) timeout_slider = QSlider(Qt.Orientation.Horizontal)
timeout_slider.setRange(1, 60) timeout_slider.setRange(1, 60)
timeout_slider.setSingleStep(1) timeout_slider.setSingleStep(1)
timeout_slider.setTickInterval(5) timeout_slider.setTickInterval(5)
timeout_slider.setTickPosition(QSlider.TicksBelow) timeout_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
timeout_slider.setTracking(True) timeout_slider.setTracking(True)
timeout_msg = QLabel( timeout_msg = QLabel(
_("Clear the session after the specified period " _("Clear the session after the specified period "

26
electrum/plugins/trezor/qt.py

@ -2,8 +2,8 @@ from functools import partial
import threading import threading
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, QEventLoop, pyqtSignal from PyQt6.QtCore import Qt, QEventLoop, pyqtSignal
from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton, from PyQt6.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton,
QHBoxLayout, QButtonGroup, QGroupBox, QDialog, QHBoxLayout, QButtonGroup, QGroupBox, QDialog,
QLineEdit, QRadioButton, QCheckBox, QWidget, QLineEdit, QRadioButton, QCheckBox, QWidget,
QMessageBox, QSlider, QTabWidget) QMessageBox, QSlider, QTabWidget)
@ -74,9 +74,9 @@ class MatrixDialog(WindowModalDialog):
vbox.addLayout(grid) vbox.addLayout(grid)
self.backspace_button = QPushButton("<=") 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 = 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) buttons = Buttons(self.backspace_button, self.cancel_button)
vbox.addSpacing(40) vbox.addSpacing(40)
vbox.addLayout(buttons) vbox.addLayout(buttons)
@ -92,9 +92,9 @@ class MatrixDialog(WindowModalDialog):
def process_key(self, key): def process_key(self, key):
self.data = None self.data = None
if key == Qt.Key_Backspace: if key == Qt.Key.Key_Backspace:
self.data = '\010' self.data = '\010'
elif key == Qt.Key_Escape: elif key == Qt.Key.Key_Escape:
self.data = 'x' self.data = 'x'
elif self.is_valid(key): elif self.is_valid(key):
self.char_buttons[key - ord('1')].setFocus() self.char_buttons[key - ord('1')].setFocus()
@ -110,7 +110,7 @@ class MatrixDialog(WindowModalDialog):
def get_matrix(self, num): def get_matrix(self, num):
self.num = num self.num = num
self.refresh() self.refresh()
self.loop.exec_() self.loop.exec()
class QtHandler(QtHandlerBase): class QtHandler(QtHandlerBase):
@ -161,7 +161,7 @@ class QtHandler(QtHandlerBase):
vbox.addWidget(matrix) vbox.addWidget(matrix)
vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog))) vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog)))
dialog.setLayout(vbox) dialog.setLayout(vbox)
dialog.exec_() dialog.exec()
self.response = str(matrix.get_value()) self.response = str(matrix.get_value())
self.done.set() self.done.set()
@ -232,7 +232,7 @@ class QtHandler(QtHandlerBase):
OnDevice_button.clicked.connect(on_device_clicked) OnDevice_button.clicked.connect(on_device_clicked)
OnDevice_button.clicked.connect(d.accept) OnDevice_button.clicked.connect(d.accept)
d.exec_() d.exec()
self.done.set() self.done.set()
@ -259,7 +259,7 @@ class QtPlugin(QtPluginBase):
return device_id return device_id
def show_dialog(device_id): def show_dialog(device_id):
if 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) 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" msg = _("Are you SURE you want to wipe the device?\n"
"Your wallet still has bitcoins in it!") "Your wallet still has bitcoins in it!")
if not self.question(msg, title=title, if not self.question(msg, title=title,
icon=QMessageBox.Critical): icon=QMessageBox.Icon.Critical):
return return
invoke_client('wipe_device', unpair_after=True) invoke_client('wipe_device', unpair_after=True)
@ -721,11 +721,11 @@ class SettingsDialog(WindowModalDialog):
# Settings tab - Session Timeout # Settings tab - Session Timeout
timeout_label = QLabel(_("Session Timeout")) timeout_label = QLabel(_("Session Timeout"))
timeout_minutes = QLabel() timeout_minutes = QLabel()
timeout_slider = QSlider(Qt.Horizontal) timeout_slider = QSlider(Qt.Orientation.Horizontal)
timeout_slider.setRange(1, 60) timeout_slider.setRange(1, 60)
timeout_slider.setSingleStep(1) timeout_slider.setSingleStep(1)
timeout_slider.setTickInterval(5) timeout_slider.setTickInterval(5)
timeout_slider.setTickPosition(QSlider.TicksBelow) timeout_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
timeout_slider.setTracking(True) timeout_slider.setTracking(True)
timeout_msg = QLabel( timeout_msg = QLabel(
_("Clear the session after the specified period " _("Clear the session after the specified period "

14
electrum/plugins/trustedcoin/qt.py

@ -28,9 +28,9 @@ import threading
import os import os
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtGui import QPixmap, QMovie, QColor from PyQt6.QtGui import QPixmap, QMovie, QColor
from PyQt5.QtCore import QObject, pyqtSignal, QSize, Qt from PyQt6.QtCore import QObject, pyqtSignal, QSize, Qt
from PyQt5.QtWidgets import (QTextEdit, QVBoxLayout, QLabel, QGridLayout, QHBoxLayout, from PyQt6.QtWidgets import (QTextEdit, QVBoxLayout, QLabel, QGridLayout, QHBoxLayout,
QRadioButton, QCheckBox, QLineEdit, QPushButton, QWidget) QRadioButton, QCheckBox, QLineEdit, QPushButton, QWidget)
from electrum.i18n import _ from electrum.i18n import _
@ -128,7 +128,7 @@ class Plugin(TrustedCoinPlugin):
label.setWordWrap(1) label.setWordWrap(1)
vbox.addWidget(label) vbox.addWidget(label)
vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
if not d.exec_(): if not d.exec():
return return
return pw.get_amount() return pw.get_amount()
@ -221,7 +221,7 @@ class Plugin(TrustedCoinPlugin):
n = wallet.billing_info.get('tx_remaining', 0) n = wallet.billing_info.get('tx_remaining', 0)
grid.addWidget(QLabel(_("Your wallet has {} prepaid transactions.").format(n)), i, 0) grid.addWidget(QLabel(_("Your wallet has {} prepaid transactions.").format(n)), i, 0)
vbox.addLayout(Buttons(CloseButton(d))) vbox.addLayout(Buttons(CloseButton(d)))
d.exec_() d.exec()
@hook @hook
def init_wallet_wizard(self, wizard: 'QENewWalletWizard'): def init_wallet_wizard(self, wizard: 'QENewWalletWizard'):
@ -436,7 +436,7 @@ class WCShowConfirmOTP(WizardComponent):
self.spinner_l.setMovie(self.spinner) self.spinner_l.setMovie(self.spinner)
self.otp_status_l = QLabel() 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.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.')) 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.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.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().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.layout().addStretch(1)
self._valid = True self._valid = True

Loading…
Cancel
Save