diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index 79855174c..2ee32d7aa 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -56,6 +56,7 @@ from .main_window import ElectrumWindow from .network_dialog import NetworkDialog from .stylesheet_patcher import patch_qt_stylesheet from .lightning_dialog import LightningDialog +from .watchtower_dialog import WatchtowerDialog if TYPE_CHECKING: from electrum.daemon import Daemon @@ -115,6 +116,7 @@ class ElectrumGui(Logger): self.network_dialog = None self.lightning_dialog = None + self.watchtower_dialog = None self.network_updated_signal_obj = QNetworkUpdatedSignalObject() self._num_wizards_in_progress = 0 self._num_wizards_lock = threading.Lock() @@ -155,6 +157,7 @@ class ElectrumGui(Logger): m.clear() if self.config.get('lightning'): m.addAction(_("Lightning"), self.show_lightning_dialog) + m.addAction(_("Watchtower"), self.show_watchtower_dialog) for window in self.windows: name = window.wallet.basename() submenu = m.addMenu(name) @@ -191,6 +194,8 @@ class ElectrumGui(Logger): self.network_dialog.close() if self.lightning_dialog: self.lightning_dialog.close() + if self.watchtower_dialog: + self.watchtower_dialog.close() def new_window(self, path, uri=None): # Use a signal as can be called from daemon thread @@ -201,6 +206,11 @@ class ElectrumGui(Logger): self.lightning_dialog = LightningDialog(self) self.lightning_dialog.bring_to_top() + def show_watchtower_dialog(self): + if not self.watchtower_dialog: + self.watchtower_dialog = WatchtowerDialog(self) + self.watchtower_dialog.bring_to_top() + def show_network_dialog(self, parent): if not self.daemon.network: parent.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline')) diff --git a/electrum/gui/qt/lightning_dialog.py b/electrum/gui/qt/lightning_dialog.py index cd3b8bb34..3d4ac1d9e 100644 --- a/electrum/gui/qt/lightning_dialog.py +++ b/electrum/gui/qt/lightning_dialog.py @@ -32,28 +32,6 @@ from electrum.i18n import _ from .util import HelpLabel, MyTreeView, Buttons -class WatcherList(MyTreeView): - def __init__(self, parent): - super().__init__(parent, self.create_menu, stretch_column=0, editable_columns=[]) - self.setModel(QStandardItemModel(self)) - self.setSortingEnabled(True) - self.update() - - def create_menu(self, x): - pass - - def update(self): - if self.parent.lnwatcher is None: - return - self.model().clear() - self.update_headers({0:_('Outpoint'), 1:_('Tx'), 2:_('Status')}) - lnwatcher = self.parent.lnwatcher - l = lnwatcher.list_sweep_tx() - for outpoint in l: - n = lnwatcher.get_num_tx(outpoint) - status = lnwatcher.get_channel_status(outpoint) - items = [QStandardItem(e) for e in [outpoint, "%d"%n, status]] - self.model().insertRow(self.model().rowCount(), items) class LightningDialog(QDialog): @@ -63,37 +41,23 @@ class LightningDialog(QDialog): self.gui_object = gui_object self.config = gui_object.config self.network = gui_object.daemon.network - self.lnwatcher = self.network.local_watchtower - self.setWindowTitle(_('Lightning')) + self.setWindowTitle(_('Lightning Network')) self.setMinimumSize(600, 20) - self.watcher_list = WatcherList(self) - # channel_db - network_w = QWidget() - network_vbox = QVBoxLayout(network_w) + + vbox = QVBoxLayout(self) self.num_peers = QLabel('') - network_vbox.addWidget(self.num_peers) + vbox.addWidget(self.num_peers) self.num_nodes = QLabel('') - network_vbox.addWidget(self.num_nodes) + vbox.addWidget(self.num_nodes) self.num_channels = QLabel('') - network_vbox.addWidget(self.num_channels) + vbox.addWidget(self.num_channels) self.status = QLabel('') - network_vbox.addWidget(self.status) - network_vbox.addStretch(1) - # watchtower - watcher_w = QWidget() - watcher_vbox = QVBoxLayout(watcher_w) - watcher_vbox.addWidget(self.watcher_list) + vbox.addWidget(self.status) + vbox.addStretch(1) - # tabs - tabs = QTabWidget() - tabs.addTab(network_w, _('Network')) - tabs.addTab(watcher_w, _('Watchtower')) - vbox = QVBoxLayout(self) - vbox.addWidget(tabs) b = QPushButton(_('Close')) b.clicked.connect(self.close) vbox.addLayout(Buttons(b)) - self.watcher_list.update() self.network.register_callback(self.update_status, ['ln_status']) def update_status(self, event, num_peers, num_nodes, known, unknown): @@ -116,5 +80,5 @@ class LightningDialog(QDialog): self.raise_() def closeEvent(self, event): - self.gui_object.watchtower_window = None + self.gui_object.lightning_dialog = None event.accept() diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 93c8cf8a6..1d7b352fb 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -635,6 +635,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): tools_menu.addAction(_("&Network"), lambda: self.gui_object.show_network_dialog(self)) if self.config.get('lightning'): tools_menu.addAction(_("&Lightning"), self.gui_object.show_lightning_dialog) + tools_menu.addAction(_("&Watchtower"), self.gui_object.show_watchtower_dialog) tools_menu.addAction(_("&Plugins"), self.plugins_dialog) tools_menu.addSeparator() tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message) diff --git a/electrum/gui/qt/watchtower_dialog.py b/electrum/gui/qt/watchtower_dialog.py new file mode 100644 index 000000000..4a37f32e2 --- /dev/null +++ b/electrum/gui/qt/watchtower_dialog.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2012 thomasv@gitorious +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from PyQt5.QtGui import QStandardItemModel, QStandardItem +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import (QDialog, QWidget, QLabel, QVBoxLayout, QCheckBox, + QGridLayout, QPushButton, QLineEdit, QTabWidget) + +from electrum.i18n import _ +from .util import HelpLabel, MyTreeView, Buttons + + +class WatcherList(MyTreeView): + def __init__(self, parent): + super().__init__(parent, self.create_menu, stretch_column=0, editable_columns=[]) + self.setModel(QStandardItemModel(self)) + self.setSortingEnabled(True) + self.update() + + def create_menu(self, x): + pass + + def update(self): + if self.parent.lnwatcher is None: + return + self.model().clear() + self.update_headers({0:_('Outpoint'), 1:_('Tx'), 2:_('Status')}) + lnwatcher = self.parent.lnwatcher + l = lnwatcher.list_sweep_tx() + for outpoint in l: + n = lnwatcher.get_num_tx(outpoint) + status = lnwatcher.get_channel_status(outpoint) + items = [QStandardItem(e) for e in [outpoint, "%d"%n, status]] + self.model().insertRow(self.model().rowCount(), items) + + +class WatchtowerDialog(QDialog): + + def __init__(self, gui_object): + QDialog.__init__(self) + self.gui_object = gui_object + self.config = gui_object.config + self.network = gui_object.daemon.network + self.lnwatcher = self.network.local_watchtower + self.setWindowTitle(_('Watchtower')) + self.setMinimumSize(600, 20) + self.watcher_list = WatcherList(self) + + vbox = QVBoxLayout(self) + vbox.addWidget(self.watcher_list) + b = QPushButton(_('Close')) + b.clicked.connect(self.close) + vbox.addLayout(Buttons(b)) + self.watcher_list.update() + + def is_hidden(self): + return self.isMinimized() or self.isHidden() + + def show_or_hide(self): + if self.is_hidden(): + self.bring_to_top() + else: + self.hide() + + def bring_to_top(self): + self.show() + self.raise_() + + def closeEvent(self, event): + self.gui_object.watchtower_dialog = None + event.accept()