1/* 2 SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org> 3 4 SPDX-License-Identifier: GPL-2.0-or-later 5*/ 6 7import QtQuick 2.6 8import QtQuick.Layouts 1.1 9import QtQuick.Window 2.1 10import org.kde.plasma.core 2.0 as PlasmaCore 11import org.kde.plasma.components 2.0 as PlasmaComponents // For Highlight 12import org.kde.plasma.components 3.0 as PlasmaComponents3 13import org.kde.plasma.extras 2.0 as PlasmaExtras 14import org.kde.milou 0.1 as Milou 15 16ColumnLayout { 17 id: root 18 property string query 19 property string runner 20 property bool showHistory: false 21 property alias runnerManager: results.runnerManager 22 23 LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft 24 LayoutMirroring.childrenInherit: true 25 26 onQueryChanged: { 27 queryField.text = query; 28 } 29 30 Connections { 31 target: runnerWindow 32 function onVisibleChanged() { 33 if (runnerWindow.visible) { 34 queryField.forceActiveFocus(); 35 listView.currentIndex = -1 36 if (runnerManager.retainPriorSearch) { 37 // If we manually specified a query(D-Bus invocation) we don't want to retain the prior search 38 if (!query) { 39 queryField.text = runnerManager.priorSearch 40 queryField.select(root.query.length, 0) 41 } 42 } 43 } else { 44 if (runnerManager.retainPriorSearch) { 45 runnerManager.priorSearch = root.query 46 } 47 root.runner = "" 48 root.query = "" 49 root.showHistory = false 50 } 51 } 52 } 53 54 Connections { 55 target: root 56 function onShowHistoryChanged() { 57 if (showHistory) { 58 // we store 50 entries in the history but only show 20 in the UI so it doesn't get too huge 59 listView.model = runnerManager.history.slice(0, 20) 60 } else { 61 listView.model = [] 62 } 63 } 64 } 65 66 RowLayout { 67 Layout.alignment: Qt.AlignTop 68 PlasmaComponents3.ToolButton { 69 icon.name: "configure" 70 onClicked: { 71 runnerWindow.visible = false 72 runnerWindow.displayConfiguration() 73 } 74 Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Configure") 75 Accessible.description: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Configure Search Plugins") 76 visible: runnerWindow.canConfigure 77 PlasmaComponents3.ToolTip { 78 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Configure KRunner…") 79 } 80 } 81 PlasmaComponents3.TextField { 82 id: queryField 83 property bool allowCompletion: false 84 85 clearButtonShown: true 86 Layout.minimumWidth: PlasmaCore.Units.gridUnit * 25 87 Layout.maximumWidth: PlasmaCore.Units.gridUnit * 25 88 89 inputMethodHints: Qt.ImhNoPredictiveText 90 91 activeFocusOnPress: true 92 placeholderText: results.runnerName ? i18ndc("plasma_lookandfeel_org.kde.lookandfeel", 93 "Textfield placeholder text, query specific KRunner", 94 "Search '%1'…", results.runnerName) 95 : i18ndc("plasma_lookandfeel_org.kde.lookandfeel", 96 "Textfield placeholder text", "Search…") 97 98 PlasmaComponents3.BusyIndicator { 99 anchors { 100 right: parent.right 101 top: parent.top 102 bottom: parent.bottom 103 margins: PlasmaCore.Units.smallSpacing 104 rightMargin: height 105 } 106 107 Timer { 108 id: queryTimer 109 property bool queryDisplay: false 110 running: results.querying 111 repeat: true 112 onRunningChanged: if (queryDisplay && !running) { 113 queryDisplay = false 114 } 115 onTriggered: if (!queryDisplay) { 116 queryDisplay = true 117 } 118 interval: 500 119 } 120 121 running: queryTimer.queryDisplay 122 } 123 function move_up() { 124 if (length === 0) { 125 root.showHistory = true; 126 if (listView.count > 0) { 127 listView.forceActiveFocus(); 128 } 129 } else if (results.count > 0) { 130 results.forceActiveFocus(); 131 results.decrementCurrentIndex(); 132 } 133 } 134 135 function move_down() { 136 if (length === 0) { 137 root.showHistory = true; 138 if (listView.count > 0) { 139 listView.forceActiveFocus(); 140 } 141 } else if (results.count > 0) { 142 results.forceActiveFocus(); 143 results.incrementCurrentIndex(); 144 } 145 } 146 147 onTextChanged: { 148 root.query = queryField.text 149 if (allowCompletion && length > 0 && runnerManager.historyEnabled) { 150 var oldText = text 151 var suggestedText = runnerManager.getHistorySuggestion(text); 152 if (suggestedText.length > 0) { 153 text = text + suggestedText.substr(oldText.length) 154 select(text.length, oldText.length) 155 } 156 } 157 } 158 Keys.onPressed: { 159 allowCompletion = (event.key !== Qt.Key_Backspace && event.key !== Qt.Key_Delete) 160 161 if (event.modifiers & Qt.ControlModifier) { 162 if (event.key === Qt.Key_J) { 163 move_down() 164 event.accepted = true; 165 } else if (event.key === Qt.Key_K) { 166 move_up() 167 event.accepted = true; 168 } 169 } 170 } 171 Keys.onUpPressed: move_up() 172 Keys.onDownPressed: move_down() 173 function closeOrRun(event) { 174 // Close KRunner if no text was typed and enter was pressed, FEATURE: 211225 175 if (!root.query) { 176 runnerWindow.visible = false 177 } else { 178 results.runCurrentIndex(event) 179 } 180 } 181 Keys.onEnterPressed: closeOrRun(event) 182 Keys.onReturnPressed: closeOrRun(event) 183 184 Keys.onEscapePressed: { 185 runnerWindow.visible = false 186 } 187 188 PlasmaCore.SvgItem { 189 anchors { 190 right: parent.right 191 rightMargin: 6 // from PlasmaStyle TextFieldStyle 192 verticalCenter: parent.verticalCenter 193 } 194 // match clear button 195 width: Math.max(parent.height * 0.8, PlasmaCore.Units.iconSizes.small) 196 height: width 197 svg: PlasmaCore.Svg { 198 imagePath: "widgets/arrows" 199 colorGroup: PlasmaCore.Theme.ButtonColorGroup 200 } 201 elementId: "down-arrow" 202 visible: queryField.length === 0 && runnerManager.historyEnabled 203 204 MouseArea { 205 anchors.fill: parent 206 onPressed: { 207 root.showHistory = !root.showHistory 208 if (root.showHistory) { 209 listView.forceActiveFocus(); // is the history list 210 } else { 211 queryField.forceActiveFocus(); 212 } 213 } 214 } 215 } 216 } 217 PlasmaComponents3.ToolButton { 218 checkable: true 219 checked: runnerWindow.pinned 220 onToggled: runnerWindow.pinned = checked 221 icon.name: "window-pin" 222 Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Pin") 223 Accessible.description: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Pin Search") 224 PlasmaComponents3.ToolTip { 225 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Keep Open") 226 } 227 } 228 } 229 230 PlasmaExtras.ScrollArea { 231 Layout.alignment: Qt.AlignTop 232 visible: results.count > 0 233 enabled: visible 234 Layout.fillWidth: true 235 Layout.preferredHeight: Math.min(Screen.height, results.contentHeight) 236 237 Milou.ResultsView { 238 id: results 239 queryString: root.query 240 runner: root.runner 241 242 Keys.onPressed: { 243 var ctrl = event.modifiers & Qt.ControlModifier; 244 if (ctrl && event.key === Qt.Key_J) { 245 incrementCurrentIndex() 246 } else if (ctrl && event.key === Qt.Key_K) { 247 decrementCurrentIndex() 248 } else if (event.text !== "") { 249 // This prevents unprintable control characters from being inserted 250 if (!/[\x00-\x1F\x7F]/.test(event.text)) { 251 queryField.text += event.text; 252 } 253 queryField.cursorPosition = queryField.text.length 254 queryField.focus = true; 255 } 256 } 257 258 Keys.onEscapePressed: { 259 runnerWindow.visible = false 260 } 261 262 onActivated: { 263 runnerWindow.visible = false 264 } 265 266 onUpdateQueryString: { 267 queryField.text = text 268 queryField.cursorPosition = cursorPosition 269 } 270 } 271 } 272 273 PlasmaExtras.ScrollArea { 274 Layout.alignment: Qt.AlignTop 275 Layout.fillWidth: true 276 visible: root.query.length === 0 && listView.count > 0 277 // don't accept keyboard input when not visible so the keys propagate to the other list 278 enabled: visible 279 Layout.preferredHeight: Math.min(Screen.height, listView.contentHeight) 280 281 ListView { 282 id: listView // needs this id so the delegate can access it 283 keyNavigationWraps: true 284 highlight: PlasmaComponents.Highlight {} 285 highlightMoveDuration: 0 286 activeFocusOnTab: true 287 model: [] 288 delegate: Milou.ResultDelegate { 289 id: resultDelegate 290 width: listView.width 291 typeText: index === 0 ? i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Recent Queries") : "" 292 additionalActions: [{ 293 icon: "list-remove", 294 text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Remove") 295 }] 296 Accessible.description: i18n("in category recent queries") 297 } 298 299 onActiveFocusChanged: { 300 if (!activeFocus && currentIndex == listView.count-1) { 301 currentIndex = 0; 302 } 303 } 304 Keys.onReturnPressed: runCurrentIndex(event) 305 Keys.onEnterPressed: runCurrentIndex(event) 306 307 Keys.onTabPressed: { 308 if (currentIndex == listView.count-1) { 309 listView.nextItemInFocusChain(true).forceActiveFocus(); 310 } else { 311 incrementCurrentIndex() 312 } 313 } 314 Keys.onBacktabPressed: { 315 if (currentIndex == 0) { 316 listView.nextItemInFocusChain(false).forceActiveFocus(); 317 } else { 318 decrementCurrentIndex() 319 } 320 } 321 Keys.onPressed: { 322 var ctrl = event.modifiers & Qt.ControlModifier; 323 if (ctrl && event.key === Qt.Key_J) { 324 incrementCurrentIndex() 325 } else if (ctrl && event.key === Qt.Key_K) { 326 decrementCurrentIndex() 327 } else if (event.text !== "") { 328 // This prevents unprintable control characters from being inserted 329 if (event.key == Qt.Key_Escape) { 330 root.showHistory = false 331 } else if (!/[\x00-\x1F\x7F]/.test(event.text)) { 332 queryField.text += event.text; 333 } 334 queryField.focus = true; 335 } 336 } 337 338 Keys.onUpPressed: decrementCurrentIndex() 339 Keys.onDownPressed: incrementCurrentIndex() 340 341 function runCurrentIndex(event) { 342 var entry = runnerManager.history[currentIndex] 343 if (entry) { 344 // If user presses Shift+Return to invoke an action, invoke the first runner action 345 if (event && event.modifiers === Qt.ShiftModifier 346 && currentItem.additionalActions && currentItem.additionalActions.length > 0) { 347 runAction(0); 348 return 349 } 350 351 queryField.text = entry 352 queryField.forceActiveFocus(); 353 } 354 } 355 356 function runAction(actionIndex) { 357 if (actionIndex === 0) { 358 // QStringList changes just reset the model, so we'll remember the index and set it again 359 var currentIndex = listView.currentIndex 360 runnerManager.removeFromHistory(currentIndex) 361 model = runnerManager.history 362 listView.currentIndex = currentIndex 363 } 364 } 365 } 366 367 } 368} 369