From 568c14ca78a2fbb543adb023597cfb21b8b07b2e Mon Sep 17 00:00:00 2001 From: ThomasV Date: Fri, 7 Jul 2017 22:56:43 +0200 Subject: [PATCH] Refactor Network and Blockchain dialogs in qt and kivy --- ...ckpoint_dialog.py => blockchain_dialog.py} | 35 +-- gui/kivy/uix/dialogs/settings.py | 6 +- gui/qt/main_window.py | 2 + gui/qt/network_dialog.py | 241 ++++++++++-------- lib/network.py | 9 +- 5 files changed, 149 insertions(+), 144 deletions(-) rename gui/kivy/uix/dialogs/{checkpoint_dialog.py => blockchain_dialog.py} (54%) diff --git a/gui/kivy/uix/dialogs/checkpoint_dialog.py b/gui/kivy/uix/dialogs/blockchain_dialog.py similarity index 54% rename from gui/kivy/uix/dialogs/checkpoint_dialog.py rename to gui/kivy/uix/dialogs/blockchain_dialog.py index 37eb02b05..439c68951 100644 --- a/gui/kivy/uix/dialogs/checkpoint_dialog.py +++ b/gui/kivy/uix/dialogs/blockchain_dialog.py @@ -8,7 +8,7 @@ from electrum.i18n import _ Builder.load_string(''' #:import _ electrum_gui.kivy.i18n._ - + id: popup title: _('Blockchain') size_hint: 1, 1 @@ -30,39 +30,12 @@ Builder.load_string(''' Widget: size_hint: 1, 0.1 TopLabel: - text: _("In order to verify the history returned by your main server, Electrum downloads block headers from random nodes. These headers are then used to check that transactions sent by the server really are in the blockchain.") + text: _("Electrum connects to several nodes in order to download block headers and find out the longest blockchain.") + _("This blockchain is used to verify the transactions sent by your transaction server.") font_size: '6pt' Widget: size_hint: 1, 0.1 - GridLayout: - orientation: 'horizontal' - cols: 2 - height: '36dp' - TopLabel: - text: _('Checkpoint') + ':' - height: '36dp' - TextInput: - id: height_input - multiline: False - input_type: 'number' - height: '36dp' - size_hint_y: None - text: '%d'%root.cp_height - TopLabel: - text: _('Block hash') + ':' - TxHashLabel: - data: root.cp_value Widget: size_hint: 1, 0.1 - Label: - font_size: '6pt' - text: _('If there is a fork of the blockchain, you need to configure your checkpoint in order to make sure that you are on the correct side of the fork. Enter a block number to fetch a checkpoint from your main server, and check its value from independent sources.') - halign: 'left' - text_size: self.width, None - size: self.texture_size - - Widget: - size_hint: 1, 0.3 BoxLayout: orientation: 'horizontal' size_hint: 1, 0.2 @@ -80,9 +53,11 @@ Builder.load_string(''' popup.dismiss() ''') -class CheckpointDialog(Factory.Popup): +class BlockchainDialog(Factory.Popup): def __init__(self, network, callback): Factory.Popup.__init__(self) self.network = network self.callback = callback self.is_split = len(self.network.blockchains) > 1 + + self.checkpoint_height = network.get_checkpoint() diff --git a/gui/kivy/uix/dialogs/settings.py b/gui/kivy/uix/dialogs/settings.py index a786c8b27..7b8f338ea 100644 --- a/gui/kivy/uix/dialogs/settings.py +++ b/gui/kivy/uix/dialogs/settings.py @@ -123,7 +123,7 @@ Builder.load_string(''' SettingsItem: status: "%d blocks"% app.num_blocks title: _('Blockchain') + ': ' + self.status - description: _("Configure checkpoints") + description: _("Blockchain status") action: partial(root.blockchain_dialog, self) ''') @@ -192,12 +192,12 @@ class SettingsDialog(Factory.Popup): self._coinselect_dialog.open() def blockchain_dialog(self, item, dt): - from checkpoint_dialog import CheckpointDialog + from blockchain_dialog import BlockchainDialog if self._blockchain_dialog is None: def callback(height, value): if value: self.app.network.blockchain.set_checkpoint(height, value) - self._blockchain_dialog = CheckpointDialog(self.app.network, callback) + self._blockchain_dialog = BlockchainDialog(self.app.network, callback) self._blockchain_dialog.open() def proxy_status(self): diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 4130c6e64..bd28d587e 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -265,6 +265,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): def on_network(self, event, *args): if event == 'updated': self.need_update.set() + self.emit(QtCore.SIGNAL('updated'), event, *args) + elif event == 'new_transaction': self.tx_notifications.append(args[0]) elif event in ['status', 'banner', 'verified', 'fee']: diff --git a/gui/qt/network_dialog.py b/gui/qt/network_dialog.py index 69d3dce7d..d32184345 100644 --- a/gui/qt/network_dialog.py +++ b/gui/qt/network_dialog.py @@ -26,6 +26,7 @@ import socket from PyQt4.QtGui import * from PyQt4.QtCore import * +import PyQt4.QtCore as QtCore from electrum.i18n import _ from electrum.network import DEFAULT_PORTS @@ -43,14 +44,18 @@ class NetworkDialog(WindowModalDialog): self.nlayout = NetworkChoiceLayout(network, config) vbox = QVBoxLayout(self) vbox.addLayout(self.nlayout.layout()) - vbox.addLayout(Buttons(CancelButton(self), OkButton(self))) + vbox.addLayout(Buttons(CloseButton(self))) + self.connect(parent, QtCore.SIGNAL('updated'), self.on_update) def do_exec(self): result = self.exec_() - if result: - self.nlayout.accept() + #if result: + # self.nlayout.accept() return result + def on_update(self): + self.nlayout.update() + class NodesListWidget(QTreeWidget): @@ -87,29 +92,47 @@ class NodesListWidget(QTreeWidget): pt.setX(50) self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), pt) + def update(self, network): + self.clear() + checkpoint = network.get_checkpoint() + n_chains = len(network.blockchains) + if n_chains > 1: + for b in network.blockchains.values(): + name = network.get_blockchain_name(b) + x = QTreeWidgetItem([name, '%d'%checkpoint]) + x.setData(0, Qt.UserRole, 1) + x.setData(1, Qt.UserRole, b.checkpoint) + for i in network.interfaces.values(): + if i.blockchain == b: + item = QTreeWidgetItem([i.host, '%d'%i.tip]) + item.setData(0, Qt.UserRole, 0) + item.setData(1, Qt.UserRole, i.server) + x.addChild(item) + self.addTopLevelItem(x) + x.setExpanded(True) + else: + for i in network.interfaces.values(): + item = QTreeWidgetItem([i.host, '%d'%i.tip]) + item.setData(0, Qt.UserRole, 0) + item.setData(1, Qt.UserRole, i.server) + self.addTopLevelItem(item) + + h = self.header() + h.setStretchLastSection(False) + h.setResizeMode(0, QHeaderView.Stretch) + h.setResizeMode(1, QHeaderView.ResizeToContents) + class NetworkChoiceLayout(object): + def __init__(self, network, config, wizard=False): self.network = network self.config = config self.protocol = None self.tor_proxy = None - self.servers = network.get_servers() - host, port, protocol, proxy_config, auto_connect = network.get_parameters() - if not proxy_config: - proxy_config = { "mode":"none", "host":"localhost", "port":"9050"} - - if not wizard: - if network.is_connected(): - status = _("Server") + ": %s"%(host) - else: - status = _("Disconnected from server") - else: - status = _("Please choose a server.") + "\n" + _("Press 'Next' if you are offline.") - tabs = QTabWidget() server_tab = QWidget() proxy_tab = QWidget() @@ -122,22 +145,20 @@ class NetworkChoiceLayout(object): grid = QGridLayout(server_tab) grid.setSpacing(8) - # server self.server_host = QLineEdit() self.server_host.setFixedWidth(200) self.server_port = QLineEdit() self.server_port.setFixedWidth(60) - - # use SSL self.ssl_cb = QCheckBox(_('Use SSL')) - self.ssl_cb.setChecked(auto_connect) - self.ssl_cb.stateChanged.connect(self.change_protocol) - - # auto connect self.autoconnect_cb = QCheckBox(_('Select server automatically')) - self.autoconnect_cb.setChecked(auto_connect) self.autoconnect_cb.setEnabled(self.config.is_modifiable('auto_connect')) + self.server_host.editingFinished.connect(self.set_server) + self.server_port.editingFinished.connect(self.set_server) + self.ssl_cb.stateChanged.connect(self.change_protocol) + self.autoconnect_cb.stateChanged.connect(self.set_server) + self.autoconnect_cb.clicked.connect(self.enable_set_server) + msg = _("Electrum sends your wallet addresses to a single server, in order to receive your transaction history.") grid.addWidget(QLabel(_('Server') + ':'), 0, 0) grid.addWidget(self.server_host, 0, 1, 1, 2) @@ -157,18 +178,6 @@ class NetworkChoiceLayout(object): self.servers_list_widget.setColumnWidth(0, 240) grid.addWidget(self.servers_list_widget, 3, 0, 1, 5) - def enable_set_server(): - if config.is_modifiable('server'): - enabled = not self.autoconnect_cb.isChecked() - self.server_host.setEnabled(enabled) - self.server_port.setEnabled(enabled) - self.servers_list_widget.setEnabled(enabled) - else: - for w in [self.autoconnect_cb, self.server_host, self.server_port, self.ssl_cb, self.servers_list_widget]: - w.setEnabled(False) - - self.autoconnect_cb.clicked.connect(enable_set_server) - enable_set_server() # Proxy tab grid = QGridLayout(proxy_tab) @@ -176,11 +185,11 @@ class NetworkChoiceLayout(object): # proxy setting self.proxy_mode = QComboBox() + self.proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP']) self.proxy_host = QLineEdit() self.proxy_host.setFixedWidth(200) self.proxy_port = QLineEdit() self.proxy_port.setFixedWidth(60) - self.proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP']) self.proxy_user = QLineEdit() self.proxy_user.setPlaceholderText(_("Proxy user")) self.proxy_password = QLineEdit() @@ -188,21 +197,14 @@ class NetworkChoiceLayout(object): self.proxy_password.setEchoMode(QLineEdit.Password) self.proxy_password.setFixedWidth(60) - def check_for_disable(index = False): - if self.config.is_modifiable('proxy'): - for w in [self.proxy_host, self.proxy_port, self.proxy_user, self.proxy_password]: - w.setEnabled(self.proxy_mode.currentText() != 'NONE') - else: - for w in [self.proxy_host, self.proxy_port, self.proxy_mode]: w.setEnabled(False) - - check_for_disable() - self.proxy_mode.connect(self.proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable) + self.proxy_mode.currentIndexChanged.connect(self.set_proxy) + self.proxy_host.editingFinished.connect(self.set_proxy) + self.proxy_port.editingFinished.connect(self.set_proxy) + self.proxy_user.editingFinished.connect(self.set_proxy) + self.proxy_password.editingFinished.connect(self.set_proxy) - self.proxy_mode.setCurrentIndex(self.proxy_mode.findText(str(proxy_config.get("mode").upper()))) - self.proxy_host.setText(proxy_config.get("host")) - self.proxy_port.setText(proxy_config.get("port")) - self.proxy_user.setText(proxy_config.get("user", "")) - self.proxy_password.setText(proxy_config.get("password", "")) + self.check_disable_proxy() + self.proxy_mode.connect(self.proxy_mode, SIGNAL('currentIndexChanged(int)'), self.check_disable_proxy) self.proxy_mode.connect(self.proxy_mode, SIGNAL('currentIndexChanged(int)'), self.proxy_settings_changed) self.proxy_host.connect(self.proxy_host, SIGNAL('textEdited(QString)'), self.proxy_settings_changed) @@ -224,62 +226,24 @@ class NetworkChoiceLayout(object): grid.setRowStretch(6, 1) # Blockchain Tab - from electrum import bitcoin - from amountedit import AmountEdit grid = QGridLayout(blockchain_tab) - n = len(network.get_interfaces()) - n_chains = len(network.blockchains) - self.checkpoint_height = network.get_checkpoint() - - status = _("Connected to %d nodes.")%n if n else _("Not connected") msg = ' '.join([ _("Electrum connects to several nodes in order to download block headers and find out the longest blockchain."), _("This blockchain is used to verify the transactions sent by your transaction server.") ]) + self.status_label = QLabel('') grid.addWidget(QLabel(_('Status') + ':'), 0, 0) - grid.addWidget(QLabel(status), 0, 1, 1, 3) + grid.addWidget(self.status_label, 0, 1, 1, 3) grid.addWidget(HelpButton(msg), 0, 4) - def short_hash(h): return h.lstrip('00')[0:10] - if n_chains == 1: - height_str = "%d "%(network.get_local_height()) + _("blocks") - msg = _('This is the height of your local copy of the blockchain.') - grid.addWidget(QLabel(_("Height") + ':'), 1, 0) - grid.addWidget(QLabel(height_str), 1, 1) - grid.addWidget(HelpButton(msg), 1, 4) - else: - checkpoint = network.get_checkpoint() - _hash = network.blockchain().get_hash(checkpoint) - grid.addWidget(QLabel(_('Chain split detected at block %d')%checkpoint), 1, 0, 1, 3) - grid.addWidget(QLabel(_('You are on branch') + ' ' + short_hash(_hash)), 2, 0, 1, 3) - - nodes_list_widget = NodesListWidget(self) - grid.addWidget(nodes_list_widget, 5, 0, 1, 5) - if n_chains > 1: - for b in network.blockchains.values(): - _hash = b.get_hash(checkpoint) - x = QTreeWidgetItem([short_hash(_hash), '%d'%checkpoint]) - x.setData(0, Qt.UserRole, 1) - x.setData(1, Qt.UserRole, b.checkpoint) - for i in network.interfaces.values(): - if i.blockchain == b: - item = QTreeWidgetItem([i.host, '%d'%i.tip]) - item.setData(0, Qt.UserRole, 0) - item.setData(1, Qt.UserRole, i.server) - x.addChild(item) - nodes_list_widget.addTopLevelItem(x) - x.setExpanded(True) - else: - for i in network.interfaces.values(): - item = QTreeWidgetItem([i.host, '%d'%i.tip]) - item.setData(0, Qt.UserRole, 0) - item.setData(1, Qt.UserRole, i.server) - nodes_list_widget.addTopLevelItem(item) - - h = nodes_list_widget.header() - h.setStretchLastSection(False) - h.setResizeMode(0, QHeaderView.Stretch) - h.setResizeMode(1, QHeaderView.ResizeToContents) - + self.height_label = QLabel('') + msg = _('This is the height of your local copy of the blockchain.') + grid.addWidget(QLabel(_("Height") + ':'), 1, 0) + grid.addWidget(self.height_label, 1, 1) + grid.addWidget(HelpButton(msg), 1, 4) + self.split_label = QLabel('') + grid.addWidget(self.split_label, 2, 0, 1, 3) + self.nodes_list_widget = NodesListWidget(self) + grid.addWidget(self.nodes_list_widget, 5, 0, 1, 5) grid.setRowStretch(7, 1) vbox = QVBoxLayout() vbox.addWidget(tabs) @@ -288,17 +252,69 @@ class NetworkChoiceLayout(object): self.td = td = TorDetector() td.found_proxy.connect(self.suggest_proxy) td.start() - self.change_server(host, protocol) - self.set_protocol(protocol) self.servers_list_widget.connect( self.servers_list_widget, SIGNAL('currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)'), lambda x,y: self.server_changed(x)) + # update + self.update() + + def check_disable_proxy(self, index = False): + if self.config.is_modifiable('proxy'): + for w in [self.proxy_host, self.proxy_port, self.proxy_user, self.proxy_password]: + w.setEnabled(self.proxy_mode.currentText() != 'NONE') + else: + for w in [self.proxy_host, self.proxy_port, self.proxy_mode]: w.setEnabled(False) + + def enable_set_server(self): + if self.config.is_modifiable('server'): + enabled = not self.autoconnect_cb.isChecked() + self.server_host.setEnabled(enabled) + self.server_port.setEnabled(enabled) + self.servers_list_widget.setEnabled(enabled) + else: + for w in [self.autoconnect_cb, self.server_host, self.server_port, self.ssl_cb, self.servers_list_widget]: + w.setEnabled(False) + + def update(self): + host, port, protocol, proxy_config, auto_connect = self.network.get_parameters() + if not proxy_config: + proxy_config = { "mode":"none", "host":"localhost", "port":"9050"} + self.server_host.setText(host) + self.server_port.setText(port) + self.autoconnect_cb.setChecked(auto_connect) + #self.ssl_cb.setChecked(auto_connect) + #self.change_server(host, protocol) + self.set_protocol(protocol) + self.update_servers_list() + self.enable_set_server() + + # proxy tab + self.proxy_mode.setCurrentIndex(self.proxy_mode.findText(str(proxy_config.get("mode").upper()))) + self.proxy_host.setText(proxy_config.get("host")) + self.proxy_port.setText(proxy_config.get("port")) + self.proxy_user.setText(proxy_config.get("user", "")) + self.proxy_password.setText(proxy_config.get("password", "")) + + height_str = "%d "%(self.network.get_local_height()) + _("blocks") + self.height_label.setText(height_str) + n = len(self.network.get_interfaces()) + status = _("Connected to %d nodes.")%n if n else _("Not connected") + self.status_label.setText(status) + if len(self.network.blockchains)>1: + checkpoint = self.network.get_checkpoint() + name = self.network.get_blockchain_name(self.network.blockchain()) + msg = _('Chain split detected at block %d')%checkpoint + '\n' + _('You are on branch') + ' ' + name + else: + msg = '' + self.split_label.setText(msg) + self.nodes_list_widget.update(self.network) def layout(self): return self.layout_ - def init_servers_list(self): + def update_servers_list(self): + self.servers = self.network.get_servers() self.servers_list_widget.clear() for _host, d in sorted(self.servers.items()): if d.get(self.protocol): @@ -308,7 +324,6 @@ class NetworkChoiceLayout(object): def set_protocol(self, protocol): if protocol != self.protocol: self.protocol = protocol - self.init_servers_list() def change_protocol(self, use_ssl): p = 's' if use_ssl else 't' @@ -320,21 +335,20 @@ class NetworkChoiceLayout(object): self.server_host.setText( host ) self.server_port.setText( port ) self.set_protocol(p) + self.set_server() def server_changed(self, x): if x: self.change_server(str(x.text(0)), self.protocol) def change_server(self, host, protocol): - pp = self.servers.get(host, DEFAULT_PORTS) if protocol and protocol not in protocol_letters: - protocol = None + protocol = None if protocol: port = pp.get(protocol) if port is None: protocol = None - if not protocol: if 's' in pp.keys(): protocol = 's' @@ -342,15 +356,24 @@ class NetworkChoiceLayout(object): else: protocol = pp.keys()[0] port = pp.get(protocol) - self.server_host.setText( host ) self.server_port.setText( port ) self.ssl_cb.setChecked(protocol=='s') + self.set_server() def accept(self): + pass + + def set_server(self): + host, port, protocol, proxy, auto_connect = self.network.get_parameters() host = str(self.server_host.text()) port = str(self.server_port.text()) protocol = 's' if self.ssl_cb.isChecked() else 't' + auto_connect = self.autoconnect_cb.isChecked() + self.network.set_parameters(host, port, protocol, proxy, auto_connect) + + def set_proxy(self): + host, port, protocol, proxy, auto_connect = self.network.get_parameters() if self.proxy_mode.currentText() != 'NONE': proxy = { 'mode':str(self.proxy_mode.currentText()).lower(), 'host':str(self.proxy_host.text()), @@ -359,9 +382,7 @@ class NetworkChoiceLayout(object): 'password':str(self.proxy_password.text())} else: proxy = None - auto_connect = self.autoconnect_cb.isChecked() self.network.set_parameters(host, port, protocol, proxy, auto_connect) - #self.network.blockchain.set_checkpoint(self.checkpoint_height, self.checkpoint_value) def suggest_proxy(self, found_proxy): self.tor_proxy = found_proxy diff --git a/lib/network.py b/lib/network.py index dcb876e9c..bbba08fe3 100644 --- a/lib/network.py +++ b/lib/network.py @@ -77,6 +77,7 @@ def set_testnet(): global DEFAULT_PORTS, DEFAULT_SERVERS DEFAULT_PORTS = {'t':'51001', 's':'51002'} DEFAULT_SERVERS = { + 'testnetnode.arihanc.com': DEFAULT_PORTS, 'testnet1.bauerj.eu': DEFAULT_PORTS, '14.3.140.101': DEFAULT_PORTS, 'testnet.hsmiths.com': {'t':'53011', 's':'53012'}, @@ -527,7 +528,8 @@ class Network(util.DaemonThread): if self.interface != i: self.print_error("switching to", server) # stop any current interface in order to terminate subscriptions - self.close_interface(self.interface) + # fixme: we don't want to close headers sub + #self.close_interface(self.interface) self.interface = i self.send_subscriptions() self.set_status('connected') @@ -1016,6 +1018,11 @@ class Network(util.DaemonThread): return self.blockchains[self.blockchain_index] + def get_blockchain_name(self, blockchain): + checkpoint = self.get_checkpoint() + _hash = blockchain.get_hash(checkpoint) + return _hash.lstrip('00')[0:10] + def follow_chain(self, index): blockchain = self.blockchains.get(index) if blockchain: