Browse Source

Merge pull request #9256 from spesmilo/rm_blocking_dialog

qt: replace BlockingWaitingDialog with RunCoroutineDialog
master
ThomasV 1 year ago committed by GitHub
parent
commit
0ec60095f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      electrum/gui/qt/confirm_tx_dialog.py
  2. 32
      electrum/gui/qt/main_window.py
  3. 40
      electrum/gui/qt/swap_dialog.py
  4. 8
      electrum/gui/qt/transaction_dialog.py
  5. 54
      electrum/gui/qt/util.py

6
electrum/gui/qt/confirm_tx_dialog.py

@ -42,7 +42,7 @@ from electrum.simple_config import SimpleConfig
from electrum.bitcoin import DummyAddress from electrum.bitcoin import DummyAddress
from .util import (WindowModalDialog, ColorScheme, HelpLabel, Buttons, CancelButton, from .util import (WindowModalDialog, ColorScheme, HelpLabel, Buttons, CancelButton,
BlockingWaitingDialog, PasswordLineEdit, WWLabel, read_QIcon) PasswordLineEdit, WWLabel, read_QIcon)
from .fee_slider import FeeSlider, FeeComboBox from .fee_slider import FeeSlider, FeeComboBox
@ -532,7 +532,7 @@ class TxEditor(WindowModalDialog):
if self.not_enough_funds: if self.not_enough_funds:
self.io_widget.update(None) self.io_widget.update(None)
self.set_feerounding_visibility(False) self.set_feerounding_visibility(False)
self.messages = [] self.messages = [_('Preparing transaction...')]
else: else:
self.messages = self.get_messages() self.messages = self.get_messages()
self.update_fee_fields() self.update_fee_fields()
@ -619,7 +619,7 @@ class ConfirmTxDialog(TxEditor):
title=_("New Transaction"), # todo: adapt title for channel funding tx, swaps title=_("New Transaction"), # todo: adapt title for channel funding tx, swaps
allow_preview=allow_preview) allow_preview=allow_preview)
BlockingWaitingDialog(window, _("Preparing transaction..."), self.update) self.trigger_update()
def _update_amount_label(self): def _update_amount_label(self):
tx = self.tx tx = self.tx

32
electrum/gui/qt/main_window.py

@ -90,7 +90,7 @@ from .util import (read_QIcon, ColorScheme, text_dialog, icon_path, WaitingDialo
import_meta_gui, export_meta_gui, import_meta_gui, export_meta_gui,
filename_field, address_field, char_width_in_lineedit, webopen, filename_field, address_field, char_width_in_lineedit, webopen,
TRANSACTION_FILE_EXTENSION_FILTER_ANY, MONOSPACE_FONT, TRANSACTION_FILE_EXTENSION_FILTER_ANY, MONOSPACE_FONT,
getOpenFileName, getSaveFileName, BlockingWaitingDialog, font_height) getOpenFileName, getSaveFileName, font_height)
from .util import ButtonsLineEdit, ShowQRLineEdit from .util import ButtonsLineEdit, ShowQRLineEdit
from .util import QtEventListener, qt_event_listener, event_listener from .util import QtEventListener, qt_event_listener, event_listener
from .wizard.wallet import WIF_HELP_TEXT from .wizard.wallet import WIF_HELP_TEXT
@ -301,26 +301,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self._update_check_thread.checked.connect(on_version_received) self._update_check_thread.checked.connect(on_version_received)
self._update_check_thread.start() self._update_check_thread.start()
def run_coroutine_dialog(self, coro, text, on_result, on_cancelled): def run_coroutine_dialog(self, coro, text):
""" run coroutine in a waiting dialog, with a Cancel button that cancels the coroutine """ """ run coroutine in a waiting dialog, with a Cancel button that cancels the coroutine"""
from electrum import util from .util import RunCoroutineDialog
loop = util.get_asyncio_loop() d = RunCoroutineDialog(self, text, coro)
assert util.get_running_loop() != loop, 'must not be called from asyncio thread' return d.run()
future = asyncio.run_coroutine_threadsafe(coro, loop)
def task():
try:
return future.result()
except concurrent.futures.CancelledError:
on_cancelled()
try:
WaitingDialog(
self, text, task,
on_success=on_result,
on_error=self.on_error,
on_cancel=future.cancel)
except Exception as e:
self.show_error(str(e))
raise
def run_coroutine_from_thread(self, coro, name, on_result=None): def run_coroutine_from_thread(self, coro, name, on_result=None):
if self._cleaned_up: if self._cleaned_up:
@ -1180,10 +1165,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
if not self.wallet.lnworker.num_sats_can_send() and not self.wallet.lnworker.num_sats_can_receive(): if not self.wallet.lnworker.num_sats_can_send() and not self.wallet.lnworker.num_sats_can_receive():
self.show_error(_("You do not have liquidity in your active channels.")) self.show_error(_("You do not have liquidity in your active channels."))
return return
def get_pairs_thread():
self.network.run_from_another_thread(self.wallet.lnworker.swap_manager.get_pairs())
try: try:
BlockingWaitingDialog(self, _('Please wait...'), get_pairs_thread) self.run_coroutine_dialog(
self.wallet.lnworker.swap_manager.get_pairs(), _('Please wait...'))
except SwapServerError as e: except SwapServerError as e:
self.show_error(str(e)) self.show_error(str(e))
return return

