qt paytoedit: evaluate text on textChanged(), but no network requests
- add param to _check_text to toggle if network requests are allowed ("full check")
- every 0.5 sec, if the textedit has no focus, do full check
- on textChanged()
- detect if user copy-pasted by comparing current text against clipboard contents,
if so, do full check
- otherwise, do partial check
- on clicking ButtonsWidget btns (scan qr, paste, read file), do full check
This commit is contained in:
@@ -916,7 +916,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
|||||||
self.update_status()
|
self.update_status()
|
||||||
# resolve aliases
|
# resolve aliases
|
||||||
# FIXME this might do blocking network calls that has a timeout of several seconds
|
# FIXME this might do blocking network calls that has a timeout of several seconds
|
||||||
self.payto_e.check_text()
|
self.payto_e.on_timer_check_text()
|
||||||
self.notify_transactions()
|
self.notify_transactions()
|
||||||
|
|
||||||
def format_amount(self, amount_sat, is_diff=False, whitespaces=False) -> str:
|
def format_amount(self, amount_sat, is_diff=False, whitespaces=False) -> str:
|
||||||
@@ -1527,7 +1527,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
|||||||
from .paytoedit import PayToEdit
|
from .paytoedit import PayToEdit
|
||||||
self.amount_e = BTCAmountEdit(self.get_decimal_point)
|
self.amount_e = BTCAmountEdit(self.get_decimal_point)
|
||||||
self.payto_e = PayToEdit(self)
|
self.payto_e = PayToEdit(self)
|
||||||
self.payto_e.addPasteButton()
|
|
||||||
msg = (_("Recipient of the funds.") + "\n\n"
|
msg = (_("Recipient of the funds.") + "\n\n"
|
||||||
+ _("You may enter a Bitcoin address, a label from your list of contacts "
|
+ _("You may enter a Bitcoin address, a label from your list of contacts "
|
||||||
"(a list of completions will be proposed), "
|
"(a list of completions will be proposed), "
|
||||||
@@ -2252,17 +2251,17 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
|||||||
else:
|
else:
|
||||||
self.payment_request_error_signal.emit()
|
self.payment_request_error_signal.emit()
|
||||||
|
|
||||||
def set_lnurl6_bech32(self, lnurl: str):
|
def set_lnurl6_bech32(self, lnurl: str, *, can_use_network: bool = True):
|
||||||
try:
|
try:
|
||||||
url = decode_lnurl(lnurl)
|
url = decode_lnurl(lnurl)
|
||||||
except LnInvoiceException as e:
|
except LnInvoiceException as e:
|
||||||
self.show_error(_("Error parsing Lightning invoice") + f":\n{e}")
|
self.show_error(_("Error parsing Lightning invoice") + f":\n{e}")
|
||||||
return
|
return
|
||||||
self.set_lnurl6_url(url)
|
self.set_lnurl6_url(url, can_use_network=can_use_network)
|
||||||
|
|
||||||
def set_lnurl6_url(self, url: str, *, lnurl_data: LNURL6Data = None):
|
def set_lnurl6_url(self, url: str, *, lnurl_data: LNURL6Data = None, can_use_network: bool = True):
|
||||||
domain = urlparse(url).netloc
|
domain = urlparse(url).netloc
|
||||||
if lnurl_data is None:
|
if lnurl_data is None and can_use_network:
|
||||||
lnurl_data = request_lnurl(url, self.network.send_http_on_proxy)
|
lnurl_data = request_lnurl(url, self.network.send_http_on_proxy)
|
||||||
if not lnurl_data:
|
if not lnurl_data:
|
||||||
return
|
return
|
||||||
@@ -2303,9 +2302,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
|||||||
self._is_onchain = b
|
self._is_onchain = b
|
||||||
self.max_button.setEnabled(b)
|
self.max_button.setEnabled(b)
|
||||||
|
|
||||||
def set_bip21(self, text: str):
|
def set_bip21(self, text: str, *, can_use_network: bool = True):
|
||||||
|
on_bip70_pr = self.on_pr if can_use_network else None
|
||||||
try:
|
try:
|
||||||
out = util.parse_URI(text, self.on_pr)
|
out = util.parse_URI(text, on_bip70_pr)
|
||||||
except InvalidBitcoinURI as e:
|
except InvalidBitcoinURI as e:
|
||||||
self.show_error(_("Error parsing URI") + f":\n{e}")
|
self.show_error(_("Error parsing URI") + f":\n{e}")
|
||||||
return
|
return
|
||||||
@@ -2313,7 +2313,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
|||||||
r = out.get('r')
|
r = out.get('r')
|
||||||
sig = out.get('sig')
|
sig = out.get('sig')
|
||||||
name = out.get('name')
|
name = out.get('name')
|
||||||
if r or (name and sig):
|
if (r or (name and sig)) and can_use_network:
|
||||||
self.prepare_for_payment_request()
|
self.prepare_for_payment_request()
|
||||||
return
|
return
|
||||||
address = out.get('address')
|
address = out.get('address')
|
||||||
@@ -2322,7 +2322,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
|||||||
message = out.get('message')
|
message = out.get('message')
|
||||||
lightning = out.get('lightning')
|
lightning = out.get('lightning')
|
||||||
if lightning:
|
if lightning:
|
||||||
self.handle_payment_identifier(lightning)
|
self.handle_payment_identifier(lightning, can_use_network=can_use_network)
|
||||||
return
|
return
|
||||||
# use label as description (not BIP21 compliant)
|
# use label as description (not BIP21 compliant)
|
||||||
if label and not message:
|
if label and not message:
|
||||||
@@ -2334,7 +2334,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
|||||||
if amount:
|
if amount:
|
||||||
self.amount_e.setAmount(amount)
|
self.amount_e.setAmount(amount)
|
||||||
|
|
||||||
def handle_payment_identifier(self, text: str):
|
def handle_payment_identifier(self, text: str, *, can_use_network: bool = True):
|
||||||
"""Takes
|
"""Takes
|
||||||
Lightning identifiers:
|
Lightning identifiers:
|
||||||
* lightning-URI (containing bolt11 or lnurl)
|
* lightning-URI (containing bolt11 or lnurl)
|
||||||
@@ -2350,7 +2350,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
|||||||
invoice_or_lnurl = maybe_extract_lightning_payment_identifier(text)
|
invoice_or_lnurl = maybe_extract_lightning_payment_identifier(text)
|
||||||
if invoice_or_lnurl:
|
if invoice_or_lnurl:
|
||||||
if invoice_or_lnurl.startswith('lnurl'):
|
if invoice_or_lnurl.startswith('lnurl'):
|
||||||
self.set_lnurl6_bech32(invoice_or_lnurl)
|
self.set_lnurl6_bech32(invoice_or_lnurl, can_use_network=can_use_network)
|
||||||
else:
|
else:
|
||||||
self.set_bolt11(invoice_or_lnurl)
|
self.set_bolt11(invoice_or_lnurl)
|
||||||
elif text.lower().startswith(util.BITCOIN_BIP21_URI_SCHEME + ':'):
|
elif text.lower().startswith(util.BITCOIN_BIP21_URI_SCHEME + ':'):
|
||||||
|
|||||||
@@ -63,9 +63,10 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
|
|||||||
|
|
||||||
def __init__(self, win: 'ElectrumWindow'):
|
def __init__(self, win: 'ElectrumWindow'):
|
||||||
CompletionTextEdit.__init__(self)
|
CompletionTextEdit.__init__(self)
|
||||||
ScanQRTextEdit.__init__(self, config=win.config)
|
ScanQRTextEdit.__init__(self, config=win.config, setText=self._on_input_btn)
|
||||||
Logger.__init__(self)
|
Logger.__init__(self)
|
||||||
self.win = win
|
self.win = win
|
||||||
|
self.app = win.app
|
||||||
self.amount_edit = win.amount_e
|
self.amount_edit = win.amount_e
|
||||||
self.setFont(QFont(MONOSPACE_FONT))
|
self.setFont(QFont(MONOSPACE_FONT))
|
||||||
document = self.document()
|
document = self.document()
|
||||||
@@ -84,6 +85,8 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
|
|||||||
self.heightMax = (self.fontSpacing * 10) + self.verticalMargins
|
self.heightMax = (self.fontSpacing * 10) + self.verticalMargins
|
||||||
|
|
||||||
self.c = None
|
self.c = None
|
||||||
|
self.addPasteButton(setText=self._on_input_btn)
|
||||||
|
self.textChanged.connect(self._on_text_changed)
|
||||||
self.outputs = [] # type: List[PartialTxOutput]
|
self.outputs = [] # type: List[PartialTxOutput]
|
||||||
self.errors = [] # type: List[PayToLineError]
|
self.errors = [] # type: List[PayToLineError]
|
||||||
self.is_pr = False
|
self.is_pr = False
|
||||||
@@ -100,8 +103,8 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
|
|||||||
|
|
||||||
def setTextNoCheck(self, text: str):
|
def setTextNoCheck(self, text: str):
|
||||||
"""Sets the text, while also ensuring the new value will not be resolved/checked."""
|
"""Sets the text, while also ensuring the new value will not be resolved/checked."""
|
||||||
self.setText(text)
|
|
||||||
self.previous_payto = text
|
self.previous_payto = text
|
||||||
|
self.setText(text)
|
||||||
|
|
||||||
def do_clear(self):
|
def do_clear(self):
|
||||||
self.is_pr = False
|
self.is_pr = False
|
||||||
@@ -168,19 +171,27 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
|
|||||||
assert bitcoin.is_address(address)
|
assert bitcoin.is_address(address)
|
||||||
return address
|
return address
|
||||||
|
|
||||||
def check_text(self):
|
def _on_input_btn(self, text: str):
|
||||||
|
self.setText(text)
|
||||||
|
self._check_text(full_check=True)
|
||||||
|
|
||||||
|
def _on_text_changed(self):
|
||||||
|
if self.app.clipboard().text() == self.toPlainText():
|
||||||
|
# user likely pasted from clipboard
|
||||||
|
self._check_text(full_check=True)
|
||||||
|
else:
|
||||||
|
self._check_text(full_check=False)
|
||||||
|
|
||||||
|
def on_timer_check_text(self):
|
||||||
if self.hasFocus():
|
if self.hasFocus():
|
||||||
return
|
return
|
||||||
if self.is_pr:
|
self._check_text(full_check=True)
|
||||||
return
|
|
||||||
text = str(self.toPlainText())
|
|
||||||
text = text.strip() # strip whitespaces
|
|
||||||
if text == self.previous_payto:
|
|
||||||
return
|
|
||||||
self.previous_payto = text
|
|
||||||
self._check_text()
|
|
||||||
|
|
||||||
def _check_text(self):
|
def _check_text(self, *, full_check: bool):
|
||||||
|
if self.previous_payto == str(self.toPlainText()).strip():
|
||||||
|
return
|
||||||
|
if full_check:
|
||||||
|
self.previous_payto = str(self.toPlainText()).strip()
|
||||||
self.errors = []
|
self.errors = []
|
||||||
if self.is_pr:
|
if self.is_pr:
|
||||||
return
|
return
|
||||||
@@ -194,10 +205,10 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
|
|||||||
if len(lines) == 1:
|
if len(lines) == 1:
|
||||||
data = lines[0]
|
data = lines[0]
|
||||||
try:
|
try:
|
||||||
self.win.handle_payment_identifier(data)
|
self.win.handle_payment_identifier(data, can_use_network=full_check)
|
||||||
except LNURLError as e:
|
except LNURLError as e:
|
||||||
self.logger.exception("")
|
self.logger.exception("")
|
||||||
self.show_error(e)
|
self.win.show_error(e)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -218,17 +229,18 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
|
|||||||
self.win.set_onchain(True)
|
self.win.set_onchain(True)
|
||||||
self.win.lock_amount(False)
|
self.win.lock_amount(False)
|
||||||
return
|
return
|
||||||
# try lightning address lnurl-16 (note: names can collide with openalias, so order matters)
|
if full_check: # network requests
|
||||||
lnurl_data = self._resolve_lightning_address_lnurl16(data)
|
# try lightning address lnurl-16 (note: names can collide with openalias, so order matters)
|
||||||
if lnurl_data:
|
lnurl_data = self._resolve_lightning_address_lnurl16(data)
|
||||||
url = lightning_address_to_url(data)
|
if lnurl_data:
|
||||||
self.win.set_lnurl6_url(url, lnurl_data=lnurl_data)
|
url = lightning_address_to_url(data)
|
||||||
return
|
self.win.set_lnurl6_url(url, lnurl_data=lnurl_data)
|
||||||
# try openalias
|
return
|
||||||
oa_data = self._resolve_openalias(data)
|
# try openalias
|
||||||
if oa_data:
|
oa_data = self._resolve_openalias(data)
|
||||||
self._set_openalias(key=data, data=oa_data)
|
if oa_data:
|
||||||
return
|
self._set_openalias(key=data, data=oa_data)
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
# there are multiple lines
|
# there are multiple lines
|
||||||
self._parse_as_multiline(lines, raise_errors=False)
|
self._parse_as_multiline(lines, raise_errors=False)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from typing import Callable
|
||||||
|
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.plugin import run_hook
|
from electrum.plugin import run_hook
|
||||||
from electrum.simple_config import SimpleConfig
|
from electrum.simple_config import SimpleConfig
|
||||||
@@ -21,11 +23,16 @@ class ShowQRTextEdit(ButtonsTextEdit):
|
|||||||
|
|
||||||
class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
|
class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
|
||||||
|
|
||||||
def __init__(self, text="", allow_multi: bool = False, *, config: SimpleConfig):
|
def __init__(
|
||||||
|
self, text="", allow_multi: bool = False,
|
||||||
|
*,
|
||||||
|
config: SimpleConfig,
|
||||||
|
setText: Callable[[str], None] = None,
|
||||||
|
):
|
||||||
ButtonsTextEdit.__init__(self, text)
|
ButtonsTextEdit.__init__(self, text)
|
||||||
self.setReadOnly(False)
|
self.setReadOnly(False)
|
||||||
self.add_file_input_button(config=config, show_error=self.show_error)
|
self.add_file_input_button(config=config, show_error=self.show_error, setText=setText)
|
||||||
self.add_qr_input_button(config=config, show_error=self.show_error, allow_multi=allow_multi)
|
self.add_qr_input_button(config=config, show_error=self.show_error, allow_multi=allow_multi, setText=setText)
|
||||||
run_hook('scan_text_edit', self)
|
run_hook('scan_text_edit', self)
|
||||||
|
|
||||||
def contextMenuEvent(self, e):
|
def contextMenuEvent(self, e):
|
||||||
|
|||||||
Reference in New Issue
Block a user