Browse Source

add QEAddressListModel and initial Addresses.qml page.

show sane main view when no wallet loaded.
show error dialog when wallet could not be loaded.
show wallet up_to_date indicator in title bar.
refactor QETransactionListModel to be more self-contained.
master
Sander van Grieken 4 years ago
parent
commit
17820b9346
  1. 88
      electrum/gui/qml/components/Addresses.qml
  2. 27
      electrum/gui/qml/components/WalletMainView.qml
  3. 16
      electrum/gui/qml/components/main.qml
  4. 30
      electrum/gui/qml/qedaemon.py
  5. 113
      electrum/gui/qml/qewallet.py

88
electrum/gui/qml/components/Addresses.qml

@ -0,0 +1,88 @@
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
anchors.fill: parent
property string title: Daemon.walletName + ' - ' + qsTr('Addresses')
ColumnLayout {
id: layout
width: parent.width
height: parent.height
Item {
width: parent.width
Layout.fillHeight: true
ListView {
id: listview
width: parent.width
height: parent.height
clip: true
model: Daemon.currentWallet.addressModel
delegate: AbstractButton {
id: delegate
width: ListView.view.width
height: 30
background: Rectangle {
color: model.held ? Qt.rgba(1,0,0,0.5) :
model.numtx > 0 && model.balance == 0 ? Qt.rgba(1,1,1,0.25) :
model.type == 'receive' ? Qt.rgba(0,1,0,0.25) :
Qt.rgba(1,0.93,0,0.25)
Rectangle {
height: 1
width: parent.width
anchors.top: parent.top
border.color: Material.accentColor
visible: model.index > 0
}
}
RowLayout {
x: 10
spacing: 5
width: parent.width - 20
anchors.verticalCenter: parent.verticalCenter
Label {
font.pixelSize: 12
text: model.type
}
Label {
font.pixelSize: 12
font.family: "Courier" // TODO: use system monospace font
text: model.address
elide: Text.ElideMiddle
Layout.maximumWidth: delegate.width / 4
}
Label {
font.pixelSize: 12
text: model.label
elide: Text.ElideRight
Layout.minimumWidth: delegate.width / 4
Layout.fillWidth: true
}
Label {
font.pixelSize: 12
text: model.balance
}
Label {
font.pixelSize: 12
text: model.numtx
}
}
}
}
}
}
Component.onCompleted: Daemon.currentWallet.addressModel.init_model()
}

27
electrum/gui/qml/components/WalletMainView.qml