40
electrum/gui/qt/swap_dialog.py

@ -4,7 +4,7 @@ from PyQt6.QtCore import pyqtSignal
from PyQt6.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, UserCancelled
from electrum.bitcoin import DummyAddress from electrum.bitcoin import DummyAddress
from electrum.transaction import PartialTxOutput, PartialTransaction from electrum.transaction import PartialTxOutput, PartialTransaction
@ -319,25 +319,33 @@ class SwapDialog(WindowModalDialog, QtEventListener):
recv_amount = self.recv_amount_e.get_amount() recv_amount = self.recv_amount_e.get_amount()
self.ok_button.setEnabled(bool(send_amount) and bool(recv_amount)) self.ok_button.setEnabled(bool(send_amount) and bool(recv_amount))
def do_normal_swap(self, lightning_amount, onchain_amount, password): async def _do_normal_swap(self, lightning_amount, onchain_amount, password):
dummy_tx = self._create_tx(onchain_amount) dummy_tx = self._create_tx(onchain_amount)
assert dummy_tx assert dummy_tx
sm = self.swap_manager sm = self.swap_manager
swap, invoice = await sm.request_normal_swap(
lightning_amount_sat=lightning_amount,
expected_onchain_amount_sat=onchain_amount,
channels=self.channels,
)
self._current_swap = swap
tx = sm.create_funding_tx(swap, dummy_tx, password=password)
txid = await sm.wait_for_htlcs_and_broadcast(swap=swap, invoice=invoice, tx=tx)
return txid
def do_normal_swap(self, lightning_amount, onchain_amount, password):
self._current_swap = None self._current_swap = None
async def coro(): coro = self._do_normal_swap(lightning_amount, onchain_amount, password)
swap, invoice = await sm.request_normal_swap( try:
lightning_amount_sat=lightning_amount, funding_txid = self.window.run_coroutine_dialog(coro, _('Awaiting swap payment...'))
expected_onchain_amount_sat=onchain_amount, except UserCancelled:
channels=self.channels, self.swap_manager.cancel_normal_swap(self._current_swap)
) self.window.show_message(_('Swap cancelled'))
self._current_swap = swap return
tx = sm.create_funding_tx(swap, dummy_tx, password=password) except Exception as e:
txid = await sm.wait_for_htlcs_and_broadcast(swap=swap, invoice=invoice, tx=tx) self.window.show_error(str(e))
return txid return
self.window.run_coroutine_dialog( self.window.on_swap_result(funding_txid, is_reverse=False)
coro(), _('Awaiting swap payment...'),
on_result=lambda funding_txid: self.window.on_swap_result(funding_txid, is_reverse=False),
on_cancelled=lambda: sm.cancel_normal_swap(self._current_swap))
def get_description(self): def get_description(self):
onchain_funds = "onchain funds" onchain_funds = "onchain funds"

8
electrum/gui/qt/transaction_dialog.py

