MauiKit Terminal

Terminal.qml
1
2import QtQuick
3import QtQuick.Controls
4import QtQuick.Layouts
5
6import org.mauikit.controls as Maui
7import org.mauikit.terminal as Term
8import "private" as Private
9
10/**
11 * @brief A terminal emulator console with built-in support for touch and keyboard inputs, and many other features.
12 *
13 * @image html terminal.png "Demo of the Terminal control"
14 *
15 * @section features Features
16 *
17 * This control has a pack of built-in functionality making it ready for embedding it into any other application.
18 * A quick overview includes support for touch gestures for navigation, scrollbar, search and find, contextual menu actions and keyboard shortcuts.
19 *
20 * @note For creating your own implementation you can look into using the exposed classes `QMLTermWidget` and `QMLTermSession`.
21 *
22 * @code
23 * import QtQuick
24import QtQuick.Controls
25
26import org.mauikit.controls as Maui
27
28import org.mauikit.terminal as Term
29
30Maui.ApplicationWindow
31{
32 id: root
33
34 Maui.Page
35 {
36 Maui.Controls.showCSD: true
37 anchors.fill: parent
38
39 Term.Terminal
40 {
41 anchors.fill: parent
42 }
43 }
44}
45 * @endcode
46 */
47Maui.Page
48{
49 id: control
50
51 Maui.Theme.colorSet: Maui.Theme.Header
52 Maui.Theme.inherit: false
53
54 title: ksession.title
55 showTitle: false
56
57 headBar.visible: false
58
59 focus: true
60 clip: true
61
62 /**
63 * @see Konsole::TerminalDisplay::readOnly
64 */
65 property alias readOnly : kterminal.readOnly
67 /**
68 * @brief The resolution size of the emulated terminal display
69 */
70 readonly property size virtualResolution: Qt.size(kterminal.width, kterminal.height)
71
72 /**
73 * @brief Alias to the terminal display object
74 * @property Konsole::TerminalDisplay Terminal::kterminal
75 */
76 readonly property alias kterminal: kterminal
77
78 /**
79 * @brief Alias to the emulated terminal session
80 * @property KSession Terminal::session
81 */
82 readonly property alias session: ksession
83
84 /**
85 * @brief Alias to the TextField used for handling the search queries. It is exposed to allow appending action entries, for example.
86 * @property TextField Terminal::findBar
87 */
88 readonly property alias findBar : findBar
90 /**
91 * @brief The content of the contextual menu. A set of default actions is already added, to append more actions use this property.
92 * @code
93 * menu: [
94 * Action { text: "Action1" },
95 * MenuItem {text: "Action2"}
96 * ]
97 * @endcode
98 * @property list<QtObject> Terminal::menu
99 */
100 property alias menu : terminalMenu.contentData
101
102 /**
103 * @see TerminalDisplay::terminalSize
104 */
105 readonly property alias terminalSize: kterminal.terminalSize
106
107 /**
108 * @see TerminalDisplay::fontMetricts
109 */
110 readonly property alias fontMetrics: kterminal.fontMetrics
111
112 /**
113 * @brief Emitted when a drag&drop action has been performed and the drop contains valid URLs
114 * @param urls The list of URLs dropped
115 */
116 signal urlsDropped(var urls)
117
118 /**
119 * @brief Emitted when a keyboard shortcut has been triggered and it is not one of the default ones that are recognized
120 * @param event Object with the event information
121 */
122 signal keyPressed(var event)
123
124 /**
125 * @brief Emitted when the terminal control area has been clicked
126 */
127 signal clicked()
128
129 //Actions
130 Action
131 {
132 id: _copyAction
133 text: i18nd("mauikitterminal", "Copy")
134 icon.name: "edit-copy"
135 onTriggered:
136 {
137 kterminal.copyClipboard();
138 control.forceActiveFocus()
139 }
140 //shortcut: "Ctrl+Shift+C"
141 }
142
143 Action
144 {
145 id: _pasteAction
146 text: i18nd("mauikitterminal", "Paste")
147 icon.name: "edit-paste"
148 onTriggered:
149 {
150 kterminal.pasteClipboard()
151 control.forceActiveFocus()
152 }
153 // shortcut: "Ctrl+Shift+V"
154 }
155
156 Action
157 {
158 id: _findAction
159 text: i18nd("mauikitterminal", "Find")
160 icon.name: "edit-find"
161 // shortcut: "Ctrl+Shift+F"
162 onTriggered: toggleSearchBar()
163 }
164
166 {
167 id: terminalMenu
168
169 MenuItem
170 {
171 action: _copyAction
172 }
173
174 MenuItem
175 {
176 action: _pasteAction
177 }
178
179 MenuItem
180 {
181 action: _findAction
182 }
183 }
184
185 footBar.visible: false
186 footBar.forceCenterMiddleContent: false
187 footBar.rightContent: Maui.ToolButtonMenu
188 {
189 icon.name: "overflow-menu"
190
191 MenuItem
192 {
193 id: _findCaseSensitively
194 checkable: true
195 text: i18nd("mauikittexteditor","Case Sensitive")
196 }
197
198 MenuItem
199 {
200 id: _findWholeWords
201 checkable: true
202 text: i18nd("mauikittexteditor","Whole Words Only")
203 }
204 }
205
206 footBar.middleContent: Maui.SearchField
207 {
208 id: findBar
209 Layout.fillWidth: true
210 Layout.alignment: Qt.AlignHCenter
211 Layout.maximumWidth: 500
212 placeholderText: i18nd("mauikitterminal", "Find...")
213 onAccepted: ksession.search(text, ksession.previousLineSearch, ksession.previousColumnSearch, false)
214 onVisibleChanged:
215 {
216 if(visible)
217 {
218 findBar.forceActiveFocus()
219 }else
220 {
221 control.forceActiveFocus()
222 }
223 }
224
225 actions: [
226
227 Action
228 {
229 icon.name: "go-up"
230 // text: i18n("Previous")
231 onTriggered: ksession.search(findBar.text, ksession.previousLineSearch, ksession.previousColumnSearch, false)
232 }
233 ]
234
235 Keys.enabled: true
236
237 Keys.onPressed:
238 {
239 if ((event.key == Qt.Key_F) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier))
240 {
241 control.toggleSearchBar()
242 }
243 }
244 }
245
246 Term.QMLTermWidget
247 {
248 id: kterminal
249 anchors.fill: parent
250 // terminalUsesMouse: true
251
252 enableBold: true
253 fullCursorHeight: true
254
255 property int totalLines: kterminal.scrollbarMaximum - kterminal.scrollbarMinimum + kterminal.lines
256 // onKeyPressedSignal: console.log(e.key)
257
258 font: Maui.Style.monospacedFont
259
260 backgroundOpacity: 1
261
262 onTerminalUsesMouseChanged: console.log(terminalUsesMouse);
263
264 session: Term.QMLTermSession
265 {
266 id: ksession
267 initialWorkingDirectory: "$HOME"
268 shellProgram: "$SHELL"
269
270 onFinished:
271 {
272 console.log("Terminal finished")
273 }
274
275 // Disable search until implemented correctly
276 property int previousColumnSearch : 0
277 property int previousLineSearch: 0
278
279 onMatchFound:
280 {
281 previousColumnSearch = startColumn
282 previousLineSearch = startLine
283
284
285 _scrollBarLoader.item.highlightLine = startLine
286
287 kterminal.matchFound(startColumn, startLine, endColumn, endLine)
288 console.log("found at: %1 %2 %3 %4".arg(startColumn).arg(startLine).arg(endColumn).arg(endLine));
289 }
290
291 onNoMatchFound:
292 {
293 previousColumnSearch = 0
294 previousLineSearch = 0
295 _scrollBarLoader.item.highlightLine = -1
296
297 kterminal.noMatchFound();
298 console.log("not found");
299 }
300
301 }
302
303 customColorScheme
304 {
305 backgroundColor: Maui.Theme.backgroundColor
306 foregroundColor: Maui.Theme.textColor
307 color2: Maui.Theme.disabledTextColor
308 color3: Maui.Theme.negativeBackgroundColor
309 color4: Maui.Theme.positiveBackgroundColor
310 color5: Maui.Theme.neutralBackgroundColor
311 color6: Maui.Theme.linkColor
312 color7: Maui.Theme.visitedLinkColor
313 color8: Maui.Theme.highlightColor
314 color9: Maui.Theme.highlightedTextColor
315 }
316
317 Keys.enabled: true
318
319 Keys.onPressed: (event) =>
320 {
321 if ((event.key === Qt.Key_A) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier))
322 {
323 kterminal.selectAll()
324 event.accepted = true
325 return
326 }
327
328 if ((event.key === Qt.Key_C) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier))
329 {
330 _copyAction.trigger()
331 event.accepted = true
332 return
333 }
334
335 if ((event.key === Qt.Key_V) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier))
336 {
337 _pasteAction.trigger()
338 event.accepted = true
339 return
340 }
341
342
343 if ((event.key == Qt.Key_F) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier))
344 {
345 control.toggleSearchBar()
346 return
347 }
348
349 control.keyPressed(event)
350 }
351
352 Loader
353 {
354 asynchronous: true
355 anchors.fill: parent
356
357 sourceComponent: Private.TerminalInputArea
358 {
359 // enabled: terminalPage.state != "SELECTION"
360
361 // FIXME: should anchor to the bottom of the window to cater for the case when the OSK is up
362
363 // This is the minimum wheel event registered by the plugin (with the current settings).
364 property real wheelValue: 40
365
366 // This is needed to fake a "flickable" scrolling.
367 swipeDelta: kterminal.fontMetrics.height
368
369 // Mouse actions
370 onMouseMoveDetected:(x, y, button, buttons, modifiers) => kterminal.simulateMouseMove(x, y, button, buttons, modifiers);
371 onDoubleClickDetected:(x, y, button, buttons, modifiers) => kterminal.simulateMouseDoubleClick(x, y, button, buttons, modifiers);
372 onMousePressDetected:(x, y, button, buttons, modifiers) =>
373 {
374 kterminal.forceActiveFocus();
375 kterminal.simulateMousePress(x, y, button, buttons, modifiers);
376 control.clicked()
377 }
378 onMouseReleaseDetected: (x, y, button, buttons, modifiers) => kterminal.simulateMouseRelease(x, y, button, buttons, modifiers);
379 onMouseWheelDetected: (x, y, buttons, modifiers, angleDelta) => kterminal.simulateWheel(x, y, buttons, modifiers, angleDelta);
380
381 // Touch actions
382 onTouchPress: (x, y) =>
383 {
384 // kterminal.forceActiveFocus()
385 // control.clicked()
386 }
387
388 onTouchClick: (x, y) =>
389 {
390 kterminal.forceActiveFocus()
391 // kterminal.simulateKeyPress(Qt.Key_Tab, Qt.NoModifier, true, 0, "");
392 control.clicked()
393 }
394
395 onTouchPressAndHold: (x, y) =>
396 {
397 alternateAction(x, y);
398 }
399
400 // Swipe actions
401 onSwipeYDetected: (steps) => {
402 if (steps > 0) {
403 simulateSwipeDown(steps);
404 } else {
405 simulateSwipeUp(-steps);
406 }
407 }
408
409 onSwipeXDetected: (steps) => {
410 if (steps > 0) {
411 simulateSwipeRight(steps);
412 } else {
413 simulateSwipeLeft(-steps);
414 }
415 }
416
417 onTwoFingerSwipeYDetected: (steps) => {
418 if (steps > 0) {
419 simulateDualSwipeDown(steps);
420 } else {
421 simulateDualSwipeUp(-steps);
422 }
423 }
424
425 function simulateSwipeUp(steps) {
426 while(steps > 0) {
427 kterminal.simulateKeyPress(Qt.Key_Up, Qt.NoModifier, true, 0, "");
428 steps--;
429 }
430 }
431 function simulateSwipeDown(steps) {
432 while(steps > 0) {
433 kterminal.simulateKeyPress(Qt.Key_Down, Qt.NoModifier, true, 0, "");
434 steps--;
435 }
436 }
437 function simulateSwipeLeft(steps) {
438 while(steps > 0) {
439 kterminal.simulateKeyPress(Qt.Key_Left, Qt.NoModifier, true, 0, "");
440 steps--;
441 }
442 }
443 function simulateSwipeRight(steps) {
444 while(steps > 0) {
445 kterminal.simulateKeyPress(Qt.Key_Right, Qt.NoModifier, true, 0, "");
446 steps--;
447 }
448 }
449 function simulateDualSwipeUp(steps) {
450 while(steps > 0) {
451 kterminal.simulateWheel(width * 0.5, height * 0.5, Qt.NoButton, Qt.NoModifier, Qt.point(0, -wheelValue));
452 steps--;
453 }
454 }
455 function simulateDualSwipeDown(steps) {
456 while(steps > 0) {
457 kterminal.simulateWheel(width * 0.5, height * 0.5, Qt.NoButton, Qt.NoModifier, Qt.point(0, wheelValue));
458 steps--;
459 }
460 }
461
462 // Semantic actions
463 onAlternateAction: (x, y) => {
464 // Force the hiddenButton in the event position.
465 //hiddenButton.x = x;
466 //hiddenButton.y = y;
467 terminalMenu.show()
468
469 }
470 }
471 }
472
473 Loader
474 {
475 id: _scrollBarLoader
476 asynchronous: true
477 anchors.fill: parent
478
479 sourceComponent: Private.TerminalScrollBar
480 {
481 terminal: kterminal
482 }
483 }
484
485 Maui.FloatingButton
486 {
487 visible: Maui.Handy.isMobile
488 anchors.right: parent.right
489 anchors.bottom: parent.bottom
490 anchors.margins: Maui.Style.space.big
491 icon.name: "input-keyboard-virtual"
492 text: i18n("Toggle Virtual Keyboard")
493 onClicked:
494 {
495 if (Qt.inputMethod.visible)
496 {
497 Qt.inputMethod.hide();
498 } else
499 {
500 control.forceActiveFocus();
501 Qt.inputMethod.show();
502 }
503 }
504 }
505 }
506
507 opacity: _dropArea.containsDrag ? 0.5 : 1
508
509 DropArea
510 {
511 id: _dropArea
512 anchors.fill: parent
513 onDropped: (drop) =>
514 {
515 if(drop.hasUrls)
516 control.urlsDropped(drop.urls)
517 }
518 }
519
520 Component.onCompleted:
521 {
522 ksession.startShellProgram();
523 forceActiveFocus()
524 }
525
526 /**
527 * @brief Force to focus the terminal display for entering input
528 */
529 function forceActiveFocus()
530 {
531 kterminal.forceActiveFocus()
532 }
533
534 /**
535 * Toggle the search text field bar
536 */
537 function toggleSearchBar()
538 {
539 footBar.visible = !footBar.visible
540 }
541}
QString i18nd(const char *domain, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
KIOWIDGETS_EXPORT DropJob * drop(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags=DefaultFlags)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Aug 30 2024 11:51:42 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.