@ -9,12 +9,35 @@ Item {
property string title: Daemon.walletName property string title: Daemon.walletName
property QtObject menu: Menu { property QtObject menu: Menu {
MenuItem { text: 'Wallets'; onTriggered: stack.push(Qt.resolvedUrl('Wallets.qml')) } MenuItem { text: qsTr('Addresses'); onTriggered: stack.push(Qt.resolvedUrl('Addresses.qml')); visible: Daemon.currentWallet != null }
MenuItem { text: 'Network'; onTriggered: stack.push(Qt.resolvedUrl('NetworkStats.qml')) } MenuItem { text: qsTr('Wallets'); onTriggered: stack.push(Qt.resolvedUrl('Wallets.qml')) }
MenuItem { text: qsTr('Network'); onTriggered: stack.push(Qt.resolvedUrl('NetworkStats.qml')) }
}
ColumnLayout {
anchors.centerIn: parent
width: parent.width
spacing: 40
visible: Daemon.currentWallet == null
Label {
text: qsTr('No wallet loaded')
font.pixelSize: 24
Layout.alignment: Qt.AlignHCenter
}
Button {
text: qsTr('Open/Create Wallet')
Layout.alignment: Qt.AlignHCenter
onClicked: {
stack.push(Qt.resolvedUrl('Wallets.qml'))
}
}
} }
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
visible: Daemon.currentWallet != null
TabBar { TabBar {
id: tabbar id: tabbar

16
electrum/gui/qml/components/main.qml

@ -31,6 +31,7 @@ ApplicationWindow
onClicked: stack.pop() onClicked: stack.pop()
} }
Item { Item {
visible: Network.isTestNet
width: column.width width: column.width
height: column.height height: column.height
MouseArea { MouseArea {
@ -46,7 +47,6 @@ ApplicationWindow
Column { Column {
id: column id: column
visible: Network.isTestNet
Image { Image {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
width: 16 width: 16
@ -63,15 +63,22 @@ ApplicationWindow
} }
} }
Image {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
source: Daemon.currentWallet.isUptodate ? "../../icons/status_connected.png" : "../../icons/status_lagging.png"
}
Label { Label {
text: stack.currentItem.title text: stack.currentItem.title
elide: Label.ElideRight elide: Label.ElideRight
horizontalAlignment: Qt.AlignHCenter horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter verticalAlignment: Qt.AlignVCenter
Layout.fillWidth: true Layout.fillWidth: true
font.pointSize: 10 font.pixelSize: 14
font.bold: true font.bold: true
} }
ToolButton { ToolButton {
text: qsTr("⋮") text: qsTr("⋮")
onClicked: { onClicked: {
@ -203,5 +210,10 @@ ApplicationWindow
// var dialog = _openWallet.createObject(app) // var dialog = _openWallet.createObject(app)
//dialog.open() //dialog.open()
} }
function onWalletOpenError(error) {
console.log('wallet open error')
var dialog = app.messageDialog.createObject(app, {'text': error})
dialog.open()
}
} }
} }

30
electrum/gui/qml/qedaemon.py

@ -3,7 +3,7 @@ import os
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex
from electrum.util import register_callback, get_new_wallet_name from electrum.util import register_callback, get_new_wallet_name, WalletFileException
from electrum.logging import get_logger from electrum.logging import get_logger
from electrum.wallet import Wallet, Abstract_Wallet from electrum.wallet import Wallet, Abstract_Wallet
from electrum.storage import WalletStorage, StorageReadWriteError from electrum.storage import WalletStorage, StorageReadWriteError
@ -90,7 +90,7 @@ class QEDaemon(QObject):
walletRequiresPassword = pyqtSignal() walletRequiresPassword = pyqtSignal()
activeWalletsChanged = pyqtSignal() activeWalletsChanged = pyqtSignal()
availableWalletsChanged = pyqtSignal() availableWalletsChanged = pyqtSignal()
couldNotOpenFile = pyqtSignal() walletOpenError = pyqtSignal([str], arguments=["error"])
@pyqtSlot() @pyqtSlot()
@pyqtSlot(str) @pyqtSlot(str)
@ -107,21 +107,25 @@ class QEDaemon(QObject):
try: try:
storage = WalletStorage(self._path) storage = WalletStorage(self._path)
if not storage.file_exists(): if not storage.file_exists():
self.couldNotOpenFile.emit() self.walletOpenError.emit(qsTr('File not found'))
return return
except StorageReadWriteError as e: except StorageReadWriteError as e:
self.couldNotOpenFile.emit() self.walletOpenError.emit('Storage read/write error')
return return
wallet = self.daemon.load_wallet(self._path, password) try:
if wallet != None: wallet = self.daemon.load_wallet(self._path, password)
self._loaded_wallets.add_wallet(wallet=wallet) if wallet != None:
self._current_wallet = QEWallet(wallet) self._loaded_wallets.add_wallet(wallet=wallet)
self.walletLoaded.emit() self._current_wallet = QEWallet(wallet)
self.daemon.config.save_last_wallet(wallet) self.walletLoaded.emit()
else: self.daemon.config.save_last_wallet(wallet)
self._logger.info('password required but unset or incorrect') else:
self.walletRequiresPassword.emit() self._logger.info('password required but unset or incorrect')
self.walletRequiresPassword.emit()
except WalletFileException as e:
self._logger.error(str(e))
self.walletOpenError.emit(str(e))
@pyqtProperty('QString') @pyqtProperty('QString')
def path(self): def path(self):

113
electrum/gui/qml/qewallet.py

@ -8,9 +8,10 @@ from electrum import bitcoin
from electrum.transaction import Transaction, tx_from_any, PartialTransaction, PartialTxOutput from electrum.transaction import Transaction, tx_from_any, PartialTransaction, PartialTxOutput
from electrum.invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_UNCONFIRMED, PR_TYPE_ONCHAIN, PR_TYPE_LN from electrum.invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_UNCONFIRMED, PR_TYPE_ONCHAIN, PR_TYPE_LN
class QETransactionsListModel(QAbstractListModel): class QETransactionListModel(QAbstractListModel):
def __init__(self, parent=None): def __init__(self, wallet, parent=None):
super().__init__(parent) super().__init__(parent)
self.wallet = wallet
self.tx_history = [] self.tx_history = []
_logger = get_logger(__name__) _logger = get_logger(__name__)
@ -43,20 +44,95 @@ class QETransactionsListModel(QAbstractListModel):
self.endResetModel() self.endResetModel()
# initial model data # initial model data
def set_history(self, history): def init_model(self):
history = self.wallet.get_detailed_history(show_addresses = True)
txs = history['transactions']
# use primitives
for tx in txs:
for output in tx['outputs']:
output['value'] = output['value'].value
self.clear() self.clear()
self.beginInsertRows(QModelIndex(), 0, len(history) - 1) self.beginInsertRows(QModelIndex(), 0, len(txs) - 1)
self.tx_history = history self.tx_history = txs
self.tx_history.reverse() self.tx_history.reverse()
self.endInsertRows() self.endInsertRows()
class QEAddressListModel(QAbstractListModel):
def __init__(self, wallet, parent=None):
super().__init__(parent)
self.wallet = wallet
self.receive_addresses = []
self.change_addresses = []
_logger = get_logger(__name__)
# define listmodel rolemap
_ROLE_NAMES=('type','address','label','balance','numtx', 'held')
_ROLE_KEYS = range(Qt.UserRole + 1, Qt.UserRole + 1 + len(_ROLE_NAMES))
_ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
def rowCount(self, index):
return len(self.receive_addresses) + len(self.change_addresses)
def roleNames(self):
return self._ROLE_MAP
def data(self, index, role):
if index.row() > len(self.receive_addresses) - 1:
address = self.change_addresses[index.row() - len(self.receive_addresses)]
else:
address = self.receive_addresses[index.row()]
role_index = role - (Qt.UserRole + 1)
value = address[self._ROLE_NAMES[role_index]]
if isinstance(value, bool) or isinstance(value, list) or isinstance(value, int) or value is None:
return value
if isinstance(value, Satoshis):
return value.value
return str(value)
def clear(self):
self.beginResetModel()
self.receive_addresses = []
self.change_addresses = []
self.endResetModel()
# initial model data
@pyqtSlot()
def init_model(self):
r_addresses = self.wallet.get_receiving_addresses()
c_addresses = self.wallet.get_change_addresses()
n_addresses = len(r_addresses) + len(c_addresses)
def insert_row(atype, alist, address):
item = {}
item['type'] = atype
item['address'] = address
item['numtx'] = self.wallet.get_address_history_len(address)
item['label'] = self.wallet.get_label(address)
c, u, x = self.wallet.get_addr_balance(address)
item['balance'] = c + u + x
item['held'] = self.wallet.is_frozen_address(address)
alist.append(item)
self.clear()
self.beginInsertRows(QModelIndex(), 0, n_addresses - 1)
for address in r_addresses:
insert_row('receive', self.receive_addresses, address)
for address in c_addresses:
insert_row('change', self.change_addresses, address)
self.endInsertRows()
class QEWallet(QObject): class QEWallet(QObject):
def __init__(self, wallet, parent=None): def __init__(self, wallet, parent=None):
super().__init__(parent) super().__init__(parent)
self.wallet = wallet self.wallet = wallet
self._historyModel = QETransactionsListModel() self._historyModel = QETransactionListModel(wallet)
self.get_history() self._addressModel = QEAddressListModel(wallet)
self._historyModel.init_model()
register_callback(self.on_request_status, ['request_status']) register_callback(self.on_request_status, ['request_status'])
register_callback(self.on_status, ['status'])
_logger = get_logger(__name__) _logger = get_logger(__name__)
@ -65,15 +141,18 @@ class QEWallet(QObject):
requestStatus = pyqtSignal() requestStatus = pyqtSignal()
def on_request_status(self, event, *args): def on_request_status(self, event, *args):
self._logger.debug(str(event)) self._logger.debug(str(event))
# (wallet, addr, status) = args
# self._historyModel.add_tx()
self.requestStatus.emit() self.requestStatus.emit()
historyModelChanged = pyqtSignal() historyModelChanged = pyqtSignal()
@pyqtProperty(QETransactionsListModel, notify=historyModelChanged) @pyqtProperty(QETransactionListModel, notify=historyModelChanged)
def historyModel(self): def historyModel(self):
return self._historyModel return self._historyModel
addressModelChanged = pyqtSignal()
@pyqtProperty(QEAddressListModel, notify=addressModelChanged)
def addressModel(self):
return self._addressModel
@pyqtProperty('QString', notify=dataChanged) @pyqtProperty('QString', notify=dataChanged)
def txinType(self): def txinType(self):
return self.wallet.get_txin_type(self.wallet.dummy_address()) return self.wallet.get_txin_type(self.wallet.dummy_address())
@ -115,22 +194,16 @@ class QEWallet(QObject):
return c+x return c+x
def on_status(self, status):
self._logger.info('wallet: status update: ' + str(status))
self.isUptodateChanged.emit()
# lightning feature? # lightning feature?
isUptodateChanged = pyqtSignal() isUptodateChanged = pyqtSignal()
@pyqtProperty(bool, notify=isUptodateChanged) @pyqtProperty(bool, notify=isUptodateChanged)
def isUptodate(self): def isUptodate(self):
return self.wallet.is_up_to_date() return self.wallet.is_up_to_date()
def get_history(self):
history = self.wallet.get_detailed_history(show_addresses = True)
txs = history['transactions']
# use primitives
for tx in txs:
for output in tx['outputs']:
output['value'] = output['value'].value
self._historyModel.set_history(txs)
self.historyModelChanged.emit()
@pyqtSlot('QString', int, int, bool) @pyqtSlot('QString', int, int, bool)
def send_onchain(self, address, amount, fee=None, rbf=False): def send_onchain(self, address, amount, fee=None, rbf=False):
self._logger.info('send_onchain: ' + address + ' ' + str(amount)) self._logger.info('send_onchain: ' + address + ' ' + str(amount))

Loading…
Cancel
Save