Commit f7197141 authored by eekkelund's avatar eekkelund

[AppLauncher] Add support for searchbar and searching for applications

parent 4f4421d4
......@@ -31,6 +31,7 @@ import QtQuick.Controls.Styles.Nemo 1.0
// App Launcher page
// the place for browsing installed applications and launching them
GridView {
id: gridview
cellWidth: cellSize
......@@ -41,6 +42,8 @@ GridView {
property bool onUninstall
property alias deleter: deleter
property var switcher: null
property string searchString
property int cellSize: Math.min(parent.width,parent.height)/4
property int folderIndex: -1
property bool isRootFolder:true
......@@ -48,11 +51,71 @@ GridView {
property bool newFolder: newFolderActive && isRootFolder && folderIndex >= 0
clip: true
onContentYChanged: {
if( contentY < -140 ) {
headerItem.visible = true;
timer.running = true;
}
}
onSearchStringChanged: timer.restart()
// just for margin purposes
header: Item {
height: Math.min(parent.width,parent.height)/10
Timer{
id: timer; running: false; interval: 7000; repeat: true
onTriggered: {
if(searchString.length < 1 ) headerItem.visible = false
}
}
Connections {
target: headerItem
onHeightChanged:{
if(headerItem.oldHeight < headerItem.height)
gridview.contentY = headerItem.y
headerItem.oldHeight = headerItem.height
}
onVisibleChanged:timer.restart()
}
Connections {
target: Lipstick.compositor
onDisplayOff: {
headerItem.searchField.text = ""
headerItem.visible = false
}
onWindowAdded: {
if(window.category=="" && window.title !== "Home"){
headerItem.searchField.text = ""
headerItem.visible = false
}
}
onWindowRaised: {
if(window.category=="" && window.title !== "Home"){
headerItem.searchField.text = ""
headerItem.visible = false
}
}
}
Connections {
target: pager
onFlickEnded: {
headerItem.searchField.text = ""
headerItem.visible = false
}
}
Connections {
target: lockScreen
onVisibleChanged: {
if(lockscreenVisible()) {
headerItem.searchField.text = ""
headerItem.visible = false
}
}
}
header: SearchListView {
width: gridview.width
}
footer: Item {
height: Math.min(parent.width,parent.height)/10
}
......@@ -141,7 +204,7 @@ GridView {
property color color2: "#80ff0000"
property color color3: "#4Dff0000"
property alias text: removeLabel.text
visible: onUninstall
visible: gridview.onUninstall
height: Theme.itemHeightExtraLarge
width: gridview.width / 2
gradient: Gradient {
......@@ -169,7 +232,7 @@ GridView {
property color color3: "#4Dff0000"
property alias text: uninstallLabel.text
anchors.left: remove.right
visible: onUninstall
visible: gridview.onUninstall
width: gridview.width / 2
height: Theme.itemHeightExtraLarge
gradient: Gradient {
......
......@@ -43,7 +43,7 @@ MouseArea {
id: launcherItem
parent: parentItem.contentItem
scale: newFolder && folderIndex == cellIndex && !isFolder ? 0.5 : (reordering || folderIndex == cellIndex ? 1.3 : 1)
scale: gridview.newFolder && parentItem.folderIndex == cellIndex && !isFolder ? 0.5 : (reordering || parentItem.folderIndex == cellIndex ? 1.3 : 1)
transformOrigin: Item.Center
onXChanged: moved()
onYChanged: moved()
......@@ -69,7 +69,7 @@ MouseArea {
onPressAndHold: {
reparent(parentItem)
reorderItem = launcherItem
parentItem.reorderItem = launcherItem
drag.target = launcherItem
z = 1000
reordering = true
......@@ -87,12 +87,12 @@ MouseArea {
reorderEnded()
reorderTimer.stop()
drag.target = null
reorderItem = null
parentItem.reorderItem = null
pager.interactive = true
parentItem.onUninstall = false
deleteState="basic"
deleter.uninstalling(deleteState)
folderIndex = -1
parentItem.folderIndex = -1
reparent(parentItem.contentItem)
z = parent.z
......@@ -153,25 +153,25 @@ MouseArea {
newFolderIndex = folderIdx
reorderTimer.restart()
}
if (newFolderIndex != folderIndex) {
folderIndex = -1
if (newFolderIndex != parentItem.folderIndex) {
parentItem.folderIndex = -1
}
}
}
function reorderEnded() {
//called when icon is released and reordering is true
if (folderIndex >= 0) {
if (folderModel.get(folderIndex).type == LauncherModel.Application) {
var folder = folderModel.createFolder(folderIndex, qsTr("folder"))
if (parentItem.folderIndex >= 0) {
if (folderModel.get(parentItem.folderIndex).type == LauncherModel.Application) {
var folder = folderModel.createFolder(parentItem.folderIndex, qsTr("folder"))
if (folder) {
folderModel.moveToFolder(modelData.object, folder)
}
} else {
folderModel.moveToFolder(modelData.object, folderModel.get(folderIndex))
folderModel.moveToFolder(modelData.object, folderModel.get(parentItem.folderIndex))
}
folderIndex = -1
newFolderActive = false
parentItem.folderIndex = -1
parentItem.newFolderActive = false
}
//To drop appicon out of the folder
var realY = parseInt(parentItem.mapFromItem(launcherItem, 0, 0).y) + parseInt(((launcherItem.height*launcherItem.scale-launcherItem.height)/2).toFixed(2))
......@@ -202,11 +202,11 @@ MouseArea {
onTriggered: {
if (newFolderIndex >= 0 && newFolderIndex !== cellIndex) {
if (!folderItem.isFolder) {
newFolderActive = true
parentItem.newFolderActive = true
} else {
newFolderActive = false
parentItem.newFolderActive = false
}
folderIndex = newFolderIndex
parentItem.folderIndex = newFolderIndex
} else if (newIndex != -1 && newIndex !== cellIndex) {
folderModel.move(cellIndex, newIndex)
}
......
/****************************************************************************************
**
** Copyright (c) 2017, Eetu Kahelin
** All rights reserved.
**
** You may use this file under the terms of BSD license as follows:
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** * Neither the name of the author nor the
** names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
** ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
****************************************************************************************/
import QtQuick 2.6
import org.nemomobile.lipstick 0.1
import QtQuick.Controls.Nemo 1.0
import QtQuick.Controls.Styles.Nemo 1.0
Item {
height: (searchField.text.length > 0 ? listView.height+searchField.height : searchField.height) + (visible ? Theme.itemSpacingHuge + margin.height : 0)
visible: false
anchors.bottomMargin:Theme.itemSpacingHuge
property alias searchField: searchField
property int oldHeight
Behavior on height {
enabled:!visible
NumberAnimation{ duration: 300 }
}
onVisibleChanged: {
if( visible) searchField.focus = true
else searchField.focus = false
oldHeight=height
}
Item {
id:margin
height: Theme.itemSpacingSmall
}
TextField {
id:searchField
anchors {
top:margin.bottom
left: parent.left
right: parent.right
topMargin: Theme.itemSpacingHuge
leftMargin: Theme.itemSpacingMedium
rightMargin: Theme.itemSpacingMedium
bottomMargin:Theme.itemSpacingHuge
}
Binding {
target: gridview
property: "searchString"
value: searchField.text.toLowerCase().trim()
}
}
ListView {
id:listView
clip: true
width: parent.width
height:contentHeight
anchors.top: searchField.bottom
anchors.topMargin: Theme.itemSpacingSmall
visible: searchString.length>0
section.property: 'category'
Behavior on height {
NumberAnimation{ duration: 300 }
}
Connections {
target: gridview
onSearchStringChanged: listView.update()
}
model: ListModel {
id: listModel
}
LauncherFolderModel { id: searchLauncherModel }
function update() {
if(searchString.length<1) {
listModel.clear()
} else {
var iconTitle
var iconId
var found
var i
var titles = []
for (i = 0; i < searchLauncherModel.itemCount; ++i) {
titles.push({'iconTitle':searchLauncherModel.get(i).title, 'iconSource':searchLauncherModel.get(i).iconId, 'id':i, 'category':qsTr("Application")})
}
var filteredTitles = titles.filter(function (icon) {
return icon.iconTitle.toLowerCase().indexOf(searchString) !== -1
})
// helper objects that can be quickly accessed
var filteredTitleObject = new Object
for (i = 0; i < filteredTitles.length; ++i) {
filteredTitleObject[filteredTitles[i].iconTitle] = true
}
var existingTitleObject = new Object
for (i = 0; i < count; ++i) {
iconTitle = listModel.get(i).title
existingTitleObject[iconTitle] = true
}
// remove items no longer in filtered set
i = 0
while (i < count) {
iconTitle = listModel.get(i).title
found = filteredTitleObject.hasOwnProperty(iconTitle)
if (!found) {
listModel.remove(i)
} else {
i++
}
}
// add new items
for (i = 0; i < filteredTitles.length; ++i) {
iconTitle = filteredTitles[i].iconTitle
iconId = filteredTitles[i].iconSource
var id = filteredTitles[i].id
var category = filteredTitles[i].category
found = existingTitleObject.hasOwnProperty(iconTitle)
if (!found) {
// for simplicity, just adding to end instead of corresponding position in original list
console.log(iconTitle)
listModel.append({'title':iconTitle, 'iconSource':iconId, 'id':id, 'category':category})
}
}
}
}
delegate: Item {
property string iconCaption: model.title
property string iconSource: model.iconSource== "" ? "/usr/share/lipstick-glacier-home-qt5/qml/theme/default-icon.png" : ( model.iconSource.indexOf("/") == 0 ? "file://" : "image://theme/") + model.iconSource
Rectangle {
anchors.fill: parent
color: "#11ffffff"
visible: mouse.pressed
}
Image {
id: iconImage
width: parent.height-Theme.itemSpacingMedium
height: width
source:iconSource
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: Theme.itemSpacingLarge
}
Spinner {
id: spinner
anchors {
centerIn: iconImage
top: iconImage.top
topMargin: Theme.itemSpacingExtraSmall
}
width: iconImage.width
height: width
enabled: (searchLauncherModel.get(model.id).type === LauncherModel.Application) ? searchLauncherModel.get(model.id).isLaunching ? switcher.switchModel.getWindowIdForTitle(model.title) == 0 : false : false
Connections {
target: Lipstick.compositor
onWindowAdded: {
if(window.category=="" && window.title !== "Home"){
spinner.stop()
}
}
}
}
Label {
text:iconCaption
anchors.left: iconImage.right
anchors.leftMargin: Theme.itemSpacingLarge
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Theme.itemSpacingLarge
font.pixelSize:Theme.fontSizeLarge
height:parent.height
color:Theme.textColor
elide:Text.ElideRight
horizontalAlignment:Text.AlignHCenter
verticalAlignment:Text.AlignVCenter
}
width: parent.width
height:Theme.itemHeightExtraLarge*1.2
MouseArea {
id:mouse
anchors.fill: parent
onClicked: {
if (searchLauncherModel.get(model.id).type !== LauncherModel.Folder) {
var winId = switcher.switchModel.getWindowIdForTitle(model.title)
if (winId == 0 || !searchLauncherModel.get(model.id).isLaunching)
searchLauncherModel.get(model.id).launchApplication()
else
Lipstick.compositor.windowToFront(winId)
}
}
}
}
}
}
......@@ -112,7 +112,7 @@ Compositor {
id: overlayLayer
z: 5
visible: root.appActive
//visible: root.appActive
}
Item {
......
......@@ -46,7 +46,8 @@ qml.files = qml/MainScreen.qml \
qml/GlacierRotation.qml \
qml/DeviceLockUI.qml \
qml/LauncherItemWrapper.qml \
qml/LauncherItemFolder.qml
qml/LauncherItemFolder.qml \
qml/SearchListView.qml
qmlcompositor.path = /usr/share/lipstick-glacier-home-qt5/qml/compositor
qmlcompositor.files = qml/compositor/WindowWrapperMystic.qml \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment