From f774174c858018b818d6e92655f567b194970f78 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Fri, 30 Dec 2022 16:22:22 +0100 Subject: [PATCH] qml: add serverlistmodel.py, add server list to ServerConfigDialog.qml --- .../gui/qml/components/NetworkOverview.qml | 1 + .../gui/qml/components/ServerConfigDialog.qml | 38 ++++- .../controls/PaneInsetBackground.qml | 11 +- .../components/controls/ServerDelegate.qml | 29 ++++ electrum/gui/qml/qenetwork.py | 9 ++ electrum/gui/qml/qeserverlistmodel.py | 133 ++++++++++++++++++ 6 files changed, 212 insertions(+), 9 deletions(-) create mode 100644 electrum/gui/qml/components/controls/ServerDelegate.qml create mode 100644 electrum/gui/qml/qeserverlistmodel.py diff --git a/electrum/gui/qml/components/NetworkOverview.qml b/electrum/gui/qml/components/NetworkOverview.qml index a837ee14d..facff3902 100644 --- a/electrum/gui/qml/components/NetworkOverview.qml +++ b/electrum/gui/qml/components/NetworkOverview.qml @@ -154,6 +154,7 @@ Pane { text: qsTr('disabled'); visible: !Config.useGossip } + } } diff --git a/electrum/gui/qml/components/ServerConfigDialog.qml b/electrum/gui/qml/components/ServerConfigDialog.qml index ef55175ac..49809c9ad 100644 --- a/electrum/gui/qml/components/ServerConfigDialog.qml +++ b/electrum/gui/qml/components/ServerConfigDialog.qml @@ -30,14 +30,44 @@ ElDialog { height: parent.height spacing: 0 - ServerConfig { - id: serverconfig + ColumnLayout { Layout.fillWidth: true + Layout.fillHeight: true Layout.leftMargin: constants.paddingLarge Layout.rightMargin: constants.paddingLarge - } - Item { Layout.fillHeight: true; Layout.preferredWidth: 1 } + ServerConfig { + id: serverconfig + Layout.fillWidth: true + } + + Label { + text: qsTr('Servers') + font.pixelSize: constants.fontSizeLarge + color: Material.accentColor + } + + Rectangle { + Layout.fillWidth: true + height: 1 + color: Material.accentColor + } + + Frame { + background: PaneInsetBackground { baseColor: Material.dialogColor } + + verticalPadding: 0 + horizontalPadding: 0 + Layout.fillHeight: true + Layout.fillWidth: true + + ListView { + anchors.fill: parent + model: Network.serverListModel + delegate: ServerDelegate { } + } + } + } FlatButton { Layout.fillWidth: true diff --git a/electrum/gui/qml/components/controls/PaneInsetBackground.qml b/electrum/gui/qml/components/controls/PaneInsetBackground.qml index 8d4c316ac..d22259c9a 100644 --- a/electrum/gui/qml/components/controls/PaneInsetBackground.qml +++ b/electrum/gui/qml/components/controls/PaneInsetBackground.qml @@ -2,25 +2,26 @@ import QtQuick 2.6 import QtQuick.Controls.Material 2.0 Rectangle { + property color baseColor: Material.background Rectangle { anchors { left: parent.left; top: parent.top; right: parent.right } height: 1 - color: Qt.darker(Material.background, 1.50) + color: Qt.darker(baseColor, 1.50) } Rectangle { anchors { left: parent.left; top: parent.top; bottom: parent.bottom } width: 1 - color: Qt.darker(Material.background, 1.50) + color: Qt.darker(baseColor, 1.50) } Rectangle { anchors { left: parent.left; bottom: parent.bottom; right: parent.right } height: 1 - color: Qt.lighter(Material.background, 1.50) + color: Qt.lighter(baseColor, 1.50) } Rectangle { anchors { right: parent.right; top: parent.top; bottom: parent.bottom } width: 1 - color: Qt.lighter(Material.background, 1.50) + color: Qt.lighter(baseColor, 1.50) } - color: Qt.darker(Material.background, 1.15) + color: Qt.darker(baseColor, 1.15) } diff --git a/electrum/gui/qml/components/controls/ServerDelegate.qml b/electrum/gui/qml/components/controls/ServerDelegate.qml new file mode 100644 index 000000000..a93bfa8a2 --- /dev/null +++ b/electrum/gui/qml/components/controls/ServerDelegate.qml @@ -0,0 +1,29 @@ +import QtQuick 2.6 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.0 +import QtQuick.Controls.Material 2.0 + +import org.electrum 1.0 + +ItemDelegate { + id: root + height: itemLayout.height + width: ListView.view.width + + GridLayout { + id: itemLayout + anchors { + left: parent.left + right: parent.right + leftMargin: constants.paddingSmall + rightMargin: constants.paddingSmall + } + columns: 2 + Label { + text: model.address + } + Label { + text: model.chain + } + } +} diff --git a/electrum/gui/qml/qenetwork.py b/electrum/gui/qml/qenetwork.py index 8854a6a25..c8a776d87 100644 --- a/electrum/gui/qml/qenetwork.py +++ b/electrum/gui/qml/qenetwork.py @@ -5,12 +5,14 @@ from electrum import constants from electrum.interface import ServerAddr from .util import QtEventListener, event_listener +from .qeserverlistmodel import QEServerListModel class QENetwork(QObject, QtEventListener): def __init__(self, network, qeconfig, parent=None): super().__init__(parent) self.network = network self._qeconfig = qeconfig + self._serverListModel = None self._height = network.get_local_height() # init here, update event can take a while self.register_callbacks() @@ -186,3 +188,10 @@ class QENetwork(QObject, QtEventListener): 'db_channels': self._gossipDbChannels , 'db_policies': self._gossipDbPolicies } + + serverListModelChanged = pyqtSignal() + @pyqtProperty(QEServerListModel, notify=serverListModelChanged) + def serverListModel(self): + if self._serverListModel is None: + self._serverListModel = QEServerListModel(self.network) + return self._serverListModel diff --git a/electrum/gui/qml/qeserverlistmodel.py b/electrum/gui/qml/qeserverlistmodel.py new file mode 100644 index 000000000..a4a82f274 --- /dev/null +++ b/electrum/gui/qml/qeserverlistmodel.py @@ -0,0 +1,133 @@ +from abc import abstractmethod + +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject +from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex + +from electrum.i18n import _ +from electrum.logging import get_logger +from electrum.util import Satoshis, format_time +from electrum.interface import ServerAddr, PREFERRED_NETWORK_PROTOCOL +from electrum import blockchain + +from .util import QtEventListener, qt_event_listener, event_listener + +class QEServerListModel(QAbstractListModel, QtEventListener): + _logger = get_logger(__name__) + _chaintips = 0 + + # define listmodel rolemap + _ROLE_NAMES=('name', 'address', 'is_connected', 'is_primary', 'is_tor', 'chain', 'height') + _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 __init__(self, network, parent=None): + super().__init__(parent) + self.network = network + self.init_model() + self.register_callbacks() + self.destroyed.connect(lambda: self.unregister_callbacks()) + + @event_listener + def on_event_network_updated(self): + self._logger.info(f'network updated') + self.init_model() + + @event_listener + def on_event_blockchain_updated(self): + self._logger.info(f'blockchain updated') + self.init_model() + + @event_listener + def on_event_default_server_changed(self): + self._logger.info(f'default server changed') + self.init_model() + + def rowCount(self, index): + return len(self.servers) + + def roleNames(self): + return self._ROLE_MAP + + def data(self, index, role): + server = self.servers[index.row()] + role_index = role - Qt.UserRole + value = server[self._ROLE_NAMES[role_index]] + + if isinstance(value, (bool, list, int, str)) or value is None: + return value + if isinstance(value, Satoshis): + return value.value + return str(value) + + def clear(self): + self.beginResetModel() + self.servers = [] + self.endResetModel() + + chaintipsChanged = pyqtSignal() + @pyqtProperty(int, notify=chaintipsChanged) + def chaintips(self): + return self._chaintips + + def get_chains(self): + chains = self.network.get_blockchains() + n_chains = len(chains) + if n_chains != self._chaintips: + self._chaintips = n_chains + self.chaintipsChanged.emit() + return chains + + @pyqtSlot() + def init_model(self): + self.clear() + + chains = self.get_chains() + + for chain_id, interfaces in chains.items(): + self._logger.debug(f'chain {chain_id} has {len(interfaces)} interfaces') + b = blockchain.blockchains.get(chain_id) + if b is None: + continue + + name = b.get_name() + + self._logger.debug(f'chain {chain_id} has name={name}, max_forkpoint=@{b.get_max_forkpoint()}, height={b.height()}') + + for i in interfaces: + server = {} + server['chain'] = name + server['chain_height'] = b.height() + server['is_primary'] = i == self.network.interface + server['is_connected'] = True + server['name'] = str(i.server) + server['address'] = i.server.to_friendly_name() + server['height'] = i.tip + + self._logger.debug(f'adding server: {repr(server)}') + self.servers.append(server) + + # disconnected servers + all_servers = self.network.get_servers() + connected_hosts = set([iface.host for ifaces in chains.values() for iface in ifaces]) + protocol = PREFERRED_NETWORK_PROTOCOL + use_tor = True + for _host, d in sorted(all_servers.items()): + if _host in connected_hosts: + continue + if _host.endswith('.onion') and not use_tor: + continue + port = d.get(protocol) + if port: + s = ServerAddr(_host, port, protocol=protocol) + server = {} + server['chain'] = '' + server['chain_height'] = 0 + server['height'] = 0 + server['is_primary'] = False + server['is_connected'] = False + server['name'] = s.net_addr_str() + server['address'] = server['name'] + + self.servers.append(server) +