1/* 2 * This file is part of the KDE Milou Project 3 * SPDX-FileCopyrightText: 2013-2014 Vishesh Handa <me@vhanda.in> 4 * 5 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 6 * 7 */ 8 9import QtQuick 2.1 10 11import org.kde.plasma.components 2.0 as PlasmaComponents 12import org.kde.plasma.core 2.0 as PlasmaCore 13import org.kde.milou 0.3 as Milou 14 15ListView { 16 id: listView 17 property alias queryString: resultModel.queryString 18 property alias runner: resultModel.runner 19 property alias runnerManager: resultModel.runnerManager 20 21 property alias runnerName: resultModel.runnerName 22 property alias runnerIcon: resultModel.runnerIcon 23 property alias querying: resultModel.querying 24 property alias limit: resultModel.limit 25 property bool reversed 26 signal activated 27 signal updateQueryString(string text, int cursorPosition) 28 29 // NOTE this also flips increment/decrementCurrentIndex (Bug 360789) 30 verticalLayoutDirection: reversed ? ListView.BottomToTop : ListView.TopToBottom 31 keyNavigationWraps: true 32 highlight: PlasmaComponents.Highlight {} 33 highlightMoveDuration: 0 34 activeFocusOnTab: true 35 Accessible.role: Accessible.List 36 37 section { 38 criteria: ViewSection.FullString 39 property: "category" 40 } 41 42 // This is used to keep track if the user has pressed enter before 43 // the first result has been shown, in the case the first result should 44 // be run when the model is populated 45 property bool runAutomatically 46 47 // This is used to disable mouse selection if the user interacts only with keyboard 48 property bool moved: false 49 property point savedMousePosition: Milou.MouseHelper.globalMousePosition() 50 function mouseMovedGlobally() { 51 return savedMousePosition != Milou.MouseHelper.globalMousePosition(); 52 } 53 54 Milou.DragHelper { 55 id: dragHelper 56 dragIconSize: units.iconSizes.medium 57 } 58 59 model: Milou.ResultsModel { 60 id: resultModel 61 limit: 15 62 onQueryStringChangeRequested: { 63 listView.updateQueryString(queryString, pos) 64 } 65 Component.onCompleted: { 66 if (typeof runnerWindow !== "undefined") { 67 runnerWindow.runnerManager = runnerManager 68 } 69 } 70 onQueryStringChanged: resetView() 71 onModelReset: resetView() 72 73 onRowsInserted: { 74 // Keep the selection at the top as items inserted to the beginning will shift it downwards 75 // ListView will update its view after this signal is processed and then our callLater will set it back 76 if (listView.currentIndex === 0) { 77 Qt.callLater(function() { 78 listView.currentIndex = 0; 79 }); 80 } 81 82 if (runAutomatically) { 83 // This needs to be delayed as running a result may close the window and clear the query 84 // having us reset the model whilst in the middle of processing the insertion. 85 // The proxy model chain that comes after us really doesn't like this. 86 Qt.callLater(function() { 87 resultModel.run(resultModel.index(0, 0)); 88 listView.activated(); 89 }); 90 91 runAutomatically = false; 92 } 93 } 94 95 function resetView() { 96 listView.currentIndex = 0; 97 listView.moved = false; 98 listView.savedMousePosition = Milou.MouseHelper.globalMousePosition(); 99 } 100 } 101 102 delegate: ResultDelegate { 103 id: resultDelegate 104 width: listView.width 105 reversed: listView.reversed 106 } 107 108 // 109 // vHanda: Ideally this should have gotten handled in the delagte's onReturnPressed 110 // code, but the ListView doesn't seem forward keyboard events to the delgate when 111 // it is not in activeFocus. Even manually adding Keys.forwardTo: resultDelegate 112 // doesn't make any difference! 113 Keys.onReturnPressed: runCurrentIndex(event); 114 Keys.onEnterPressed: runCurrentIndex(event); 115 116 function runCurrentIndex(event) { 117 if (!currentItem) { 118 runAutomatically = true 119 return; 120 } else { 121 // If user presses Shift+Return to invoke an action, invoke the first runner action 122 if (event && event.modifiers === Qt.ShiftModifier 123 && currentItem.additionalActions && currentItem.additionalActions.length > 0) { 124 runAction(0) 125 return 126 } 127 128 if (currentItem.activeAction > -1) { 129 runAction(currentItem.activeAction) 130 return 131 } 132 133 if (resultModel.run(resultModel.index(currentIndex, 0))) { 134 activated() 135 } 136 runAutomatically = false 137 } 138 } 139 140 function runAction(index) { 141 if (resultModel.runAction(resultModel.index(currentIndex, 0), index)) { 142 activated() 143 } 144 } 145 146 onActiveFocusChanged: { 147 if (!activeFocus && currentIndex == listView.count-1) { 148 currentIndex = 0; 149 } 150 } 151 152 Keys.onTabPressed: { 153 if (!currentItem || !currentItem.activateNextAction()) { 154 if (reversed) { 155 if (currentIndex == 0) { 156 listView.nextItemInFocusChain(false).forceActiveFocus(); 157 return; 158 } 159 decrementCurrentIndex() 160 } else { 161 if (currentIndex == listView.count-1) { 162 listView.nextItemInFocusChain(true).forceActiveFocus(); 163 return; 164 } 165 incrementCurrentIndex() 166 } 167 } 168 } 169 Keys.onBacktabPressed: { 170 if (!currentItem || !currentItem.activatePreviousAction()) { 171 if (reversed) { 172 if (currentIndex == listView.count-1) { 173 listView.nextItemInFocusChain(true).forceActiveFocus(); 174 return; 175 } 176 incrementCurrentIndex() 177 } else { 178 if (currentIndex == 0) { 179 listView.nextItemInFocusChain(false).forceActiveFocus(); 180 return; 181 } 182 decrementCurrentIndex() 183 } 184 185 // activate previous action cannot know whether we want to back tab from an action 186 // to the main result or back tab from another search result, so we explicitly highlight 187 // the last action here to provide a consistent navigation experience 188 if (currentItem) { 189 currentItem.activateLastAction() 190 } 191 } 192 } 193 Keys.onUpPressed: reversed ? incrementCurrentIndex() : decrementCurrentIndex(); 194 Keys.onDownPressed: reversed ? decrementCurrentIndex() : incrementCurrentIndex(); 195 196 boundsBehavior: Flickable.StopAtBounds 197 198 function loadSettings() { 199 resultModel.loadSettings() 200 } 201 202 function setQueryString(queryString) { 203 resultModel.queryString = queryString 204 runAutomatically = false 205 } 206} 207