Browse Source

qml: add initial qml.ElectrumGui class, Electrum QObject wrappers and an initial QObject for QR en/decoding

patch-4
Sander van Grieken 4 years ago
parent
commit
7eb733757a
  1. 100
      electrum/gui/qml/__init__.py
  2. 128
      electrum/gui/qml/qenetwork.py
  3. 58
      electrum/gui/qml/qeqr.py
  4. 43
      electrum/gui/qml/qewallet.py

100
electrum/gui/qml/__init__.py

@ -0,0 +1,100 @@
import os
import signal
import sys
import traceback
import threading
from typing import Optional, TYPE_CHECKING, List
try:
import PyQt5
except Exception:
sys.exit("Error: Could not import PyQt5 on Linux systems, you may try 'sudo apt-get install python3-pyqt5'")
try:
import PyQt5.QtQml
except Exception:
sys.exit("Error: Could not import PyQt5.QtQml on Linux systems, you may try 'sudo apt-get install python3-pyqt5.qtquick'")
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QUrl
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import qmlRegisterType, QQmlComponent, QQmlApplicationEngine
from PyQt5.QtQuick import QQuickView
import PyQt5.QtCore as QtCore
import PyQt5.QtQml as QtQml
from electrum.i18n import _, set_language
from electrum.plugin import run_hook
from electrum.base_wizard import GoBack
from electrum.util import (UserCancelled, profiler,
WalletFileException, BitcoinException, get_new_wallet_name)
from electrum.wallet import Wallet, Abstract_Wallet
from electrum.wallet_db import WalletDB
from electrum.logging import Logger
if TYPE_CHECKING:
from electrum.daemon import Daemon
from electrum.simple_config import SimpleConfig
from electrum.plugin import Plugins
from .qenetwork import QENetwork, QEDaemon, QEWalletListModel
from .qewallet import *
from .qeqr import QEQR
class ElectrumQmlApplication(QGuiApplication):
def __init__(self, args, daemon):
super().__init__(args)
qmlRegisterType(QEWalletListModel, 'QElectrum', 1, 0, 'QEWalletListModel')
qmlRegisterType(QEWallet, 'QElectrum', 1, 0, 'QEWallet')
self.engine = QQmlApplicationEngine(parent=self)
self.context = self.engine.rootContext()
self.qen = QENetwork(daemon.network)
self.context.setContextProperty('Network', self.qen)
self.qed = QEDaemon(daemon)
self.context.setContextProperty('Daemon', self.qed)
self.qeqr = QEQR()
self.context.setContextProperty('QR', self.qeqr)
self.engine.load(QUrl('electrum/gui/qml/components/main.qml'))
class ElectrumGui(Logger):
@profiler
def __init__(self, config: 'SimpleConfig', daemon: 'Daemon', plugins: 'Plugins'):
# TODO set_language(config.get('language', get_default_language()))
Logger.__init__(self)
self.logger.info(f"Qml GUI starting up... Qt={QtCore.QT_VERSION_STR}, PyQt={QtCore.PYQT_VERSION_STR}")
# Uncomment this call to verify objects are being properly
# GC-ed when windows are closed
#network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
# ElectrumWindow], interval=5)])
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"):
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
# if hasattr(QGuiApplication, 'setDesktopFileName'):
# QGuiApplication.setDesktopFileName('electrum.desktop')
self.gui_thread = threading.current_thread()
self.config = config
self.daemon = daemon
self.plugins = plugins
self.app = ElectrumQmlApplication(sys.argv, self.daemon)
# TODO when plugin support. run_hook('init_qml', self)
def close(self):
# for window in self.windows:
# window.close()
# if self.network_dialog:
# self.network_dialog.close()
# if self.lightning_dialog:
# self.lightning_dialog.close()
# if self.watchtower_dialog:
# self.watchtower_dialog.close()
self.app.quit()
def main(self):
self.app.exec_()
def stop(self):
self.logger.info('closing GUI')
self.app.quit()

128
electrum/gui/qml/qenetwork.py