@ -62,7 +62,7 @@ from .util import (MessageBoxMixin, read_QIcon, Buttons, icon_path,
char_width_in_lineedit, TRANSACTION_FILE_EXTENSION_FILTER_SEPARATE, char_width_in_lineedit, TRANSACTION_FILE_EXTENSION_FILTER_SEPARATE,
TRANSACTION_FILE_EXTENSION_FILTER_ONLY_COMPLETE_TX, TRANSACTION_FILE_EXTENSION_FILTER_ONLY_COMPLETE_TX,
TRANSACTION_FILE_EXTENSION_FILTER_ONLY_PARTIAL_TX, TRANSACTION_FILE_EXTENSION_FILTER_ONLY_PARTIAL_TX,
BlockingWaitingDialog, getSaveFileName, ColorSchemeItem, getSaveFileName, ColorSchemeItem,
get_iconname_qrcode, VLine, WaitingDialog) get_iconname_qrcode, VLine, WaitingDialog)
from .rate_limiter import rate_limited from .rate_limiter import rate_limited
from .my_treeview import create_toolbar_with_menu, QMenuWithConfig from .my_treeview import create_toolbar_with_menu, QMenuWithConfig
@ -582,11 +582,9 @@ class TxDialog(QDialog, MessageBoxMixin):
# FIXME for PSBTs, we do a blocking fetch, as the missing data might be needed for e.g. signing # FIXME for PSBTs, we do a blocking fetch, as the missing data might be needed for e.g. signing
# - otherwise, the missing data is for display-completeness only, e.g. fee, input addresses (we do it async) # - otherwise, the missing data is for display-completeness only, e.g. fee, input addresses (we do it async)
if not tx.is_complete() and tx.is_missing_info_from_network(): if not tx.is_complete() and tx.is_missing_info_from_network():
BlockingWaitingDialog( self.main_window.run_coroutine_dialog(
self, tx.add_info_from_network(self.wallet.network, timeout=10),
_("Adding info to tx, from network..."), _("Adding info to tx, from network..."),
lambda: Network.run_from_another_thread(
tx.add_info_from_network(self.wallet.network, timeout=10)),
) )
else: else:
self.maybe_fetch_txin_data() self.maybe_fetch_txin_data()

54
electrum/gui/qt/util.py

@ -21,7 +21,7 @@ from PyQt6.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, QVBo
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import FileImportFailed, FileExportFailed, resource_path from electrum.util import FileImportFailed, FileExportFailed, resource_path
from electrum.util import EventListener, event_listener, get_logger from electrum.util import EventListener, event_listener, get_logger, UserCancelled
from electrum.invoices import PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT, PR_UNKNOWN, PR_FAILED, PR_ROUTING, PR_UNCONFIRMED, PR_BROADCASTING, PR_BROADCAST from electrum.invoices import PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT, PR_UNKNOWN, PR_FAILED, PR_ROUTING, PR_UNCONFIRMED, PR_BROADCASTING, PR_BROADCAST
from electrum.logging import Logger from electrum.logging import Logger
from electrum.qrreader import MissingQrDetectionLib from electrum.qrreader import MissingQrDetectionLib
@ -371,32 +371,32 @@ class WaitingDialog(WindowModalDialog):
self.message_label.setText(msg) self.message_label.setText(msg)
class BlockingWaitingDialog(WindowModalDialog): class RunCoroutineDialog(WaitingDialog):
"""Shows a waiting dialog whilst running a task.
Should be called from the GUI thread. The GUI thread will be blocked while def __init__(self, parent: QWidget, message: str, coroutine):
the task is running; the point of the dialog is to provide feedback from electrum import util
to the user regarding what is going on. import asyncio
""" import concurrent.futures
def __init__(self, parent: QWidget, message: str, task: Callable[[], Any]): loop = util.get_asyncio_loop()
assert parent assert util.get_running_loop() != loop, 'must not be called from asyncio thread'
if isinstance(parent, MessageBoxMixin): self._exception = None
parent = parent.top_level_window() self._result = None
WindowModalDialog.__init__(self, parent, _("Please wait")) self._future = asyncio.run_coroutine_threadsafe(coroutine, loop)
self.message_label = QLabel(message) def task():
vbox = QVBoxLayout(self) try:
vbox.addWidget(self.message_label) self._result = self._future.result()
self.finished.connect(self.deleteLater) # see #3956 except concurrent.futures.CancelledError:
# show popup self._exception = UserCancelled
self.show() except Exception as e:
# refresh GUI; needed for popup to appear and for message_label to get drawn self._exception = e
QCoreApplication.processEvents() WaitingDialog.__init__(self, parent, message, task, on_cancel=self._future.cancel)
QCoreApplication.processEvents()
try: def run(self):
# block and run given task self.exec()
task() if self._exception:
finally: raise self._exception
# close popup else:
self.accept() return self._result
def line_dialog(parent, title, label, ok_label, default=None): def line_dialog(parent, title, label, ok_label, default=None):

Loading…
Cancel
Save