Top level window fix for tx_dialog and h/w wallets

This commit is contained in:
Neil Booth 2016-01-23 16:06:32 +09:00
parent 43fd49aa8f
commit f92843bb10
3 changed files with 45 additions and 17 deletions

View File

@ -178,9 +178,24 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.fetch_alias() self.fetch_alias()
self.require_fee_update = False self.require_fee_update = False
self.tx_notifications = [] self.tx_notifications = []
self.tl_windows = []
self.load_wallet(wallet) self.load_wallet(wallet)
self.connect_slots(gui_object.timer) 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): def diagnostic_name(self):
return "%s/%s" % (PrintError.diagnostic_name(self), return "%s/%s" % (PrintError.diagnostic_name(self),
self.wallet.basename() if self.wallet else "None") 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. return value of the wrapped function, or None if cancelled.
''' '''
def request_password(self, *args, **kwargs): def request_password(self, *args, **kwargs):
parent = kwargs.get('parent', self.top_level_window()) parent = self.top_level_window()
password = None password = None
while self.wallet.use_encryption: while self.wallet.use_encryption:
password = self.password_dialog(parent=parent) password = self.password_dialog(parent=parent)
@ -1254,14 +1269,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.show_transaction(tx) self.show_transaction(tx)
self.do_clear() self.do_clear()
else: else:
self.broadcast_transaction(tx, tx_desc, self) self.broadcast_transaction(tx, tx_desc)
self.sign_tx_with_password(tx, sign_done, password, self) self.sign_tx_with_password(tx, sign_done, password)
@protected @protected
def sign_tx(self, tx, callback, password, parent): def sign_tx(self, tx, callback, password):
self.sign_tx_with_password(tx, callback, password, parent) 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 '''Sign the transaction in a separate thread. When done, calls
the callback with a success code of True or False. the callback with a success code of True or False.
''' '''
@ -1270,7 +1285,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
return return
# call hook to see if plugin needs gui interaction # 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): def on_signed(result):
callback(True) callback(True)
@ -1279,10 +1294,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
callback(False) callback(False)
task = partial(self.wallet.sign_transaction, tx, password) task = partial(self.wallet.sign_transaction, tx, password)
WaitingDialog(parent, _('Signing transaction...'), task, WaitingDialog(self, _('Signing transaction...'), task,
on_signed, on_failed) on_signed, on_failed)
def broadcast_transaction(self, tx, tx_desc, parent): def broadcast_transaction(self, tx, tx_desc):
def broadcast_thread(): def broadcast_thread():
# non-GUI thread # non-GUI thread
@ -1304,6 +1319,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
msg = ack_msg msg = ack_msg
return status, msg return status, msg
# Capture current TL window; override might be removed on return
parent = self.top_level_window()
def broadcast_done(result): def broadcast_done(result):
# GUI thread # GUI thread
if result: if result:
@ -1311,14 +1329,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if status: if status:
if tx_desc is not None and tx.is_complete(): if tx_desc is not None and tx.is_complete():
self.wallet.set_label(tx.hash(), tx_desc) self.wallet.set_label(tx.hash(), tx_desc)
self.show_message(_('Payment sent.') + '\n' + msg, parent.show_message(_('Payment sent.') + '\n' + msg)
parent=parent)
self.invoices_list.update() self.invoices_list.update()
self.do_clear() self.do_clear()
else: 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) broadcast_thread, broadcast_done, self.on_error)
def prepare_for_payment_request(self): def prepare_for_payment_request(self):

View File

@ -116,7 +116,11 @@ class TxDialog(QDialog, MessageBoxMixin):
self.update() self.update()
def do_broadcast(self): 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.broadcast = True
self.update() self.update()
@ -140,6 +144,7 @@ class TxDialog(QDialog, MessageBoxMixin):
def sign(self): def sign(self):
def sign_done(success): def sign_done(success):
self.sign_button.setDisabled(False) self.sign_button.setDisabled(False)
self.main_window.pop_top_level_window(self)
if success: if success:
self.prompt_if_unsaved = False self.prompt_if_unsaved = False
self.saved = False self.saved = False
@ -148,7 +153,8 @@ class TxDialog(QDialog, MessageBoxMixin):
self.sign_button.setDisabled(True) self.sign_button.setDisabled(True)
# Note sign_tx is wrapped and parent= is actually passed # Note sign_tx is wrapped and parent= is actually passed
# to the password input dialog box # 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): def save(self):
name = 'signed_%s.txn' % (self.tx.hash()[0:8]) if self.tx.is_complete() else 'unsigned.txn' name = 'signed_%s.txn' % (self.tx.hash()[0:8]) if self.tx.is_complete() else 'unsigned.txn'

View File

@ -146,15 +146,18 @@ class CancelButton(QPushButton):
self.clicked.connect(dialog.reject) self.clicked.connect(dialog.reject)
class MessageBoxMixin(object): class MessageBoxMixin(object):
def top_level_window(self, window=None): def top_level_window_recurse(self, window=None):
window = window or self window = window or self
classes = (WindowModalDialog, QMessageBox) classes = (WindowModalDialog, QMessageBox)
for n, child in enumerate(window.children()): for n, child in enumerate(window.children()):
# Test for visibility as old closed dialogs may not be GC-ed # Test for visibility as old closed dialogs may not be GC-ed
if isinstance(child, classes) and child.isVisible(): if isinstance(child, classes) and child.isVisible():
return self.top_level_window(child) return self.top_level_window_recurse(child)
return window return window
def top_level_window(self):
return self.top_level_window_recurse()
def question(self, msg, parent=None, title=None, icon=None): def question(self, msg, parent=None, title=None, icon=None):
Yes, No = QMessageBox.Yes, QMessageBox.No Yes, No = QMessageBox.Yes, QMessageBox.No
return self.msg_box(icon or QMessageBox.Question, return self.msg_box(icon or QMessageBox.Question,
@ -200,6 +203,8 @@ class WaitingDialog(WindowModalDialog):
necessary to maintain a reference to this dialog.''' necessary to maintain a reference to this dialog.'''
def __init__(self, parent, message, task, on_success=None, on_error=None): def __init__(self, parent, message, task, on_success=None, on_error=None):
assert parent assert parent
if isinstance(parent, MessageBoxMixin):
parent = parent.top_level_window()
WindowModalDialog.__init__(self, parent, _("Please wait")) WindowModalDialog.__init__(self, parent, _("Please wait"))
vbox = QVBoxLayout(self) vbox = QVBoxLayout(self)
vbox.addWidget(QLabel(message)) vbox.addWidget(QLabel(message))