Warning, /plasma/plasma-workspace/krunner/qml/RunCommand.qml is written in an unsupported language. File is not indexed.
0001 /*
0002 SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
0003
0004 SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006
0007 import QtQuick 2.15
0008 import QtQuick.Controls 2.15 as QQC2
0009 import QtQuick.Layouts 1.1
0010 import QtQuick.Window 2.1
0011
0012 import org.kde.config // KAuthorized
0013 import org.kde.kcmutils // KCMLauncher
0014 import org.kde.plasma.components 3.0 as PlasmaComponents3
0015 import org.kde.plasma.extras 2.0 as PlasmaExtras
0016 import org.kde.milou 0.1 as Milou
0017 import org.kde.krunner.private.view
0018 import org.kde.kirigami 2.20 as Kirigami
0019
0020 ColumnLayout {
0021 id: root
0022 property string query
0023 property string singleRunner
0024 property bool showHistory: false
0025 property string priorSearch: ""
0026 property alias runnerManager: results.runnerManager
0027
0028 LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
0029 LayoutMirroring.childrenInherit: true
0030
0031 // Spacing needs to be 0 to not make the last spacer item add a fake margin
0032 spacing: 0
0033
0034 Connections {
0035 target: runnerWindow
0036 function onHistoryBehaviorChanged() {
0037 runnerManager.historyEnabled = runnerWindow.historyBehavior !== HistoryBehavior.Disabled
0038 }
0039 function onFavoriteIdsChanged() {
0040 results.model.favoriteIds = runnerWindow.favoriteIds
0041 }
0042 function onActivityChanged(activity) {
0043 runnerManager.setHistoryEnvironmentIdentifier(activity)
0044 }
0045 }
0046 Component.onCompleted: {
0047 runnerManager.historyEnabled = runnerWindow.historyBehavior !== HistoryBehavior.Disabled
0048 results.model.favoriteIds = runnerWindow.favoriteIds
0049 }
0050
0051 onQueryChanged: {
0052 queryField.text = query;
0053 }
0054
0055 Connections {
0056 target: runnerWindow
0057 function onVisibleChanged() {
0058 if (runnerWindow.visible) {
0059 queryField.forceActiveFocus();
0060 listView.currentIndex = -1
0061 if (runnerWindow.retainPriorSearch) {
0062 // If we manually specified a query(D-Bus invocation) we don't want to retain the prior search
0063 if (!query) {
0064 queryField.text = priorSearch
0065 queryField.select(root.query.length, 0)
0066 }
0067 }
0068 } else {
0069 if (runnerWindow.retainPriorSearch) {
0070 priorSearch = root.query
0071 }
0072 root.singleRunner = ""
0073 root.query = ""
0074 fadedTextCompletion.text = "";
0075 root.showHistory = false
0076 }
0077 }
0078 }
0079
0080 Connections {
0081 target: root
0082 function onShowHistoryChanged() {
0083 if (showHistory) {
0084 // we store 50 entries in the history but only show 20 in the UI so it doesn't get too huge
0085 listView.model = runnerManager.history.slice(0, 20)
0086 } else {
0087 listView.model = []
0088 }
0089 }
0090 }
0091
0092 RowLayout {
0093 Layout.alignment: Qt.AlignTop
0094 PlasmaComponents3.ToolButton {
0095 icon.name: "configure"
0096 onClicked: {
0097 runnerWindow.visible = false
0098 KCMLauncher.open("plasma/kcms/desktop/kcm_krunnersettings")
0099 }
0100 Accessible.name: i18n("Configure")
0101 Accessible.description: i18n("Configure KRunner Behavior")
0102 visible: KAuthorized.authorizeControlModule("kcm_krunnersettings")
0103 PlasmaComponents3.ToolTip {
0104 text: i18n("Configure KRunner…")
0105 }
0106 }
0107 PlasmaComponents3.ToolButton {
0108 visible: !!root.singleRunner
0109 icon.name: results.singleRunnerMetaData.iconName
0110 onClicked: () => {
0111 root.singleRunner = ""
0112 root.query = ""
0113 fadedTextCompletion.text = ""
0114 queryField.forceActiveFocus();
0115 }
0116 checked: true
0117 Accessible.description: i18n("Showing only results from %1", results.singleRunnerMetaData.name)
0118 PlasmaComponents3.ToolTip {
0119 text: parent.Accessible.description
0120 }
0121 }
0122 PlasmaExtras.SearchField {
0123 id: queryField
0124 property bool allowCompletion: false
0125
0126 Layout.minimumWidth: Kirigami.Units.gridUnit * 25
0127 Layout.maximumWidth: Kirigami.Units.gridUnit * 25
0128
0129 activeFocusOnPress: true
0130 placeholderText: results.singleRunner ? i18nc("Textfield placeholder text, query specific KRunner plugin",
0131 "Search '%1'…", results.singleRunnerMetaData.name)
0132 : i18nc("Textfield placeholder text", "Search…")
0133 background.z: -2 // The fadedTextCompletion has -1, so it appears over the background
0134
0135 QQC2.Label {
0136 id: fadedTextCompletion
0137 leftPadding: parent.leftPadding
0138 topPadding: parent.topPadding
0139 rightPadding: parent.rightPadding
0140 bottomPadding: parent.bottomPadding
0141 width: parent.width
0142 height: parent.height
0143 elide: Text.ElideRight
0144 opacity: 0.5
0145 text: ""
0146 textFormat: Text.PlainText
0147 renderType: parent.renderType
0148 color: parent.color
0149 focus: false
0150 z: -1
0151 }
0152
0153 PlasmaComponents3.BusyIndicator {
0154 anchors {
0155 right: parent.right
0156 top: parent.top
0157 bottom: parent.bottom
0158 margins: Kirigami.Units.smallSpacing
0159 rightMargin: height
0160 }
0161
0162 Timer {
0163 id: queryTimer
0164 property bool queryDisplay: false
0165 running: results.querying
0166 repeat: true
0167 onRunningChanged: if (queryDisplay && !running) {
0168 queryDisplay = false
0169 }
0170 onTriggered: if (!queryDisplay) {
0171 queryDisplay = true
0172 }
0173 interval: 500
0174 }
0175
0176 running: queryTimer.queryDisplay
0177 }
0178 function move_up() {
0179 if (length === 0) {
0180 root.showHistory = true;
0181 } else if (results.count > 0) {
0182 results.decrementCurrentIndex();
0183 }
0184 focusCurrentListView()
0185 }
0186
0187 function move_down() {
0188 if (length === 0) {
0189 root.showHistory = true;
0190 } else if (results.count > 0) {
0191 results.incrementCurrentIndex();
0192 }
0193 focusCurrentListView()
0194 }
0195
0196 function focusCurrentListView() {
0197 if (listView.count > 0) {
0198 listView.forceActiveFocus();
0199 } else if (results.count > 0) {
0200 results.forceActiveFocus();
0201 }
0202 }
0203
0204 onTextChanged: {
0205 root.query = queryField.text
0206 if (!allowCompletion || !root.query ) { // Clear suggestion in case it was disabled or the query is cleared
0207 fadedTextCompletion.text = ""
0208 } else if (runnerWindow.historyBehavior === HistoryBehavior.CompletionSuggestion) {
0209 fadedTextCompletion.text = runnerManager.getHistorySuggestion(text)
0210 } else if (length > 0 && runnerWindow.historyBehavior === HistoryBehavior.ImmediateCompletion) {
0211 var oldText = text
0212 var suggestedText = runnerManager.getHistorySuggestion(text);
0213 if (suggestedText.length > 0) {
0214 text = text + suggestedText.substr(oldText.length)
0215 select(text.length, oldText.length)
0216 }
0217 }
0218 }
0219 Keys.onPressed: event => {
0220 allowCompletion = (event.key !== Qt.Key_Backspace && event.key !== Qt.Key_Delete)
0221
0222 if (event.modifiers & Qt.ControlModifier) {
0223 if (event.key === Qt.Key_J) {
0224 move_down()
0225 event.accepted = true;
0226 } else if (event.key === Qt.Key_K) {
0227 move_up()
0228 event.accepted = true;
0229 }
0230 }
0231 // We only need to handle the Key_End case, the first item is focused by default
0232 if (event.key === Qt.Key_End && results.count > 0 && cursorPosition === text.length) {
0233 results.currentIndex = results.count - 1
0234 event.accepted = true;
0235 focusCurrentListView()
0236 }
0237 if (runnerWindow.historyBehavior === HistoryBehavior.CompletionSuggestion
0238 && fadedTextCompletion.text.length > 0
0239 && cursorPosition === text.length
0240 && event.key === Qt.Key_Right
0241 ) {
0242 queryField.text = fadedTextCompletion.text
0243 fadedTextCompletion.text = ""
0244 }
0245 }
0246 Keys.onTabPressed: event => {
0247 if (runnerWindow.historyBehavior === HistoryBehavior.CompletionSuggestion) {
0248 if (fadedTextCompletion.text && queryField.text !== fadedTextCompletion.text) {
0249 queryField.text = fadedTextCompletion.text
0250 fadedTextCompletion.text = ""
0251 } else {
0252 event.accepted = false
0253 }
0254 }
0255 }
0256 Keys.onUpPressed: move_up()
0257 Keys.onDownPressed: move_down()
0258 function closeOrRun(event) {
0259 // Close KRunner if no text was typed and enter was pressed, FEATURE: 211225
0260 if (!root.query) {
0261 runnerWindow.visible = false
0262 } else {
0263 results.runCurrentIndex(event)
0264 }
0265 }
0266 Keys.onEnterPressed: event => closeOrRun(event)
0267 Keys.onReturnPressed: event => closeOrRun(event)
0268
0269 Keys.onEscapePressed: {
0270 runnerWindow.visible = false
0271 }
0272
0273 Kirigami.Icon {
0274 anchors {
0275 right: parent.right
0276 rightMargin: 6 // from PlasmaStyle TextFieldStyle
0277 verticalCenter: parent.verticalCenter
0278 }
0279 // match clear button
0280 width: Math.max(parent.height * 0.8, Kirigami.Units.iconSizes.small)
0281 height: width
0282 source: "expand"
0283 visible: queryField.length === 0 && runnerManager.historyEnabled
0284
0285 MouseArea {
0286 anchors.fill: parent
0287 onPressed: {
0288 root.showHistory = !root.showHistory
0289 if (root.showHistory) {
0290 listView.forceActiveFocus(); // is the history list
0291 } else {
0292 queryField.forceActiveFocus();
0293 }
0294 }
0295 }
0296 }
0297 }
0298
0299 PlasmaComponents3.ToolButton {
0300 visible: runnerWindow.helpEnabled
0301 checkable: true
0302 checked: root.query.startsWith("?")
0303 // Reset if out quers starts with "?", otherwise set it to "?"
0304 onClicked: root.query = root.query.startsWith("?") ? "" : "?"
0305 icon.name: "question"
0306 Accessible.name: i18n("Show Usage Help")
0307 Accessible.description: i18n("Show Usage Help")
0308 PlasmaComponents3.ToolTip {
0309 text: i18n("Show Usage Help")
0310 }
0311 }
0312 PlasmaComponents3.ToolButton {
0313 checkable: true
0314 checked: runnerWindow.pinned
0315 onToggled: runnerWindow.pinned = checked
0316 icon.name: "window-pin"
0317 Accessible.name: i18n("Pin")
0318 Accessible.description: i18n("Pin Search")
0319 PlasmaComponents3.ToolTip {
0320 text: i18n("Keep Open")
0321 }
0322 }
0323 }
0324
0325 PlasmaComponents3.ScrollView {
0326 visible: results.count > 0
0327 enabled: visible
0328 Layout.fillWidth: true
0329 Layout.fillHeight: true
0330 Layout.maximumHeight: Math.max(listView.contentHeight, results.contentHeight)
0331 // This replaces the ColumnLayout spacing
0332 Layout.topMargin: Kirigami.Units.smallSpacing
0333
0334 Milou.ResultsView {
0335 id: results
0336 queryString: root.query
0337 singleRunner: root.singleRunner
0338
0339 Keys.onPressed: event => {
0340 var ctrl = event.modifiers & Qt.ControlModifier;
0341 if (ctrl && event.key === Qt.Key_J) {
0342 incrementCurrentIndex()
0343 } else if (ctrl && event.key === Qt.Key_K) {
0344 decrementCurrentIndex()
0345 } else if (event.key === Qt.Key_Home) {
0346 results.currentIndex = 0
0347 } else if (event.key === Qt.Key_End) {
0348 results.currentIndex = results.count - 1
0349 } else if (event.text !== "") {
0350 // This prevents unprintable control characters from being inserted
0351 if (!/[\x00-\x1F\x7F]/.test(event.text)) {
0352 queryField.text += event.text;
0353 }
0354 queryField.cursorPosition = queryField.text.length
0355 queryField.focus = true;
0356 }
0357 }
0358
0359 Keys.onEscapePressed: {
0360 runnerWindow.visible = false
0361 }
0362
0363 onActivated: {
0364 if (!runnerWindow.pinned) {
0365 runnerWindow.visible = false
0366 }
0367 }
0368
0369 onUpdateQueryString: {
0370 queryField.text = text
0371 queryField.select(cursorPosition, root.query.length)
0372 queryField.focus = true;
0373 }
0374 }
0375 }
0376
0377 PlasmaComponents3.ScrollView {
0378 Layout.fillWidth: true
0379 Layout.fillHeight: true
0380 Layout.maximumHeight: listView.contentHeight
0381 // This replaces the ColumnLayout spacing
0382 Layout.topMargin: Kirigami.Units.smallSpacing
0383 visible: root.query.length === 0 && listView.count > 0
0384 // don't accept keyboard input when not visible so the keys propagate to the other list
0385 enabled: visible
0386
0387 ListView {
0388 id: listView // needs this id so the delegate can access it
0389 keyNavigationWraps: true
0390 highlight: PlasmaExtras.Highlight {}
0391 highlightMoveDuration: 0
0392 activeFocusOnTab: true
0393 model: []
0394 reuseItems: true
0395 delegate: Milou.ResultDelegate {
0396 id: resultDelegate
0397 width: listView.width
0398 typeText: index === 0 ? i18n("Recent Queries") : ""
0399 additionalActions: [{
0400 icon: "list-remove",
0401 text: i18n("Remove")
0402 }]
0403 Accessible.description: i18n("Recent Queries")
0404 }
0405
0406 onActiveFocusChanged: {
0407 if (!activeFocus && currentIndex == listView.count-1) {
0408 currentIndex = 0;
0409 }
0410 }
0411 Keys.onReturnPressed: runCurrentIndex(event)
0412 Keys.onEnterPressed: runCurrentIndex(event)
0413
0414 Keys.onTabPressed: {
0415 if (currentIndex == listView.count-1) {
0416 listView.nextItemInFocusChain(true).forceActiveFocus();
0417 } else {
0418 incrementCurrentIndex()
0419 }
0420 }
0421 Keys.onBacktabPressed: {
0422 if (currentIndex == 0) {
0423 listView.nextItemInFocusChain(false).forceActiveFocus();
0424 } else {
0425 decrementCurrentIndex()
0426 }
0427 }
0428 Keys.onPressed: event => {
0429 var ctrl = event.modifiers & Qt.ControlModifier;
0430 if (ctrl && event.key === Qt.Key_J) {
0431 incrementCurrentIndex()
0432 } else if (ctrl && event.key === Qt.Key_K) {
0433 decrementCurrentIndex()
0434 } else if (event.key === Qt.Key_Home) {
0435 currentIndex = 0
0436 } else if (event.key === Qt.Key_End) {
0437 currentIndex = count - 1
0438 } else if (event.text !== "") {
0439 // This prevents unprintable control characters from being inserted
0440 if (event.key == Qt.Key_Escape) {
0441 root.showHistory = false
0442 } else if (!/[\x00-\x1F\x7F]/.test(event.text)) {
0443 queryField.text += event.text;
0444 }
0445 queryField.focus = true;
0446 }
0447 }
0448
0449 Keys.onUpPressed: decrementCurrentIndex()
0450 Keys.onDownPressed: incrementCurrentIndex()
0451
0452 function runCurrentIndex(event) {
0453 var entry = runnerManager.history[currentIndex]
0454 if (entry) {
0455 // If user presses Shift+Return to invoke an action, invoke the first runner action
0456 if (event && event.modifiers === Qt.ShiftModifier
0457 && currentItem.additionalActions && currentItem.additionalActions.length > 0) {
0458 runAction(0);
0459 return
0460 }
0461
0462 queryField.text = entry
0463 queryField.forceActiveFocus();
0464 }
0465 }
0466
0467 function runAction(actionIndex) {
0468 if (actionIndex === 0) {
0469 // QStringList changes just reset the model, so we'll remember the index and set it again
0470 var currentIndex = listView.currentIndex
0471 runnerManager.removeFromHistory(currentIndex)
0472 model = runnerManager.history
0473 listView.currentIndex = currentIndex
0474 }
0475 }
0476 }
0477
0478 }
0479
0480 // to align the layout to the top of any pending space in the view
0481 Item {
0482 Layout.fillHeight: true
0483 }
0484 }