RunCommand.qml 13.7 KB
Newer Older
1
/*
2
3
4
5
    SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>

    SPDX-License-Identifier: GPL-2.0-or-later
*/
6

Marco Martin's avatar
Marco Martin committed
7
import QtQuick 2.6
8
import QtQuick.Layouts 1.1
9
import QtQuick.Window 2.1
10
import org.kde.plasma.core 2.0 as PlasmaCore
11
12
import org.kde.plasma.components 2.0 as PlasmaComponents // For Highlight
import org.kde.plasma.components 3.0 as PlasmaComponents3
13
14
15
16
17
18
19
import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.milou 0.1 as Milou

ColumnLayout {
    id: root
    property string query
    property string runner
20
    property bool showHistory: false
Alexander Lohnau's avatar
Alexander Lohnau committed
21
    property alias runnerManager: results.runnerManager
22

23
24
25
    LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
    LayoutMirroring.childrenInherit: true

26
27
28
29
30
31
    onQueryChanged: {
        queryField.text = query;
    }

    Connections {
        target: runnerWindow
Konrad Materka's avatar
Konrad Materka committed
32
        function onVisibleChanged() {
33
34
            if (runnerWindow.visible) {
                queryField.forceActiveFocus();
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
35
                listView.currentIndex = -1
Alexander Lohnau's avatar
Alexander Lohnau committed
36
                if (runnerManager.retainPriorSearch) {
37
38
                    // If we manually specified a query(D-Bus invocation) we don't want to retain the prior search
                    if (!query) {
Alexander Lohnau's avatar
Alexander Lohnau committed
39
                        queryField.text = runnerManager.priorSearch
40
41
                        queryField.select(root.query.length, 0)
                    }
42
                }
43
            } else {
Alexander Lohnau's avatar
Alexander Lohnau committed
44
45
                if (runnerManager.retainPriorSearch) {
                    runnerManager.priorSearch = root.query
46
                }
47
48
                root.runner = ""
                root.query = ""
49
                root.showHistory = false
50
51
52
53
            }
        }
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
54
55
56
57
58
59
60
61
62
63
64
65
    Connections {
        target: root
        function onShowHistoryChanged() {
            if (showHistory) {
                // we store 50 entries in the history but only show 20 in the UI so it doesn't get too huge
                listView.model = runnerManager.history.slice(0, 20)
            } else {
                listView.model = []
            }
        }
    }

66
    RowLayout {
Marco Martin's avatar
Marco Martin committed
67
        Layout.alignment: Qt.AlignTop
68
69
        PlasmaComponents3.ToolButton {
            icon.name: "configure"
70
71
72
73
            onClicked: {
                runnerWindow.visible = false
                runnerWindow.displayConfiguration()
            }
74
75
            Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Configure")
            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
84
            property bool allowCompletion: false

85
            clearButtonShown: true
Nate Graham's avatar
Nate Graham committed
86
87
            Layout.minimumWidth: PlasmaCore.Units.gridUnit * 25
            Layout.maximumWidth: PlasmaCore.Units.gridUnit * 25
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
88

89
90
            inputMethodHints: Qt.ImhNoPredictiveText

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
91
            activeFocusOnPress: true
92
93
            placeholderText: results.runnerName ? i18ndc("plasma_lookandfeel_org.kde.lookandfeel",
                                                         "Textfield placeholder text, query specific KRunner",
94
                                                         "Search '%1'…", results.runnerName)
95
                                                : i18ndc("plasma_lookandfeel_org.kde.lookandfeel",
96
                                                         "Textfield placeholder text", "Search…")
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
97

98
            PlasmaComponents3.BusyIndicator {
99
100
101
102
                anchors {
                    right: parent.right
                    top: parent.top
                    bottom: parent.bottom
Nate Graham's avatar
Nate Graham committed
103
                    margins: PlasmaCore.Units.smallSpacing
104
105
106
107
108
                    rightMargin: height
                }

                Timer {
                    id: queryTimer
109
                    property bool queryDisplay: false
110
111
112
                    running: results.querying
                    repeat: true
                    onRunningChanged: if (queryDisplay && !running) {
113
114
                        queryDisplay = false
                    }
115
116
117
                    onTriggered: if (!queryDisplay) {
                        queryDisplay = true
                    }
118
119
120
                    interval: 500
                }

121
                running: queryTimer.queryDisplay
122
            }
123
124
125
            function move_up() {
                if (length === 0) {
                    root.showHistory = true;
126
127
128
                    if (listView.count > 0) {
                        listView.forceActiveFocus();
                    }
129
130
131
132
133
134
135
136
137
                } else if (results.count > 0) {
                    results.forceActiveFocus();
                    results.decrementCurrentIndex();
                }
            }

            function move_down() {
                if (length === 0) {
                    root.showHistory = true;
138
139
140
                    if (listView.count > 0) {
                        listView.forceActiveFocus();
                    }
141
142
143
144
145
146
                } else if (results.count > 0) {
                    results.forceActiveFocus();
                    results.incrementCurrentIndex();
                }
            }

147
            onTextChanged: {
148
                root.query = queryField.text
Alexander Lohnau's avatar
Alexander Lohnau committed
149
150
151
152
                if (allowCompletion && length > 0 && runnerManager.historyEnabled) {
                    var oldText = text
                    var suggestedText = runnerManager.getHistorySuggestion(text);
                    if (suggestedText.length > 0) {
153
                        text = text + suggestedText.substr(oldText.length)
Alexander Lohnau's avatar
Alexander Lohnau committed
154
                        select(text.length, oldText.length)
155
156
                    }
                }
157
            }
158
159
160
161
            Keys.onPressed: {
                allowCompletion = (event.key !== Qt.Key_Backspace && event.key !== Qt.Key_Delete)

                if (event.modifiers & Qt.ControlModifier) {
Laurent Montel's avatar
Laurent Montel committed
162
                    if (event.key === Qt.Key_J) {
163
164
                        move_down()
                        event.accepted = true;
Laurent Montel's avatar
Laurent Montel committed
165
                    } else if (event.key === Qt.Key_K) {
166
167
168
                        move_up()
                        event.accepted = true;
                    }
169
170
                }
            }
171
172
            Keys.onUpPressed: move_up()
            Keys.onDownPressed: move_down()
173
174
175
176
177
178
179
180
181
182
            function closeOrRun(event) {
                // Close KRunner if no text was typed and enter was pressed, FEATURE: 211225
                if (!root.query) {
                    runnerWindow.visible = false
                } else {
                    results.runCurrentIndex(event)
                }
            }
            Keys.onEnterPressed: closeOrRun(event)
            Keys.onReturnPressed: closeOrRun(event)
183

184
185
186
            Keys.onEscapePressed: {
                runnerWindow.visible = false
            }
187
188
189
190
191
192
193
194

            PlasmaCore.SvgItem {
                anchors {
                    right: parent.right
                    rightMargin: 6 // from PlasmaStyle TextFieldStyle
                    verticalCenter: parent.verticalCenter
                }
                // match clear button
Nate Graham's avatar
Nate Graham committed
195
                width: Math.max(parent.height * 0.8, PlasmaCore.Units.iconSizes.small)
196
197
198
199
200
201
                height: width
                svg: PlasmaCore.Svg {
                    imagePath: "widgets/arrows"
                    colorGroup: PlasmaCore.Theme.ButtonColorGroup
                }
                elementId: "down-arrow"
Alexander Lohnau's avatar
Alexander Lohnau committed
202
                visible: queryField.length === 0 && runnerManager.historyEnabled
203
204
205
206
207
208
209
210
211
212
213
214
215

                MouseArea {
                    anchors.fill: parent
                    onPressed: {
                        root.showHistory = !root.showHistory
                        if (root.showHistory) {
                            listView.forceActiveFocus(); // is the history list
                        } else {
                            queryField.forceActiveFocus();
                        }
                    }
                }
            }
216
        }
217
        PlasmaComponents3.ToolButton {
218
219
220
221
222
223
            checkable: true
            checked: runnerWindow.pinned
            onToggled: runnerWindow.pinned = checked
            icon.name: "window-pin"
            Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Pin")
            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 {
Marco Martin's avatar
Marco Martin committed
231
        Layout.alignment: Qt.AlignTop
232
        visible: results.count > 0
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
233
        enabled: visible
234
        Layout.fillWidth: true
235
        Layout.preferredHeight: Math.min(Screen.height, results.contentHeight)
236

237
238
239
        Milou.ResultsView {
            id: results
            queryString: root.query
240
            runner: root.runner
241

Marco Martin's avatar
Marco Martin committed
242
            Keys.onPressed: {
243
                var ctrl = event.modifiers & Qt.ControlModifier;
Laurent Montel's avatar
Laurent Montel committed
244
                if (ctrl && event.key === Qt.Key_J) {
245
                    incrementCurrentIndex()
Laurent Montel's avatar
Laurent Montel committed
246
                } else if (ctrl && event.key === Qt.Key_K) {
247
                    decrementCurrentIndex()
Laurent Montel's avatar
Laurent Montel committed
248
                } else if (event.text !== "") {
249
250
251
252
                    // This prevents unprintable control characters from being inserted
                    if (!/[\x00-\x1F\x7F]/.test(event.text)) {
                        queryField.text += event.text;
                    }
253
                    queryField.cursorPosition = queryField.text.length
Marco Martin's avatar
Marco Martin committed
254
255
256
257
                    queryField.focus = true;
                }
            }

258
259
260
261
            Keys.onEscapePressed: {
                runnerWindow.visible = false
            }

262
263
264
            onActivated: {
                runnerWindow.visible = false
            }
265
266
267
268
269

            onUpdateQueryString: {
                queryField.text = text
                queryField.cursorPosition = cursorPosition
            }
270
271
        }
    }
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
272
273
274
275
276
277
278
279
280
281
282
283
284
285

    PlasmaExtras.ScrollArea {
        Layout.alignment: Qt.AlignTop
        Layout.fillWidth: true
        visible: root.query.length === 0 && listView.count > 0
        // don't accept keyboard input when not visible so the keys propagate to the other list
        enabled: visible
        Layout.preferredHeight: Math.min(Screen.height, listView.contentHeight)

        ListView {
            id: listView // needs this id so the delegate can access it
            keyNavigationWraps: true
            highlight: PlasmaComponents.Highlight {}
            highlightMoveDuration: 0
Marco Martin's avatar
Marco Martin committed
286
            activeFocusOnTab: true
Alexander Lohnau's avatar
Alexander Lohnau committed
287
            model: []
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
288
289
290
291
            delegate: Milou.ResultDelegate {
                id: resultDelegate
                width: listView.width
                typeText: index === 0 ? i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Recent Queries") : ""
292
293
294
295
                additionalActions: [{
                    icon: "list-remove",
                    text: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Remove")
                }]
Marco Martin's avatar
Marco Martin committed
296
                Accessible.description: i18n("in category recent queries")
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
297
298
            }

Marco Martin's avatar
Marco Martin committed
299
300
301
302
303
            onActiveFocusChanged: {
                if (!activeFocus && currentIndex == listView.count-1) {
                    currentIndex = 0;
                }
            }
304
305
            Keys.onReturnPressed: runCurrentIndex(event)
            Keys.onEnterPressed: runCurrentIndex(event)
Marco Martin's avatar
Marco Martin committed
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
            
            Keys.onTabPressed: {
                if (currentIndex == listView.count-1) {
                    listView.nextItemInFocusChain(true).forceActiveFocus();
                } else {
                    incrementCurrentIndex()
                }
            }
            Keys.onBacktabPressed: {
                if (currentIndex == 0) {
                    listView.nextItemInFocusChain(false).forceActiveFocus();
                } else {
                    decrementCurrentIndex()
                }
            }
            Keys.onPressed: {
322
                var ctrl = event.modifiers & Qt.ControlModifier;
Laurent Montel's avatar
Laurent Montel committed
323
                if (ctrl && event.key === Qt.Key_J) {
324
                    incrementCurrentIndex()
Laurent Montel's avatar
Laurent Montel committed
325
                } else if (ctrl && event.key === Qt.Key_K) {
326
                    decrementCurrentIndex()
Laurent Montel's avatar
Laurent Montel committed
327
                } else if (event.text !== "") {
328
329
330
331
332
333
                    // This prevents unprintable control characters from being inserted
                    if (event.key == Qt.Key_Escape) {
                        root.showHistory = false
                    } else if (!/[\x00-\x1F\x7F]/.test(event.text)) {
                        queryField.text += event.text;
                    }
Marco Martin's avatar
Marco Martin committed
334
335
336
337
                    queryField.focus = true;
                }
            }
  
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
338
339
340
            Keys.onUpPressed: decrementCurrentIndex()
            Keys.onDownPressed: incrementCurrentIndex()

341
            function runCurrentIndex(event) {
Alexander Lohnau's avatar
Alexander Lohnau committed
342
                var entry = runnerManager.history[currentIndex]
343
                if (entry) {
344
345
346
347
348
349
350
                    // If user presses Shift+Return to invoke an action, invoke the first runner action
                    if (event && event.modifiers === Qt.ShiftModifier
                            && currentItem.additionalActions && currentItem.additionalActions.length > 0) {
                        runAction(0);
                        return
                    }

351
                    queryField.text = entry
Marco Martin's avatar
Marco Martin committed
352
                    queryField.forceActiveFocus();
353
354
355
356
357
358
359
                }
            }

            function runAction(actionIndex) {
                if (actionIndex === 0) {
                    // QStringList changes just reset the model, so we'll remember the index and set it again
                    var currentIndex = listView.currentIndex
Alexander Lohnau's avatar
Alexander Lohnau committed
360
361
                    runnerManager.removeFromHistory(currentIndex)
                    model = runnerManager.history
362
363
                    listView.currentIndex = currentIndex
                }
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
364
365
366
367
            }
        }

    }
368
}