qt: refactor send_tab, paytoedit
This commit is contained in:
@@ -13,7 +13,6 @@ from electrum.util import (format_satoshis_plain, decimal_point_to_base_unit_nam
|
||||
FEERATE_PRECISION, quantize_feerate, DECIMAL_POINT)
|
||||
from electrum.bitcoin import COIN, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC
|
||||
|
||||
|
||||
_NOT_GIVEN = object() # sentinel value
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
from functools import partial
|
||||
from typing import NamedTuple, Sequence, Optional, List, TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtCore import QObject, pyqtSignal
|
||||
from PyQt5.QtGui import QFontMetrics, QFont
|
||||
from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QTextEdit, QVBoxLayout
|
||||
|
||||
@@ -48,6 +50,10 @@ frozen_style = "QWidget {border:none;}"
|
||||
normal_style = "QPlainTextEdit { }"
|
||||
|
||||
|
||||
class InvalidPaymentIdentifier(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ResizingTextEdit(QTextEdit):
|
||||
|
||||
def __init__(self):
|
||||
@@ -63,113 +69,139 @@ class ResizingTextEdit(QTextEdit):
|
||||
self.verticalMargins += documentMargin * 2
|
||||
self.heightMin = self.fontSpacing + self.verticalMargins
|
||||
self.heightMax = (self.fontSpacing * 10) + self.verticalMargins
|
||||
self.single_line = True
|
||||
self.update_size()
|
||||
|
||||
def update_size(self):
|
||||
docLineCount = self.document().lineCount()
|
||||
docHeight = max(3, docLineCount) * self.fontSpacing
|
||||
docHeight = max(1 if self.single_line else 3, docLineCount) * self.fontSpacing
|
||||
h = docHeight + self.verticalMargins
|
||||
h = min(max(h, self.heightMin), self.heightMax)
|
||||
self.setMinimumHeight(int(h))
|
||||
self.setMaximumHeight(int(h))
|
||||
self.verticalScrollBar().setHidden(docHeight + self.verticalMargins < self.heightMax)
|
||||
if self.single_line:
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap)
|
||||
else:
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.verticalScrollBar().setHidden(docHeight + self.verticalMargins < self.heightMax)
|
||||
self.setLineWrapMode(QTextEdit.LineWrapMode.WidgetWidth)
|
||||
|
||||
class PayToEdit(QObject, Logger, GenericInputHandler):
|
||||
|
||||
class PayToEdit(Logger, GenericInputHandler):
|
||||
paymentIdentifierChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, send_tab: 'SendTab'):
|
||||
QObject.__init__(self, parent=send_tab)
|
||||
Logger.__init__(self)
|
||||
GenericInputHandler.__init__(self)
|
||||
self.line_edit = QLineEdit()
|
||||
|
||||
self.text_edit = ResizingTextEdit()
|
||||
self.text_edit.hide()
|
||||
self.text_edit.textChanged.connect(self._on_text_edit_text_changed)
|
||||
self._is_paytomany = False
|
||||
for w in [self.line_edit, self.text_edit]:
|
||||
w.setFont(QFont(MONOSPACE_FONT))
|
||||
w.textChanged.connect(self._on_text_changed)
|
||||
self.text_edit.setFont(QFont(MONOSPACE_FONT))
|
||||
self.send_tab = send_tab
|
||||
self.config = send_tab.config
|
||||
self.win = send_tab.window
|
||||
self.app = QApplication.instance()
|
||||
self.amount_edit = self.send_tab.amount_e
|
||||
|
||||
self.logger.debug(util.ColorScheme.RED.as_stylesheet(True))
|
||||
self.is_multiline = False
|
||||
self.disable_checks = False
|
||||
self.is_alias = False
|
||||
# self.is_alias = False
|
||||
self.payto_scriptpubkey = None # type: Optional[bytes]
|
||||
self.previous_payto = ''
|
||||
# editor methods
|
||||
self.setStyleSheet = self.editor.setStyleSheet
|
||||
self.setText = self.editor.setText
|
||||
self.setEnabled = self.editor.setEnabled
|
||||
self.setReadOnly = self.editor.setReadOnly
|
||||
self.setFocus = self.editor.setFocus
|
||||
self.setStyleSheet = self.text_edit.setStyleSheet
|
||||
self.setText = self.text_edit.setText
|
||||
self.setFocus = self.text_edit.setFocus
|
||||
self.setToolTip = self.text_edit.setToolTip
|
||||
# button handlers
|
||||
self.on_qr_from_camera_input_btn = partial(
|
||||
self.input_qr_from_camera,
|
||||
config=self.config,
|
||||
allow_multi=False,
|
||||
show_error=self.win.show_error,
|
||||
setText=self._on_input_btn,
|
||||
parent=self.win,
|
||||
show_error=self.send_tab.show_error,
|
||||
setText=self.try_payment_identifier,
|
||||
parent=self.send_tab.window,
|
||||
)
|
||||
self.on_qr_from_screenshot_input_btn = partial(
|
||||
self.input_qr_from_screenshot,
|
||||
allow_multi=False,
|
||||
show_error=self.win.show_error,
|
||||
setText=self._on_input_btn,
|
||||
show_error=self.send_tab.show_error,
|
||||
setText=self.try_payment_identifier,
|
||||
)
|
||||
self.on_input_file = partial(
|
||||
self.input_file,
|
||||
config=self.config,
|
||||
show_error=self.win.show_error,
|
||||
setText=self._on_input_btn,
|
||||
show_error=self.send_tab.show_error,
|
||||
setText=self.try_payment_identifier,
|
||||
)
|
||||
#
|
||||
self.line_edit.contextMenuEvent = partial(editor_contextMenuEvent, self.line_edit, self)
|
||||
|
||||
self.text_edit.contextMenuEvent = partial(editor_contextMenuEvent, self.text_edit, self)
|
||||
|
||||
@property
|
||||
def editor(self):
|
||||
return self.text_edit if self.is_paytomany() else self.line_edit
|
||||
self.payment_identifier = None
|
||||
|
||||
def set_text(self, text: str):
|
||||
self.text_edit.setText(text)
|
||||
|
||||
def update_editor(self):
|
||||
if self.text_edit.toPlainText() != self.payment_identifier.text:
|
||||
self.text_edit.setText(self.payment_identifier.text)
|
||||
self.text_edit.single_line = not self.payment_identifier.is_multiline()
|
||||
self.text_edit.update_size()
|
||||
|
||||
'''set payment identifier only if valid, else exception'''
|
||||
def try_payment_identifier(self, text):
|
||||
text = text.strip()
|
||||
pi = PaymentIdentifier(self.send_tab.wallet, text)
|
||||
if not pi.is_valid():
|
||||
raise InvalidPaymentIdentifier('Invalid payment identifier')
|
||||
self.set_payment_identifier(text)
|
||||
|
||||
def set_payment_identifier(self, text):
|
||||
text = text.strip()
|
||||
if self.payment_identifier and self.payment_identifier.text == text:
|
||||
# no change.
|
||||
return
|
||||
|
||||
self.payment_identifier = PaymentIdentifier(self.send_tab.wallet, text)
|
||||
|
||||
# toggle to multiline if payment identifier is a multiline
|
||||
self.is_multiline = self.payment_identifier.is_multiline()
|
||||
self.logger.debug(f'is_multiline {self.is_multiline}')
|
||||
if self.is_multiline and not self._is_paytomany:
|
||||
self.set_paytomany(True)
|
||||
|
||||
# if payment identifier gets set externally, we want to update the text_edit
|
||||
# Note: this triggers the change handler, but we shortcut if it's the same payment identifier
|
||||
self.update_editor()
|
||||
|
||||
self.paymentIdentifierChanged.emit()
|
||||
|
||||
def set_paytomany(self, b):
|
||||
has_focus = self.editor.hasFocus()
|
||||
self._is_paytomany = b
|
||||
self.line_edit.setVisible(not b)
|
||||
self.text_edit.setVisible(b)
|
||||
self.text_edit.single_line = not self._is_paytomany
|
||||
self.text_edit.update_size()
|
||||
self.send_tab.paytomany_menu.setChecked(b)
|
||||
if has_focus:
|
||||
self.editor.setFocus()
|
||||
|
||||
def toggle_paytomany(self):
|
||||
self.set_paytomany(not self._is_paytomany)
|
||||
|
||||
def toPlainText(self):
|
||||
return self.text_edit.toPlainText() if self.is_paytomany() else self.line_edit.text()
|
||||
|
||||
def is_paytomany(self):
|
||||
return self._is_paytomany
|
||||
|
||||
def setFrozen(self, b):
|
||||
self.setReadOnly(b)
|
||||
self.text_edit.setReadOnly(b)
|
||||
if not b:
|
||||
self.setStyleSheet(normal_style)
|
||||
|
||||
def setTextNoCheck(self, text: str):
|
||||
"""Sets the text, while also ensuring the new value will not be resolved/checked."""
|
||||
self.previous_payto = text
|
||||
self.setText(text)
|
||||
def isFrozen(self):
|
||||
return self.text_edit.isReadOnly()
|
||||
|
||||
def do_clear(self):
|
||||
self.is_multiline = False
|
||||
self.set_paytomany(False)
|
||||
self.disable_checks = False
|
||||
self.is_alias = False
|
||||
self.line_edit.setText('')
|
||||
self.text_edit.setText('')
|
||||
self.setFrozen(False)
|
||||
self.setEnabled(True)
|
||||
self.payment_identifier = None
|
||||
|
||||
def setGreen(self):
|
||||
self.setStyleSheet(util.ColorScheme.GREEN.as_stylesheet(True))
|
||||
@@ -177,53 +209,18 @@ class PayToEdit(Logger, GenericInputHandler):
|
||||
def setExpired(self):
|
||||
self.setStyleSheet(util.ColorScheme.RED.as_stylesheet(True))
|
||||
|
||||
def _on_input_btn(self, text: str):
|
||||
self.setText(text)
|
||||
def _on_text_edit_text_changed(self):
|
||||
self._handle_text_change(self.text_edit.toPlainText())
|
||||
|
||||
def _on_text_changed(self):
|
||||
text = self.toPlainText()
|
||||
# False if user pasted from clipboard
|
||||
full_check = self.app.clipboard().text() != text
|
||||
self._check_text(text, full_check=full_check)
|
||||
if self.is_multiline and not self._is_paytomany:
|
||||
self.set_paytomany(True)
|
||||
self.text_edit.setText(text)
|
||||
self.text_edit.setFocus()
|
||||
def _handle_text_change(self, text):
|
||||
if self.isFrozen():
|
||||
# if editor is frozen, we ignore text changes as they might not be a payment identifier
|
||||
# but a user friendly representation.
|
||||
return
|
||||
|
||||
def _check_text(self, text, *, full_check: bool):
|
||||
""" side effects: self.is_multiline """
|
||||
text = str(text).strip()
|
||||
if not text:
|
||||
return
|
||||
if self.previous_payto == text:
|
||||
return
|
||||
if full_check:
|
||||
self.previous_payto = text
|
||||
if self.disable_checks:
|
||||
return
|
||||
pi = PaymentIdentifier(self.send_tab.wallet, text)
|
||||
self.is_multiline = bool(pi.multiline_outputs) # TODO: why both is_multiline and set_paytomany(True)??
|
||||
self.logger.debug(f'is_multiline {self.is_multiline}')
|
||||
if pi.is_valid():
|
||||
self.send_tab.set_payment_identifier(text)
|
||||
else:
|
||||
if not full_check and pi.error:
|
||||
self.send_tab.show_error(
|
||||
_('Clipboard text is not a valid payment identifier') + '\n' + str(pi.error))
|
||||
return
|
||||
|
||||
def handle_multiline(self, outputs):
|
||||
total = 0
|
||||
is_max = False
|
||||
for output in outputs:
|
||||
if parse_max_spend(output.value):
|
||||
is_max = True
|
||||
else:
|
||||
total += output.value
|
||||
self.send_tab.set_onchain(True)
|
||||
self.send_tab.max_button.setChecked(is_max)
|
||||
if self.send_tab.max_button.isChecked():
|
||||
self.send_tab.spend_max()
|
||||
else:
|
||||
self.amount_edit.setAmount(total if outputs else None)
|
||||
#self.send_tab.lock_amount(self.send_tab.max_button.isChecked() or bool(outputs))
|
||||
self.set_payment_identifier(text)
|
||||
if self.app.clipboard().text() and self.app.clipboard().text().strip() == self.payment_identifier.text:
|
||||
# user pasted from clipboard
|
||||
self.logger.debug('from clipboard')
|
||||
if self.payment_identifier.error:
|
||||
self.send_tab.show_error(_('Clipboard text is not a valid payment identifier') + '\n' + self.payment_identifier.error)
|
||||
|
||||
@@ -6,14 +6,13 @@ import asyncio
|
||||
from decimal import Decimal
|
||||
from typing import Optional, TYPE_CHECKING, Sequence, List, Callable
|
||||
from PyQt5.QtCore import pyqtSignal, QPoint
|
||||
from PyQt5.QtWidgets import (QLabel, QVBoxLayout, QGridLayout,
|
||||
QHBoxLayout, QCompleter, QWidget, QToolTip, QPushButton)
|
||||
from PyQt5.QtWidgets import (QLabel, QVBoxLayout, QGridLayout, QHBoxLayout,
|
||||
QWidget, QToolTip, QPushButton, QApplication)
|
||||
|
||||
from electrum.plugin import run_hook
|
||||
from electrum.i18n import _
|
||||
|
||||
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates, parse_max_spend
|
||||
from electrum.payment_identifier import PaymentIdentifier
|
||||
from electrum.invoices import PR_PAID, Invoice, PR_BROADCASTING, PR_BROADCAST
|
||||
|
||||
from electrum.transaction import Transaction, PartialTxInput, PartialTxOutput
|
||||
@@ -21,8 +20,10 @@ from electrum.network import TxBroadcastError, BestEffortRequestFailed
|
||||
from electrum.logging import Logger
|
||||
|
||||
from .amountedit import AmountEdit, BTCAmountEdit, SizedFreezableLineEdit
|
||||
from .util import WaitingDialog, HelpLabel, MessageBoxMixin, EnterButton, char_width_in_lineedit
|
||||
from .util import get_iconname_camera, get_iconname_qrcode, read_QIcon
|
||||
from .paytoedit import InvalidPaymentIdentifier
|
||||
from .util import (WaitingDialog, HelpLabel, MessageBoxMixin, EnterButton,
|
||||
char_width_in_lineedit, get_iconname_camera, get_iconname_qrcode,
|
||||
read_QIcon)
|
||||
from .confirm_tx_dialog import ConfirmTxDialog
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -38,7 +39,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
def __init__(self, window: 'ElectrumWindow'):
|
||||
QWidget.__init__(self, window)
|
||||
Logger.__init__(self)
|
||||
|
||||
self.app = QApplication.instance()
|
||||
self.window = window
|
||||
self.wallet = window.wallet
|
||||
self.fx = window.fx
|
||||
@@ -49,7 +50,6 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
self.format_amount = window.format_amount
|
||||
self.base_unit = window.base_unit
|
||||
|
||||
self.payment_identifier = None
|
||||
self.pending_invoice = None
|
||||
|
||||
# A 4-column grid layout. All the stretch is in the last column.
|
||||
@@ -73,7 +73,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
"e.g. set one amount to '2!' and another to '3!' to split your coins 40-60."))
|
||||
payto_label = HelpLabel(_('Pay to'), msg)
|
||||
grid.addWidget(payto_label, 0, 0)
|
||||
grid.addWidget(self.payto_e.line_edit, 0, 1, 1, 4)
|
||||
# grid.addWidget(self.payto_e.line_edit, 0, 1, 1, 4)
|
||||
grid.addWidget(self.payto_e.text_edit, 0, 1, 1, 4)
|
||||
|
||||
#completer = QCompleter()
|
||||
@@ -119,11 +119,9 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
btn_width = 10 * char_width_in_lineedit()
|
||||
self.max_button.setFixedWidth(btn_width)
|
||||
self.max_button.setCheckable(True)
|
||||
self.max_button.setEnabled(False)
|
||||
grid.addWidget(self.max_button, 3, 3)
|
||||
|
||||
self.save_button = EnterButton(_("Save"), self.do_save_invoice)
|
||||
self.send_button = EnterButton(_("Pay") + "...", self.do_pay_or_get_invoice)
|
||||
self.clear_button = EnterButton(_("Clear"), self.do_clear)
|
||||
self.paste_button = QPushButton()
|
||||
self.paste_button.clicked.connect(self.do_paste)
|
||||
self.paste_button.setIcon(read_QIcon('copy.png'))
|
||||
@@ -131,9 +129,15 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
self.paste_button.setMaximumWidth(35)
|
||||
grid.addWidget(self.paste_button, 0, 5)
|
||||
|
||||
self.save_button = EnterButton(_("Save"), self.do_save_invoice)
|
||||
self.save_button.setEnabled(False)
|
||||
self.send_button = EnterButton(_("Pay") + "...", self.do_pay_or_get_invoice)
|
||||
self.send_button.setEnabled(False)
|
||||
self.clear_button = EnterButton(_("Clear"), self.do_clear)
|
||||
|
||||
buttons = QHBoxLayout()
|
||||
buttons.addStretch(1)
|
||||
#buttons.addWidget(self.paste_button)
|
||||
|
||||
buttons.addWidget(self.clear_button)
|
||||
buttons.addWidget(self.save_button)
|
||||
buttons.addWidget(self.send_button)
|
||||
@@ -143,14 +147,11 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
|
||||
def reset_max(text):
|
||||
self.max_button.setChecked(False)
|
||||
enable = not bool(text) and not self.amount_e.isReadOnly()
|
||||
# self.max_button.setEnabled(enable)
|
||||
|
||||
self.amount_e.textChanged.connect(self.on_amount_changed)
|
||||
self.amount_e.textEdited.connect(reset_max)
|
||||
self.fiat_send_e.textEdited.connect(reset_max)
|
||||
|
||||
self.set_onchain(False)
|
||||
|
||||
self.invoices_label = QLabel(_('Invoices'))
|
||||
from .invoice_list import InvoiceList
|
||||
self.invoice_list = InvoiceList(self)
|
||||
@@ -184,30 +185,33 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
self.resolve_done_signal.connect(self.on_resolve_done)
|
||||
self.finalize_done_signal.connect(self.on_finalize_done)
|
||||
self.notify_merchant_done_signal.connect(self.on_notify_merchant_done)
|
||||
self.payto_e.paymentIdentifierChanged.connect(self._handle_payment_identifier)
|
||||
|
||||
def on_amount_changed(self, text):
|
||||
# FIXME: implement full valid amount check to enable/disable Pay button
|
||||
pi_valid = self.payto_e.payment_identifier.is_valid() if self.payto_e.payment_identifier else False
|
||||
self.send_button.setEnabled(bool(self.amount_e.get_amount()) and pi_valid)
|
||||
|
||||
|
||||
def do_paste(self):
|
||||
text = self.window.app.clipboard().text()
|
||||
if not text:
|
||||
return
|
||||
self.set_payment_identifier(text)
|
||||
try:
|
||||
self.payto_e.try_payment_identifier(self.app.clipboard().text())
|
||||
except InvalidPaymentIdentifier as e:
|
||||
self.show_error(_('Invalid payment identifier on clipboard'))
|
||||
|
||||
def set_payment_identifier(self, text):
|
||||
self.payment_identifier = PaymentIdentifier(self.wallet, text)
|
||||
if self.payment_identifier.error:
|
||||
self.show_error(_('Clipboard text is not a valid payment identifier') + '\n' + self.payment_identifier.error)
|
||||
return
|
||||
if self.payment_identifier.is_multiline():
|
||||
self.payto_e.set_paytomany(True)
|
||||
self.payto_e.text_edit.setText(text)
|
||||
else:
|
||||
self.payto_e.setTextNoCheck(text)
|
||||
self._handle_payment_identifier(can_use_network=True)
|
||||
self.logger.debug('set_payment_identifier')
|
||||
try:
|
||||
self.payto_e.try_payment_identifier(text)
|
||||
except InvalidPaymentIdentifier as e:
|
||||
self.show_error(_('Invalid payment identifier'))
|
||||
|
||||
def spend_max(self):
|
||||
assert self.payto_e.payment_identifier is not None
|
||||
assert self.payto_e.payment_identifier.type in ['spk', 'multiline']
|
||||
if run_hook('abort_send', self):
|
||||
return
|
||||
amount = self.get_amount()
|
||||
outputs = self.payment_identifier.get_onchain_outputs(amount)
|
||||
outputs = self.payto_e.payment_identifier.get_onchain_outputs('!')
|
||||
if not outputs:
|
||||
return
|
||||
make_tx = lambda fee_est, *, confirmed_only=False: self.wallet.make_unsigned_transaction(
|
||||
@@ -296,9 +300,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
text = _("Not enough funds")
|
||||
frozen_str = self.get_frozen_balance_str()
|
||||
if frozen_str:
|
||||
text += " ({} {})".format(
|
||||
frozen_str, _("are frozen")
|
||||
)
|
||||
text += " ({} {})".format(frozen_str, _("are frozen"))
|
||||
return text
|
||||
|
||||
def get_frozen_balance_str(self) -> Optional[str]:
|
||||
@@ -308,31 +310,26 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
return self.format_amount_and_units(frozen_bal)
|
||||
|
||||
def do_clear(self):
|
||||
self.lock_fields(lock_recipient=False, lock_amount=False, lock_max=True, lock_description=False)
|
||||
self.max_button.setChecked(False)
|
||||
self.payto_e.do_clear()
|
||||
self.set_onchain(False)
|
||||
for w in [self.comment_e, self.comment_label]:
|
||||
w.setVisible(False)
|
||||
for e in [self.message_e, self.amount_e, self.fiat_send_e]:
|
||||
e.setText('')
|
||||
self.set_field_style(e, None, False)
|
||||
for e in [self.send_button, self.save_button, self.clear_button, self.amount_e, self.fiat_send_e]:
|
||||
e.setEnabled(True)
|
||||
for e in [self.save_button, self.send_button]:
|
||||
e.setEnabled(False)
|
||||
self.window.update_status()
|
||||
run_hook('do_clear', self)
|
||||
|
||||
def set_onchain(self, b):
|
||||
self._is_onchain = b
|
||||
self.max_button.setEnabled(b)
|
||||
|
||||
def prepare_for_send_tab_network_lookup(self):
|
||||
self.window.show_send_tab()
|
||||
self.payto_e.disable_checks = True
|
||||
#for e in [self.payto_e, self.message_e]:
|
||||
self.payto_e.setFrozen(True)
|
||||
# self.payto_e.setFrozen(True)
|
||||
for btn in [self.save_button, self.send_button, self.clear_button]:
|
||||
btn.setEnabled(False)
|
||||
self.payto_e.setTextNoCheck(_("please wait..."))
|
||||
# self.payto_e.setTextNoCheck(_("please wait..."))
|
||||
|
||||
def payment_request_error(self, error):
|
||||
self.show_message(error)
|
||||
@@ -348,45 +345,90 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
style = ColorScheme.RED.as_stylesheet(True)
|
||||
if text is not None:
|
||||
w.setStyleSheet(style)
|
||||
w.setReadOnly(True)
|
||||
else:
|
||||
w.setStyleSheet('')
|
||||
w.setReadOnly(False)
|
||||
|
||||
def lock_fields(self, *,
|
||||
lock_recipient: Optional[bool] = None,
|
||||
lock_amount: Optional[bool] = None,
|
||||
lock_max: Optional[bool] = None,
|
||||
lock_description: Optional[bool] = None
|
||||
) -> None:
|
||||
self.logger.debug(f'locking fields, r={lock_recipient}, a={lock_amount}, m={lock_max}, d={lock_description}')
|
||||
if lock_recipient is not None:
|
||||
self.payto_e.setFrozen(lock_recipient)
|
||||
if lock_amount is not None:
|
||||
self.amount_e.setFrozen(lock_amount)
|
||||
if lock_max is not None:
|
||||
self.max_button.setEnabled(not lock_max)
|
||||
if lock_description is not None:
|
||||
self.message_e.setFrozen(lock_description)
|
||||
|
||||
def update_fields(self):
|
||||
recipient, amount, description, comment, validated = self.payment_identifier.get_fields_for_GUI()
|
||||
if recipient:
|
||||
self.payto_e.setTextNoCheck(recipient)
|
||||
elif self.payment_identifier.multiline_outputs:
|
||||
self.payto_e.handle_multiline(self.payment_identifier.multiline_outputs)
|
||||
if description:
|
||||
self.message_e.setText(description)
|
||||
if amount:
|
||||
self.amount_e.setAmount(amount)
|
||||
for w in [self.comment_e, self.comment_label]:
|
||||
w.setVisible(not bool(comment))
|
||||
self.set_field_style(self.payto_e, recipient or self.payment_identifier.multiline_outputs, validated)
|
||||
self.set_field_style(self.message_e, description, validated)
|
||||
self.set_field_style(self.amount_e, amount, validated)
|
||||
self.set_field_style(self.fiat_send_e, amount, validated)
|
||||
pi = self.payto_e.payment_identifier
|
||||
|
||||
def _handle_payment_identifier(self, *, can_use_network: bool = True):
|
||||
is_valid = self.payment_identifier.is_valid()
|
||||
self.save_button.setEnabled(is_valid)
|
||||
self.send_button.setEnabled(is_valid)
|
||||
if not is_valid:
|
||||
if pi.is_multiline():
|
||||
self.lock_fields(lock_recipient=False, lock_amount=True, lock_max=True, lock_description=False)
|
||||
self.set_field_style(self.payto_e, pi.multiline_outputs, False if not pi.is_valid() else None)
|
||||
self.save_button.setEnabled(pi.is_valid())
|
||||
self.send_button.setEnabled(pi.is_valid())
|
||||
if pi.is_valid():
|
||||
self.handle_multiline(pi.multiline_outputs)
|
||||
else:
|
||||
# self.payto_e.setToolTip('\n'.join(list(map(lambda x: f'{x.idx}: {x.line_content}', pi.get_error()))))
|
||||
self.payto_e.setToolTip(pi.get_error())
|
||||
return
|
||||
|
||||
if not pi.is_valid():
|
||||
self.lock_fields(lock_recipient=False, lock_amount=False, lock_max=True, lock_description=False)
|
||||
self.save_button.setEnabled(False)
|
||||
self.send_button.setEnabled(False)
|
||||
return
|
||||
|
||||
lock_recipient = pi.type != 'spk'
|
||||
self.lock_fields(lock_recipient=lock_recipient,
|
||||
lock_amount=pi.is_amount_locked(),
|
||||
lock_max=pi.is_amount_locked(),
|
||||
lock_description=False)
|
||||
if lock_recipient:
|
||||
recipient, amount, description, comment, validated = pi.get_fields_for_GUI()
|
||||
if recipient:
|
||||
self.payto_e.setText(recipient)
|
||||
if description:
|
||||
self.message_e.setText(description)
|
||||
self.lock_fields(lock_description=True)
|
||||
if amount:
|
||||
self.amount_e.setAmount(amount)
|
||||
for w in [self.comment_e, self.comment_label]:
|
||||
w.setVisible(bool(comment))
|
||||
self.set_field_style(self.payto_e, recipient or pi.multiline_outputs, validated)
|
||||
self.set_field_style(self.message_e, description, validated)
|
||||
self.set_field_style(self.amount_e, amount, validated)
|
||||
self.set_field_style(self.fiat_send_e, amount, validated)
|
||||
|
||||
self.send_button.setEnabled(bool(self.amount_e.get_amount()) and not pi.has_expired())
|
||||
self.save_button.setEnabled(True)
|
||||
|
||||
def _handle_payment_identifier(self):
|
||||
is_valid = self.payto_e.payment_identifier.is_valid()
|
||||
self.logger.debug(f'handle PI, valid={is_valid}')
|
||||
|
||||
self.update_fields()
|
||||
if self.payment_identifier.need_resolve():
|
||||
|
||||
if not is_valid:
|
||||
self.logger.debug(f'PI error: {self.payto_e.payment_identifier.error}')
|
||||
return
|
||||
|
||||
if self.payto_e.payment_identifier.need_resolve():
|
||||
self.prepare_for_send_tab_network_lookup()
|
||||
self.payment_identifier.resolve(on_finished=self.resolve_done_signal.emit)
|
||||
# update fiat amount
|
||||
self.payto_e.payment_identifier.resolve(on_finished=self.resolve_done_signal.emit)
|
||||
# update fiat amount (and reset max)
|
||||
self.amount_e.textEdited.emit("")
|
||||
self.window.show_send_tab()
|
||||
|
||||
def on_resolve_done(self, pi):
|
||||
if self.payment_identifier.error:
|
||||
self.show_error(self.payment_identifier.error)
|
||||
if self.payto_e.payment_identifier.error:
|
||||
self.show_error(self.payto_e.payment_identifier.error)
|
||||
self.do_clear()
|
||||
return
|
||||
self.update_fields()
|
||||
@@ -404,10 +446,10 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
self.show_error(_('No amount'))
|
||||
return
|
||||
|
||||
invoice = self.payment_identifier.get_invoice(amount_sat, self.get_message())
|
||||
invoice = self.payto_e.payment_identifier.get_invoice(amount_sat, self.get_message())
|
||||
#except Exception as e:
|
||||
if not invoice:
|
||||
self.show_error('error getting invoice' + self.payment_identifier.error)
|
||||
self.show_error('error getting invoice' + self.payto_e.payment_identifier.error)
|
||||
return
|
||||
if not self.wallet.has_lightning() and not invoice.can_be_paid_onchain():
|
||||
self.show_error(_('Lightning is disabled'))
|
||||
@@ -439,18 +481,17 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
return self.amount_e.get_amount() or 0
|
||||
|
||||
def on_finalize_done(self, pi):
|
||||
self.do_clear()
|
||||
if pi.error:
|
||||
self.show_error(pi.error)
|
||||
self.do_clear()
|
||||
return
|
||||
self.update_fields(pi)
|
||||
self.update_fields()
|
||||
invoice = pi.get_invoice(self.get_amount(), self.get_message())
|
||||
self.pending_invoice = invoice
|
||||
self.logger.debug(f'after finalize invoice: {invoice!r}')
|
||||
self.do_pay_invoice(invoice)
|
||||
|
||||
def do_pay_or_get_invoice(self):
|
||||
pi = self.payment_identifier
|
||||
pi = self.payto_e.payment_identifier
|
||||
if pi.need_finalize():
|
||||
self.prepare_for_send_tab_network_lookup()
|
||||
pi.finalize(amount_sat=self.get_amount(), comment=self.message_e.text(),
|
||||
@@ -511,9 +552,9 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
"""Returns whether there are errors.
|
||||
Also shows error dialog to user if so.
|
||||
"""
|
||||
error = self.payment_identifier.get_error()
|
||||
error = self.payto_e.payment_identifier.get_error()
|
||||
if error:
|
||||
if not self.payment_identifier.is_multiline():
|
||||
if not self.payto_e.payment_identifier.is_multiline():
|
||||
err = error
|
||||
self.show_warning(
|
||||
_("Failed to parse 'Pay to' line") + ":\n" +
|
||||
@@ -527,13 +568,13 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
# for err in errors]))
|
||||
return True
|
||||
|
||||
warning = self.payment_identifier.warning
|
||||
warning = self.payto_e.payment_identifier.warning
|
||||
if warning:
|
||||
warning += '\n' + _('Do you wish to continue?')
|
||||
if not self.question(warning):
|
||||
return True
|
||||
|
||||
if self.payment_identifier.has_expired():
|
||||
if self.payto_e.payment_identifier.has_expired():
|
||||
self.show_error(_('Payment request has expired'))
|
||||
return True
|
||||
|
||||
@@ -619,7 +660,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
|
||||
def broadcast_thread():
|
||||
# non-GUI thread
|
||||
if self.payment_identifier.has_expired():
|
||||
if self.payto_e.payment_identifier.has_expired():
|
||||
return False, _("Invoice has expired")
|
||||
try:
|
||||
self.network.run_from_another_thread(self.network.broadcast_transaction(tx))
|
||||
@@ -629,9 +670,9 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
return False, repr(e)
|
||||
# success
|
||||
txid = tx.txid()
|
||||
if self.payment_identifier.need_merchant_notify():
|
||||
if self.payto_e.payment_identifier.need_merchant_notify():
|
||||
refund_address = self.wallet.get_receiving_address()
|
||||
self.payment_identifier.notify_merchant(
|
||||
self.payto_e.payment_identifier.notify_merchant(
|
||||
tx=tx,
|
||||
refund_address=refund_address,
|
||||
on_finished=self.notify_merchant_done_signal.emit
|
||||
@@ -683,10 +724,23 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
self.window.show_send_tab()
|
||||
self.payto_e.do_clear()
|
||||
if len(paytos) == 1:
|
||||
self.logger.debug('payto_e setText 1')
|
||||
self.payto_e.setText(paytos[0])
|
||||
self.amount_e.setFocus()
|
||||
else:
|
||||
self.payto_e.setFocus()
|
||||
text = "\n".join([payto + ", 0" for payto in paytos])
|
||||
self.logger.debug('payto_e setText n')
|
||||
self.payto_e.setText(text)
|
||||
self.payto_e.setFocus()
|
||||
|
||||
def handle_multiline(self, outputs):
|
||||
total = 0
|
||||
for output in outputs:
|
||||
if parse_max_spend(output.value):
|
||||
self.max_button.setChecked(True) # TODO: remove and let spend_max set this?
|
||||
self.spend_max()
|
||||
return
|
||||
else:
|
||||
total += output.value
|
||||
self.amount_e.setAmount(total if outputs else None)
|
||||
|
||||
@@ -562,7 +562,10 @@ class GenericInputHandler:
|
||||
new_text = self.text() + data + '\n'
|
||||
else:
|
||||
new_text = data
|
||||
setText(new_text)
|
||||
try:
|
||||
setText(new_text)
|
||||
except Exception as e:
|
||||
show_error(_('Invalid payment identifier in QR') + ':\n' + repr(e))
|
||||
|
||||
from .qrreader import scan_qrcode
|
||||
if parent is None:
|
||||
@@ -599,7 +602,10 @@ class GenericInputHandler:
|
||||
new_text = self.text() + data + '\n'
|
||||
else:
|
||||
new_text = data
|
||||
setText(new_text)
|
||||
try:
|
||||
setText(new_text)
|
||||
except Exception as e:
|
||||
show_error(_('Invalid payment identifier in QR') + ':\n' + repr(e))
|
||||
|
||||
def input_file(
|
||||
self,
|
||||
@@ -628,7 +634,10 @@ class GenericInputHandler:
|
||||
except BaseException as e:
|
||||
show_error(_('Error opening file') + ':\n' + repr(e))
|
||||
else:
|
||||
setText(data)
|
||||
try:
|
||||
setText(data)
|
||||
except Exception as e:
|
||||
show_error(_('Invalid payment identifier in file') + ':\n' + repr(e))
|
||||
|
||||
def input_paste_from_clipboard(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user