diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/EffectsSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/EffectsSection.qml index 635c2fd539d..4da7d9fb5d3 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/EffectsSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/EffectsSection.qml @@ -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 diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml index 9e80c293192..536ba9dd90d 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml @@ -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(); + } } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml index 3ec325d8f14..c797860c762 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/emptyPane.qml @@ -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 diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml index dea81cab815..88237093c66 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml @@ -507,6 +507,8 @@ Section { } PropertyLabel { + readonly property bool __inDynamicPropertiesSection: true + text: propertyName tooltip: propertyType Layout.alignment: Qt.AlignTop diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyEditorPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyEditorPane.qml index fa0def82c7d..192c53cc5ce 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyEditorPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyEditorPane.qml @@ -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 diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyLabel.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyLabel.qml index 85bded2924c..85874946b65 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyLabel.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertyLabel.qml @@ -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) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertySearchBar.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertySearchBar.qml new file mode 100644 index 00000000000..0083eac6355 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/PropertySearchBar.qml @@ -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; + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SecondColumnLayout.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SecondColumnLayout.qml index e35317d0147..fa8d9a7e5c2 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SecondColumnLayout.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/SecondColumnLayout.qml @@ -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 + } + } + ] } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml index a24edc4904d..5af99822c71 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml @@ -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 diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index 4f1626203cd..09ddc373e7c 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -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;