Browse Source

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
patch-4
SomberNight 3 years ago
parent
commit
df974c2384
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 24
      electrum/gui/qt/main_window.py
  2. 62
      electrum/gui/qt/paytoedit.py
  3. 13
      electrum/gui/qt/qrtextedit.py

24
electrum/gui/qt/main_window.py

@ -916,7 +916,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self.update_status()
# resolve aliases
# 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()
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
self.amount_e = BTCAmountEdit(self.get_decimal_point)
self.payto_e = PayToEdit(self)
self.payto_e.addPasteButton()
msg = (_("Recipient of the funds.") + "\n\n"
+ _("You may enter a Bitcoin address, a label from your list of contacts "
"(a list of completions will be proposed), "
@ -2252,17 +2251,17 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
else:
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:
url = decode_lnurl(lnurl)
except LnInvoiceException as e:
self.show_error(_("Error parsing Lightning invoice") + f":\n{e}")
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
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)
if not lnurl_data:
return
@ -2303,9 +2302,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self._is_onchain = 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:
out = util.parse_URI(text, self.on_pr)
out = util.parse_URI(text, on_bip70_pr)
except InvalidBitcoinURI as e:
self.show_error(_("Error parsing URI") + f":\n{e}")
return
@ -2313,7 +2313,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
r = out.get('r')
sig = out.get('sig')
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()
return
address = out.get('address')
@ -2322,7 +2322,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
message = out.get('message')
lightning = out.get('lightning')
if lightning:
self.handle_payment_identifier(lightning)
self.handle_payment_identifier(lightning, can_use_network=can_use_network)
return
# use label as description (not BIP21 compliant)
if label and not message:
@ -2334,7 +2334,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
if 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
Lightning identifiers:
* 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)
if invoice_or_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:
self.set_bolt11(invoice_or_lnurl)
elif text.lower().startswith(util.BITCOIN_BIP21_URI_SCHEME + ':'):

62
electrum/gui/qt/paytoedit.py

@ -63,9 +63,10 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
def __init__(self, win: 'ElectrumWindow'):
CompletionTextEdit.__init__(self)
ScanQRTextEdit.__init__(self, config=win.config)
ScanQRTextEdit.__init__(self, config=win.config, setText=self._on_input_btn)
Logger.__init__(self)
self.win = win
self.app = win.app
self.amount_edit = win.amount_e
self.setFont(QFont(MONOSPACE_FONT))
document = self.document()
@ -84,6 +85,8 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
self.heightMax = (self.fontSpacing * 10) + self.verticalMargins
self.c = None
self.addPasteButton(setText=self._on_input_btn)
self.textChanged.connect(self._on_text_changed)
self.outputs = [] # type: List[PartialTxOutput]
self.errors = [] # type: List[PayToLineError]
self.is_pr = False
@ -100,8 +103,8 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
def setTextNoCheck(self, text: str):
"""Sets the text, while also ensuring the new value will not be resolved/checked."""
self.setText(text)
self.previous_payto = text
self.setText(text)
def do_clear(self):
self.is_pr = False
@ -168,19 +171,27 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
assert bitcoin.is_address(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():
return
if self.is_pr:
return
text = str(self.toPlainText())
text = text.strip() # strip whitespaces
if text == self.previous_payto:
return
self.previous_payto = text
self._check_text()
self._check_text(full_check=True)
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 = []
if self.is_pr:
return
@ -194,10 +205,10 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
if len(lines) == 1:
data = lines[0]
try:
self.win.handle_payment_identifier(data)
self.win.handle_payment_identifier(data, can_use_network=full_check)
except LNURLError as e:
self.logger.exception("")
self.show_error(e)
self.win.show_error(e)
except ValueError:
pass
else:
@ -218,17 +229,18 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
self.win.set_onchain(True)
self.win.lock_amount(False)
return
# try lightning address lnurl-16 (note: names can collide with openalias, so order matters)
lnurl_data = self._resolve_lightning_address_lnurl16(data)
if lnurl_data:
url = lightning_address_to_url(data)
self.win.set_lnurl6_url(url, lnurl_data=lnurl_data)
return
# try openalias
oa_data = self._resolve_openalias(data)
if oa_data:
self._set_openalias(key=data, data=oa_data)
return
if full_check: # network requests
# try lightning address lnurl-16 (note: names can collide with openalias, so order matters)
lnurl_data = self._resolve_lightning_address_lnurl16(data)
if lnurl_data:
url = lightning_address_to_url(data)
self.win.set_lnurl6_url(url, lnurl_data=lnurl_data)
return
# try openalias
oa_data = self._resolve_openalias(data)
if oa_data:
self._set_openalias(key=data, data=oa_data)
return
else:
# there are multiple lines
self._parse_as_multiline(lines, raise_errors=False)

13
electrum/gui/qt/qrtextedit.py

@ -1,3 +1,5 @@
from typing import Callable
from electrum.i18n import _
from electrum.plugin import run_hook
from electrum.simple_config import SimpleConfig
@ -21,11 +23,16 @@ class ShowQRTextEdit(ButtonsTextEdit):
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)
self.setReadOnly(False)
self.add_file_input_button(config=config, show_error=self.show_error)
self.add_qr_input_button(config=config, show_error=self.show_error, allow_multi=allow_multi)
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, setText=setText)
run_hook('scan_text_edit', self)
def contextMenuEvent(self, e):

Loading…
Cancel
Save