Merge pull request #9782 from f321x/fix_qr_input_from_file
qt: add qr reading from file to ScanQRTextEdit and SendTab
This commit is contained in:
BIN
electrum/gui/icons/qr_file.png
Normal file
BIN
electrum/gui/icons/qr_file.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -161,6 +161,13 @@ class PayToEdit(QWidget, Logger, GenericInputHandler):
|
|||||||
show_error=self.send_tab.show_error,
|
show_error=self.send_tab.show_error,
|
||||||
setText=self.try_payment_identifier,
|
setText=self.try_payment_identifier,
|
||||||
)
|
)
|
||||||
|
self.on_qr_from_file_input_btn = partial(
|
||||||
|
self.input_qr_from_file,
|
||||||
|
allow_multi=False,
|
||||||
|
config=self.config,
|
||||||
|
show_error=self.send_tab.show_error,
|
||||||
|
setText=self.try_payment_identifier,
|
||||||
|
)
|
||||||
self.on_input_file = partial(
|
self.on_input_file = partial(
|
||||||
self.input_file,
|
self.input_file,
|
||||||
config=self.config,
|
config=self.config,
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ import sys
|
|||||||
from typing import Callable, Optional, TYPE_CHECKING, Mapping, Sequence
|
from typing import Callable, Optional, TYPE_CHECKING, Mapping, Sequence
|
||||||
|
|
||||||
from PyQt6.QtWidgets import QMessageBox, QWidget
|
from PyQt6.QtWidgets import QMessageBox, QWidget
|
||||||
from PyQt6.QtGui import QImage
|
from PyQt6.QtGui import QImage, QPainter, QColor
|
||||||
|
from PyQt6.QtCore import QRect
|
||||||
|
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.util import UserFacingException
|
from electrum.util import UserFacingException
|
||||||
@@ -59,16 +60,34 @@ def scan_qrcode(
|
|||||||
def scan_qr_from_image(image: QImage) -> Sequence[QrCodeResult]:
|
def scan_qr_from_image(image: QImage) -> Sequence[QrCodeResult]:
|
||||||
"""Might raise exception: MissingQrDetectionLib."""
|
"""Might raise exception: MissingQrDetectionLib."""
|
||||||
qr_reader = get_qr_reader()
|
qr_reader = get_qr_reader()
|
||||||
image_y800 = image.convertToFormat(QImage.Format.Format_Grayscale8)
|
|
||||||
res = qr_reader.read_qr_code(
|
for attempt in range(4):
|
||||||
image_y800.constBits().__int__(),
|
image_y800 = image.convertToFormat(QImage.Format.Format_Grayscale8)
|
||||||
image_y800.sizeInBytes(),
|
res = qr_reader.read_qr_code(
|
||||||
image_y800.bytesPerLine(),
|
image_y800.constBits().__int__(),
|
||||||
image_y800.width(),
|
image_y800.sizeInBytes(),
|
||||||
image_y800.height(),
|
image_y800.bytesPerLine(),
|
||||||
)
|
image_y800.width(),
|
||||||
|
image_y800.height(),
|
||||||
|
)
|
||||||
|
if res:
|
||||||
|
break
|
||||||
|
# zbar doesn't like qr codes that are too large in relation to the whole image
|
||||||
|
image = _reduce_qr_code_density(image)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def _reduce_qr_code_density(image: QImage) -> QImage:
|
||||||
|
""" Reduces the size of the qr code relative to the whole image. """
|
||||||
|
new_image = QImage(image.width(), image.height(), QImage.Format.Format_RGB32)
|
||||||
|
new_image.fill(QColor(255, 255, 255)) # Fill white
|
||||||
|
|
||||||
|
painter = QPainter(new_image)
|
||||||
|
source_rect = QRect(0, 0, image.width(), image.height())
|
||||||
|
target_rect = QRect(0, 0, int(image.width() * 0.75), int(image.height() * 0.75))
|
||||||
|
painter.drawImage(target_rect, image, source_rect)
|
||||||
|
painter.end()
|
||||||
|
|
||||||
|
return new_image
|
||||||
|
|
||||||
def find_system_cameras() -> Mapping[str, str]:
|
def find_system_cameras() -> Mapping[str, str]:
|
||||||
"""Returns a camera_description -> camera_path map."""
|
"""Returns a camera_description -> camera_path map."""
|
||||||
|
|||||||
@@ -47,6 +47,13 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
|
|||||||
show_error=self.show_error,
|
show_error=self.show_error,
|
||||||
setText=setText,
|
setText=setText,
|
||||||
)
|
)
|
||||||
|
self.on_qr_from_file_input_btn = partial(
|
||||||
|
self.input_qr_from_file,
|
||||||
|
allow_multi=allow_multi,
|
||||||
|
config=config,
|
||||||
|
show_error=self.show_error,
|
||||||
|
setText=setText,
|
||||||
|
)
|
||||||
self.on_input_file = partial(
|
self.on_input_file = partial(
|
||||||
self.input_file,
|
self.input_file,
|
||||||
config=config,
|
config=config,
|
||||||
@@ -62,7 +69,8 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
|
|||||||
self.add_menu_button(
|
self.add_menu_button(
|
||||||
options=[
|
options=[
|
||||||
("picture_in_picture.png", _("Read QR code from screen"), self.on_qr_from_screenshot_input_btn),
|
("picture_in_picture.png", _("Read QR code from screen"), self.on_qr_from_screenshot_input_btn),
|
||||||
("file.png", _("Read file"), self.on_input_file),
|
("qr_file.png", _("Read QR code from file"), self.on_qr_from_file_input_btn),
|
||||||
|
("file.png", _("Read text from file"), self.on_input_file),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
self.add_qr_input_from_camera_button(config=config, show_error=self.show_error, allow_multi=allow_multi, setText=setText)
|
self.add_qr_input_from_camera_button(config=config, show_error=self.show_error, allow_multi=allow_multi, setText=setText)
|
||||||
@@ -72,7 +80,8 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
|
|||||||
m.addSeparator()
|
m.addSeparator()
|
||||||
m.addAction(get_icon_camera(), _("Read QR code with camera"), self.on_qr_from_camera_input_btn)
|
m.addAction(get_icon_camera(), _("Read QR code with camera"), self.on_qr_from_camera_input_btn)
|
||||||
m.addAction(read_QIcon("picture_in_picture.png"), _("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(read_QIcon("file.png"), _("Read file"), self.on_input_file)
|
m.addAction(read_QIcon("qr_file.png"), _("Read QR code from file"), self.on_qr_from_file_input_btn)
|
||||||
|
m.addAction(read_QIcon("file.png"), _("Read text from file"), self.on_input_file)
|
||||||
m.exec(e.globalPos())
|
m.exec(e.globalPos())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
|||||||
|
|
||||||
menu.addAction(get_icon_camera(), _("Read QR code with camera"), self.payto_e.on_qr_from_camera_input_btn)
|
menu.addAction(get_icon_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("qr_file.png"), _("Read QR code from file"), self.payto_e.on_qr_from_file_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)
|
||||||
self.paytomany_menu = menu.addToggle(_("&Pay to many"), self.toggle_paytomany)
|
self.paytomany_menu = menu.addToggle(_("&Pay to many"), self.toggle_paytomany)
|
||||||
menu.addSeparator()
|
menu.addSeparator()
|
||||||
|
|||||||
@@ -839,6 +839,8 @@ class GenericInputHandler:
|
|||||||
fileName = getOpenFileName(
|
fileName = getOpenFileName(
|
||||||
parent=None,
|
parent=None,
|
||||||
title='select file',
|
title='select file',
|
||||||
|
# trying to open non-text things like pdfs makes electrum freeze
|
||||||
|
filter="Text files (*.txt *.csv);;All files (*)",
|
||||||
config=config,
|
config=config,
|
||||||
)
|
)
|
||||||
if not fileName:
|
if not fileName:
|
||||||
@@ -859,6 +861,52 @@ class GenericInputHandler:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
show_error(_('Invalid payment identifier in file') + ':\n' + repr(e))
|
show_error(_('Invalid payment identifier in file') + ':\n' + repr(e))
|
||||||
|
|
||||||
|
def input_qr_from_file(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
allow_multi: bool = False,
|
||||||
|
config: 'SimpleConfig',
|
||||||
|
show_error: Callable[[str], None],
|
||||||
|
setText: Callable[[str], None] = None,
|
||||||
|
):
|
||||||
|
from .qrreader import scan_qr_from_image
|
||||||
|
if setText is None:
|
||||||
|
setText = self.setText
|
||||||
|
|
||||||
|
file_name = getOpenFileName(
|
||||||
|
parent=None,
|
||||||
|
title=_("Select image file"),
|
||||||
|
config=config,
|
||||||
|
filter="Image files (*.png *.jpg *.jpeg *.bmp);;",
|
||||||
|
)
|
||||||
|
if not file_name:
|
||||||
|
return
|
||||||
|
image = QImage(file_name)
|
||||||
|
if image.isNull():
|
||||||
|
show_error(_("Failed to open image file."))
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
scan_result: Sequence[QrCodeResult] = scan_qr_from_image(image)
|
||||||
|
except MissingQrDetectionLib as e:
|
||||||
|
show_error(_("Unable to scan image.") + "\n" + repr(e))
|
||||||
|
return
|
||||||
|
if len(scan_result) < 1:
|
||||||
|
show_error(_("No QR code was found in the image."))
|
||||||
|
return
|
||||||
|
if len(scan_result) > 1 and not allow_multi:
|
||||||
|
show_error(_("More than one QR code was found in the image."))
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(scan_result) > 1:
|
||||||
|
result_text = "\n".join([r.data for r in scan_result])
|
||||||
|
else:
|
||||||
|
result_text = scan_result[0].data
|
||||||
|
|
||||||
|
try:
|
||||||
|
setText(result_text)
|
||||||
|
except Exception as e:
|
||||||
|
show_error(_("Couldn't set result") + ':\n' + repr(e))
|
||||||
|
|
||||||
def input_paste_from_clipboard(
|
def input_paste_from_clipboard(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
|
|||||||
Reference in New Issue
Block a user