Skip to content

Introducing an improved IP Address and Port input control in ProxySettings component #391

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/qml/bitcoin_qml.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<file>controls/Header.qml</file>
<file>controls/Icon.qml</file>
<file>controls/InformationPage.qml</file>
<file>controls/IPAddressValueInput.qml</file>
<file>controls/NavButton.qml</file>
<file>controls/PageIndicator.qml</file>
<file>controls/NavigationBar.qml</file>
Expand Down
51 changes: 38 additions & 13 deletions src/qml/components/ProxySettings.qml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,18 @@ ColumnLayout {
}
Separator { Layout.fillWidth: true }
Setting {
id: defaultProxyEnable
Layout.fillWidth: true
header: qsTr("Enable")
actionItem: OptionSwitch {}
actionItem: OptionSwitch {
onCheckedChanged: {
if (checked == false) {
defaultProxy.state = "DISABLED"
} else {
defaultProxy.state = "FILLED"
}
}
}
onClicked: {
loadedItem.toggle()
loadedItem.toggled()
Expand All @@ -33,14 +42,18 @@ ColumnLayout {
id: defaultProxy
Layout.fillWidth: true
header: qsTr("IP and Port")
actionItem: ValueInput {
errorText: qsTr("Invalid IP address or port format. Please use the format '255.255.255.255:65535'.")
state: !defaultProxyEnable.loadedItem.checked ? "DISABLED" : "FILLED"
showErrorText: !defaultProxy.loadedItem.validInput && defaultProxyEnable.loadedItem.checked
actionItem: IPAddressValueInput {
parentState: defaultProxy.state
description: "127.0.0.1:9050"
onEditingFinished: {
defaultProxy.forceActiveFocus()
}
activeFocusOnTab: true
}
onClicked: {
loadedItem.filled = true
loadedItem.forceActiveFocus()
}
onClicked: loadedItem.forceActiveFocus()
}
Separator { Layout.fillWidth: true }
Header {
Expand All @@ -55,10 +68,18 @@ ColumnLayout {
}
Separator { Layout.fillWidth: true }
Setting {
id: torProxyEnable
Layout.fillWidth: true
header: qsTr("Enable")
actionItem: OptionSwitch {}
description: qsTr("When disabled, Tor connections will use the default proxy (if enabled).")
actionItem: OptionSwitch {
onCheckedChanged: {
if (checked == false) {
torProxy.state = "DISABLED"
} else {
torProxy.state = "FILLED"
}
}
}
onClicked: {
loadedItem.toggle()
loadedItem.toggled()
Expand All @@ -69,14 +90,18 @@ ColumnLayout {
id: torProxy
Layout.fillWidth: true
header: qsTr("IP and Port")
actionItem: ValueInput {
errorText: qsTr("Invalid IP address or port format. Please use the format '255.255.255.255:65535'.")
state: !torProxyEnable.loadedItem.checked ? "DISABLED" : "FILLED"
showErrorText: !torProxy.loadedItem.validInput && torProxyEnable.loadedItem.checked
actionItem: IPAddressValueInput {
parentState: torProxy.state
description: "127.0.0.1:9050"
onEditingFinished: {
torProxy.forceActiveFocus()
}
activeFocusOnTab: true
}
onClicked: {
loadedItem.filled = true
loadedItem.forceActiveFocus()
}
onClicked: loadedItem.forceActiveFocus()
}
Separator { Layout.fillWidth: true }
}
82 changes: 82 additions & 0 deletions src/qml/controls/IPAddressValueInput.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// 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
// Expose a property to indicate validity, initial value will be true (no error message displayed)
property bool validInput: true
enabled: true
state: root.parentState
validator: RegExpValidator { regExp: /[0-9.:]*/ } // Allow only digits, dots, and colons

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;
}

// Connections element to ensure validation on editing finished
Connections {
target: root
function onTextChanged() {
// Validate the input whenever editing is finished
validInput = isValidIPPort(root.text);
}
}
}