Browse Source

Rework PaytoEdit:

- show a QLineEdit by default, a QTextEdit only if paytomany is active.
   paytomany is a rare use case, it should not interfer with regular
   use (e.g. when a user inadvertently types enter).
 - this also fixes the visual appearance if the payto line
 - keep paytomany menu in sync with actual state
master
ThomasV 3 years ago
parent
commit
1f4cedf56a
  1. 180
      electrum/gui/qt/paytoedit.py
  2. 50
      electrum/gui/qt/send_tab.py
  3. 208
      electrum/gui/qt/util.py

180
electrum/gui/qt/paytoedit.py

@ -25,14 +25,15 @@
import re import re
import decimal import decimal
from functools import partial
from decimal import Decimal from decimal import Decimal
from typing import NamedTuple, Sequence, Optional, List, TYPE_CHECKING from typing import NamedTuple, Sequence, Optional, List, TYPE_CHECKING
from PyQt5.QtGui import QFontMetrics, QFont from PyQt5.QtGui import QFontMetrics, QFont
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QTextEdit, QVBoxLayout
from electrum import bitcoin from electrum import bitcoin
from electrum.util import bfh, parse_max_spend, FailedToParsePaymentIdentifier from electrum.util import parse_max_spend, FailedToParsePaymentIdentifier
from electrum.transaction import PartialTxOutput from electrum.transaction import PartialTxOutput
from electrum.bitcoin import opcodes, construct_script from electrum.bitcoin import opcodes, construct_script
from electrum.logging import Logger from electrum.logging import Logger
@ -41,7 +42,7 @@ from electrum.lnurl import LNURLError
from .qrtextedit import ScanQRTextEdit from .qrtextedit import ScanQRTextEdit
from .completion_text_edit import CompletionTextEdit from .completion_text_edit import CompletionTextEdit
from . import util from . import util
from .util import MONOSPACE_FONT from .util import MONOSPACE_FONT, GenericInputHandler, editor_contextMenuEvent
if TYPE_CHECKING: if TYPE_CHECKING:
from .main_window import ElectrumWindow from .main_window import ElectrumWindow
@ -61,48 +62,112 @@ class PayToLineError(NamedTuple):
is_multiline: bool = False is_multiline: bool = False
class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
def __init__(self, send_tab: 'SendTab'): class ResizingTextEdit(QTextEdit):
CompletionTextEdit.__init__(self)
ScanQRTextEdit.__init__(self, config=send_tab.config, setText=self._on_input_btn, is_payto=True) def __init__(self):
Logger.__init__(self) QTextEdit.__init__(self)
self.send_tab = send_tab
self.win = send_tab.window
self.app = QApplication.instance()
self.amount_edit = self.send_tab.amount_e
self.setFont(QFont(MONOSPACE_FONT))
document = self.document() document = self.document()
document.contentsChanged.connect(self.update_size) document.contentsChanged.connect(self.update_size)
fontMetrics = QFontMetrics(document.defaultFont()) fontMetrics = QFontMetrics(document.defaultFont())
self.fontSpacing = fontMetrics.lineSpacing() self.fontSpacing = fontMetrics.lineSpacing()
margins = self.contentsMargins() margins = self.contentsMargins()
documentMargin = document.documentMargin() documentMargin = document.documentMargin()
self.verticalMargins = margins.top() + margins.bottom() self.verticalMargins = margins.top() + margins.bottom()
self.verticalMargins += self.frameWidth() * 2 self.verticalMargins += self.frameWidth() * 2
self.verticalMargins += documentMargin * 2 self.verticalMargins += documentMargin * 2
self.heightMin = self.fontSpacing + self.verticalMargins self.heightMin = self.fontSpacing + self.verticalMargins
self.heightMax = (self.fontSpacing * 10) + self.verticalMargins self.heightMax = (self.fontSpacing * 10) + self.verticalMargins
self.update_size()
def update_size(self):
docLineCount = self.document().lineCount()
docHeight = max(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)
class PayToEdit(Logger, GenericInputHandler):
self.c = None def __init__(self, send_tab: 'SendTab'):
self.addPasteButton(setText=self._on_input_btn) Logger.__init__(self)
self.textChanged.connect(self._on_text_changed) GenericInputHandler.__init__(self)
self.line_edit = QLineEdit()
self.text_edit = ResizingTextEdit()
self.text_edit.hide()
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.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.is_multiline = False
self.outputs = [] # type: List[PartialTxOutput] self.outputs = [] # type: List[PartialTxOutput]
self.errors = [] # type: List[PayToLineError] self.errors = [] # type: List[PayToLineError]
self.disable_checks = False self.disable_checks = False
self.is_alias = False self.is_alias = False
self.update_size()
self.payto_scriptpubkey = None # type: Optional[bytes] self.payto_scriptpubkey = None # type: Optional[bytes]
self.lightning_invoice = None self.lightning_invoice = None
self.previous_payto = '' 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
# 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,
)
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,
)
self.on_input_file = partial(
self.input_file,
config=self.config,
show_error=self.win.show_error,
setText=self._on_input_btn,
)
#
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
def set_paytomany(self, b):
self._is_paytomany = b
self.line_edit.setVisible(not b)
self.text_edit.setVisible(b)
self.send_tab.paytomany_menu.setChecked(b)
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): def setFrozen(self, b):
self.setReadOnly(b) self.setReadOnly(b)
self.setStyleSheet(frozen_style if b else normal_style) if not b:
self.overlay_widget.setHidden(b) self.setStyleSheet(normal_style)
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."""
@ -110,9 +175,11 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
self.setText(text) self.setText(text)
def do_clear(self): def do_clear(self):
self.set_paytomany(False)
self.disable_checks = False self.disable_checks = False
self.is_alias = False self.is_alias = False
self.setText('') self.line_edit.setText('')
self.text_edit.setText('')
self.setFrozen(False) self.setFrozen(False)
self.setEnabled(True) self.setEnabled(True)
@ -134,12 +201,12 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
def parse_output(self, x) -> bytes: def parse_output(self, x) -> bytes:
try: try:
address = self.parse_address(x) address = self.parse_address(x)
return bfh(bitcoin.address_to_script(address)) return bytes.fromhex(bitcoin.address_to_script(address))
except Exception: except Exception:
pass pass
try: try:
script = self.parse_script(x) script = self.parse_script(x)
return bfh(script) return bytes.fromhex(script)
except Exception: except Exception:
pass pass
raise Exception("Invalid address or script.") raise Exception("Invalid address or script.")
@ -151,7 +218,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
opcode_int = opcodes[word] opcode_int = opcodes[word]
script += construct_script([opcode_int]) script += construct_script([opcode_int])
else: else:
bfh(word) # to test it is hex data bytes.fromhex(word) # to test it is hex data
script += construct_script([word]) script += construct_script([word])
return script return script
@ -176,30 +243,37 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
def _on_input_btn(self, text: str): def _on_input_btn(self, text: str):
self.setText(text) self.setText(text)
self._check_text(full_check=True)
def _on_text_changed(self): def _on_text_changed(self):
if self.app.clipboard().text() == self.toPlainText(): text = self.toPlainText()
# user likely pasted from clipboard # False if user pasted from clipboard
self._check_text(full_check=True) full_check = self.app.clipboard().text() != text
else: self._check_text(text, full_check=full_check)
self._check_text(full_check=False) if self.is_multiline and not self._is_paytomany:
self.set_paytomany(True)
self.text_edit.setText(text)
def on_timer_check_text(self): def on_timer_check_text(self):
if self.hasFocus(): if self.editor.hasFocus():
return return
self._check_text(full_check=True) text = self.toPlainText()
self._check_text(text, full_check=True)
def _check_text(self, *, full_check: bool):
if self.previous_payto == str(self.toPlainText()).strip(): def _check_text(self, text, *, full_check: bool):
"""
side effects: self.is_multiline, self.errors, self.outputs
"""
if self.previous_payto == str(text).strip():
return return
if full_check: if full_check:
self.previous_payto = str(self.toPlainText()).strip() self.previous_payto = str(text).strip()
self.errors = [] self.errors = []
if self.disable_checks: if self.disable_checks:
return return
# filter out empty lines # filter out empty lines
lines = [i for i in self.lines() if i] lines = text.split('\n')
lines = [i for i in lines if i]
self.is_multiline = len(lines)>1
self.payto_scriptpubkey = None self.payto_scriptpubkey = None
self.lightning_invoice = None self.lightning_invoice = None
@ -242,6 +316,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
# there are multiple lines # there are multiple lines
self._parse_as_multiline(lines, raise_errors=False) self._parse_as_multiline(lines, raise_errors=False)
def _parse_as_multiline(self, lines, *, raise_errors: bool): def _parse_as_multiline(self, lines, *, raise_errors: bool):
outputs = [] # type: List[PartialTxOutput] outputs = [] # type: List[PartialTxOutput]
total = 0 total = 0
@ -292,33 +367,6 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
return self.outputs[:] return self.outputs[:]
def lines(self):
return self.toPlainText().split('\n')
def is_multiline(self):
return len(self.lines()) > 1
def paytomany(self):
self.setTextNoCheck("\n\n\n")
self.update_size()
def update_size(self):
docLineCount = self.document().lineCount()
if self.cursorRect().right() + 1 >= self.overlay_widget.pos().x():
# Add a line if we are under the overlay widget
docLineCount += 1
docHeight = 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)
# The scrollbar visibility can have changed so we update the overlay position here
self._updateOverlayPos()
def _resolve_openalias(self, text: str) -> Optional[dict]: def _resolve_openalias(self, text: str) -> Optional[dict]:
key = text key = text
key = key.strip() # strip whitespaces key = key.strip() # strip whitespaces

50
electrum/gui/qt/send_tab.py

@ -9,7 +9,7 @@ from urllib.parse import urlparse
from PyQt5.QtCore import pyqtSignal, QPoint from PyQt5.QtCore import pyqtSignal, QPoint
from PyQt5.QtWidgets import (QLabel, QVBoxLayout, QGridLayout, from PyQt5.QtWidgets import (QLabel, QVBoxLayout, QGridLayout,
QHBoxLayout, QCompleter, QWidget, QToolTip) QHBoxLayout, QCompleter, QWidget, QToolTip, QPushButton)
from electrum import util, paymentrequest from electrum import util, paymentrequest
from electrum import lnutil from electrum import lnutil
@ -85,20 +85,21 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
"e.g. set one amount to '2!' and another to '3!' to split your coins 40-60.")) "e.g. set one amount to '2!' and another to '3!' to split your coins 40-60."))
payto_label = HelpLabel(_('Pay to'), msg) payto_label = HelpLabel(_('Pay to'), msg)
grid.addWidget(payto_label, 1, 0) grid.addWidget(payto_label, 1, 0)
grid.addWidget(self.payto_e, 1, 1, 1, -1) grid.addWidget(self.payto_e.line_edit, 1, 1, 1, 4)
grid.addWidget(self.payto_e.text_edit, 1, 1, 1, 4)
completer = QCompleter() #completer = QCompleter()
completer.setCaseSensitivity(False) #completer.setCaseSensitivity(False)
self.payto_e.set_completer(completer) #self.payto_e.set_completer(completer)
completer.setModel(self.window.completions) #completer.setModel(self.window.completions)
msg = _('Description of the transaction (not mandatory).') + '\n\n' \ msg = _('Description of the transaction (not mandatory).') + '\n\n' \
+ _( + _(
'The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.') 'The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.')
description_label = HelpLabel(_('Description'), msg) description_label = HelpLabel(_('Description'), msg)
grid.addWidget(description_label, 2, 0) grid.addWidget(description_label, 2, 0)
self.message_e = SizedFreezableLineEdit(width=700) self.message_e = SizedFreezableLineEdit(width=600)
grid.addWidget(self.message_e, 2, 1, 1, -1) grid.addWidget(self.message_e, 2, 1, 1, 4)
msg = (_('The amount to be received by the recipient.') + ' ' msg = (_('The amount to be received by the recipient.') + ' '
+ _('Fees are paid by the sender.') + '\n\n' + _('Fees are paid by the sender.') + '\n\n'
@ -127,9 +128,16 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
self.save_button = EnterButton(_("Save"), self.do_save_invoice) self.save_button = EnterButton(_("Save"), self.do_save_invoice)
self.send_button = EnterButton(_("Pay") + "...", self.do_pay_or_get_invoice) self.send_button = EnterButton(_("Pay") + "...", self.do_pay_or_get_invoice)
self.clear_button = EnterButton(_("Clear"), self.do_clear) self.clear_button = EnterButton(_("Clear"), self.do_clear)
self.paste_button = QPushButton()
self.paste_button.clicked.connect(lambda: self.payto_e._on_input_btn(self.window.app.clipboard().text()))
self.paste_button.setIcon(read_QIcon('copy.png'))
self.paste_button.setToolTip(_('Paste invoice from clipboard'))
self.paste_button.setMaximumWidth(35)
grid.addWidget(self.paste_button, 1, 5)
buttons = QHBoxLayout() buttons = QHBoxLayout()
buttons.addStretch(1) buttons.addStretch(1)
#buttons.addWidget(self.paste_button)
buttons.addWidget(self.clear_button) buttons.addWidget(self.clear_button)
buttons.addWidget(self.save_button) buttons.addWidget(self.save_button)
buttons.addWidget(self.send_button) buttons.addWidget(self.send_button)
@ -151,10 +159,12 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
from .invoice_list import InvoiceList from .invoice_list import InvoiceList
self.invoice_list = InvoiceList(self) self.invoice_list = InvoiceList(self)
self.toolbar, menu = self.invoice_list.create_toolbar_with_menu('') self.toolbar, menu = self.invoice_list.create_toolbar_with_menu('')
menu.addAction(read_QIcon(get_iconname_camera()), _("Read QR code with camera"), self.payto_e.on_qr_from_camera_input_btn) menu.addAction(read_QIcon(get_iconname_camera()), _("Read QR code with camera"), self.payto_e.on_qr_from_camera_input_btn)
menu.addAction(read_QIcon("picture_in_picture.png"), _("Read QR code from screen"), self.payto_e.on_qr_from_screenshot_input_btn) menu.addAction(read_QIcon("picture_in_picture.png"), _("Read QR code from screen"), self.payto_e.on_qr_from_screenshot_input_btn)
menu.addAction(read_QIcon("file.png"), _("Read invoice from file"), self.payto_e.on_input_file) menu.addAction(read_QIcon("file.png"), _("Read invoice from file"), self.payto_e.on_input_file)
menu.addToggle(_("&Pay to many"), self.paytomany) self.paytomany_menu = menu.addToggle(_("&Pay to many"), self.toggle_paytomany)
menu.addSeparator() menu.addSeparator()
menu.addAction(_("Import invoices"), self.window.import_invoices) menu.addAction(_("Import invoices"), self.window.import_invoices)
menu.addAction(_("Export invoices"), self.window.export_invoices) menu.addAction(_("Export invoices"), self.window.export_invoices)
@ -755,18 +765,16 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
WaitingDialog(self, _('Broadcasting transaction...'), WaitingDialog(self, _('Broadcasting transaction...'),
broadcast_thread, broadcast_done, self.window.on_error) broadcast_thread, broadcast_done, self.window.on_error)
def paytomany(self): def toggle_paytomany(self):
if self.payto_e.is_multiline(): self.payto_e.toggle_paytomany()
self.payto_e.do_clear() if self.payto_e.is_paytomany():
return message = '\n'.join([
self.payto_e.paytomany() _('Enter a list of outputs in the \'Pay to\' field.'),
message = '\n'.join([ _('One output per line.'),
_('Enter a list of outputs in the \'Pay to\' field.'), _('Format: address, amount'),
_('One output per line.'), _('You may load a CSV file using the file icon.')
_('Format: address, amount'), ])
_('You may load a CSV file using the file icon.') self.window.show_tooltip_after_delay(message)
])
self.window.show_tooltip_after_delay(message)
def payto_contacts(self, labels): def payto_contacts(self, labels):
paytos = [self.window.get_contact_payto(label) for label in labels] paytos = [self.window.get_contact_payto(label) for label in labels]

208
electrum/gui/qt/util.py

@ -920,7 +920,116 @@ def get_iconname_camera() -> str:
return "camera_white.png" if ColorScheme.dark_scheme else "camera_dark.png" return "camera_white.png" if ColorScheme.dark_scheme else "camera_dark.png"
class OverlayControlMixin: def editor_contextMenuEvent(self, p, e):
m = self.createStandardContextMenu()
m.addSeparator()
m.addAction(read_QIcon(get_iconname_camera()), _("Read QR code with camera"), p.on_qr_from_camera_input_btn)
m.addAction(read_QIcon("picture_in_picture.png"), _("Read QR code from screen"), p.on_qr_from_screenshot_input_btn)
m.addAction(read_QIcon("file.png"), _("Read file"), p.on_input_file)
m.exec_(e.globalPos())
class GenericInputHandler:
def input_qr_from_camera(
self,
*,
config: 'SimpleConfig',
allow_multi: bool = False,
show_error: Callable[[str], None],
setText: Callable[[str], None] = None,
) -> None:
if setText is None:
setText = self.setText
def cb(success: bool, error: str, data):
if not success:
if error:
show_error(error)
return
if not data:
data = ''
if allow_multi:
new_text = self.text() + data + '\n'
else:
new_text = data
setText(new_text)
from .qrreader import scan_qrcode
scan_qrcode(parent=self, config=config, callback=cb)
def input_qr_from_screenshot(
self,
*,
allow_multi: bool = False,
show_error: Callable[[str], None],
setText: Callable[[str], None] = None,
) -> None:
if setText is None:
setText = self.setText
from .qrreader import scan_qr_from_image
scanned_qr = None
for screen in QApplication.instance().screens():
try:
scan_result = scan_qr_from_image(screen.grabWindow(0).toImage())
except MissingQrDetectionLib as e:
show_error(_("Unable to scan image.") + "\n" + repr(e))
return
if len(scan_result) > 0:
if (scanned_qr is not None) or len(scan_result) > 1:
show_error(_("More than one QR code was found on the screen."))
return
scanned_qr = scan_result
if scanned_qr is None:
show_error(_("No QR code was found on the screen."))
return
data = scanned_qr[0].data
if allow_multi:
new_text = self.text() + data + '\n'
else:
new_text = data
setText(new_text)
def input_file(
self,
*,
config: 'SimpleConfig',
show_error: Callable[[str], None],
setText: Callable[[str], None] = None,
) -> None:
if setText is None:
setText = self.setText
fileName = getOpenFileName(
parent=None,
title='select file',
config=config,
)
if not fileName:
return
try:
try:
with open(fileName, "r") as f:
data = f.read()
except UnicodeError as e:
with open(fileName, "rb") as f:
data = f.read()
data = data.hex()
except BaseException as e:
show_error(_('Error opening file') + ':\n' + repr(e))
else:
setText(data)
def input_paste_from_clipboard(
self,
*,
setText: Callable[[str], None] = None,
) -> None:
if setText is None:
setText = self.setText
app = QApplication.instance()
setText(app.clipboard().text())
class OverlayControlMixin(GenericInputHandler):
STYLE_SHEET_COMMON = ''' STYLE_SHEET_COMMON = '''
QPushButton { border-width: 1px; padding: 0px; margin: 0px; } QPushButton { border-width: 1px; padding: 0px; margin: 0px; }
''' '''
@ -931,6 +1040,7 @@ class OverlayControlMixin:
''' '''
def __init__(self, middle: bool = False): def __init__(self, middle: bool = False):
GenericInputHandler.__init__(self)
assert isinstance(self, QWidget) assert isinstance(self, QWidget)
assert isinstance(self, OverlayControlMixin) # only here for type-hints in IDE assert isinstance(self, OverlayControlMixin) # only here for type-hints in IDE
self.middle = middle self.middle = middle
@ -1107,102 +1217,6 @@ class OverlayControlMixin:
menu.addAction(read_QIcon(opt_icon), opt_text, opt_cb) menu.addAction(read_QIcon(opt_icon), opt_text, opt_cb)
btn.setMenu(menu) btn.setMenu(menu)
def input_qr_from_camera(
self,
*,
config: 'SimpleConfig',
allow_multi: bool = False,
show_error: Callable[[str], None],
setText: Callable[[str], None] = None,
) -> None:
if setText is None:
setText = self.setText
def cb(success: bool, error: str, data):
if not success:
if error:
show_error(error)
return
if not data:
data = ''
if allow_multi:
new_text = self.text() + data + '\n'
else:
new_text = data
setText(new_text)
from .qrreader import scan_qrcode
scan_qrcode(parent=self, config=config, callback=cb)
def input_qr_from_screenshot(
self,
*,
allow_multi: bool = False,
show_error: Callable[[str], None],
setText: Callable[[str], None] = None,
) -> None:
if setText is None:
setText = self.setText
from .qrreader import scan_qr_from_image
scanned_qr = None
for screen in QApplication.instance().screens():
try:
scan_result = scan_qr_from_image(screen.grabWindow(0).toImage())
except MissingQrDetectionLib as e:
show_error(_("Unable to scan image.") + "\n" + repr(e))
return
if len(scan_result) > 0:
if (scanned_qr is not None) or len(scan_result) > 1:
show_error(_("More than one QR code was found on the screen."))
return
scanned_qr = scan_result
if scanned_qr is None:
show_error(_("No QR code was found on the screen."))
return
data = scanned_qr[0].data
if allow_multi:
new_text = self.text() + data + '\n'
else:
new_text = data
setText(new_text)
def input_file(
self,
*,
config: 'SimpleConfig',
show_error: Callable[[str], None],
setText: Callable[[str], None] = None,
) -> None:
if setText is None:
setText = self.setText
fileName = getOpenFileName(
parent=self,
title='select file',
config=config,
)
if not fileName:
return
try:
try:
with open(fileName, "r") as f:
data = f.read()
except UnicodeError as e:
with open(fileName, "rb") as f:
data = f.read()
data = data.hex()
except BaseException as e:
show_error(_('Error opening file') + ':\n' + repr(e))
else:
setText(data)
def input_paste_from_clipboard(
self,
*,
setText: Callable[[str], None] = None,
) -> None:
if setText is None:
setText = self.setText
app = QApplication.instance()
setText(app.clipboard().text())
class ButtonsLineEdit(OverlayControlMixin, QLineEdit): class ButtonsLineEdit(OverlayControlMixin, QLineEdit):

Loading…
Cancel
Save