electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 568c14ca78a2fbb543adb023597cfb21b8b07b2e
parent c6bdd3c1b0e41968b0f123a80631df09f0870787
Author: ThomasV <thomasv@electrum.org>
Date:   Fri,  7 Jul 2017 22:56:43 +0200

Refactor Network and Blockchain dialogs in qt and kivy

Diffstat:
Agui/kivy/uix/dialogs/blockchain_dialog.py | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dgui/kivy/uix/dialogs/checkpoint_dialog.py | 88-------------------------------------------------------------------------------
Mgui/kivy/uix/dialogs/settings.py | 6+++---
Mgui/qt/main_window.py | 2++
Mgui/qt/network_dialog.py | 241+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mlib/network.py | 9++++++++-
6 files changed, 207 insertions(+), 202 deletions(-)

diff --git a/gui/kivy/uix/dialogs/blockchain_dialog.py b/gui/kivy/uix/dialogs/blockchain_dialog.py @@ -0,0 +1,63 @@ +from kivy.app import App +from kivy.factory import Factory +from kivy.properties import ObjectProperty +from kivy.lang import Builder + +from electrum.i18n import _ + +Builder.load_string(''' +#:import _ electrum_gui.kivy.i18n._ + +<BlockchainDialog@Popup> + id: popup + title: _('Blockchain') + size_hint: 1, 1 + cp_height: 0 + cp_value: '' + + BoxLayout: + orientation: 'vertical' + padding: '10dp' + spacing: '10dp' + TopLabel: + height: '48dp' + id: bc_height + text: _("Verified headers: %d blocks.")% app.num_blocks + TopLabel: + height: '48dp' + id: bc_status + text: _("Connected to %d nodes.")% app.num_nodes if app.num_nodes else _("Not connected?") + Widget: + size_hint: 1, 0.1 + TopLabel: + 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 + Widget: + size_hint: 1, 0.1 + BoxLayout: + orientation: 'horizontal' + size_hint: 1, 0.2 + Button: + text: _('Cancel') + size_hint: 0.5, None + height: '48dp' + on_release: popup.dismiss() + Button: + text: _('OK') + size_hint: 0.5, None + height: '48dp' + on_release: + root.callback(root.cp_height, root.cp_value) + popup.dismiss() +''') + +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/checkpoint_dialog.py b/gui/kivy/uix/dialogs/checkpoint_dialog.py @@ -1,88 +0,0 @@ -from kivy.app import App -from kivy.factory import Factory -from kivy.properties import ObjectProperty -from kivy.lang import Builder - -from electrum.i18n import _ - -Builder.load_string(''' -#:import _ electrum_gui.kivy.i18n._ - -<CheckpointDialog@Popup> - id: popup - title: _('Blockchain') - size_hint: 1, 1 - cp_height: 0 - cp_value: '' - - BoxLayout: - orientation: 'vertical' - padding: '10dp' - spacing: '10dp' - TopLabel: - height: '48dp' - id: bc_height - text: _("Verified headers: %d blocks.")% app.num_blocks - TopLabel: - height: '48dp' - id: bc_status - text: _("Connected to %d nodes.")% app.num_nodes if app.num_nodes else _("Not connected?") - 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.") - 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 - Button: - text: _('Cancel') - size_hint: 0.5, None - height: '48dp' - on_release: popup.dismiss() - Button: - text: _('OK') - size_hint: 0.5, None - height: '48dp' - on_release: - root.callback(root.cp_height, root.cp_value) - popup.dismiss() -''') - -class CheckpointDialog(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 diff --git 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 @@ -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 @@ -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 @@ -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: