Skip to content

Commit b6bf91f

Browse files
committed
Merge #391: Introducing an improved IP Address and Port input control in ProxySettings component
d47bc1a qml, control: new IPAddressValueInput (pablomartin4btc) a7e1b25 qml, component: correct ProxySettings behaviour (pablomartin4btc) Pull request description: Adding a new improved `ValueInput` control to handle both `IP` address and port values combined in the same field (as it was designed for `QML`, current `QT` project have them separately) with the correct visual formatting (`255.255.255.255:65535`). In order to add/ change the `IP` address and port value, user needs to enable the fields (currently in `main` branch this doesn't work and this PR fixes it). Currently the `IP` address and port input fields don't allow more than 5 digits and this PR also correct it so the user can enter a complete `IP` address and port value. <details> <summary>If an invalid <code>IP</code> address (e.g. <code>127.0.:9050</code>) and/ or port (e.g. <code>127.0.0.1:</code>) are entered an error message will be shown as we do in other fields.</summary> ![image](https://github.com/bitcoin-core/gui-qml/assets/110166421/7c7b85ef-5be7-436a-b4a5-3747d79ed563) </details> <details> <summary>Current <code>master</code>/ <code>main</code> branch screenshot.</summary> ![image](https://github.com/bitcoin-core/gui-qml/assets/110166421/fbd09cec-7edf-49ac-bfc3-7dbc48d0e842) </details> <details> <summary>Current master branch in <code>QT</code> <a href="https://github.com/bitcoin-core/gui">repo</a> (just for reference).</summary> ![image](https://github.com/bitcoin-core/gui-qml/assets/110166421/e96f64a6-be55-4a01-be89-7bef37d65c5c) </details> <details> <summary>This PRs branch screenshot.</summary> ![image](https://github.com/bitcoin-core/gui-qml/assets/110166421/3c31c728-42aa-4751-9ff7-4bd411d46396) </details> <details> <summary><a href="https://www.figma.com/file/ek8w3n3upbluw5UL2lGhRx/Bitcoin-Core-App-Design?type=design&node-id=7635-19847&mode=design&t=Ns1KRBputDp3sBjs-0">Design</a> in Figma.</summary> ![image](https://github.com/bitcoin-core/gui-qml/assets/110166421/0fd0b112-1166-463a-85d7-a2bae6d68e7e) </details> --- **Implementation details**: In this iteration, validation for this control is implemented using both a function within the control itself focused solely on ensuring correct formatting and a simple regex validator for the valid input characters. Future versions will see integration with network model classes, enabling parsing of IP addresses and ports and additional checks, such as those outlined in [`doc/p2p-bad-ports.md`](https://github.com/bitcoin/bitcoin/tree/master/doc/p2p-bad-ports.md). **Note**: Persistence/ wiring of the settings and creating the proper network connection thru the Proxy will be performed in a different PR, the idea is to isolate UI experience/ testing in case we need to make changes on that aspect separately and we concentrate on the functionality behind later. ACKs for top commit: johnny9: ACK d47bc1a Tree-SHA512: f471a40696e5fcf0ba60808f379c5f89fbecc714971b7d69aa91ad38a002fe8d33accd0138ddb9b34c7a2fdd3ab30001088dbb8c74a3b6b9a91c700bcacd7455
2 parents be965bf + d47bc1a commit b6bf91f

File tree

3 files changed

+121
-13
lines changed

3 files changed

+121
-13
lines changed

src/qml/bitcoin_qml.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<file>controls/Header.qml</file>
2727
<file>controls/Icon.qml</file>
2828
<file>controls/InformationPage.qml</file>
29+
<file>controls/IPAddressValueInput.qml</file>
2930
<file>controls/NavButton.qml</file>
3031
<file>controls/PageIndicator.qml</file>
3132
<file>controls/NavigationBar.qml</file>

src/qml/components/ProxySettings.qml

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,18 @@ ColumnLayout {
2020
}
2121
Separator { Layout.fillWidth: true }
2222
Setting {
23+
id: defaultProxyEnable
2324
Layout.fillWidth: true
2425
header: qsTr("Enable")
25-
actionItem: OptionSwitch {}
26+
actionItem: OptionSwitch {
27+
onCheckedChanged: {
28+
if (checked == false) {
29+
defaultProxy.state = "DISABLED"
30+
} else {
31+
defaultProxy.state = "FILLED"
32+
}
33+
}
34+
}
2635
onClicked: {
2736
loadedItem.toggle()
2837
loadedItem.toggled()
@@ -33,14 +42,18 @@ ColumnLayout {
3342
id: defaultProxy
3443
Layout.fillWidth: true
3544
header: qsTr("IP and Port")
36-
actionItem: ValueInput {
45+
errorText: qsTr("Invalid IP address or port format. Please use the format '255.255.255.255:65535'.")
46+
state: !defaultProxyEnable.loadedItem.checked ? "DISABLED" : "FILLED"
47+
showErrorText: !defaultProxy.loadedItem.validInput && defaultProxyEnable.loadedItem.checked
48+
actionItem: IPAddressValueInput {
3749
parentState: defaultProxy.state
3850
description: "127.0.0.1:9050"
39-
onEditingFinished: {
40-
defaultProxy.forceActiveFocus()
41-
}
51+
activeFocusOnTab: true
52+
}
53+
onClicked: {
54+
loadedItem.filled = true
55+
loadedItem.forceActiveFocus()
4256
}
43-
onClicked: loadedItem.forceActiveFocus()
4457
}
4558
Separator { Layout.fillWidth: true }
4659
Header {
@@ -55,10 +68,18 @@ ColumnLayout {
5568
}
5669
Separator { Layout.fillWidth: true }
5770
Setting {
71+
id: torProxyEnable
5872
Layout.fillWidth: true
5973
header: qsTr("Enable")
60-
actionItem: OptionSwitch {}
61-
description: qsTr("When disabled, Tor connections will use the default proxy (if enabled).")
74+
actionItem: OptionSwitch {
75+
onCheckedChanged: {
76+
if (checked == false) {
77+
torProxy.state = "DISABLED"
78+
} else {
79+
torProxy.state = "FILLED"
80+
}
81+
}
82+
}
6283
onClicked: {
6384
loadedItem.toggle()
6485
loadedItem.toggled()
@@ -69,14 +90,18 @@ ColumnLayout {
6990
id: torProxy
7091
Layout.fillWidth: true
7192
header: qsTr("IP and Port")
72-
actionItem: ValueInput {
93+
errorText: qsTr("Invalid IP address or port format. Please use the format '255.255.255.255:65535'.")
94+
state: !torProxyEnable.loadedItem.checked ? "DISABLED" : "FILLED"
95+
showErrorText: !torProxy.loadedItem.validInput && torProxyEnable.loadedItem.checked
96+
actionItem: IPAddressValueInput {
7397
parentState: torProxy.state
7498
description: "127.0.0.1:9050"
75-
onEditingFinished: {
76-
torProxy.forceActiveFocus()
77-
}
99+
activeFocusOnTab: true
100+
}
101+
onClicked: {
102+
loadedItem.filled = true
103+
loadedItem.forceActiveFocus()
78104
}
79-
onClicked: loadedItem.forceActiveFocus()
80105
}
81106
Separator { Layout.fillWidth: true }
82107
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) 2024 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
import QtQuick 2.15
6+
import QtQuick.Controls 2.15
7+
8+
TextInput {
9+
id: root
10+
required property string parentState
11+
property string description: ""
12+
property bool filled: false
13+
property int descriptionSize: 18
14+
property color textColor: root.filled ? Theme.color.neutral9 : Theme.color.neutral5
15+
// Expose a property to indicate validity, initial value will be true (no error message displayed)
16+
property bool validInput: true
17+
enabled: true
18+
state: root.parentState
19+
validator: RegExpValidator { regExp: /[0-9.:]*/ } // Allow only digits, dots, and colons
20+
21+
maximumLength: 21
22+
23+
states: [
24+
State {
25+
name: "ACTIVE"
26+
PropertyChanges { target: root; textColor: Theme.color.orange }
27+
},
28+
State {
29+
name: "HOVER"
30+
PropertyChanges {
31+
target: root
32+
textColor: root.filled ? Theme.color.orangeLight1 : Theme.color.neutral5
33+
}
34+
},
35+
State {
36+
name: "DISABLED"
37+
PropertyChanges {
38+
target: root
39+
enabled: false
40+
textColor: Theme.color.neutral4
41+
}
42+
}
43+
]
44+
45+
font.family: "Inter"
46+
font.styleName: "Regular"
47+
font.pixelSize: root.descriptionSize
48+
color: root.textColor
49+
text: root.description
50+
horizontalAlignment: Text.AlignRight
51+
wrapMode: Text.WordWrap
52+
53+
Behavior on color {
54+
ColorAnimation { duration: 150 }
55+
}
56+
57+
function isValidIPPort(input)
58+
{
59+
var parts = input.split(":");
60+
if (parts.length !== 2) return false;
61+
if (parts[1].length === 0) return false; // port part is empty
62+
var ipAddress = parts[0];
63+
var ipAddressParts = ipAddress.split(".");
64+
if (ipAddressParts.length !== 4) return false;
65+
for (var i = 0; (i < ipAddressParts.length); i++) {
66+
if (ipAddressParts[i].length === 0) return false; // ip group number part is empty
67+
if (parseInt(ipAddressParts[i]) > 255) return false;
68+
}
69+
var port = parseInt(parts[1]);
70+
if (port < 1 || port > 65535) return false;
71+
return true;
72+
}
73+
74+
// Connections element to ensure validation on editing finished
75+
Connections {
76+
target: root
77+
function onTextChanged() {
78+
// Validate the input whenever editing is finished
79+
validInput = isValidIPPort(root.text);
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)