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 }