@ -0,0 +1,128 @@
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex
from electrum.util import register_callback
from electrum.logging import get_logger
from electrum.wallet import Wallet, Abstract_Wallet
from .qewallet import QEWallet
class QENetwork(QObject):
def __init__(self, network, parent=None):
super().__init__(parent)
self.network = network
register_callback(self.on_network_updated, ['network_updated'])
register_callback(self.on_blockchain_updated, ['blockchain_updated'])
register_callback(self.on_default_server_changed, ['default_server_changed'])
register_callback(self.on_proxy_set, ['proxy_set'])
register_callback(self.on_status, ['status'])
_logger = get_logger(__name__)
network_updated = pyqtSignal()
blockchain_updated = pyqtSignal()
default_server_changed = pyqtSignal()
proxy_set = pyqtSignal()
status_updated = pyqtSignal()
_num_updates = 0
_server = ""
_height = 0
_status = ""
def on_network_updated(self, event, *args):
self._num_updates = self._num_updates + 1
self.network_updated.emit()
def on_blockchain_updated(self, event, *args):
self._logger.info('chainupdate: ' + str(event) + str(args))
self._height = self.network.get_local_height()
self.blockchain_updated.emit()
def on_default_server_changed(self, event, *args):
netparams = self.network.get_parameters()
self._server = str(netparams.server)
self.default_server_changed.emit()
def on_proxy_set(self, event, *args):
self._logger.info('proxy set')
self.proxy_set.emit()
def on_status(self, event, *args):
self._logger.info('status updated')
self._status = self.network.connection_status
self.status_updated.emit()
@pyqtProperty(int,notify=network_updated)
def updates(self):
return self._num_updates
@pyqtProperty(int,notify=blockchain_updated)
def height(self):
return self._height
@pyqtProperty('QString',notify=default_server_changed)
def server(self):
return self._server
@pyqtProperty('QString',notify=status_updated)
def status(self):
return self._status
class QEWalletListModel(QAbstractListModel):
def __init__(self, parent=None):
QAbstractListModel.__init__(self, parent)
self.wallets = []
def rowCount(self, index):
return len(self.wallets)
def data(self, index, role):
if role == Qt.DisplayRole:
return self.wallets[index.row()].basename()
def add_wallet(self, wallet: Abstract_Wallet = None):
if wallet == None:
return
self.beginInsertRows(QModelIndex(), len(self.wallets), len(self.wallets));
self.wallets.append(wallet);
self.endInsertRows();
class QEDaemon(QObject):
def __init__(self, daemon, parent=None):
super().__init__(parent)
self.daemon = daemon
_logger = get_logger(__name__)
_wallet = ''
_loaded_wallets = QEWalletListModel()
wallet_loaded = pyqtSignal()
@pyqtSlot()
def load_wallet(self, path=None, password=None):
self._logger.info(str(self.daemon.get_wallets()))
if path == None:
path = self.daemon.config.get('recently_open')[0]
wallet = self.daemon.load_wallet(path, password)
if wallet != None:
self._loaded_wallets.add_wallet(wallet)
self._wallet = wallet.basename()
self._current_wallet = QEWallet(wallet)
self.wallet_loaded.emit()
self._logger.info(str(self.daemon.get_wallets()))
else:
self._logger.info('fail open wallet')
@pyqtProperty('QString',notify=wallet_loaded)
def walletName(self):
return self._wallet
@pyqtProperty(QEWalletListModel)
def activeWallets(self):
return self._loaded_wallets
@pyqtProperty(QEWallet,notify=wallet_loaded)
def currentWallet(self):
return self._current_wallet

58
electrum/gui/qml/qeqr.py

@ -0,0 +1,58 @@
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl
from electrum.logging import get_logger
from PIL import Image
from ctypes import *
class QEQR(QObject):
def __init__(self, text=None, parent=None):
super().__init__(parent)
self._text = text
_logger = get_logger(__name__)
scan_ready_changed = pyqtSignal()
_ready = True
@pyqtSlot('QImage')
def scanImage(self, image=None):
if not self._ready:
self._logger.warning("Already processing an image. Check 'ready' property before calling scanImage")
return
self._ready = False
self.scan_ready_changed.emit()
pilimage = self.convertToPILImage(image)
parseQR(pilimage)
self._ready = True
def logImageStats(self, image):
self._logger.info('width: ' + str(image.width()))
self._logger.info('height: ' + str(image.height()))
self._logger.info('depth: ' + str(image.depth()))
self._logger.info('format: ' + str(image.format()))
def convertToPILImage(self, image) -> Image:
self.logImageStats(image)
rawimage = image.constBits()
# assumption: pixels are 32 bits ARGB
numbytes = image.width() * image.height() * 4
self._logger.info(type(rawimage))
buf = bytearray(numbytes)
c_buf = (c_byte * numbytes).from_buffer(buf)
memmove(c_buf, c_void_p(rawimage.__int__()), numbytes)
buf2 = bytes(buf)
return Image.frombytes('RGBA', (image.width(), image.height()), buf2, 'raw')
def parseQR(self, image):
pass
@pyqtProperty(bool, notify=scan_ready_changed)
def ready(self):
return self._ready

43
electrum/gui/qml/qewallet.py

@ -0,0 +1,43 @@
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex
from electrum.util import register_callback
from electrum.logging import get_logger
from electrum.wallet import Wallet, Abstract_Wallet
class QETransactionsListModel(QAbstractListModel):
def __init__(self, parent=None):
super().__init__(parent)
self.tx_history = []
def rowCount(self, index):
return len(self.tx_history)
def data(self, index, role):
if role == Qt.DisplayRole:
return str(self.tx_history[index.row()]['bc_value'])
def set_history(self, history):
self.beginInsertRows(QModelIndex(), 0, len(history) - 1)
self.tx_history = history
self.endInsertRows()
class QEWallet(QObject):
def __init__(self, wallet, parent=None):
super().__init__(parent)
self.wallet = wallet
self.get_history()
_logger = get_logger(__name__)
_historyModel = QETransactionsListModel()
@pyqtProperty(QETransactionsListModel)
def historyModel(self):
return self._historyModel
def get_history(self):
history = self.wallet.get_detailed_history(show_addresses = True)
txs = history['transactions']
self._historyModel.set_history(txs)
Loading…
Cancel
Save