1
0

plugins: psbt_nostr: qt: offer 3 choices for each PSBT; 'Open, Discard, Save to wallet'

This commit is contained in:
Sander van Grieken
2025-04-15 17:19:52 +02:00
parent 60bd6327ce
commit 3b97ab7407
3 changed files with 71 additions and 45 deletions

View File

@@ -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):

View File

@@ -244,5 +244,6 @@ class CosignerWallet(Logger):
if on_failure:
on_failure(str(e))
else:
self.wallet.save_db()
if on_success:
on_success()

View File

@@ -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)