feat: changes to Network selector

This commit is contained in:
Dario Gabriel Lipicar 2025-02-06 18:00:49 -03:00
parent 8f38e10d0a
commit 1bd1021d34
No known key found for this signature in database
GPG Key ID: 9625E9494309D203
13 changed files with 238 additions and 98 deletions

View File

@ -23,6 +23,18 @@ SplitView {
orientation: Qt.Vertical orientation: Qt.Vertical
SplitView.fillWidth: true SplitView.fillWidth: true
QtObject {
id: d
property SortFilterProxyModel networksModel: SortFilterProxyModel {
sourceModel: NetworksModel.flatNetworks
filters: IndexFilter {
minimumIndex: 0
maximumIndex: singleActiveNetworkCheckBox.checked ? 0 : -1
}
}
}
Item { Item {
id: container id: container
@ -34,11 +46,11 @@ SplitView {
anchors.centerIn: parent anchors.centerIn: parent
flatNetworks: NetworksModel.flatNetworks flatNetworks: d.networksModel
multiSelection: multiSelectionCheckBox.checked multiSelection: multiSelectionCheckBox.checked
showAllSelectedText: ctrlShowAllSelectedText.checked
showTitle: ctrlShowTitle.checked showTitle: ctrlShowTitle.checked
showManageNetworksButton: ctrlShowManageNetworksButton.checked
selectionAllowed: selectionAllowedCheckBox.checked selectionAllowed: selectionAllowedCheckBox.checked
showSelectionIndicator: (ctrlShowCheckBoxes.checked && multiSelection) || (ctrlShowRadioButtons.checked && !multiSelection) showSelectionIndicator: (ctrlShowCheckBoxes.checked && multiSelection) || (ctrlShowRadioButtons.checked && !multiSelection)
} }
@ -88,9 +100,8 @@ SplitView {
} }
CheckBox { CheckBox {
id: ctrlShowAllSelectedText id: ctrlShowManageNetworksButton
text: "Show 'All networks' text" text: "Show 'Manage networks' button"
visible: multiSelectionCheckBox.checked
checked: true checked: true
} }
@ -100,6 +111,12 @@ SplitView {
checked: true checked: true
} }
CheckBox {
id: singleActiveNetworkCheckBox
text: "Single active network"
checked: false
}
ColumnLayout { ColumnLayout {
visible: !multiSelectionCheckBox.checked visible: !multiSelectionCheckBox.checked
Label { Label {

View File

@ -5,6 +5,7 @@ import QtQuick.Layouts 1.15
import StatusQ 0.1 import StatusQ 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Popups 0.1 import StatusQ.Popups 0.1
import utils 1.0 import utils 1.0
@ -30,39 +31,61 @@ SplitView {
anchors.fill: parent anchors.fill: parent
// Leave some space so that the popup will be opened without accounting for Layer RowLayout {
ColumnLayout { Layout.fillWidth: true
Layout.maximumHeight: 50
}
NetworkFilter { // Dummy item to make space for popup
id: networkFilter Item {
id: popupPlaceholder
Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: networkSelectPopup.implicitWidth
Layout.preferredHeight: networkSelectPopup.implicitHeight
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
flatNetworks: availableNetworks NetworkSelectPopup {
multiSelection: multiSelectionCheckbox.checked id: networkSelectPopup
} flatNetworks: d.activeNetworks
multiSelection: multiSelectionCheckbox.checked
showManageNetworksButton: showManageNetworksButtonCheckbox.checked
closePolicy: Popup.NoAutoClose
visible: true
Binding on selection {
value: d.selection
}
onToggleNetwork: d.toggleNetworkEnabled(chainId)
}
}
// Dummy item to make space for popup // Filler
Item { ColumnLayout {
id: popupPlaceholder Layout.preferredHeight: 100
Layout.maximumHeight: 100
}
Layout.preferredWidth: networkSelectPopup.implicitWidth Item {
Layout.preferredHeight: networkSelectPopup.implicitHeight id: filterPlaceholder
NetworkSelectPopup { Layout.preferredWidth: networkFilter.implicitWidth
id: networkSelectPopup Layout.preferredHeight: networkFilter.implicitHeight
flatNetworks: availableNetworks Layout.alignment: Qt.AlignRight | Qt.AlignTop
multiSelection: multiSelectionCheckbox.checked
closePolicy: Popup.NoAutoClose NetworkFilter {
visible: true id: networkFilter
flatNetworks: d.activeNetworks
multiSelection: multiSelectionCheckbox.checked
showManageNetworksButton: showManageNetworksButtonCheckbox.checked
Binding on selection {
value: d.selection
}
onToggleNetwork: d.toggleNetworkEnabled(chainId)
}
} }
} }
ColumnLayout { ColumnLayout {
Layout.preferredHeight: 30 Layout.preferredHeight: 100
Layout.maximumHeight: 30 Layout.maximumHeight: 100
} }
RowLayout { RowLayout {
@ -77,7 +100,7 @@ SplitView {
ModelEntry { ModelEntry {
id: selectedEntry id: selectedEntry
sourceModel: availableNetworks sourceModel: d.activeNetworks
key: "chainId" key: "chainId"
} }
} }
@ -97,7 +120,7 @@ SplitView {
active: false active: false
sourceComponent: NetworkSelectPopup { sourceComponent: NetworkSelectPopup {
flatNetworks: availableNetworks flatNetworks: d.activeNetworks
selection: selectedEntry.available ? [selectedEntry.value] : [] selection: selectedEntry.available ? [selectedEntry.value] : []
onClosed: selectPopupLoader.active = false onClosed: selectPopupLoader.active = false
@ -117,7 +140,7 @@ SplitView {
} }
} }
Pane { Pane {
SplitView.minimumWidth: 300 SplitView.minimumWidth: 400
SplitView.fillWidth: true SplitView.fillWidth: true
SplitView.minimumHeight: 300 SplitView.minimumHeight: 300
@ -130,7 +153,7 @@ SplitView {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
model: availableNetworks model: d.availableNetworks
delegate: ItemDelegate { delegate: ItemDelegate {
required property var model required property var model
@ -144,28 +167,26 @@ SplitView {
id: delegateRowLayout id: delegateRowLayout
anchors.fill: parent anchors.fill: parent
Column { ColumnLayout {
Layout.margins: 5 Layout.margins: 5
spacing: 3 spacing: 3
Label { text: model.chainName } Label { text: model.chainName }
Row { RowLayout {
spacing: 5 spacing: 5
Label { text: `<b>${model.shortName}</b>` } Label { text: `<b>${model.shortName}</b>` }
Label { text: `ID <b>${model.chainId}</b>` } Label { text: `ID <b>${model.chainId}</b>` }
CheckBox { CheckBox {
checkState: networkSelectPopup.selection.includes(model.chainId) ? Qt.Checked : Qt.Unchecked text: "Enabled"
onToggled: { checkState: model.isEnabled ? Qt.Checked : Qt.Unchecked
let currentSelection = networkSelectPopup.selection onToggled: d.setIsEnabled(model.chainId, checkState === Qt.Checked)
if (checkState === Qt.Checked && !currentSelection.includes(model.chainId)) { }
currentSelection.push(model.chainId) CheckBox {
} else { text: "Active"
currentSelection = currentSelection.filter(id => id !== model.chainId) checkState: model.isActive ? Qt.Checked : Qt.Unchecked
} onToggled: d.setIsActive(model.chainId, checkState === Qt.Checked)
networkSelectPopup.selection = [...currentSelection]
}
} }
} }
} }
@ -181,6 +202,16 @@ SplitView {
checked: true checked: true
} }
CheckBox {
id: showManageNetworksButtonCheckbox
Layout.margins: 5
text: "Show 'Manage networks' button"
checked: true
}
CheckBox { CheckBox {
id: testModeCheckbox id: testModeCheckbox
@ -201,12 +232,55 @@ SplitView {
} }
} }
SortFilterProxyModel { QtObject {
id: availableNetworks id: d
sourceModel: NetworksModel.flatNetworks function toggleNetworkEnabled(chainId) {
filters: ValueFilter { roleName: "isTest"; value: testModeCheckbox.checked; } let isEnabled = ModelUtils.getByKey(NetworksModel.flatNetworks, "chainId", chainId, "isEnabled")
d.setIsEnabled(chainId, !isEnabled)
}
function setIsEnabled(chainId, value) {
let index = ModelUtils.indexOf(NetworksModel.flatNetworks, "chainId", chainId)
NetworksModel.flatNetworks.setProperty(index, "isEnabled", value)
}
function setIsActive(chainId, value) {
let index = ModelUtils.indexOf(NetworksModel.flatNetworks, "chainId", chainId)
NetworksModel.flatNetworks.setProperty(index, "isActive", value)
}
readonly property var availableNetworks: SortFilterProxyModel {
sourceModel: NetworksModel.flatNetworks
filters: [
ValueFilter { roleName: "isTest"; value: testModeCheckbox.checked; }
]
}
readonly property var activeNetworks: SortFilterProxyModel {
sourceModel: d.availableNetworks
filters: [
ValueFilter { roleName: "isActive"; value: true; }
]
}
readonly property var enabledNetworks: SortFilterProxyModel {
sourceModel: d.activeNetworks
filters: [
ValueFilter { roleName: "isEnabled"; value: true; }
]
}
readonly property var chainIdsAggregator: FunctionAggregator {
model: d.enabledNetworks
initialValue: []
roleName: "chainId"
aggregateFunction: (aggr, value) => [...aggr, value]
}
readonly property var selection: d.chainIdsAggregator.value
} }
} }
// category: Popups // category: Popups

View File

@ -10,7 +10,7 @@ import Models 1.0
Item { Item {
id: root id: root
width: 600 width: 600
height: 400 height: 600
Component { Component {
id: componentUnderTest id: componentUnderTest
@ -69,7 +69,7 @@ Item {
// multi selection - select using the view // multi selection - select using the view
const thirdDelegate = findChild(controlUnderTest.contentItem, "networkSelectorDelegate_" + controlUnderTest.flatNetworks.get(2).chainName) const thirdDelegate = findChild(controlUnderTest.contentItem, "networkSelectorDelegate_" + controlUnderTest.flatNetworks.get(2).chainName)
mouseClick(thirdDelegate) mouseClick(thirdDelegate)
compare(controlUnderTest.selection, [controlUnderTest.flatNetworks.get(0).chainId, controlUnderTest.flatNetworks.get(1).chainId, controlUnderTest.flatNetworks.get(2).chainId]) compare(controlUnderTest.selection.sort(), [controlUnderTest.flatNetworks.get(0).chainId, controlUnderTest.flatNetworks.get(1).chainId, controlUnderTest.flatNetworks.get(2).chainId].sort())
compare(selectionChangedSpy.count, 4) compare(selectionChangedSpy.count, 4)
} }

View File

@ -165,8 +165,8 @@ Item {
compare(toggleNetworkSpy.count, 1) compare(toggleNetworkSpy.count, 1)
compare(selectionChangedSpy.count, 2) compare(selectionChangedSpy.count, 2)
compare(controlUnderTest.selection.length, 2) compare(controlUnderTest.selection.length, 2)
compare(controlUnderTest.selection[0], controlUnderTest.model.get(1).chainId) verify(controlUnderTest.selection.includes(controlUnderTest.model.get(1).chainId))
compare(controlUnderTest.selection[1], controlUnderTest.model.get(2).chainId) verify(controlUnderTest.selection.includes(controlUnderTest.model.get(2).chainId))
compare(delegate.checkState, Qt.Checked) compare(delegate.checkState, Qt.Checked)
delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(2).chainName) delegate = findChild(controlUnderTest, "networkSelectorDelegate_" + controlUnderTest.model.get(2).chainName)
compare(delegate.checkState, Qt.Checked) compare(delegate.checkState, Qt.Checked)

View File

@ -119,6 +119,8 @@ SettingsContentBase {
root.settingsSubSubsection === Constants.walletSettingsSubsection.manageHidden || root.settingsSubSubsection === Constants.walletSettingsSubsection.manageHidden ||
root.settingsSubSubsection === Constants.walletSettingsSubsection.manageAdvanced root.settingsSubSubsection === Constants.walletSettingsSubsection.manageAdvanced
readonly property bool isManageNetworksSubsection: root.settingsSubSubsection === Constants.walletSettingsSubsection.manageNetworks
readonly property var walletSettings: Settings { readonly property var walletSettings: Settings {
category: "walletSettings-" + root.myPublicKey category: "walletSettings-" + root.myPublicKey
} }
@ -143,6 +145,12 @@ SettingsContentBase {
restoreMode: Binding.RestoreNone restoreMode: Binding.RestoreNone
} }
Binding on currentIndex {
value: root.networksViewIndex
when: root.settingsSubSubsection === Constants.walletSettingsSubsection.manageNetworks
restoreMode: Binding.RestoreNone
}
onCurrentIndexChanged: { onCurrentIndexChanged: {
root.rootStore.backButtonName = "" root.rootStore.backButtonName = ""
root.sectionTitle = root.walletSectionTitle root.sectionTitle = root.walletSectionTitle

View File

@ -50,6 +50,8 @@ Item {
signal dappConnectRequested() signal dappConnectRequested()
signal dappDisconnectRequested(string dappUrl) signal dappDisconnectRequested(string dappUrl)
signal manageNetworksRequested()
// TODO: remove tokenType parameter from signals below // TODO: remove tokenType parameter from signals below
signal sendTokenRequested(string senderAddress, string tokenId, int tokenType) signal sendTokenRequested(string senderAddress, string tokenId, int tokenType)
signal bridgeTokenRequested(string tokenId, int tokenType) signal bridgeTokenRequested(string tokenId, int tokenType)
@ -272,6 +274,8 @@ Item {
onLaunchBuyCryptoModal: d.launchBuyCryptoModal() onLaunchBuyCryptoModal: d.launchBuyCryptoModal()
onSendTokenRequested: root.sendTokenRequested(senderAddress, tokenId, tokenType) onSendTokenRequested: root.sendTokenRequested(senderAddress, tokenId, tokenType)
onManageNetworksRequested: root.manageNetworksRequested()
} }
} }

View File

@ -30,12 +30,13 @@ StatusComboBox {
property bool multiSelection: true property bool multiSelection: true
property bool showSelectionIndicator: true property bool showSelectionIndicator: true
property bool showAllSelectedText: true
property bool showTitle: true property bool showTitle: true
property bool selectionAllowed: true property bool selectionAllowed: true
property bool showManageNetworksButton: false
property var selection: [] property var selection: []
signal toggleNetwork(int chainId, int index) signal toggleNetwork(int chainId, int index)
signal manageNetworksClicked()
onSelectionChanged: { onSelectionChanged: {
if (root.selection !== networkSelectorView.selection) { if (root.selection !== networkSelectorView.selection) {
@ -81,7 +82,7 @@ StatusComboBox {
Row { Row {
id: row id: row
spacing: -4 spacing: -4
visible: (!d.allSelected || !root.showAllSelectedText) && chainRepeater.count > 0 visible: chainRepeater.count > 0
Repeater { Repeater {
id: chainRepeater id: chainRepeater
model: SortFilterProxyModel { model: SortFilterProxyModel {
@ -152,6 +153,7 @@ StatusComboBox {
selectionAllowed: root.selectionAllowed selectionAllowed: root.selectionAllowed
multiSelection: root.multiSelection multiSelection: root.multiSelection
showSelectionIndicator: root.showSelectionIndicator showSelectionIndicator: root.showSelectionIndicator
showManageNetworksButton: root.showManageNetworksButton
selection: root.selection selection: root.selection
onSelectionChanged: { onSelectionChanged: {
@ -161,6 +163,11 @@ StatusComboBox {
} }
onToggleNetwork: root.toggleNetwork(chainId, index) onToggleNetwork: root.toggleNetwork(chainId, index)
onManageNetworksClicked: {
control.popup.close()
root.manageNetworksClicked()
}
} }
Connections { Connections {
@ -175,7 +182,6 @@ StatusComboBox {
QtObject { QtObject {
id: d id: d
readonly property int networksCount: root.flatNetworks.ModelCount.count readonly property int networksCount: root.flatNetworks.ModelCount.count
readonly property bool allSelected: root.selection.length === networksCount
readonly property bool noneSelected: root.selection.length === 0 readonly property bool noneSelected: root.selection.length === 0
readonly property bool oneSelected: root.selection.length === 1 readonly property bool oneSelected: root.selection.length === 1
readonly property bool selectionUnavailable: d.networksCount <= 1 && d.oneSelected readonly property bool selectionUnavailable: d.networksCount <= 1 && d.oneSelected
@ -198,9 +204,6 @@ StatusComboBox {
if (d.noneSelected) { if (d.noneSelected) {
return qsTr("Select networks") return qsTr("Select networks")
} }
if (d.allSelected && root.showAllSelectedText) {
return qsTr("All networks")
}
} }
return "" return ""

View File

@ -41,6 +41,7 @@ Item {
signal dappListRequested() signal dappListRequested()
signal dappConnectRequested() signal dappConnectRequested()
signal dappDisconnectRequested(string dappUrl) signal dappDisconnectRequested(string dappUrl)
signal manageNetworksRequested()
implicitHeight: 88 implicitHeight: 88
@ -177,11 +178,14 @@ Item {
// network filter // network filter
NetworkFilter { NetworkFilter {
id: networkFilter id: networkFilter
showTitle: false
showManageNetworksButton: true
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
flatNetworks: root.walletStore.filteredFlatModel flatNetworks: root.walletStore.filteredFlatModel
onToggleNetwork: root.walletStore.toggleNetwork(chainId) onToggleNetwork: root.walletStore.toggleNetwork(chainId)
onManageNetworksClicked: root.manageNetworksRequested()
Binding on selection { Binding on selection {
value: chainIdsAggregator.value value: chainIdsAggregator.value

View File

@ -1,9 +1,11 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15 import QtGraphicalEffects 1.15
import StatusQ 0.1 import StatusQ 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups.Dialog 0.1 import StatusQ.Popups.Dialog 0.1
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
@ -21,9 +23,11 @@ Popup {
property bool showSelectionIndicator: true property bool showSelectionIndicator: true
property bool selectionAllowed: true property bool selectionAllowed: true
property bool multiSelection: false property bool multiSelection: false
property bool showManageNetworksButton: false
property var selection: [] property var selection: []
signal toggleNetwork(int chainId, int index) signal toggleNetwork(int chainId, int index)
signal manageNetworksClicked()
onSelectionChanged: { onSelectionChanged: {
if (root.selection !== scrollView.selection) { if (root.selection !== scrollView.selection) {
@ -51,25 +55,44 @@ Popup {
} }
} }
contentItem: NetworkSelectorView { contentItem: ColumnLayout {
id: scrollView anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 4
model: root.flatNetworks NetworkSelectorView {
interactive: root.selectionAllowed id: scrollView
multiSelection: root.multiSelection Layout.fillWidth: true
showIndicator: root.showSelectionIndicator
selection: root.selection
onSelectionChanged: { model: root.flatNetworks
if (root.selection !== scrollView.selection) { interactive: root.selectionAllowed
root.selection = scrollView.selection multiSelection: root.multiSelection
showIndicator: root.showSelectionIndicator
selection: root.selection
onSelectionChanged: {
if (root.selection !== scrollView.selection) {
root.selection = scrollView.selection
}
}
onToggleNetwork: {
if (!root.multiSelection && root.closePolicy !== Popup.NoAutoClose)
root.close()
root.toggleNetwork(chainId, index)
} }
} }
onToggleNetwork: { StatusButton {
if (!root.multiSelection && root.closePolicy !== Popup.NoAutoClose) id: manageNetworksButton
root.close() visible: root.showManageNetworksButton
root.toggleNetwork(chainId, index) Layout.fillWidth: true
Layout.margins: 4
icon.name: "settings"
text: qsTr("Manage networks")
isOutline: true
onClicked: root.manageNetworksClicked()
} }
} }
} }

View File

@ -38,26 +38,10 @@ StatusListView {
objectName: "networkSelectorList" objectName: "networkSelectorList"
onSelectionChanged: { onSelectionChanged: d.reprocessSelection()
if (!root.multiSelection && selection.length > 1) { onMultiSelectionChanged: d.reprocessSelection()
console.warn("Warning: Multi-selection is disabled, but multiple items are selected. Automatically selecting the first inserted item.") Component.onCompleted: d.reprocessSelection()
selection = [selection[0]]
}
}
onMultiSelectionChanged: {
if (root.multiSelection) return;
// When changing the multi-selection mode, we need to ensure that the selection is valid
if (root.selection.length > 1) {
root.selection = [root.selection[0]]
}
// Single selection defaults to first item if no selection is made
if (root.selection.length === 0 && root.count > 0) {
root.selection = [ModelUtils.get(root.model, 0).chainId]
}
}
implicitWidth: 300 implicitWidth: 300
implicitHeight: contentHeight implicitHeight: contentHeight
@ -99,7 +83,9 @@ StatusListView {
QtObject { QtObject {
id: d id: d
readonly property bool allSelected: root.selection.length === root.count readonly property int networksCount: root.model.ModelCount.count
onNetworksCountChanged: d.reprocessSelection()
readonly property bool allSelected: root.selection.length === networksCount
function onToggled(initialState, chainId) { function onToggled(initialState, chainId) {
let selection = root.selection let selection = root.selection
@ -114,12 +100,27 @@ StatusListView {
root.selection = [...selection] root.selection = [...selection]
} }
}
Component.onCompleted: { function reprocessSelection() {
if(root.selection.length === 0 && !root.multiSelection && root.count > 0) { let selection = root.selection
const firstChain = ModelUtils.get(root.model, 0).chainId
root.selection = [firstChain] if (d.networksCount === 0) {
selection = []
} else {
if (!root.multiSelection) {
// One and only one chain must be selected
if (selection.length === 0) {
selection = [ModelUtils.get(root.model, 0, "chainId")]
} else if (selection.length > 1) {
console.warn("Warning: Multi-selection is disabled, but multiple items are selected. Automatically selecting the first inserted item.")
selection = [selection[0]]
}
}
}
if (root.selection.sort().join(',') !== selection.sort().join(',')) {
root.selection = [...selection]
}
} }
} }
} }

View File

@ -34,6 +34,7 @@ FocusScope {
signal dappListRequested() signal dappListRequested()
signal dappConnectRequested() signal dappConnectRequested()
signal dappDisconnectRequested(string dappUrl) signal dappDisconnectRequested(string dappUrl)
signal manageNetworksRequested()
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
@ -53,6 +54,7 @@ FocusScope {
onDappListRequested: root.dappListRequested() onDappListRequested: root.dappListRequested()
onDappConnectRequested: root.dappConnectRequested() onDappConnectRequested: root.dappConnectRequested()
onDappDisconnectRequested: (dappUrl) =>root.dappDisconnectRequested(dappUrl) onDappDisconnectRequested: (dappUrl) =>root.dappDisconnectRequested(dappUrl)
onManageNetworksRequested: root.manageNetworksRequested()
} }
Item { Item {

View File

@ -40,6 +40,11 @@ RightTabBaseView {
signal launchSwapModal(string tokensKey) signal launchSwapModal(string tokensKey)
signal sendTokenRequested(string senderAddress, string tokenId, int tokenType) signal sendTokenRequested(string senderAddress, string tokenId, int tokenType)
onManageNetworksRequested: {
Global.changeAppSectionBySectionType(Constants.appSection.profile,
Constants.settingsSubsection.wallet,
Constants.walletSettingsSubsection.manageNetworks)
}
function resetView() { function resetView() {
resetStack() resetStack()

View File

@ -82,7 +82,6 @@ Rectangle {
flatNetworks: root.chainsModel flatNetworks: root.chainsModel
showTitle: true showTitle: true
multiSelection: root.multipleChainSelection multiSelection: root.multipleChainSelection
showAllSelectedText: false
selectionAllowed: false selectionAllowed: false
// disable interactions w/o looking disabled // disable interactions w/o looking disabled