From 492f246b9a9939920752c8979a91196412a674bd Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Fri, 18 Mar 2022 15:00:29 +0100 Subject: [PATCH] qml: add QR code imageprovider using qrcode/PIL adds buildozer 'pillow' recipe to requirements add initial PoC on qml receive tab --- contrib/android/buildozer_qml.spec | 3 +- electrum/gui/qml/components/Receive.qml | 32 ++++++++++++ .../gui/qml/components/WalletMainView.qml | 8 ++- electrum/gui/qml/qeapp.py | 7 ++- electrum/gui/qml/qeqr.py | 51 +++++++++++++++---- 5 files changed, 82 insertions(+), 19 deletions(-) create mode 100644 electrum/gui/qml/components/Receive.qml diff --git a/contrib/android/buildozer_qml.spec b/contrib/android/buildozer_qml.spec index e83fd52c8..6eb434298 100644 --- a/contrib/android/buildozer_qml.spec +++ b/contrib/android/buildozer_qml.spec @@ -51,7 +51,8 @@ requirements = libsecp256k1, cryptography, pyqt5sip, - pyqt5 + pyqt5, + pillow # (str) Presplash of the application #presplash.filename = %(source.dir)s/gui/kivy/theming/splash.png diff --git a/electrum/gui/qml/components/Receive.qml b/electrum/gui/qml/components/Receive.qml new file mode 100644 index 000000000..174428e58 --- /dev/null +++ b/electrum/gui/qml/components/Receive.qml @@ -0,0 +1,32 @@ +import QtQuick 2.6 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 2.0 +import QtQuick.Controls.Material 2.0 + +import org.electrum 1.0 + +Pane { + id: rootItem + visible: Daemon.currentWallet !== undefined + + ColumnLayout { + width: parent.width + spacing: 20 + + Image { + id: img + } + + TextField { + id: text + } + + Button { + text: 'generate' + onClicked: { + img.source = 'image://qrgen/' + text.text + } + } + } + +} diff --git a/electrum/gui/qml/components/WalletMainView.qml b/electrum/gui/qml/components/WalletMainView.qml index 06291f438..a096c1649 100644 --- a/electrum/gui/qml/components/WalletMainView.qml +++ b/electrum/gui/qml/components/WalletMainView.qml @@ -63,11 +63,9 @@ Item { currentIndex: tabbar.currentIndex Item { - - ColumnLayout { - width: parent.width - y: 20 - spacing: 20 + Receive { + id: receive + anchors.fill: parent } } diff --git a/electrum/gui/qml/qeapp.py b/electrum/gui/qml/qeapp.py index 6f9525345..0298edbcd 100644 --- a/electrum/gui/qml/qeapp.py +++ b/electrum/gui/qml/qeapp.py @@ -10,7 +10,7 @@ from .qeconfig import QEConfig from .qedaemon import QEDaemon, QEWalletListModel from .qenetwork import QENetwork from .qewallet import QEWallet -from .qeqr import QEQR +from .qeqr import QEQR, QEQRImageProvider from .qewalletdb import QEWalletDB from .qebitcoin import QEBitcoin @@ -36,6 +36,9 @@ class ElectrumQmlApplication(QGuiApplication): self.engine = QQmlApplicationEngine(parent=self) self.engine.addImportPath('./qml') + self.qr_ip = QEQRImageProvider() + self.engine.addImageProvider('qrgen', self.qr_ip) + self.context = self.engine.rootContext() self._singletons['config'] = QEConfig(config) self._singletons['network'] = QENetwork(daemon.network) @@ -63,7 +66,7 @@ class ElectrumQmlApplication(QGuiApplication): def message_handler(self, line, funct, file): # filter out common harmless messages - if re.search('file:///.*TypeError:\ Cannot\ read\ property.*null$', file): + if re.search('file:///.*TypeError: Cannot read property.*null$', file): return self.logger.warning(file) diff --git a/electrum/gui/qml/qeqr.py b/electrum/gui/qml/qeqr.py index cdbe914c4..d93323733 100644 --- a/electrum/gui/qml/qeqr.py +++ b/electrum/gui/qml/qeqr.py @@ -1,8 +1,15 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl +from PyQt5.QtGui import QImage +from PyQt5.QtQuick import QQuickImageProvider from electrum.logging import get_logger -#from PIL import Image +import qrcode +#from qrcode.image.styledpil import StyledPilImage +#from qrcode.image.styles.moduledrawers import * + +from PIL import Image, ImageQt + from ctypes import * class QEQR(QObject): @@ -11,22 +18,25 @@ class QEQR(QObject): self._text = text _logger = get_logger(__name__) - scan_ready_changed = pyqtSignal() - _ready = True + scanReadyChanged = pyqtSignal() + imageChanged = pyqtSignal() + + _scanReady = True + _image = None @pyqtSlot('QImage') def scanImage(self, image=None): - if not self._ready: + if not self._scanReady: self._logger.warning("Already processing an image. Check 'ready' property before calling scanImage") return - self._ready = False - self.scan_ready_changed.emit() + self._scanReady = False + self.scanReadyChanged.emit() pilimage = self.convertToPILImage(image) self.parseQR(pilimage) - self._ready = True + self._scanReady = True def logImageStats(self, image): self._logger.info('width: ' + str(image.width())) @@ -47,13 +57,32 @@ class QEQR(QObject): memmove(c_buf, c_void_p(rawimage.__int__()), numbytes) buf2 = bytes(buf) - return None #Image.frombytes('RGBA', (image.width(), image.height()), buf2, 'raw') + return Image.frombytes('RGBA', (image.width(), image.height()), buf2, 'raw') def parseQR(self, image): # TODO pass - @pyqtProperty(bool, notify=scan_ready_changed) - def ready(self): - return self._ready + @pyqtProperty(bool, notify=scanReadyChanged) + def scanReady(self): + return self._scanReady + + @pyqtProperty('QImage', notify=imageChanged) + def image(self): + return self._image + +class QEQRImageProvider(QQuickImageProvider): + def __init__(self, parent=None): + super().__init__(QQuickImageProvider.Image) + + _logger = get_logger(__name__) + + def requestImage(self, qstr, size): + self._logger.debug('QR requested for %s' % qstr) + qr = qrcode.QRCode(version=1, box_size=8, border=2) + qr.add_data(qstr) + qr.make(fit=True) + pimg = qr.make_image(fill_color='black', back_color='white') #image_factory=StyledPilImage, module_drawer=CircleModuleDrawer()) + qimg = ImageQt.ImageQt(pimg) + return qimg, qimg.size()