diff --git a/electrum/gui/qml/components/NetworkStats.qml b/electrum/gui/qml/components/NetworkStats.qml index abf42a7de..50ad7fe07 100644 --- a/electrum/gui/qml/components/NetworkStats.qml +++ b/electrum/gui/qml/components/NetworkStats.qml @@ -3,6 +3,8 @@ import QtQuick.Layouts 1.0 import QtQuick.Controls 2.0 import QtQuick.Controls.Material 2.0 +import "controls" + Pane { property string title: qsTr('Network') @@ -45,17 +47,9 @@ Pane { color: Material.primaryHighlightedTextColor; font.bold: true } - Image { - Layout.preferredWidth: constants.iconSizeSmall - Layout.preferredHeight: constants.iconSizeSmall - source: Network.status == 'connecting' || Network.status == 'disconnected' - ? '../../icons/status_disconnected.png' - : Network.status == 'connected' - ? Daemon.currentWallet && !Daemon.currentWallet.isUptodate - ? '../../icons/status_lagging.png' - : '../../icons/status_connected.png' - : '../../icons/status_connected.png' - } + + NetworkStatusIndicator {} + Label { text: Network.status } diff --git a/electrum/gui/qml/components/controls/NetworkStatusIndicator.qml b/electrum/gui/qml/components/controls/NetworkStatusIndicator.qml new file mode 100644 index 000000000..cdfe41ca5 --- /dev/null +++ b/electrum/gui/qml/components/controls/NetworkStatusIndicator.qml @@ -0,0 +1,58 @@ +import QtQuick 2.6 + +Image { + id: root + + sourceSize.width: constants.iconSizeMedium + sourceSize.height: constants.iconSizeMedium + + property bool connected: Network.status == 'connected' + property bool lagging: connected && Network.isLagging + property bool fork: connected && Network.chaintips > 1 + property bool syncing: connected && Daemon.currentWallet && Daemon.currentWallet.synchronizing + + // ?: in order to keep this a binding.. + source: !connected + ? '../../../icons/status_disconnected.png' + : syncing + ? '../../../icons/status_waiting.png' + : lagging + ? fork + ? '../../../icons/status_lagging_fork.png' + : '../../../icons/status_lagging.png' + : fork + ? '../../../icons/status_connected_fork.png' + : '../../../icons/status_connected.png' + + states: [ + State { + name: 'disconnected' + when: !connected + PropertyChanges { target: root; rotation: 0 } + }, + State { + name: 'normal' + when: !(syncing || fork) + PropertyChanges { target: root; rotation: 0 } + }, + State { + name: 'syncing' + when: syncing + PropertyChanges { target: spin; running: true } + }, + State { + name: 'forked' + when: fork + PropertyChanges { target: root; rotation: 0 } + } + ] + + RotationAnimation { + id: spin + target: root + from: 0 + to: 360 + duration: 1000 + loops: Animation.Infinite + } +} diff --git a/electrum/gui/qml/components/main.qml b/electrum/gui/qml/components/main.qml index a93303de5..b90d74919 100644 --- a/electrum/gui/qml/components/main.qml +++ b/electrum/gui/qml/components/main.qml @@ -88,17 +88,7 @@ ApplicationWindow scale: 1.5 } - Image { - Layout.preferredWidth: constants.iconSizeSmall - Layout.preferredHeight: constants.iconSizeSmall - source: Network.status == 'connecting' || Network.status == 'disconnected' - ? '../../icons/status_disconnected.png' - : Network.status == 'connected' - ? Daemon.currentWallet && !Daemon.currentWallet.isUptodate - ? '../../icons/status_lagging.png' - : '../../icons/status_connected.png' - : '../../icons/status_connected.png' - } + NetworkStatusIndicator { } Rectangle { color: 'transparent' diff --git a/electrum/gui/qml/qenetwork.py b/electrum/gui/qml/qenetwork.py index 36300c1d1..87a9ba214 100644 --- a/electrum/gui/qml/qenetwork.py +++ b/electrum/gui/qml/qenetwork.py @@ -10,6 +10,7 @@ class QENetwork(QObject, QtEventListener): def __init__(self, network, parent=None): super().__init__(parent) self.network = network + self._height = network.get_local_height() # init here, update event can take a while self.register_callbacks() _logger = get_logger(__name__) @@ -22,12 +23,16 @@ class QENetwork(QObject, QtEventListener): proxyChanged = pyqtSignal() statusChanged = pyqtSignal() feeHistogramUpdated = pyqtSignal() + chaintipsChanged = pyqtSignal() + isLaggingChanged = pyqtSignal() # shared signal for static properties dataChanged = pyqtSignal() _height = 0 _status = "" + _chaintips = 1 + _islagging = False _fee_histogram = [] @event_listener @@ -35,7 +40,7 @@ class QENetwork(QObject, QtEventListener): self.networkUpdated.emit() @event_listener - def on_event_blockchain_updated(self, *args): + def on_event_blockchain_updated(self): if self._height != self.network.get_local_height(): self._height = self.network.get_local_height() self._logger.debug('new height: %d' % self._height) @@ -57,6 +62,16 @@ class QENetwork(QObject, QtEventListener): if self._status != self.network.connection_status: self._status = self.network.connection_status self.statusChanged.emit() + chains = len(self.network.get_blockchains()) + if chains != self._chaintips: + self._logger.debug('chain tips # changed: ' + chains) + self._chaintips = chains + self.chaintipsChanged.emit() + server_lag = self.network.get_local_height() - self.network.get_server_height() + if self._islagging ^ (server_lag > 1): + self._logger.debug('lagging changed: ' + (server_lag > 1)) + self._islagging = server_lag > 1 + self.isLaggingChanged.emit() @event_listener def on_event_fee_histogram(self, histogram): @@ -68,7 +83,7 @@ class QENetwork(QObject, QtEventListener): def height(self): return self._height - @pyqtProperty('QString', notify=defaultServerChanged) + @pyqtProperty(str, notify=defaultServerChanged) def server(self): return str(self.network.get_parameters().server) @@ -83,15 +98,23 @@ class QENetwork(QObject, QtEventListener): net_params = net_params._replace(server=server) self.network.run_from_another_thread(self.network.set_parameters(net_params)) - @pyqtProperty('QString', notify=statusChanged) + @pyqtProperty(str, notify=statusChanged) def status(self): return self._status + @pyqtProperty(int, notify=chaintipsChanged) + def chaintips(self): + return self._chaintips + + @pyqtProperty(bool, notify=isLaggingChanged) + def isLagging(self): + return self._islagging + @pyqtProperty(bool, notify=dataChanged) def isTestNet(self): return constants.net.TESTNET - @pyqtProperty('QString', notify=dataChanged) + @pyqtProperty(str, notify=dataChanged) def networkName(self): return constants.net.__name__.replace('Bitcoin','') diff --git a/electrum/gui/qml/qewallet.py b/electrum/gui/qml/qewallet.py index 2bab517f1..721f01671 100644 --- a/electrum/gui/qml/qewallet.py +++ b/electrum/gui/qml/qewallet.py @@ -63,6 +63,10 @@ class QEWallet(AuthMixin, QObject, QtEventListener): _network_signal = pyqtSignal(str, object) + _isUpToDate = False + _synchronizing = False + _synchronizing_progress = '' + def __init__(self, wallet, parent=None): super().__init__(parent) self.wallet = wallet @@ -91,11 +95,52 @@ class QEWallet(AuthMixin, QObject, QtEventListener): @pyqtProperty(bool, notify=isUptodateChanged) def isUptodate(self): - return self.wallet.is_up_to_date() + return self._isUpToDate + + synchronizingChanged = pyqtSignal() + @pyqtProperty(bool, notify=synchronizingChanged) + def synchronizing(self): + return self._synchronizing + + @synchronizing.setter + def synchronizing(self, synchronizing): + if self._synchronizing != synchronizing: + self._synchronizing = synchronizing + self.synchronizingChanged.emit() + + synchronizingProgressChanged = pyqtSignal() + @pyqtProperty(str, notify=synchronizingProgressChanged) + def synchronizing_progress(self): + return self._synchronizing_progress + + @synchronizing_progress.setter + def synchronizing_progress(self, progress): + if self._synchronizing_progress != progress: + self._synchronizing_progress = progress + self.synchronizingProgressChanged.emit() @event_listener - def on_event_status(self, *args, **kwargs): - self.isUptodateChanged.emit() + def on_event_status(self): + self._logger.debug('status') + uptodate = self.wallet.is_up_to_date() + if self._isUpToDate != uptodate: + self._isUpToDate = uptodate + self.isUptodateChanged.emit() + + if self.wallet.network.is_connected(): + server_height = self.wallet.network.get_server_height() + server_lag = self.wallet.network.get_local_height() - server_height + # Server height can be 0 after switching to a new server + # until we get a headers subscription request response. + # Display the synchronizing message in that case. + if not self._isUpToDate or server_height == 0: + num_sent, num_answered = self.wallet.adb.get_history_sync_state_details() + self.synchronizing_progress = ("{} ({}/{})" + .format(_("Synchronizing..."), num_answered, num_sent)) + self.synchronizing = True + else: + self.synchronizing_progress = '' + self.synchronizing = False @event_listener def on_event_request_status(self, wallet, key, status): @@ -116,8 +161,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener): self._logger.debug(f'No invoice found for key {key}') @qt_event_listener - def on_event_new_transaction(self, *args): - wallet, tx = args + def on_event_new_transaction(self, wallet, tx): if wallet == self.wallet: self.add_tx_notification(tx) self.historyModel.init_model() # TODO: be less dramatic