diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 55787a4a2..b06a95e2f 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -178,9 +178,24 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.fetch_alias() self.require_fee_update = False self.tx_notifications = [] + self.tl_windows = [] self.load_wallet(wallet) self.connect_slots(gui_object.timer) + def push_top_level_window(self, window): + '''Used for e.g. tx dialog box to ensure new dialogs are appropriately + parented. This used to be done by explicitly providing the parent + window, but that isn't something hardware wallet prompts know.''' + self.tl_windows.append(window) + + def pop_top_level_window(self, window): + self.tl_windows.remove(window) + + def top_level_window(self): + '''Do the right thing in the presence of tx dialog windows''' + override = self.tl_windows[-1] if self.tl_windows else None + return self.top_level_window_recurse(override) + def diagnostic_name(self): return "%s/%s" % (PrintError.diagnostic_name(self), self.wallet.basename() if self.wallet else "None") @@ -1141,7 +1156,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): return value of the wrapped function, or None if cancelled. ''' def request_password(self, *args, **kwargs): - parent = kwargs.get('parent', self.top_level_window()) + parent = self.top_level_window() password = None while self.wallet.use_encryption: password = self.password_dialog(parent=parent) @@ -1254,14 +1269,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.show_transaction(tx) self.do_clear() else: - self.broadcast_transaction(tx, tx_desc, self) - self.sign_tx_with_password(tx, sign_done, password, self) + self.broadcast_transaction(tx, tx_desc) + self.sign_tx_with_password(tx, sign_done, password) @protected - def sign_tx(self, tx, callback, password, parent): - self.sign_tx_with_password(tx, callback, password, parent) + def sign_tx(self, tx, callback, password): + self.sign_tx_with_password(tx, callback, password) - def sign_tx_with_password(self, tx, callback, password, parent): + def sign_tx_with_password(self, tx, callback, password): '''Sign the transaction in a separate thread. When done, calls the callback with a success code of True or False. ''' @@ -1270,7 +1285,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): return # call hook to see if plugin needs gui interaction - run_hook('sign_tx', parent, tx) + run_hook('sign_tx', self, tx) def on_signed(result): callback(True) @@ -1279,10 +1294,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): callback(False) task = partial(self.wallet.sign_transaction, tx, password) - WaitingDialog(parent, _('Signing transaction...'), task, + WaitingDialog(self, _('Signing transaction...'), task, on_signed, on_failed) - def broadcast_transaction(self, tx, tx_desc, parent): + def broadcast_transaction(self, tx, tx_desc): def broadcast_thread(): # non-GUI thread @@ -1304,6 +1319,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): msg = ack_msg return status, msg + # Capture current TL window; override might be removed on return + parent = self.top_level_window() + def broadcast_done(result): # GUI thread if result: @@ -1311,14 +1329,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): if status: if tx_desc is not None and tx.is_complete(): self.wallet.set_label(tx.hash(), tx_desc) - self.show_message(_('Payment sent.') + '\n' + msg, - parent=parent) + parent.show_message(_('Payment sent.') + '\n' + msg) self.invoices_list.update() self.do_clear() else: - self.show_error(msg, parent=parent) + parent.show_error(msg) - WaitingDialog(parent, _('Broadcasting transaction...'), + WaitingDialog(self, _('Broadcasting transaction...'), broadcast_thread, broadcast_done, self.on_error) def prepare_for_payment_request(self): diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py index ec920c4c1..2fd886408 100644 --- a/gui/qt/transaction_dialog.py +++ b/gui/qt/transaction_dialog.py @@ -116,7 +116,11 @@ class TxDialog(QDialog, MessageBoxMixin): self.update() def do_broadcast(self): - self.main_window.broadcast_transaction(self.tx, self.desc, self) + self.main_window.push_top_level_window(self) + try: + self.main_window.broadcast_transaction(self.tx, self.desc) + finally: + self.main_window.pop_top_level_window(self) self.broadcast = True self.update() @@ -140,6 +144,7 @@ class TxDialog(QDialog, MessageBoxMixin): def sign(self): def sign_done(success): self.sign_button.setDisabled(False) + self.main_window.pop_top_level_window(self) if success: self.prompt_if_unsaved = False self.saved = False @@ -148,7 +153,8 @@ class TxDialog(QDialog, MessageBoxMixin): self.sign_button.setDisabled(True) # Note sign_tx is wrapped and parent= is actually passed # to the password input dialog box - self.main_window.sign_tx(self.tx, sign_done, parent=self) + self.main_window.push_top_level_window(self) + self.main_window.sign_tx(self.tx, sign_done) def save(self): name = 'signed_%s.txn' % (self.tx.hash()[0:8]) if self.tx.is_complete() else 'unsigned.txn' diff --git a/gui/qt/util.py b/gui/qt/util.py index 34759a707..28b0088e5 100644 --- a/gui/qt/util.py +++ b/gui/qt/util.py @@ -146,15 +146,18 @@ class CancelButton(QPushButton): self.clicked.connect(dialog.reject) class MessageBoxMixin(object): - def top_level_window(self, window=None): + def top_level_window_recurse(self, window=None): window = window or self classes = (WindowModalDialog, QMessageBox) for n, child in enumerate(window.children()): # Test for visibility as old closed dialogs may not be GC-ed if isinstance(child, classes) and child.isVisible(): - return self.top_level_window(child) + return self.top_level_window_recurse(child) return window + def top_level_window(self): + return self.top_level_window_recurse() + def question(self, msg, parent=None, title=None, icon=None): Yes, No = QMessageBox.Yes, QMessageBox.No return self.msg_box(icon or QMessageBox.Question, @@ -200,6 +203,8 @@ class WaitingDialog(WindowModalDialog): necessary to maintain a reference to this dialog.''' def __init__(self, parent, message, task, on_success=None, on_error=None): assert parent + if isinstance(parent, MessageBoxMixin): + parent = parent.top_level_window() WindowModalDialog.__init__(self, parent, _("Please wait")) vbox = QVBoxLayout(self) vbox.addWidget(QLabel(message))