diff --git a/applications/filebrowser/package/contents/ui/Browser.qml b/applications/filebrowser/package/contents/ui/Browser.qml index 8f32e8c5..b1af19af 100644 --- a/applications/filebrowser/package/contents/ui/Browser.qml +++ b/applications/filebrowser/package/contents/ui/Browser.qml @@ -1,302 +1,304 @@ /* * Copyright 2011 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 1.1 import org.kde.metadatamodels 0.1 as MetadataModels import org.kde.plasma.components 0.1 as PlasmaComponents import org.kde.plasma.core 0.1 as PlasmaCore import org.kde.plasma.mobilecomponents 0.1 as MobileComponents import org.kde.plasma.slccomponents 0.1 as SlcComponents import org.kde.draganddrop 1.0 import org.kde.qtextracomponents 0.1 import org.kde.dirmodel 0.1 PlasmaComponents.Page { anchors { fill: parent topMargin: toolBar.height } tools: Item { width: parent.width height: childrenRect.height PlasmaCore.DataSource { id: hotplugSource engine: "hotplug" connectedSources: sources } PlasmaCore.DataSource { id: devicesSource engine: "soliddevice" connectedSources: hotplugSource.sources onDataChanged: { //access it here due to the async nature of the dataengine if (resultsGrid.model != dirModel && devicesSource.data[devicesTabBar.currentUdi]["File Path"] != "") { dirModel.url = devicesSource.data[devicesTabBar.currentUdi]["File Path"] resultsGrid.model = dirModel } } } PlasmaCore.DataModel { id: devicesModel dataSource: hotplugSource } DirModel { id: dirModel onUrlChanged: { breadCrumb.path = url.substr(devicesSource.data[devicesTabBar.currentUdi]["File Path"].length) } } Breadcrumb { id: breadCrumb onPathChanged: { dirModel.url = devicesSource.data[devicesTabBar.currentUdi]["File Path"] + path } anchors { left: parent.left right: searchBox.left verticalCenter: parent.verticalCenter leftMargin: y } } PlasmaComponents.TabBar { id: devicesTabBar anchors { right: parent.right verticalCenter: parent.verticalCenter rightMargin: y } height: theme.largeIconSize width: height * tabCount property int tabCount: 1 property string currentUdi function updateSize() { var visibleChildCount = devicesTabBar.layout.children.length for (var i = 0; i < devicesTabBar.layout.children.length; ++i) { if (!devicesTabBar.layout.children[i].visible || devicesTabBar.layout.children[i].text === undefined) { --visibleChildCount } } devicesTabBar.tabCount = visibleChildCount } opacity: tabCount > 1 ? 1 : 0 Behavior on opacity { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } PlasmaComponents.TabButton { id: localButton height: width property bool current: devicesTabBar.currentTab == localButton iconSource: "drive-harddisk" onCurrentChanged: { if (current) { resultsGrid.model = metadataModel //nepomuk db, not filesystem devicesTabBar.currentUdi = "" } } } Repeater { id: devicesRepeater model: devicesModel onCountChanged: devicesTabBar.updateSize() delegate: PlasmaComponents.TabButton { id: removableButton visible: devicesSource.data[udi]["Removable"] == true onVisibleChanged: devicesTabBar.updateSize() iconSource: model["icon"] property bool current: devicesTabBar.currentTab == removableButton onCurrentChanged: { if (current) { devicesTabBar.currentUdi = udi if (devicesSource.data[udi]["Accessible"]) { dirModel.url = devicesSource.data[devicesTabBar.currentUdi]["File Path"] resultsGrid.model = dirModel } else { var service = devicesSource.serviceForSource(udi); var operation = service.operationDescription("mount"); service.startOperationCall(operation); } } } } } } MobileComponents.ViewSearch { id: searchBox anchors.centerIn: parent onSearchQueryChanged: { metadataModel.extraParameters["nfo:fileName"] = searchBox.searchQuery + busy = (searchBox.searchQuery.length > 0) } } } ListModel { id: selectedModel } //For some reason onCountChanged doesn't get binded directly in ListModel Connections { target: selectedModel onCountChanged: { var newUrls = new Array() for (var i = 0; i < selectedModel.count; ++i) { newUrls[i] = selectedModel.get(i).url } dragArea.mimeData.urls = newUrls } } Connections { target: metadataModel onModelReset: selectedModel.clear() + onCountChanged: { searchBox.restartBusyTimer() } } //This pinch area is for selection PinchArea { id: pinchArea property bool selecting: false property int selectingX property int selectingY pinch.target: parent onPinchStarted: { //hotspot to start select procedures print("point1: " + pinch.point1.x + " " + pinch.point1.y) print("Selecting") selecting = true selectingX = pinch.point2.x selectingY = pinch.point2.y } onPinchUpdated: { if (selecting) { print("Selected" + resultsGrid.childAt(pinch.point2.x, pinch.point2.y)) selectingX = pinch.point2.x selectingY = pinch.point2.y } } onPinchFinished: selecting = false anchors.fill: parent DragArea { id: dragArea anchors.fill: parent //startDragDistance: 200 enabled: false mimeData { source: parent } onDrop: enabled = false MouseEventListener { anchors.fill: parent onPressed: startY = mouse.y onPositionChanged: { if (selectedModel.count > 0 && Math.abs(mouse.y - startY) > 200) { parent.enabled = true } } MobileComponents.IconGrid { id: resultsGrid anchors.fill: parent model: metadataModel delegate: Item { id: resourceDelegate width: resultsGrid.delegateWidth height: resultsGrid.delegateHeight PlasmaCore.FrameSvgItem { id: highlightFrame imagePath: "widgets/viewitem" prefix: "selected+hover" anchors.fill: parent property bool contains: (pinchArea.selectingX > resourceDelegate.x && pinchArea.selectingX < resourceDelegate.x + resourceDelegate.width) && (pinchArea.selectingY > resourceDelegate.y && pinchArea.selectingY < resourceDelegate.y + resourceDelegate.height) opacity: 0 Behavior on opacity { NumberAnimation {duration: 250} } onContainsChanged: { if (contains) { for (var i = 0; i < selectedModel.count; ++i) { if ((model.url && model.url == selectedModel.get(i).url)) { opacity = 0 selectedModel.remove(i) return } } selectedModel.append({"url": model.url}) opacity = 1 } } } MobileComponents.ResourceDelegate { className: model["className"] ? model["className"] : "" genericClassName: (resultsGrid.model == metadataModel) ? (model["genericClassName"] ? model["genericClassName"] : "") : "FileDataObject" width: resultsGrid.delegateWidth height: resultsGrid.delegateHeight infoLabelVisible: false onClicked: Qt.openUrlExternally(model.url) } } } } } } ParallelAnimation { id: positionAnim property Item target property int x property int y NumberAnimation { target: positionAnim.target to: positionAnim.y properties: "y" duration: 250 easing.type: Easing.InOutQuad } NumberAnimation { target: positionAnim.target to: positionAnim.x properties: "x" duration: 250 easing.type: Easing.InOutQuad } } } diff --git a/applications/imageviewer/package/contents/ui/Browser.qml b/applications/imageviewer/package/contents/ui/Browser.qml index 2600669c..2356edfd 100644 --- a/applications/imageviewer/package/contents/ui/Browser.qml +++ b/applications/imageviewer/package/contents/ui/Browser.qml @@ -1,131 +1,137 @@ /* * Copyright 2011 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 1.0 import org.kde.metadatamodels 0.1 as MetadataModels import org.kde.plasma.components 0.1 as PlasmaComponents import org.kde.plasma.core 0.1 as PlasmaCore import org.kde.plasma.mobilecomponents 0.1 as MobileComponents import org.kde.plasma.slccomponents 0.1 as SlcComponents import org.kde.qtextracomponents 0.1 PlasmaComponents.Page { anchors { fill: parent topMargin: toolBar.height } tools: Item { width: parent.width height: childrenRect.height PlasmaCore.DataSource { id: hotplugSource engine: "hotplug" connectedSources: sources } PlasmaCore.DataSource { id: devicesSource engine: "soliddevice" connectedSources: hotplugSource.sources } PlasmaCore.DataModel { id: devicesModel dataSource: hotplugSource } Row { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter spacing: 8 opacity: (imageViewer.state == "browsing") ? 1 : 0 MobileComponents.IconButton { icon: QIcon("drive-harddisk") opacity: resultsGrid.model == metadataModel ? 0.2 : 1 width: 48 height: 48 onClicked: { resultsGrid.model = metadataModel } } Repeater { model: devicesModel MobileComponents.IconButton { id: deviceButton icon: QIcon(model["icon"]) //FIXME: use the declarative branch in workspace that tells about removable visible: devicesSource.data[udi]["Removable"] == true opacity: (dirModel.url == devicesSource.data[udi]["File Path"] && resultsGrid.model == dirModel) ? 1 : 0.2 width: 48 height: 48 onClicked: { dirModel.url = devicesSource.data[udi]["File Path"] resultsGrid.model = dirModel } } } } MobileComponents.ViewSearch { id: searchBox anchors { left: parent.left right: parent.right verticalCenter: parent.verticalCenter } onSearchQueryChanged: { metadataModel.extraParameters["nfo:fileName"] = searchBox.searchQuery + busy = (searchBox.searchQuery.length > 0) } } } + Connections { + target: metadataModel + onCountChanged: { searchBox.restartBusyTimer() } + } + MobileComponents.IconGrid { id: resultsGrid anchors.fill: parent model: metadataModel delegateWidth: 130 delegateHeight: 120 delegate: MobileComponents.ResourceDelegate { id: resourceDelegate className: model["className"]?model["className"]:"Image" width: 130 height: 120 infoLabelVisible: false property string label: model["label"]?model["label"]:model["display"] onPressAndHold: { resourceInstance.uri = model["url"]?model["url"]:model["resourceUri"] resourceInstance.title = model["label"] } onClicked: { if (mimeType == "inode/directory") { dirModel.url = model["url"] resultsGrid.model = dirModel } else if (!mainStack.busy) { loadImage(model["url"]) } } } } } diff --git a/applications/videoplayer/package/contents/ui/Toolbar.qml b/applications/videoplayer/package/contents/ui/Toolbar.qml index 0e2d8de1..c98e3061 100644 --- a/applications/videoplayer/package/contents/ui/Toolbar.qml +++ b/applications/videoplayer/package/contents/ui/Toolbar.qml @@ -1,124 +1,129 @@ /* * Copyright 2011 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 1.0 import org.kde.plasma.core 0.1 as PlasmaCore import org.kde.plasma.mobilecomponents 0.1 as MobileComponents import org.kde.qtextracomponents 0.1 PlasmaCore.FrameSvgItem { id: toolbar anchors { left: parent.left right: parent.right } signal zoomIn() signal zoomOut() height: childrenRect.height + margins.bottom imagePath: "widgets/frame" prefix: "raised" enabledBorders: "BottomBorder" z: 9000 Behavior on y { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } QIconItem { icon: QIcon("go-previous") width: 48 height: 48 opacity: viewer.scale==1?1:0 anchors.verticalCenter: parent.verticalCenter Behavior on opacity { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } MouseArea { anchors.fill: parent onClicked: imageViewer.state = "browsing" } } Text { text: i18n("%1 of %2", fullList.currentIndex+1, fullList.count) anchors.centerIn: parent font.pointSize: 14 font.bold: true color: theme.textColor visible: viewer.scale==1 style: Text.Raised styleColor: theme.backgroundColor } MobileComponents.ViewSearch { id: searchBox anchors { left: parent.left right:parent.right top:parent.top } onSearchQueryChanged: { filterModel.filterRegExp = ".*"+searchBox.searchQuery+".*" + busy = (searchBox.searchQuery.length > 0) + } + Connections { + target: filterModel + onCountChanged: { searchBox.restartBusyTimer() } } opacity: viewer.scale==1?0:1 Behavior on opacity { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } } PlasmaCore.Svg { id: iconsSvg imagePath: "widgets/configuration-icons" } Row { opacity: viewer.scale==1?1:0 Behavior on opacity { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } anchors { verticalCenter: parent.verticalCenter right: parent.right } //TODO: ad hoc icons MobileComponents.ActionButton { svg: iconsSvg elementId: "add" onClicked: { toolbar.zoomIn() } } MobileComponents.ActionButton { svg: iconsSvg elementId: "remove" onClicked: { toolbar.zoomOut() } } } } diff --git a/components/mobilecomponents/ViewSearch.qml b/components/mobilecomponents/ViewSearch.qml index bddd5efe..d4db80b3 100644 --- a/components/mobilecomponents/ViewSearch.qml +++ b/components/mobilecomponents/ViewSearch.qml @@ -1,58 +1,81 @@ /* Copyright 2011 Marco Martin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 1.0 import org.kde.plasma.core 0.1 as PlasmaCore import org.kde.plasma.components 0.1 as PlasmaComponents import org.kde.qtextracomponents 0.1 import org.kde.plasma.mobilecomponents 0.1 as MobileComponents Item { id: searchFieldContainer property string searchQuery property int delay : 100 + property bool busy: false onSearchQueryChanged: { searchField.text = searchQuery } width: searchField.width height: searchField.height PlasmaComponents.TextField { id : searchField placeholderText: i18n("Search...") clearButtonShown: true anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter onTextChanged: searchTimer.restart() } + PlasmaComponents.BusyIndicator { + anchors.verticalCenter: searchField.verticalCenter + anchors.right: searchField.right + anchors.rightMargin: searchFieldContainer.height + height: searchField.height + width: searchField.height + visible: searchFieldContainer.busy + running: searchFieldContainer.busy + } + + function restartBusyTimer() { + busyTimer.restart() + } + + Timer { + id: busyTimer + repeat: false + interval: 1000 + running: false + onTriggered: { searchFieldContainer.busy = false } + } + Timer { id: searchTimer interval: delay running: false repeat: false onTriggered: searchQuery = searchField.text } } diff --git a/qmlpackages/launcher/contents/ui/main.qml b/qmlpackages/launcher/contents/ui/main.qml index d3dc31be..e74a9bb1 100644 --- a/qmlpackages/launcher/contents/ui/main.qml +++ b/qmlpackages/launcher/contents/ui/main.qml @@ -1,170 +1,173 @@ /* Copyright 2010 Marco Martin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 1.0 import org.kde.plasma.components 0.1 as PlasmaComponents import org.kde.plasma.core 0.1 as PlasmaCore import org.kde.plasma.mobilecomponents 0.1 as MobileComponents import org.kde.plasma.slccomponents 0.1 as SlcComponents import org.kde.runnermodel 0.1 as RunnerModels MouseArea { id: main width: 400 height: 150 //just to hide the keyboard onClicked: main.forceActiveFocus() signal itemLaunched function resetStatus() { searchField.searchQuery = "" appGrid.currentPage = 0 } Image { id: background width: parent.width * 1.5 height:parent.height source: "image://appbackgrounds/contextarea" fillMode: Image.Tile x: -((width-parent.width) * (appGrid.currentPage / appGrid.pagesCount)) Behavior on x { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } } MobileComponents.ResourceInstance { id: resourceInstance } SlcComponents.SlcMenu { id: contextMenu } PlasmaCore.Theme { id: theme } PlasmaCore.DataSource { id: appsSource engine: "org.kde.active.apps" connectedSources: ["Apps"] } PlasmaCore.SortFilterModel { id: appsModel sourceModel: PlasmaCore.DataModel { keyRoleFilter: ".*" dataSource: appsSource } sortRole: "name" } RunnerModels.RunnerModel { id: runnerModel runners: [ "services", "nepomuksearch", "recentdocuments", "desktopsessions" , "PowerDevil", "calculator" ] + onCountChanged: { searchField.restartBusyTimer() } } MobileComponents.ViewSearch { id: searchField anchors { left: parent.left right: parent.right top: parent.top topMargin: 8 } onSearchQueryChanged: { if (searchQuery.length < 3) { //HACK: assigning null makes the view really discard the old model and assign the new one appGrid.model = null appGrid.model = appsModel runnerModel.query = "" + busy = false } else { appGrid.model = runnerModel runnerModel.query = searchQuery + busy = true } } Component.onCompleted: { delay = 10 } } MobileComponents.IconGrid { id: appGrid model: appsModel onCurrentPageChanged: resourceInstance.uri = "" delegate: Component { MobileComponents.ResourceDelegate { id: launcherDelegate width: appGrid.delegateWidth height: appGrid.delegateHeight className: "FileDataObject" genericClassName: "FileDataObject" property string label: model["name"] ? model["name"] : model["label"] //property string mimeType: model["mimeType"] ? model["mimeType"] : "application/x-desktop" onPressAndHold: ParallelAnimation { MobileComponents.ReleasedAnimation { targetItem: launcherDelegate } ScriptAction { script: { resourceInstance.uri = model["resourceUri"] ? model["resourceUri"] : model["entryPath"] resourceInstance.title = model["name"] ? model["name"] : model["text"] } } } onClicked: { //showing apps model? if (searchField.searchQuery == "") { var service = appsSource.serviceForSource(appsSource.connectedSources[0]) var operation = service.operationDescription("launch") operation["Path"] = model["entryPath"] service.startOperationCall(operation) } else { runnerModel.run(index) } resetStatus() itemLaunched() } onPressed: MobileComponents.PressedAnimation { targetItem: launcherDelegate } onReleased: MobileComponents.ReleasedAnimation { targetItem: launcherDelegate } } } anchors { left: parent.left right: parent.right top: searchField.bottom bottom: parent.bottom topMargin: 6 bottomMargin: 4 } } } diff --git a/shell/widgetsexplorer/package/contents/ui/view.qml b/shell/widgetsexplorer/package/contents/ui/view.qml index 1e867a81..fd9af259 100644 --- a/shell/widgetsexplorer/package/contents/ui/view.qml +++ b/shell/widgetsexplorer/package/contents/ui/view.qml @@ -1,167 +1,171 @@ /* * Copyright 2010 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 1.0 import org.kde.plasma.components 0.1 as PlasmaComponents import org.kde.plasma.core 0.1 as PlasmaCore import org.kde.plasma.mobilecomponents 0.1 as MobileComponents import org.kde.metadatamodels 0.1 as MetadataModels import org.kde.runnermodel 0.1 as RunnerModels MobileComponents.Sheet { id: widgetsExplorer objectName: "widgetsExplorer" title: i18n("Add items") acceptButtonText: i18n("Add items") rejectButtonText: i18n("Close") signal addAppletRequested(string plugin) signal closeRequested function addItems() { var service = metadataSource.serviceForSource("") var operation = service.operationDescription("connectToActivity") operation["ActivityUrl"] = activitySource.data["Status"]["Current"] for (var i = 0; i < selectedModel.count; ++i) { var item = selectedModel.get(i) if (item.resourceUri) { operation["ResourceUrl"] = item.resourceUri service.startOperationCall(operation) } else if (item.pluginName) { widgetsExplorer.addAppletRequested(item.pluginName) } } } //used only toexplicitly close the keyboard TextInput { id: inputPanelController; width:0; height:0} PlasmaCore.DataSource { id: metadataSource engine: "org.kde.active.metadata" } Binding { target: acceptButton property: "enabled" value: selectedModel.count > 0 } onAccepted: { widgetsExplorer.addItems() } onStatusChanged: { if (status == PlasmaComponents.DialogStatus.Closed) { closeRequested() } } ListModel { id: selectedModel } MetadataModels.MetadataUserTypes { id: userTypes } MetadataModels.MetadataCloudModel { id: cloudModel cloudCategory: "rdf:type" allowedCategories: userTypes.userTypes } PlasmaCore.DataSource { id: activitySource engine: "org.kde.activities" connectedSources: ["Status"] interval: 0 } Component.onCompleted: open() /*Timer { running: true interval: 100 onTriggered: open() }*/ content: [ MobileComponents.ViewSearch { id: searchField MobileComponents.IconButton { icon: QIcon("go-previous") width: 32 height: 32 onClicked: { searchField.searchQuery = "" stack.pop() } opacity: stack.depth > 1 ? 1 : 0 Behavior on opacity { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } } anchors { left: parent.left right: parent.right top: parent.top } onSearchQueryChanged: { if (stack.depth == 1 && searchQuery.length > 3) { stack.push(globalSearchComponent) + busy = true + } else { + busy = false } } }, MenuTabBar { id: tabBar }, PlasmaComponents.PageStack { id: stack clip: true anchors { left: parent.left right: parent.right top: tabBar.bottom bottom: parent.bottom } initialPage: tabBar.startComponent } ] Component { id: globalSearchComponent ResourceBrowser { model: MetadataModels.MetadataModel { id: runnerModel queryString: searchField.searchQuery.length > 3 ? searchField.searchQuery : "" onQueryStringChanged: { if (searchField.searchQuery.length <= 3) { stack.pop() } } + onCountChanged: { searchField.restartBusyTimer() } } } } }