qt: PayToEdit and OverlayControlMixin: move around input buttons
This commit is contained in:
BIN
electrum/gui/icons/menu_vertical.png
Normal file
BIN
electrum/gui/icons/menu_vertical.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 997 B |
BIN
electrum/gui/icons/menu_vertical_white.png
Normal file
BIN
electrum/gui/icons/menu_vertical_white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
BIN
electrum/gui/icons/picture_in_picture.png
Normal file
BIN
electrum/gui/icons/picture_in_picture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1,10 +1,12 @@
|
|||||||
|
from functools import partial
|
||||||
from typing import Callable
|
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
|
||||||
|
|
||||||
from .util import ButtonsTextEdit, MessageBoxMixin
|
from .util import ButtonsTextEdit, MessageBoxMixin, ColorScheme, read_QIcon
|
||||||
|
from .util import get_iconname_camera, get_iconname_qrcode
|
||||||
|
|
||||||
|
|
||||||
class ShowQRTextEdit(ButtonsTextEdit):
|
class ShowQRTextEdit(ButtonsTextEdit):
|
||||||
@@ -31,15 +33,41 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
|
|||||||
):
|
):
|
||||||
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, setText=setText)
|
|
||||||
self.add_qr_input_button(config=config, show_error=self.show_error, allow_multi=allow_multi, setText=setText)
|
input_qr_from_camera = partial(
|
||||||
|
self.input_qr_from_camera,
|
||||||
|
config=config,
|
||||||
|
allow_multi=allow_multi,
|
||||||
|
show_error=self.show_error,
|
||||||
|
setText=setText,
|
||||||
|
)
|
||||||
|
self.on_qr_from_camera_input_btn = input_qr_from_camera
|
||||||
|
|
||||||
|
input_qr_from_screenshot = partial(
|
||||||
|
self.input_qr_from_screenshot,
|
||||||
|
allow_multi=allow_multi,
|
||||||
|
show_error=self.show_error,
|
||||||
|
setText=setText,
|
||||||
|
)
|
||||||
|
self.on_qr_from_screenshot_input_btn = input_qr_from_screenshot
|
||||||
|
|
||||||
|
input_file = partial(self.input_file, config=config, show_error=self.show_error, setText=setText)
|
||||||
|
|
||||||
|
self.add_menu_button(
|
||||||
|
options=[
|
||||||
|
("picture_in_picture.png", _("Read QR code from screen"), input_qr_from_screenshot),
|
||||||
|
("file.png", _("Read file"), input_file),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.add_qr_input_from_camera_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):
|
||||||
m = self.createStandardContextMenu()
|
m = self.createStandardContextMenu()
|
||||||
m.addSeparator()
|
m.addSeparator()
|
||||||
m.addAction(_("Read QR code from camera"), self.on_qr_from_camera_input_btn)
|
m.addAction(read_QIcon(get_iconname_camera()), _("Read QR code from camera"), self.on_qr_from_camera_input_btn)
|
||||||
m.addAction(_("Read QR code from screen"), self.on_qr_from_screenshot_input_btn)
|
m.addAction(read_QIcon("picture_in_picture.png"), _("Read QR code from screen"), self.on_qr_from_screenshot_input_btn)
|
||||||
m.exec_(e.globalPos())
|
m.exec_(e.globalPos())
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +76,7 @@ class ScanShowQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
|
|||||||
def __init__(self, text="", allow_multi: bool = False, *, config: SimpleConfig):
|
def __init__(self, text="", allow_multi: bool = False, *, config: SimpleConfig):
|
||||||
ButtonsTextEdit.__init__(self, text)
|
ButtonsTextEdit.__init__(self, text)
|
||||||
self.setReadOnly(False)
|
self.setReadOnly(False)
|
||||||
self.add_qr_input_button(config=config, show_error=self.show_error, allow_multi=allow_multi)
|
self.add_qr_input_combined_button(config=config, show_error=self.show_error, allow_multi=allow_multi)
|
||||||
self.add_qr_show_button(config=config)
|
self.add_qr_show_button(config=config)
|
||||||
run_hook('scan_text_edit', self)
|
run_hook('scan_text_edit', self)
|
||||||
run_hook('show_text_edit', self)
|
run_hook('show_text_edit', self)
|
||||||
@@ -56,7 +84,7 @@ class ScanShowQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
|
|||||||
def contextMenuEvent(self, e):
|
def contextMenuEvent(self, e):
|
||||||
m = self.createStandardContextMenu()
|
m = self.createStandardContextMenu()
|
||||||
m.addSeparator()
|
m.addSeparator()
|
||||||
m.addAction(_("Read QR code from camera"), self.on_qr_from_camera_input_btn)
|
m.addAction(read_QIcon(get_iconname_camera()), _("Read QR code from camera"), self.on_qr_from_camera_input_btn)
|
||||||
m.addAction(_("Read QR code from screen"), self.on_qr_from_screenshot_input_btn)
|
m.addAction(read_QIcon("picture_in_picture.png"), _("Read QR code from screen"), self.on_qr_from_screenshot_input_btn)
|
||||||
m.addAction(_("Show as QR code"), self.on_qr_show_btn)
|
m.addAction(read_QIcon(get_iconname_qrcode()), _("Show as QR code"), self.on_qr_show_btn)
|
||||||
m.exec_(e.globalPos())
|
m.exec_(e.globalPos())
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ from .util import (MessageBoxMixin, read_QIcon, Buttons, icon_path,
|
|||||||
char_width_in_lineedit, TRANSACTION_FILE_EXTENSION_FILTER_SEPARATE,
|
char_width_in_lineedit, TRANSACTION_FILE_EXTENSION_FILTER_SEPARATE,
|
||||||
TRANSACTION_FILE_EXTENSION_FILTER_ONLY_COMPLETE_TX,
|
TRANSACTION_FILE_EXTENSION_FILTER_ONLY_COMPLETE_TX,
|
||||||
TRANSACTION_FILE_EXTENSION_FILTER_ONLY_PARTIAL_TX,
|
TRANSACTION_FILE_EXTENSION_FILTER_ONLY_PARTIAL_TX,
|
||||||
BlockingWaitingDialog, getSaveFileName, ColorSchemeItem)
|
BlockingWaitingDialog, getSaveFileName, ColorSchemeItem,
|
||||||
|
get_iconname_qrcode)
|
||||||
|
|
||||||
from .fee_slider import FeeSlider, FeeComboBox
|
from .fee_slider import FeeSlider, FeeComboBox
|
||||||
from .confirm_tx_dialog import TxEditor
|
from .confirm_tx_dialog import TxEditor
|
||||||
@@ -273,8 +274,7 @@ class BaseTxDialog(QDialog, MessageBoxMixin):
|
|||||||
action.triggered.connect(lambda: self.copy_to_clipboard(tx=gettx()))
|
action.triggered.connect(lambda: self.copy_to_clipboard(tx=gettx()))
|
||||||
menu.addAction(action)
|
menu.addAction(action)
|
||||||
|
|
||||||
qr_icon = "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png"
|
action = QAction(read_QIcon(get_iconname_qrcode()), _("Show as QR code"), self)
|
||||||
action = QAction(read_QIcon(qr_icon), _("Show as QR code"), self)
|
|
||||||
action.triggered.connect(lambda: self.show_qr(tx=gettx()))
|
action.triggered.connect(lambda: self.show_qr(tx=gettx()))
|
||||||
menu.addAction(action)
|
menu.addAction(action)
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import webbrowser
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from functools import partial, lru_cache, wraps
|
from functools import partial, lru_cache, wraps
|
||||||
from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, Union, List, Dict, Any,
|
from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, Union, List, Dict, Any,
|
||||||
Sequence, Iterable)
|
Sequence, Iterable, Tuple)
|
||||||
|
|
||||||
from PyQt5.QtGui import (QFont, QColor, QCursor, QPixmap, QStandardItem, QImage,
|
from PyQt5.QtGui import (QFont, QColor, QCursor, QPixmap, QStandardItem, QImage,
|
||||||
QPalette, QIcon, QFontMetrics, QShowEvent, QPainter, QHelpEvent)
|
QPalette, QIcon, QFontMetrics, QShowEvent, QPainter, QHelpEvent)
|
||||||
@@ -845,6 +845,14 @@ class MySortModel(QSortFilterProxyModel):
|
|||||||
return v1 < v2
|
return v1 < v2
|
||||||
|
|
||||||
|
|
||||||
|
def get_iconname_qrcode() -> str:
|
||||||
|
return "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png"
|
||||||
|
|
||||||
|
|
||||||
|
def get_iconname_camera() -> str:
|
||||||
|
return "camera_white.png" if ColorScheme.dark_scheme else "camera_dark.png"
|
||||||
|
|
||||||
|
|
||||||
class OverlayControlMixin:
|
class OverlayControlMixin:
|
||||||
STYLE_SHEET_COMMON = '''
|
STYLE_SHEET_COMMON = '''
|
||||||
QPushButton { border-width: 1px; padding: 0px; margin: 0px; }
|
QPushButton { border-width: 1px; padding: 0px; margin: 0px; }
|
||||||
@@ -916,13 +924,11 @@ class OverlayControlMixin:
|
|||||||
*,
|
*,
|
||||||
setText: Callable[[str], None] = None,
|
setText: Callable[[str], None] = None,
|
||||||
):
|
):
|
||||||
if setText is None:
|
input_paste_from_clipboard = partial(
|
||||||
setText = self.setText
|
self.input_paste_from_clipboard,
|
||||||
def on_paste():
|
setText=setText,
|
||||||
app = QApplication.instance()
|
)
|
||||||
setText(app.clipboard().text())
|
self.addButton("copy.png", input_paste_from_clipboard, _("Paste from clipboard"))
|
||||||
|
|
||||||
self.addButton("copy.png", on_paste, _("Paste from clipboard"))
|
|
||||||
|
|
||||||
def add_qr_show_button(self, *, config: 'SimpleConfig', title: Optional[str] = None):
|
def add_qr_show_button(self, *, config: 'SimpleConfig', title: Optional[str] = None):
|
||||||
if title is None:
|
if title is None:
|
||||||
@@ -943,12 +949,11 @@ class OverlayControlMixin:
|
|||||||
config=config,
|
config=config,
|
||||||
).exec_()
|
).exec_()
|
||||||
|
|
||||||
icon = "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png"
|
self.addButton(get_iconname_qrcode(), qr_show, _("Show as QR code"))
|
||||||
self.addButton(icon, qr_show, _("Show as QR code"))
|
|
||||||
# side-effect: we export this method:
|
# side-effect: we export this method:
|
||||||
self.on_qr_show_btn = qr_show
|
self.on_qr_show_btn = qr_show
|
||||||
|
|
||||||
def add_qr_input_button(
|
def add_qr_input_combined_button(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
config: 'SimpleConfig',
|
config: 'SimpleConfig',
|
||||||
@@ -956,58 +961,49 @@ class OverlayControlMixin:
|
|||||||
show_error: Callable[[str], None],
|
show_error: Callable[[str], None],
|
||||||
setText: Callable[[str], None] = None,
|
setText: Callable[[str], None] = None,
|
||||||
):
|
):
|
||||||
if setText is None:
|
input_qr_from_camera = partial(
|
||||||
setText = self.setText
|
self.input_qr_from_camera,
|
||||||
def qr_from_camera_input() -> None:
|
config=config,
|
||||||
def cb(success: bool, error: str, data):
|
allow_multi=allow_multi,
|
||||||
if not success:
|
show_error=show_error,
|
||||||
if error:
|
setText=setText,
|
||||||
show_error(error)
|
)
|
||||||
return
|
input_qr_from_screenshot = partial(
|
||||||
if not data:
|
self.input_qr_from_screenshot,
|
||||||
data = ''
|
allow_multi=allow_multi,
|
||||||
if allow_multi:
|
show_error=show_error,
|
||||||
new_text = self.text() + data + '\n'
|
setText=setText,
|
||||||
else:
|
)
|
||||||
new_text = data
|
self.add_menu_button(
|
||||||
setText(new_text)
|
icon=get_iconname_camera(),
|
||||||
|
tooltip=_("Read QR code"),
|
||||||
from .qrreader import scan_qrcode
|
options=[
|
||||||
scan_qrcode(parent=self, config=config, callback=cb)
|
(get_iconname_camera(), _("Read QR code from camera"), input_qr_from_camera),
|
||||||
|
("picture_in_picture.png", _("Read QR code from screen"), input_qr_from_screenshot),
|
||||||
def qr_from_screenshot_input() -> None:
|
],
|
||||||
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)
|
|
||||||
|
|
||||||
icon = "camera_white.png" if ColorScheme.dark_scheme else "camera_dark.png"
|
|
||||||
btn = self.addButton(icon, lambda: None, _("Read QR code"))
|
|
||||||
menu = QMenu()
|
|
||||||
menu.addAction(_("Read QR code from camera"), qr_from_camera_input)
|
|
||||||
menu.addAction(_("Read QR code from screen"), qr_from_screenshot_input)
|
|
||||||
btn.setMenu(menu)
|
|
||||||
# side-effect: we export these methods:
|
# side-effect: we export these methods:
|
||||||
self.on_qr_from_camera_input_btn = qr_from_camera_input
|
self.on_qr_from_camera_input_btn = input_qr_from_camera
|
||||||
self.on_qr_from_screenshot_input_btn = qr_from_screenshot_input
|
self.on_qr_from_screenshot_input_btn = input_qr_from_screenshot
|
||||||
|
|
||||||
|
def add_qr_input_from_camera_button(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
config: 'SimpleConfig',
|
||||||
|
allow_multi: bool = False,
|
||||||
|
show_error: Callable[[str], None],
|
||||||
|
setText: Callable[[str], None] = None,
|
||||||
|
):
|
||||||
|
input_qr_from_camera = partial(
|
||||||
|
self.input_qr_from_camera,
|
||||||
|
config=config,
|
||||||
|
allow_multi=allow_multi,
|
||||||
|
show_error=show_error,
|
||||||
|
setText=setText,
|
||||||
|
)
|
||||||
|
self.addButton(get_iconname_camera(), input_qr_from_camera, _("Read QR code from camera"))
|
||||||
|
# side-effect: we export these methods:
|
||||||
|
self.on_qr_from_camera_input_btn = input_qr_from_camera
|
||||||
|
|
||||||
def add_file_input_button(
|
def add_file_input_button(
|
||||||
self,
|
self,
|
||||||
@@ -1015,31 +1011,131 @@ class OverlayControlMixin:
|
|||||||
config: 'SimpleConfig',
|
config: 'SimpleConfig',
|
||||||
show_error: Callable[[str], None],
|
show_error: Callable[[str], None],
|
||||||
setText: Callable[[str], None] = None,
|
setText: Callable[[str], None] = None,
|
||||||
|
) -> None:
|
||||||
|
input_file = partial(
|
||||||
|
self.input_file,
|
||||||
|
config=config,
|
||||||
|
show_error=show_error,
|
||||||
|
setText=setText,
|
||||||
|
)
|
||||||
|
self.addButton("file.png", input_file, _("Read file"))
|
||||||
|
|
||||||
|
def add_menu_button(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
options: Sequence[Tuple[Optional[str], str, Callable[[], None]]], # list of (icon, text, cb)
|
||||||
|
icon: Optional[str] = None,
|
||||||
|
tooltip: Optional[str] = None,
|
||||||
|
):
|
||||||
|
if icon is None:
|
||||||
|
icon = "menu_vertical_white.png" if ColorScheme.dark_scheme else "menu_vertical.png"
|
||||||
|
if tooltip is None:
|
||||||
|
tooltip = _("Other options")
|
||||||
|
btn = self.addButton(icon, lambda: None, tooltip)
|
||||||
|
menu = QMenu()
|
||||||
|
for opt_icon, opt_text, opt_cb in options:
|
||||||
|
if opt_icon is None:
|
||||||
|
menu.addAction(opt_text, opt_cb)
|
||||||
|
else:
|
||||||
|
menu.addAction(read_QIcon(opt_icon), opt_text, opt_cb)
|
||||||
|
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:
|
) -> None:
|
||||||
if setText is None:
|
if setText is None:
|
||||||
setText = self.setText
|
setText = self.setText
|
||||||
def file_input():
|
def cb(success: bool, error: str, data):
|
||||||
fileName = getOpenFileName(
|
if not success:
|
||||||
parent=self,
|
if error:
|
||||||
title='select file',
|
show_error(error)
|
||||||
config=config,
|
|
||||||
)
|
|
||||||
if not fileName:
|
|
||||||
return
|
return
|
||||||
try:
|
if not data:
|
||||||
try:
|
data = ''
|
||||||
with open(fileName, "r") as f:
|
if allow_multi:
|
||||||
data = f.read()
|
new_text = self.text() + data + '\n'
|
||||||
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:
|
else:
|
||||||
setText(data)
|
new_text = data
|
||||||
|
setText(new_text)
|
||||||
|
|
||||||
self.addButton("file.png", file_input, _("Read file"))
|
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):
|
||||||
|
|||||||
Reference in New Issue
Block a user