plugins: psbt_nostr: qt: offer 3 choices for each PSBT; 'Open, Discard, Save to wallet'
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -244,5 +244,6 @@ class CosignerWallet(Logger):
|
||||
if on_failure:
|
||||
on_failure(str(e))
|
||||
else:
|
||||
self.wallet.save_db()
|
||||
if on_success:
|
||||
on_success()
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user