diff --git a/electrum/gui/qml/components/ServerConfigDialog.qml b/electrum/gui/qml/components/ServerConfigDialog.qml index 80cc54a31..9fed62953 100644 --- a/electrum/gui/qml/components/ServerConfigDialog.qml +++ b/electrum/gui/qml/components/ServerConfigDialog.qml @@ -39,6 +39,7 @@ ElDialog { FlatButton { Layout.fillWidth: true text: qsTr('Ok') + enabled: serverconfig.addressValid icon.source: '../../icons/confirmed.png' onClicked: { let auto_connect = serverconfig.serverConnectMode == ServerConnectModeComboBox.Mode.Autoconnect diff --git a/electrum/gui/qml/components/controls/ServerConfig.qml b/electrum/gui/qml/components/controls/ServerConfig.qml index 47bd4e680..aae493bae 100644 --- a/electrum/gui/qml/components/controls/ServerConfig.qml +++ b/electrum/gui/qml/components/controls/ServerConfig.qml @@ -12,6 +12,7 @@ Item { property bool showAutoselectServer: true property alias address: address_tf.text property alias serverConnectMode: server_connect_mode_cb.currentValue + property alias addressValid: address_tf.valid implicitHeight: rootLayout.height @@ -28,6 +29,11 @@ Item { ServerConnectModeComboBox { id: server_connect_mode_cb + onCurrentValueChanged: { + if (currentValue == ServerConnectModeComboBox.Mode.Autoconnect) { + address_tf.text = "" + } + } } Item { @@ -63,6 +69,26 @@ Item { enabled: server_connect_mode_cb.currentValue != ServerConnectModeComboBox.Mode.Autoconnect width: parent.width inputMethodHints: Qt.ImhNoPredictiveText + + property bool valid: true + + function validate() { + if (!enabled) { + valid = true + return + } + valid = Network.isValidServerAddress(address_tf.text) + } + + onTextChanged: validate() + onEnabledChanged: validate() + + Rectangle { + anchors.fill: parent + color: "red" + opacity: 0.2 + visible: !parent.valid + } } } diff --git a/electrum/gui/qml/components/wizard/WCServerConfig.qml b/electrum/gui/qml/components/wizard/WCServerConfig.qml index bc784d1b6..b0ab17082 100644 --- a/electrum/gui/qml/components/wizard/WCServerConfig.qml +++ b/electrum/gui/qml/components/wizard/WCServerConfig.qml @@ -5,7 +5,7 @@ import QtQuick.Controls import "../controls" WizardComponent { - valid: true + valid: sc.addressValid last: true title: qsTr('Server') diff --git a/electrum/gui/qml/qenetwork.py b/electrum/gui/qml/qenetwork.py index b25712d13..fa088822d 100644 --- a/electrum/gui/qml/qenetwork.py +++ b/electrum/gui/qml/qenetwork.py @@ -206,6 +206,10 @@ class QENetwork(QObject, QtEventListener): def server(self): return self._server + @pyqtSlot(str, result=bool) + def isValidServerAddress(self, server: str) -> bool: + return ServerAddr.from_str_with_inference(server) is not None + @pyqtSlot(str, bool, bool) def setServerParameters(self, server_str: str, auto_connect: bool, one_server: bool): net_params = self.network.get_parameters() diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py index ad4ff4823..9545e45b7 100644 --- a/electrum/gui/qt/network_dialog.py +++ b/electrum/gui/qt/network_dialog.py @@ -361,6 +361,8 @@ class ServerWidget(QWidget, QtEventListener): ConnectMode.ONESERVER: messages.MSG_CONNECTMODE_ONESERVER, } + server_e_valid = pyqtSignal(bool) + def __init__(self, network: Network, parent=None): super().__init__(parent) self.network = network @@ -390,6 +392,7 @@ class ServerWidget(QWidget, QtEventListener): grid.addWidget(self.connect_combo, 0, 1, 1, 3) self.server_e = QLineEdit() + self.server_e.textChanged.connect(self.validate_server_e) self.server_e.editingFinished.connect(self.on_server_settings_changed) grid.addWidget(QLabel(_('Server') + ':'), 1, 0) grid.addWidget(self.server_e, 1, 1, 1, 3) @@ -502,6 +505,7 @@ class ServerWidget(QWidget, QtEventListener): self.status_label_header, self.status_label, self.status_label_helpbutton, self.height_label_header, self.height_label, self.height_label_helpbutton]: item.setVisible(self.network._was_started) + self.validate_server_e() msg = _('Fork detection disabled') if self.is_one_server() else '' if self.network._was_started: # Network was started, so we don't run in initial setup wizard. @@ -522,6 +526,15 @@ class ServerWidget(QWidget, QtEventListener): msg += _('Your server is on branch {0} ({1} blocks)').format(name, chain.get_branch_size()) self.split_label.setText(msg) + def validate_server_e(self): + if not self.server_e.isEnabled(): + self.server_e.setStyleSheet("") + self.server_e_valid.emit(True) + return + server = ServerAddr.from_str_with_inference(self.server_e.text()) + self.server_e.setStyleSheet("background-color: rgba(255, 0, 0, 0.2);" if not server else "") + self.server_e_valid.emit(server is not None) + def update_from_config(self): auto_connect = self.config.NETWORK_AUTO_CONNECT one_server = self.config.NETWORK_ONESERVER diff --git a/electrum/gui/qt/wizard/server_connect.py b/electrum/gui/qt/wizard/server_connect.py index c7a5f3d2b..19b403d13 100644 --- a/electrum/gui/qt/wizard/server_connect.py +++ b/electrum/gui/qt/wizard/server_connect.py @@ -91,7 +91,10 @@ class WCServerConfig(WizardComponent): WizardComponent.__init__(self, parent, wizard, title=_('Server')) self.sw = ServerWidget(wizard._daemon.network, self) self.layout().addWidget(self.sw) - self._valid = True + self.sw.server_e_valid.connect(self.on_server_e_valid) + + def on_server_e_valid(self, valid): + self.valid = valid def apply(self): self.wizard_data['autoconnect'] = self.sw.server_e.text().strip() == '' diff --git a/electrum/wizard.py b/electrum/wizard.py index f15ea6de5..a71e2dba7 100644 --- a/electrum/wizard.py +++ b/electrum/wizard.py @@ -826,7 +826,7 @@ class ServerConnectWizard(AbstractWizard): self.navmap = { 'welcome': { 'next': lambda d: 'proxy_config' if d['want_proxy'] else 'server_config', - 'accept': self.do_configure_autoconnect, + 'accept': lambda d: self.do_enable_autoconnect(d) if d['autoconnect'] else None, 'last': lambda d: bool(d['autoconnect'] and not d['want_proxy']) }, 'proxy_config': { @@ -855,23 +855,27 @@ class ServerConnectWizard(AbstractWizard): def do_configure_server(self, wizard_data: dict): self._logger.debug(f'configuring server: {wizard_data!r}') net_params = self._daemon.network.get_parameters() - server = '' + server = None oneserver = wizard_data.get('one_server', False) if not wizard_data['autoconnect']: - try: - server = ServerAddr.from_str_with_inference(wizard_data['server']) - if not server: - raise Exception('failed to parse server %s' % wizard_data['server']) - except Exception: - return - net_params = net_params._replace(server=server, auto_connect=wizard_data['autoconnect'], oneserver=oneserver) + server = ServerAddr.from_str_with_inference(wizard_data.get('server', '')) + if not server: + self._logger.warn('failed to parse server %s' % wizard_data.get('server', '')) + return # Network._start() will set autoconnect and default server + net_params = net_params._replace( + server=server or net_params.server, + auto_connect=wizard_data['autoconnect'], + oneserver=oneserver, + ) self._daemon.network.run_from_another_thread(self._daemon.network.set_parameters(net_params)) - def do_configure_autoconnect(self, wizard_data: dict): - self._logger.debug(f'configuring autoconnect: {wizard_data!r}') + def do_enable_autoconnect(self, wizard_data: dict): + # NETWORK_AUTO_CONNECT will only get explicitly set True, 'autoconnect': False means + # the user requested manual server configuration + self._logger.debug(f'enabling autoconnect: {wizard_data!r}') + assert wizard_data.get('autoconnect'), wizard_data if self._daemon.config.cv.NETWORK_AUTO_CONNECT.is_modifiable(): - if wizard_data.get('autoconnect') is not None: - self._daemon.config.NETWORK_AUTO_CONNECT = wizard_data.get('autoconnect') + self._daemon.config.NETWORK_AUTO_CONNECT = True def start(self, *, start_viewstate: WizardViewState = None) -> WizardViewState: self.reset() diff --git a/tests/test_wizard.py b/tests/test_wizard.py index 6bbab88c9..7c5b740a8 100644 --- a/tests/test_wizard.py +++ b/tests/test_wizard.py @@ -81,7 +81,7 @@ class ServerConnectWizardTestCase(WizardTestCase): self.assertFalse(w.is_last_view(v_init.view, d)) v = w.resolve_next(v_init.view, d) self.assertEqual('server_config', v.view) - self.assertEqual(False, self.config.NETWORK_AUTO_CONNECT) + self.assertFalse(self.config.cv.NETWORK_AUTO_CONNECT.is_set()) async def test_proxy(self): w = ServerConnectWizard(DaemonMock(self.config)) @@ -110,7 +110,7 @@ class ServerConnectWizardTestCase(WizardTestCase): self.assertFalse(w.is_last_view(v_init.view, d)) v = w.resolve_next(v_init.view, d) self.assertEqual('proxy_config', v.view) - self.assertEqual(False, self.config.NETWORK_AUTO_CONNECT) + self.assertFalse(self.config.cv.NETWORK_AUTO_CONNECT.is_set()) d_proxy = {'enabled': False} d.update({'proxy': d_proxy}) v = w.resolve_next(v.view, d)