diff --git a/src/qml/bitcoin_qml.qrc b/src/qml/bitcoin_qml.qrc
index ae36776d5a..1c98e6dc6d 100644
--- a/src/qml/bitcoin_qml.qrc
+++ b/src/qml/bitcoin_qml.qrc
@@ -26,6 +26,7 @@
controls/Header.qml
controls/Icon.qml
controls/InformationPage.qml
+ controls/IPAddressValueInput.qml
controls/NavButton.qml
controls/PageIndicator.qml
controls/NavigationBar.qml
diff --git a/src/qml/components/ProxySettings.qml b/src/qml/components/ProxySettings.qml
index bb4ee6d6d8..ca479edc78 100644
--- a/src/qml/components/ProxySettings.qml
+++ b/src/qml/components/ProxySettings.qml
@@ -43,15 +43,25 @@ ColumnLayout {
Layout.fillWidth: true
header: qsTr("IP and Port")
errorText: qsTr("Invalid IP address or port format. Please use the format '255.255.255.255:65535'.")
+ showErrorText: !loadedItem.isValidIPPort(loadedItem.description)
state: !defaultProxyEnable.loadedItem.checked ? "DISABLED" : "ACTIVE"
- actionItem: ValueInput {
+ actionItem: IPAddressValueInput {
parentState: defaultProxy.state
description: "127.0.0.1:9050"
+ activeFocusOnTab: true
onEditingFinished: {
- defaultProxy.forceActiveFocus()
+ if (isValidIPPort(text)) {
+ defaultProxy.forceActiveFocus()
+ defaultProxy.showErrorText = false
+ } else {
+ defaultProxy.showErrorText = true
+ }
}
}
- onClicked: loadedItem.forceActiveFocus()
+ onClicked: {
+ loadedItem.filled = true
+ loadedItem.forceActiveFocus()
+ }
}
Separator { Layout.fillWidth: true }
Header {
@@ -90,15 +100,24 @@ ColumnLayout {
Layout.fillWidth: true
header: qsTr("IP and Port")
errorText: qsTr("Invalid IP address or port format. Please use the format '255.255.255.255:65535'.")
+ showErrorText: !loadedItem.isValidIPPort(loadedItem.description)
state: !torProxyEnable.loadedItem.checked ? "DISABLED" : "ACTIVE"
- actionItem: ValueInput {
+ actionItem: IPAddressValueInput {
parentState: torProxy.state
description: "127.0.0.1:9050"
onEditingFinished: {
- torProxy.forceActiveFocus()
+ if (isValidIPPort(text)) {
+ torProxy.forceActiveFocus()
+ torProxy.showErrorText = false
+ } else {
+ torProxy.showErrorText = true
+ }
}
}
- onClicked: loadedItem.forceActiveFocus()
+ onClicked: {
+ loadedItem.filled = true
+ loadedItem.forceActiveFocus()
+ }
}
Separator { Layout.fillWidth: true }
}
diff --git a/src/qml/controls/IPAddressValueInput.qml b/src/qml/controls/IPAddressValueInput.qml
new file mode 100644
index 0000000000..d8edef299d
--- /dev/null
+++ b/src/qml/controls/IPAddressValueInput.qml
@@ -0,0 +1,72 @@
+// Copyright (c) 2024 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+TextInput {
+ id: root
+ required property string parentState
+ property string description: ""
+ property bool filled: false
+ property int descriptionSize: 18
+ property color textColor: root.filled ? Theme.color.neutral9 : Theme.color.neutral5
+ enabled: true
+ state: root.parentState
+ validator: RegExpValidator { regExp: /[0-9.:]*/ } // Allow only digits, dots, and colons
+ selectByMouse: true // Enable text selection with the mouse
+
+ maximumLength: 21
+
+ states: [
+ State {
+ name: "ACTIVE"
+ PropertyChanges { target: root; textColor: Theme.color.orange }
+ },
+ State {
+ name: "HOVER"
+ PropertyChanges {
+ target: root
+ textColor: root.filled ? Theme.color.orangeLight1 : Theme.color.neutral5
+ }
+ },
+ State {
+ name: "DISABLED"
+ PropertyChanges {
+ target: root
+ enabled: false
+ textColor: Theme.color.neutral4
+ }
+ }
+ ]
+
+ font.family: "Inter"
+ font.styleName: "Regular"
+ font.pixelSize: root.descriptionSize
+ color: root.textColor
+ text: root.description
+ horizontalAlignment: Text.AlignRight
+ wrapMode: Text.WordWrap
+
+ Behavior on color {
+ ColorAnimation { duration: 150 }
+ }
+
+ function isValidIPPort(input)
+ {
+ var parts = input.split(":");
+ if (parts.length !== 2) return false;
+ if (parts[1].length === 0) return false; // port part is empty
+ var ipAddress = parts[0];
+ var ipAddressParts = ipAddress.split(".");
+ if (ipAddressParts.length !== 4) return false;
+ for (var i = 0; (i < ipAddressParts.length); i++) {
+ if (ipAddressParts[i].length === 0) return false; // ip group number part is empty
+ if (parseInt(ipAddressParts[i]) > 255) return false;
+ }
+ var port = parseInt(parts[1]);
+ if (port < 1 || port > 65535) return false;
+ return true;
+ }
+}