diff --git a/electrum/gui/qml/components/Channels.qml b/electrum/gui/qml/components/Channels.qml index ada4c4693..5462edc7f 100644 --- a/electrum/gui/qml/components/Channels.qml +++ b/electrum/gui/qml/components/Channels.qml @@ -22,7 +22,8 @@ Pane { Label { Layout.columnSpan: 2 - text: '' + text: qsTr('You have %1 open channels').arg(listview.count) + color: Material.accentColor } Label { @@ -30,8 +31,20 @@ Pane { color: Material.accentColor } - Label { - text: '' + RowLayout { + Layout.fillWidth: true + Label { + text: Config.formatSats(Daemon.currentWallet.lightningCanSend) + } + Label { + text: Config.baseUnit + color: Material.accentColor + } + Label { + text: Daemon.fx.enabled + ? '(' + Daemon.fx.fiatValue(Daemon.currentWallet.lightningCanSend) + ' ' + Daemon.fx.fiatCurrency + ')' + : '' + } } Label { @@ -39,20 +52,23 @@ Pane { color: Material.accentColor } - Label { - text: '' - } - RowLayout { - Layout.columnSpan: 2 - - Button { - text: qsTr('Open Channel') - onClicked: app.stack.push(Qt.resolvedUrl('OpenChannel.qml')) + Layout.fillWidth: true + Label { + text: Config.formatSats(Daemon.currentWallet.lightningCanReceive) + } + Label { + text: Config.baseUnit + color: Material.accentColor + } + Label { + text: Daemon.fx.enabled + ? '(' + Daemon.fx.fiatValue(Daemon.currentWallet.lightningCanReceive) + ' ' + Daemon.fx.fiatCurrency + ')' + : '' } } - } + } Frame { id: channelsFrame @@ -92,7 +108,7 @@ Pane { model: Daemon.currentWallet.channelModel delegate: ChannelDelegate { - highlighted: ListView.isCurrentItem + //highlighted: ListView.isCurrentItem } ScrollIndicator.vertical: ScrollIndicator { } @@ -100,6 +116,15 @@ Pane { } } + RowLayout { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Button { + text: qsTr('Open Channel') + onClicked: app.stack.push(Qt.resolvedUrl('OpenChannel.qml')) + } + } + } } diff --git a/electrum/gui/qml/components/Constants.qml b/electrum/gui/qml/components/Constants.qml index bd4d9e0ea..da633b95b 100644 --- a/electrum/gui/qml/components/Constants.qml +++ b/electrum/gui/qml/components/Constants.qml @@ -27,4 +27,8 @@ Item { property color colorDebit: "#ffff8080" property color mutedForeground: 'gray' //Qt.lighter(Material.background, 2) property color colorMine: "yellow" + + property color colorLightningLocal: "blue" + property color colorLightningRemote: "yellow" + } diff --git a/electrum/gui/qml/components/controls/ChannelDelegate.qml b/electrum/gui/qml/components/controls/ChannelDelegate.qml index a9d46da4b..b8aed2a51 100644 --- a/electrum/gui/qml/components/controls/ChannelDelegate.qml +++ b/electrum/gui/qml/components/controls/ChannelDelegate.qml @@ -33,7 +33,7 @@ ItemDelegate { id: walleticon source: "../../../icons/lightning.png" fillMode: Image.PreserveAspectFit - Layout.rowSpan: 2 + Layout.rowSpan: 3 Layout.preferredWidth: constants.iconSizeLarge Layout.preferredHeight: constants.iconSizeLarge } @@ -58,17 +58,57 @@ ItemDelegate { Label { Layout.fillWidth: true text: model.short_cid + color: constants.mutedForeground } Label { text: Config.formatSats(model.capacity) + font.family: FixedFont } Label { text: Config.baseUnit + color: Material.accentColor } } + Item { + id: chviz + Layout.fillWidth: true + height: 10 + onWidthChanged: { + var cap = model.capacity.satsInt * 1000 + var twocap = cap * 2 + b1.width = width * (cap - model.can_send.msatsInt) / twocap + b2.width = width * model.can_send.msatsInt / twocap + b3.width = width * model.can_receive.msatsInt / twocap + b4.width = width * (cap - model.can_receive.msatsInt) / twocap + } + Rectangle { + id: b1 + x: 0 + height: parent.height + color: 'gray' + } + Rectangle { + id: b2 + anchors.left: b1.right + height: parent.height + color: constants.colorLightningLocal + } + Rectangle { + id: b3 + anchors.left: b2.right + height: parent.height + color: constants.colorLightningRemote + } + Rectangle { + id: b4 + anchors.left: b3.right + height: parent.height + color: 'gray' + } + } Rectangle { Layout.columnSpan: 2 Layout.fillWidth: true diff --git a/electrum/gui/qml/qechannellistmodel.py b/electrum/gui/qml/qechannellistmodel.py index 8d6e266b2..77a05cc2b 100644 --- a/electrum/gui/qml/qechannellistmodel.py +++ b/electrum/gui/qml/qechannellistmodel.py @@ -5,15 +5,29 @@ from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex from electrum.logging import get_logger from electrum.util import Satoshis, register_callback +from electrum.lnutil import LOCAL, REMOTE from .qetypes import QEAmount class QEChannelListModel(QAbstractListModel): + _logger = get_logger(__name__) + + # define listmodel rolemap + _ROLE_NAMES=('cid','state','initiator','capacity','can_send','can_receive', + 'l_csv_delat','r_csv_delay','send_frozen','receive_frozen', + 'type','node_id','node_alias','short_cid','funding_tx') + _ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES)) + _ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES])) + _ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS)) + + _network_signal = pyqtSignal(str, object) + def __init__(self, wallet, parent=None): super().__init__(parent) self.wallet = wallet self.init_model() + self._network_signal.connect(self.on_network_qt) interests = ['channel', 'channels_updated', 'gossip_peers', 'ln_gossip_sync_progress', 'unknown_channels', 'channel_db', 'gossip_db_loaded'] @@ -23,15 +37,25 @@ class QEChannelListModel(QAbstractListModel): # partials, lambdas or methods of subobjects. Hence... register_callback(self.on_network, interests) - _logger = get_logger(__name__) + def on_network(self, event, *args): + if event == 'channel': + # Handle in GUI thread (_network_signal -> on_network_qt) + self._network_signal.emit(event, args) + else: + self.on_network_qt(event, args) + + def on_network_qt(self, event, args=None): + if event == 'channel': + wallet, channel = args + if wallet == self.wallet: + self.on_channel_updated(channel) + elif event == 'channels_updated': + wallet, = args + if wallet == self.wallet: + self.init_model() # TODO: remove/add less crude than full re-init + else: + self._logger.debug('unhandled event %s: %s' % (event, repr(args))) - # define listmodel rolemap - _ROLE_NAMES=('cid','state','initiator','capacity','can_send','can_receive', - 'l_csv_delat','r_csv_delay','send_frozen','receive_frozen', - 'type','node_id','node_alias','short_cid','funding_tx') - _ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES)) - _ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES])) - _ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS)) def rowCount(self, index): return len(self.channels) @@ -62,6 +86,8 @@ class QEChannelListModel(QAbstractListModel): item['short_cid'] = lnc.short_id_for_GUI() item['state'] = lnc.get_state_for_GUI() item['capacity'] = QEAmount(amount_sat=lnc.get_capacity()) + item['can_send'] = QEAmount(amount_msat=lnc.available_to_spend(LOCAL)) + item['can_receive'] = QEAmount(amount_msat=lnc.available_to_spend(REMOTE)) self._logger.debug(repr(item)) return item @@ -85,18 +111,6 @@ class QEChannelListModel(QAbstractListModel): self.channels = channels self.endInsertRows() - def on_network(self, event, *args): - if event == 'channel': - wallet, channel = args - if wallet == self.wallet: - self.on_channel_updated(channel) - elif event == 'channels_updated': - wallet, = args - if wallet == self.wallet: - self.init_model() # TODO: remove/add less crude than full re-init - else: - self._logger.debug('unhandled event %s: %s' % (event, repr(args))) - def on_channel_updated(self, channel): i = 0 for c in self.channels: diff --git a/electrum/gui/qml/qewallet.py b/electrum/gui/qml/qewallet.py index 3e08b7a25..28af7692a 100644 --- a/electrum/gui/qml/qewallet.py +++ b/electrum/gui/qml/qewallet.py @@ -121,9 +121,14 @@ class QEWallet(QObject): if wallet == self.wallet: self._logger.debug('wallet %s updated' % str(wallet)) self.balanceChanged.emit() - elif event in ['channel','channels_updated']: - # TODO update balance/can-spend etc - pass + elif event == 'channel': + wallet, channel = args + if wallet == self.wallet: + self.balanceChanged.emit() + elif event == 'channels_updated': + wallet, = args + if wallet == self.wallet: + self.balanceChanged.emit() else: self._logger.debug('unhandled event: %s %s' % (event, str(args))) @@ -279,6 +284,21 @@ class QEWallet(QObject): self._lightningbalance = QEAmount(amount_sat=self.wallet.lnworker.get_balance()) return self._lightningbalance + @pyqtProperty(QEAmount, notify=balanceChanged) + def lightningCanSend(self): + if not self.isLightning: + return QEAmount() + self._lightningcansend = QEAmount(amount_sat=self.wallet.lnworker.num_sats_can_send()) + return self._lightningcansend + + @pyqtProperty(QEAmount, notify=balanceChanged) + def lightningCanReceive(self): + if not self.isLightning: + return QEAmount() + self._lightningcanreceive = QEAmount(amount_sat=self.wallet.lnworker.num_sats_can_receive()) + return self._lightningcanreceive + + @pyqtSlot('QString', int, int, bool) def send_onchain(self, address, amount, fee=None, rbf=False): self._logger.info('send_onchain: %s %d' % (address,amount))