Skip to content

Commit

Permalink
Merge #391: Introducing an improved IP Address and Port input control…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
hebasto committed Mar 27, 2024
2 parents be965bf + d47bc1a commit b6bf91f
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 13 deletions.
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);
}
}
}

0 comments on commit b6bf91f

Please sign in to comment.