diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 6ee320b1d..1410d5324 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -7,7 +7,7 @@ import queue import os import webbrowser 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, Union) from PyQt6 import QtCore from PyQt6.QtGui import (QFont, QColor, QCursor, QPixmap, QImage, @@ -17,7 +17,7 @@ from PyQt6.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, QVBo QStyle, QDialog, QGroupBox, QButtonGroup, QRadioButton, QFileDialog, QWidget, QToolButton, QPlainTextEdit, QApplication, QToolTip, QGraphicsEffect, QGraphicsScene, QGraphicsPixmapItem, QLayoutItem, QLayout, QMenu, - QFrame) + QFrame, QAbstractButton) from electrum.i18n import _ from electrum.util import (FileImportFailed, FileExportFailed, resource_path, EventListener, event_listener, @@ -262,13 +262,13 @@ class MessageBoxMixin(object): return self.top_level_window_recurse(test_func) def question(self, msg, parent=None, title=None, icon=None, **kwargs) -> bool: - Yes, No = QMessageBox.StandardButton.Yes, QMessageBox.StandardButton.No - return Yes == self.msg_box(icon=icon or QMessageBox.Icon.Question, + yes, no = QMessageBox.StandardButton.Yes, QMessageBox.StandardButton.No + return yes == self.msg_box(icon=icon or QMessageBox.Icon.Question, parent=parent, title=title or '', text=msg, - buttons=Yes|No, - defaultButton=No, + buttons=yes | no, + defaultButton=no, **kwargs) def show_warning(self, msg, parent=None, title=None, **kwargs): @@ -283,22 +283,27 @@ class MessageBoxMixin(object): return self.msg_box(QMessageBox.Icon.Critical, parent, title or _('Critical Error'), msg, **kwargs) - def show_message(self, msg, parent=None, title=None, **kwargs): - return self.msg_box(QMessageBox.Icon.Information, parent, - title or _('Information'), msg, **kwargs) + def show_message(self, msg, parent=None, title=None, icon=QMessageBox.Icon.Information, **kwargs): + return self.msg_box(icon, parent, title or _('Information'), msg, **kwargs) - def msg_box(self, icon, parent, title, text, *, buttons=QMessageBox.StandardButton.Ok, - defaultButton=QMessageBox.StandardButton.NoButton, rich_text=False, - checkbox=None): + def msg_box( + self, + icon: Union[QMessageBox.Icon, QPixmap], + parent: QWidget, + title: str, + text: str, + *, + buttons: Union[QMessageBox.StandardButton, + List[Union[QMessageBox.StandardButton, Tuple[QAbstractButton, QMessageBox.ButtonRole, int]]]] = QMessageBox.StandardButton.Ok, + defaultButton: QMessageBox.StandardButton = QMessageBox.StandardButton.NoButton, + rich_text: bool = False, + checkbox: Optional[bool] = None + ): parent = parent or self.top_level_window() - return custom_message_box(icon=icon, - parent=parent, - title=title, - text=text, - buttons=buttons, - defaultButton=defaultButton, - rich_text=rich_text, - checkbox=checkbox) + return custom_message_box( + icon=icon, parent=parent, title=title, text=text, buttons=buttons, defaultButton=defaultButton, + rich_text=rich_text, checkbox=checkbox + ) def query_choice(self, msg: Optional[str], @@ -327,15 +332,35 @@ class MessageBoxMixin(object): return d.run() - -def custom_message_box(*, icon, parent, title, text, buttons=QMessageBox.StandardButton.Ok, - defaultButton=QMessageBox.StandardButton.NoButton, rich_text=False, - checkbox=None): +def custom_message_box( + *, + icon: Union[QMessageBox.Icon, QPixmap], + parent: QWidget, + title: str, + text: str, + buttons: Union[QMessageBox.StandardButton, + List[Union[QMessageBox.StandardButton, Tuple[QAbstractButton, QMessageBox.ButtonRole, int]]]] = QMessageBox.StandardButton.Ok, + defaultButton: QMessageBox.StandardButton = QMessageBox.StandardButton.NoButton, + rich_text: bool = False, + checkbox: Optional[bool] = None +) -> int: + custom_buttons = [] + standard_buttons = QMessageBox.StandardButton.NoButton + if buttons: + if not isinstance(buttons, list): + buttons = [buttons] + for button in buttons: + if isinstance(button, QMessageBox.StandardButton): + standard_buttons |= button + else: + custom_buttons.append(button) if type(icon) is QPixmap: - d = QMessageBox(QMessageBox.Icon.Information, title, str(text), buttons, parent) + d = QMessageBox(QMessageBox.Icon.Information, title, str(text), standard_buttons, parent) d.setIconPixmap(icon) else: - d = QMessageBox(icon, title, str(text), buttons, parent) + d = QMessageBox(icon, title, str(text), standard_buttons, parent) + for button, role, _ in custom_buttons: + d.addButton(button, role) d.setWindowModality(Qt.WindowModality.WindowModal) d.setDefaultButton(defaultButton) if rich_text: @@ -350,7 +375,11 @@ def custom_message_box(*, icon, parent, title, text, buttons=QMessageBox.Standar d.setTextFormat(Qt.TextFormat.PlainText) if checkbox is not None: d.setCheckBox(checkbox) - return d.exec() + result = d.exec() + for button, _, value in custom_buttons: + if button == d.clickedButton(): + return value + return result class WindowModalDialog(QDialog, MessageBoxMixin): diff --git a/electrum/plugins/psbt_nostr/psbt_nostr.py b/electrum/plugins/psbt_nostr/psbt_nostr.py index d180ef33f..19a843130 100644 --- a/electrum/plugins/psbt_nostr/psbt_nostr.py +++ b/electrum/plugins/psbt_nostr/psbt_nostr.py @@ -244,5 +244,6 @@ class CosignerWallet(Logger): if on_failure: on_failure(str(e)) else: + self.wallet.save_db() if on_success: on_success() diff --git a/electrum/plugins/psbt_nostr/qt.py b/electrum/plugins/psbt_nostr/qt.py index abe696c40..9b1aa6bd0 100644 --- a/electrum/plugins/psbt_nostr/qt.py +++ b/electrum/plugins/psbt_nostr/qt.py @@ -27,7 +27,7 @@ from functools import partial from typing import TYPE_CHECKING, List, Tuple, Optional from PyQt6.QtCore import QObject, pyqtSignal -from PyQt6.QtWidgets import QPushButton +from PyQt6.QtWidgets import QPushButton, QMessageBox from electrum.plugin import hook from electrum.i18n import _ @@ -35,13 +35,11 @@ from electrum.wallet import Multisig_Wallet, Abstract_Wallet from electrum.util import UserCancelled, event_listener, EventListener from electrum.gui.qt.transaction_dialog import show_transaction, TxDialog -from .psbt_nostr import PsbtNostrPlugin, CosignerWallet, now +from .psbt_nostr import PsbtNostrPlugin, CosignerWallet if TYPE_CHECKING: from electrum.gui.qt.main_window import ElectrumWindow -USER_PROMPT_COOLDOWN = 10 - class QReceiveSignalObject(QObject): cosignerReceivedPsbt = pyqtSignal(str, str, object) @@ -83,7 +81,6 @@ class QtCosignerWallet(EventListener, CosignerWallet): self.obj = QReceiveSignalObject() self.obj.cosignerReceivedPsbt.connect(self.on_receive) self.register_callbacks() - self.user_prompt_cooldown = None def close(self): super().close() @@ -113,11 +110,7 @@ class QtCosignerWallet(EventListener, CosignerWallet): d.cosigner_send_button.setVisible(False) def send_to_cosigners(self, tx): - def ok(): - self.logger.debug('ADDED') - def nok(msg: str): - self.logger.debug(f'NOT ADDED: {msg}') - self.add_transaction_to_wallet(tx, on_success=ok, on_failure=nok) + self.add_transaction_to_wallet(tx, on_failure=self.on_add_fail) self.send_psbt(tx) def do_send(self, messages: List[Tuple[str, str]], txid: Optional[str] = None): @@ -139,18 +132,21 @@ class QtCosignerWallet(EventListener, CosignerWallet): _("Your transaction was sent to your cosigners via Nostr.") + '\n\n' + txid) def on_receive(self, pubkey, event_id, tx): - open_now = False - if not (self.user_prompt_cooldown and self.user_prompt_cooldown > now()): - open_now = self.window.question( - _("A transaction was received from your cosigner ({}).").format(str(event_id)[0:8]) + '\n' + - _("Do you want to open it now?")) - if not open_now: - self.user_prompt_cooldown = now() + USER_PROMPT_COOLDOWN - if open_now: + msg = _("A transaction was received from your cosigner ({}).").format(str(event_id)[0:8]) + '\n' + \ + _("Do you want to open it now?") + result = self.window.show_message(msg, icon=QMessageBox.Icon.Question, buttons=[ + QMessageBox.StandardButton.Open, + (QPushButton('Discard'), QMessageBox.ButtonRole.DestructiveRole, 100), + (QPushButton('Save to wallet'), QMessageBox.ButtonRole.AcceptRole, 101)] + ) + if result == QMessageBox.StandardButton.Open: show_transaction(tx, parent=self.window, prompt_if_unsaved=True, on_closed=partial(self.on_tx_dialog_closed, event_id)) else: self.mark_pending_event_rcvd(event_id) + if result == 100: # Discard + return self.add_transaction_to_wallet(tx, on_failure=self.on_add_fail) + self.window.update_tabs() def on_tx_dialog_closed(self, event_id): self.mark_pending_event_rcvd(event_id)