From 1abecf25c92a3084e99939982db438a1057cc9c3 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 24 Jan 2021 06:45:48 +0100 Subject: [PATCH] qt block explorer: allow custom URL The QTextEdit expects values such as (one per line): ``` https://blockstream.info/testnet/ https://192.168.0.38:3021/ ("https://blockstream.info/testnet/", {'tx': 'tx/', 'addr': 'address/'}) ``` closes #4831 --- electrum/gui/qt/settings_dialog.py | 44 +++++++++++++++++++++++++----- electrum/util.py | 36 ++++++++++++++++++++---- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/electrum/gui/qt/settings_dialog.py b/electrum/gui/qt/settings_dialog.py index 5c8ab414d..57945cb38 100644 --- a/electrum/gui/qt/settings_dialog.py +++ b/electrum/gui/qt/settings_dialog.py @@ -23,13 +23,14 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import ast from typing import Optional, TYPE_CHECKING from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QComboBox, QTabWidget, QSpinBox, QFileDialog, QCheckBox, QLabel, QVBoxLayout, QGridLayout, QLineEdit, - QPushButton, QWidget) + QPushButton, QWidget, QHBoxLayout) from electrum.i18n import _ from electrum import util, coinchooser, paymentrequest @@ -328,16 +329,45 @@ Use this if you want your local watchtower to keep running after you close your tx_widgets.append((outrounding_cb, None)) block_explorers = sorted(util.block_explorer_info().keys()) + BLOCK_EX_CUSTOM_ITEM = _("Custom URL") + if BLOCK_EX_CUSTOM_ITEM in block_explorers: # malicious translation? + block_explorers.remove(BLOCK_EX_CUSTOM_ITEM) + block_explorers.append(BLOCK_EX_CUSTOM_ITEM) msg = _('Choose which online block explorer to use for functions that open a web browser') block_ex_label = HelpLabel(_('Online Block Explorer') + ':', msg) block_ex_combo = QComboBox() + block_ex_custom_e = QLineEdit(self.config.get('block_explorer_custom') or '') block_ex_combo.addItems(block_explorers) - block_ex_combo.setCurrentIndex(block_ex_combo.findText(util.block_explorer(self.config))) - def on_be(x): - be_result = block_explorers[block_ex_combo.currentIndex()] - self.config.set_key('block_explorer', be_result, True) - block_ex_combo.currentIndexChanged.connect(on_be) - tx_widgets.append((block_ex_label, block_ex_combo)) + block_ex_combo.setCurrentIndex( + block_ex_combo.findText(util.block_explorer(self.config) or BLOCK_EX_CUSTOM_ITEM)) + def showhide_block_ex_custom_e(): + block_ex_custom_e.setVisible(block_ex_combo.currentText() == BLOCK_EX_CUSTOM_ITEM) + showhide_block_ex_custom_e() + def on_be_combo(x): + if block_ex_combo.currentText() == BLOCK_EX_CUSTOM_ITEM: + on_be_edit() + else: + be_result = block_explorers[block_ex_combo.currentIndex()] + self.config.set_key('block_explorer_custom', None, False) + self.config.set_key('block_explorer', be_result, True) + showhide_block_ex_custom_e() + block_ex_combo.currentIndexChanged.connect(on_be_combo) + def on_be_edit(): + val = block_ex_custom_e.text() + try: + val = ast.literal_eval(val) # to also accept tuples + except: + pass + self.config.set_key('block_explorer_custom', val) + block_ex_custom_e.editingFinished.connect(on_be_edit) + block_ex_hbox = QHBoxLayout() + block_ex_hbox.setContentsMargins(0, 0, 0, 0) + block_ex_hbox.setSpacing(0) + block_ex_hbox.addWidget(block_ex_combo) + block_ex_hbox.addWidget(block_ex_custom_e) + block_ex_hbox_w = QWidget() + block_ex_hbox_w.setLayout(block_ex_hbox) + tx_widgets.append((block_ex_label, block_ex_hbox_w)) # Fiat Currency hist_checkbox = QCheckBox() diff --git a/electrum/util.py b/electrum/util.py index 404e41075..aad0e209a 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -796,19 +796,43 @@ testnet_block_explorers = { {'tx': 'tx/', 'addr': 'address/'}), } +_block_explorer_default_api_loc = {'tx': 'tx/', 'addr': 'address/'} + + def block_explorer_info(): from . import constants return mainnet_block_explorers if not constants.net.TESTNET else testnet_block_explorers -def block_explorer(config: 'SimpleConfig') -> str: - from . import constants + +def block_explorer(config: 'SimpleConfig') -> Optional[str]: + """Returns name of selected block explorer, + or None if a custom one (not among hardcoded ones) is configured. + """ + if config.get('block_explorer_custom') is not None: + return None default_ = 'Blockstream.info' be_key = config.get('block_explorer', default_) - be = block_explorer_info().get(be_key) - return be_key if be is not None else default_ + be_tuple = block_explorer_info().get(be_key) + if be_tuple is None: + be_key = default_ + assert isinstance(be_key, str), f"{be_key!r} should be str" + return be_key + def block_explorer_tuple(config: 'SimpleConfig') -> Optional[Tuple[str, dict]]: - return block_explorer_info().get(block_explorer(config)) + custom_be = config.get('block_explorer_custom') + if custom_be: + if isinstance(custom_be, str): + return custom_be, _block_explorer_default_api_loc + if isinstance(custom_be, (tuple, list)) and len(custom_be) == 2: + return tuple(custom_be) + _logger.warning(f"not using 'block_explorer_custom' from config. " + f"expected a str or a pair but got {custom_be!r}") + return None + else: + # using one of the hardcoded block explorers + return block_explorer_info().get(block_explorer(config)) + def block_explorer_URL(config: 'SimpleConfig', kind: str, item: str) -> Optional[str]: be_tuple = block_explorer_tuple(config) @@ -818,6 +842,8 @@ def block_explorer_URL(config: 'SimpleConfig', kind: str, item: str) -> Optional kind_str = explorer_dict.get(kind) if kind_str is None: return + if explorer_url[-1] != "/": + explorer_url += "/" url_parts = [explorer_url, kind_str, item] return ''.join(url_parts)