qml: add initial qml.ElectrumGui class, Electrum QObject wrappers and an initial QObject for QR en/decoding
This commit is contained in:
100
electrum/gui/qml/__init__.py
Normal file
100
electrum/gui/qml/__init__.py
Normal file
@@ -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
Normal file
128
electrum/gui/qml/qenetwork.py
Normal file
@@ -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
Normal file
58
electrum/gui/qml/qeqr.py
Normal file
@@ -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
Normal file
43
electrum/gui/qml/qewallet.py
Normal file
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user