forked from qt-creator/qt-creator
QmlDesigner: Add a property search feature in the property view
- add the PropertySearchBar control responsible for all property search logic - add the PropertySearchBar control to the PropertyEditorPane - add properties that support searching for items in the hierarchy - add properties/states that support changing visibility - add resetView function call in property editor view Task-number: QDS-14709 Fixes: QDS-14806 Fixes: QDS-14807 Fixes: QDS-14808 Change-Id: I2e2477c95e592c7e49e13c25f0bb204de6bdb38d Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io> Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
@@ -186,6 +186,8 @@ Section {
|
||||
spacing: 1
|
||||
|
||||
Section {
|
||||
readonly property bool __isInEffectsSection: true // used by property search logic
|
||||
|
||||
sectionHeight: 37
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -232,6 +234,8 @@ Section {
|
||||
}
|
||||
|
||||
Section {
|
||||
readonly property bool __isInEffectsSection: true // used by property search logic
|
||||
|
||||
sectionHeight: 37
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -305,6 +309,8 @@ Section {
|
||||
Section {
|
||||
id: delegate
|
||||
|
||||
readonly property bool __isInEffectsSection: true // used by property search logic
|
||||
|
||||
property QtObject wrapper: modelNodeBackend.registerSubSelectionWrapper(modelData)
|
||||
property bool wasExpanded: false
|
||||
|
||||
|
@@ -90,9 +90,17 @@ PropertyEditorPane {
|
||||
|
||||
StudioControls.TabButton {
|
||||
text: backendValues.__classNamePrivateInternal.value
|
||||
onClicked: () => {
|
||||
if (itemPane.searchBar.hasDoneSearch)
|
||||
itemPane.searchBar.search();
|
||||
}
|
||||
}
|
||||
StudioControls.TabButton {
|
||||
text: qsTr("Layout")
|
||||
onClicked: () => {
|
||||
if (itemPane.searchBar.hasDoneSearch)
|
||||
itemPane.searchBar.search();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -13,6 +13,11 @@ Rectangle {
|
||||
height: 400
|
||||
color: StudioTheme.Values.themePanelBackground
|
||||
|
||||
// Called from C++ to clear the search when the selected node changes
|
||||
function clearSearch() {
|
||||
// The function is empty, because it is a placeholder to match other panes
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: mainColumn
|
||||
anchors.fill: parent
|
||||
|
@@ -507,6 +507,8 @@ Section {
|
||||
}
|
||||
|
||||
PropertyLabel {
|
||||
readonly property bool __inDynamicPropertiesSection: true
|
||||
|
||||
text: propertyName
|
||||
tooltip: propertyType
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
@@ -18,6 +18,7 @@ Rectangle {
|
||||
|
||||
default property alias content: mainColumn.children
|
||||
property alias scrollView: mainScrollView
|
||||
property alias searchBar: propertySearchBar
|
||||
|
||||
property bool headerDocked: false
|
||||
readonly property Item headerItem: headerDocked ? dockedHeaderLoader.item : undockedHeaderLoader.item
|
||||
@@ -29,10 +30,23 @@ Rectangle {
|
||||
Controller.closeContextMenu()
|
||||
}
|
||||
|
||||
// Called from C++ to clear the search when the selected node changes
|
||||
function clearSearch() {
|
||||
propertySearchBar.clear();
|
||||
}
|
||||
|
||||
PropertySearchBar {
|
||||
id: propertySearchBar
|
||||
|
||||
contentItem: mainColumn
|
||||
width: parent.width
|
||||
z: parent.z + 1
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: dockedHeaderLoader
|
||||
|
||||
anchors.top: itemPane.top
|
||||
anchors.top: propertySearchBar.bottom
|
||||
z: parent.z + 1
|
||||
height: item ? item.implicitHeight : 0
|
||||
width: parent.width
|
||||
@@ -123,6 +137,16 @@ Rectangle {
|
||||
HeaderBackground{}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 10
|
||||
|
||||
visible: propertySearchBar.hasDoneSearch && !propertySearchBar.hasMatchSearch
|
||||
text: qsTr("No match found.")
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
font.pixelSize: StudioTheme.Values.baseFont
|
||||
}
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
|
||||
|
@@ -9,10 +9,13 @@ import StudioTheme 1.0 as StudioTheme
|
||||
T.Label {
|
||||
id: label
|
||||
|
||||
readonly property bool __isPropertyLabel: true // used by property search logic
|
||||
|
||||
property alias tooltip: toolTipArea.tooltip
|
||||
|
||||
property bool blockedByContext: false
|
||||
property bool blockedByTemplate: false // MCU
|
||||
property bool searchNoMatch: false
|
||||
|
||||
width: StudioTheme.Values.propertyLabelWidth
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
@@ -34,6 +37,14 @@ T.Label {
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "searchNoMatch"
|
||||
when: searchNoMatch
|
||||
PropertyChanges {
|
||||
target: label
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "disabled"
|
||||
when: !label.enabled && !(label.blockedByContext || label.blockedByTemplate)
|
||||
|
@@ -0,0 +1,135 @@
|
||||
// Copyright (C) 2025 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
import QtQuick
|
||||
import StudioControls as StudioControls
|
||||
import StudioTheme as StudioTheme
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
readonly property alias hasMatchSearch: internal.matched
|
||||
readonly property alias hasDoneSearch: internal.searched
|
||||
|
||||
property Item contentItem: null
|
||||
|
||||
color: StudioTheme.Values.themeToolbarBackground
|
||||
height: StudioTheme.Values.toolbarHeight
|
||||
|
||||
function clear() {
|
||||
internal.clear();
|
||||
searchBox.text = "";
|
||||
internal.timer.stop();
|
||||
}
|
||||
|
||||
function search() {
|
||||
internal.search();
|
||||
}
|
||||
|
||||
StudioControls.SearchBox {
|
||||
id: searchBox
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: StudioTheme.Values.toolbarVerticalMargin
|
||||
anchors.topMargin: StudioTheme.Values.toolbarVerticalMargin
|
||||
anchors.leftMargin: StudioTheme.Values.toolbarHorizontalMargin
|
||||
anchors.rightMargin: StudioTheme.Values.toolbarHorizontalMargin
|
||||
style: StudioTheme.Values.searchControlStyle
|
||||
onSearchChanged: internal.timer.restart()
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: internal
|
||||
|
||||
readonly property var reverts: []
|
||||
readonly property Timer timer: Timer {
|
||||
interval: 300
|
||||
repeat: false
|
||||
|
||||
onTriggered: internal.search()
|
||||
}
|
||||
property bool matched: false
|
||||
property bool searched: false
|
||||
|
||||
function clear() {
|
||||
internal.reverts.forEach(revert => revert());
|
||||
internal.reverts.length = 0;
|
||||
internal.matched = false;
|
||||
internal.searched = false;
|
||||
}
|
||||
|
||||
function search() {
|
||||
internal.clear();
|
||||
const searchText = searchBox.text.toLowerCase();
|
||||
if (searchText.length > 0) {
|
||||
internal.traverse(root.contentItem, searchText);
|
||||
internal.searched = true;
|
||||
}
|
||||
}
|
||||
|
||||
function disableSearchNoMatchAction(item) {
|
||||
item.searchNoMatch = true;
|
||||
internal.reverts.push(() => {
|
||||
item.searchNoMatch = false;
|
||||
});
|
||||
}
|
||||
|
||||
function disableVisibleAction(item) {
|
||||
item.visible = false;
|
||||
internal.reverts.push(() => {
|
||||
item.visible = true;
|
||||
});
|
||||
}
|
||||
|
||||
function enableSearchHideAction(item) {
|
||||
item.searchHide = true;
|
||||
internal.reverts.push(() => {
|
||||
item.searchHide = false;
|
||||
});
|
||||
}
|
||||
|
||||
function expandSectionAction(item) {
|
||||
internal.matched = true;
|
||||
const prevValue = item.expanded;
|
||||
item.expanded = true;
|
||||
internal.reverts.push(() => {
|
||||
item.expanded = prevValue;
|
||||
});
|
||||
}
|
||||
|
||||
function traverse(item, searchText) {
|
||||
let hideSection = true;
|
||||
let hideParentSection = true;
|
||||
item.children.forEach((child, index, arr) => {
|
||||
if (!child.visible)
|
||||
return;
|
||||
|
||||
if (child.__isPropertyLabel) {
|
||||
const propertyLabel = child;
|
||||
const text = propertyLabel.text.toLowerCase();
|
||||
if (!text.includes(searchText)) {
|
||||
internal.disableSearchNoMatchAction(propertyLabel);
|
||||
const action = propertyLabel.__inDynamicPropertiesSection ? internal.disableVisibleAction
|
||||
: internal.disableSearchNoMatchAction;
|
||||
const nextItem = arr[index + 1];
|
||||
action(nextItem);
|
||||
} else {
|
||||
hideSection = false;
|
||||
}
|
||||
}
|
||||
hideSection &= internal.traverse(child, searchText);
|
||||
if (child.__isSection) {
|
||||
const action = hideSection ? internal.enableSearchHideAction
|
||||
: internal.expandSectionAction;
|
||||
action(child);
|
||||
|
||||
if (child.__isInEffectsSection && !hideSection)
|
||||
hideParentSection = false;
|
||||
|
||||
hideSection = true;
|
||||
}
|
||||
});
|
||||
return hideParentSection && hideSection;
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,6 +5,21 @@ import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
property bool searchNoMatch: false
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "searchNoMatch"
|
||||
when: searchNoMatch
|
||||
PropertyChanges {
|
||||
target: root
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -10,6 +10,8 @@ import StudioTheme as StudioTheme
|
||||
Item {
|
||||
id: section
|
||||
|
||||
readonly property bool __isSection: true // used by property search logic
|
||||
|
||||
property string caption: "Title"
|
||||
property color labelColor: StudioTheme.Values.themeTextColor
|
||||
property int labelCapitalization: Font.AllUppercase
|
||||
@@ -58,6 +60,7 @@ Item {
|
||||
property bool dropEnabled: false
|
||||
property bool highlight: false
|
||||
property bool eyeEnabled: true // eye button enabled (on)
|
||||
property bool searchHide: false
|
||||
|
||||
property bool useDefaulContextMenu: true
|
||||
|
||||
@@ -343,6 +346,14 @@ Item {
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "Hide"
|
||||
when: section.searchHide
|
||||
PropertyChanges {
|
||||
target: section
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "Collapsed"
|
||||
when: !section.expanded
|
||||
|
@@ -446,8 +446,10 @@ void PropertyEditorView::resetView()
|
||||
|
||||
setupQmlBackend();
|
||||
|
||||
if (m_qmlBackEndForCurrentType)
|
||||
if (m_qmlBackEndForCurrentType) {
|
||||
m_qmlBackEndForCurrentType->emitSelectionChanged();
|
||||
QMetaObject::invokeMethod(m_qmlBackEndForCurrentType->widget()->rootObject(), "clearSearch");
|
||||
}
|
||||
|
||||
m_locked = false;
|
||||
|
||||
|
Reference in New Issue
Block a user