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 checkable: true
162 // shortcut: "Ctrl+Shift+F"
163 onTriggered: toggleSearchBar()
164 }
165
167 {
168 id: terminalMenu
169 Maui.Controls.component: !kterminal.isTextSelected ? null : _headerComponent
170
171 Component
172 {
173 id: _headerComponent
174 Pane
175 {
176 width: ListView.view.width
177 implicitHeight: Math.min(80, contentHeight) + topPadding + bottomPadding
178 clip: true
179
180 background: Rectangle
181 {
182 color: kterminal.backgroundColor()
183 radius: Maui.Style.radiusV
184 }
185
186 contentItem: Label
187 {
188 text: kterminal.isTextSelected ? kterminal.selectedText() : ""
189 color :kterminal.foregroundColor()
190 wrapMode: Text.WordWrap
191 elide: Text.ElideRight
192 font: kterminal.font
193 }
194 }
195 }
196
197 MenuItem
198 {
199 action: _copyAction
200 enabled: kterminal.isTextSelected
201 }
202
203 MenuItem
204 {
205 action: _pasteAction
206 }
207
208 MenuSeparator {}
209
210 MenuItem
211 {
212 action: _findAction
213 }
214 }
215
216 footBar.visible: false
217 footBar.forceCenterMiddleContent: false
218 footBar.rightContent: Maui.ToolButtonMenu
219 {
220 icon.name: "overflow-menu"
221
222 MenuItem
223 {
224 id: _findCaseSensitively
225 checkable: true
226 text: i18nd("mauikittexteditor","Case Sensitive")
227 }
228
229 MenuItem
230 {
231 id: _findWholeWords
232 checkable: true
233 text: i18nd("mauikittexteditor","Whole Words Only")
234 }
235 }
236
237 footBar.middleContent: Maui.SearchField
238 {
239 id: findBar
240 Layout.fillWidth: true
241 Layout.alignment: Qt.AlignHCenter
242 Layout.maximumWidth: 500
243 placeholderText: i18nd("mauikitterminal", "Find...")
244 onAccepted: ksession.search(text, ksession.previousLineSearch, ksession.previousColumnSearch, false)
245 onVisibleChanged:
246 {
247 if(visible)
248 {
249 findBar.forceActiveFocus()
250 }else
251 {
252 control.forceActiveFocus()
253 }
254 }
255
256 actions: [
257
258 Action
259 {
260 icon.name: "go-up"
261 // text: i18n("Previous")
262 onTriggered: ksession.search(findBar.text, ksession.previousLineSearch, ksession.previousColumnSearch, false)
263 }
264 ]
265
266 Keys.enabled: true
267 Keys.onPressed: (event) =>
268 {
269 if ((event.key == Qt.Key_F) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier))
270 {
271 control.toggleSearchBar()
272 }
273 }
274 }
275
276 Term.QMLTermWidget
277 {
278 id: kterminal
279 anchors.fill: parent
280 // terminalUsesMouse: true
281
282 enableBold: true
283 fullCursorHeight: true
284
285 property int totalLines: kterminal.scrollbarMaximum - kterminal.scrollbarMinimum + kterminal.lines
286 // onKeyPressedSignal: console.log(e.key)
287
288 font: Maui.Style.monospacedFont
289
290 backgroundOpacity: 1
291
292 onTerminalUsesMouseChanged: console.log(terminalUsesMouse);
293
294 session: Term.QMLTermSession
295 {
296 id: ksession
297 initialWorkingDirectory: "$HOME"
298 shellProgram: "$SHELL"
299
300 onFinished:
301 {
302 console.log("Terminal finished")
303 }
304
305 // Disable search until implemented correctly
306 property int previousColumnSearch : 0
307 property int previousLineSearch: 0
308
309 onMatchFound:
310 {
311 previousColumnSearch = startColumn
312 previousLineSearch = startLine
313
314
315 _scrollBarLoader.item.highlightLine = startLine
316
317 kterminal.matchFound(startColumn, startLine, endColumn, endLine)
318 console.log("found at: %1 %2 %3 %4".arg(startColumn).arg(startLine).arg(endColumn).arg(endLine));
319 }
320
321 onNoMatchFound:
322 {
323 previousColumnSearch = 0
324 previousLineSearch = 0
325 _scrollBarLoader.item.highlightLine = -1
326
327 kterminal.noMatchFound();
328 console.log("not found");
329 }
330 }
331
332 customColorScheme
333 {
334 backgroundColor: Maui.Theme.backgroundColor
335 foregroundColor: Maui.Theme.textColor
336 color2: Maui.Theme.disabledTextColor
337 color3: Maui.Theme.negativeBackgroundColor
338 color4: Maui.Theme.positiveBackgroundColor
339 color5: Maui.Theme.neutralBackgroundColor
340 color6: Maui.Theme.linkColor
341 color7: Maui.Theme.visitedLinkColor
342 color8: Maui.Theme.highlightColor
343 color9: Maui.Theme.highlightedTextColor
344 }
345
346 Keys.enabled: true
347
348 Keys.onPressed: (event) =>
349 {
350 if ((event.key === Qt.Key_A) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier))
351 {
352 kterminal.selectAll()
353 event.accepted = true
354 return
355 }
356
357 if ((event.key === Qt.Key_C) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier))
358 {
359 _copyAction.trigger()
360 event.accepted = true
361 return
362 }
363
364 if ((event.key === Qt.Key_V) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier))
365 {
366 _pasteAction.trigger()
367 event.accepted = true
368 return
369 }
370
371
372 if ((event.key == Qt.Key_F) && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier))
373 {
374 control.toggleSearchBar()
375 return
376 }
377
378 control.keyPressed(event)
379 }
380
381 Loader
382 {
383 asynchronous: true
384 anchors.fill: parent
385
386 sourceComponent: Private.TerminalInputArea
387 {
388 // enabled: terminalPage.state != "SELECTION"
389
390 // FIXME: should anchor to the bottom of the window to cater for the case when the OSK is up
391
392 // This is the minimum wheel event registered by the plugin (with the current settings).
393 property real wheelValue: 40
394
395 // This is needed to fake a "flickable" scrolling.
396 swipeDelta: kterminal.fontMetrics.height
397
398 // Mouse actions
399 onMouseMoveDetected:(x, y, button, buttons, modifiers) => kterminal.simulateMouseMove(x, y, button, buttons, modifiers);
400 onDoubleClickDetected:(x, y, button, buttons, modifiers) => kterminal.simulateMouseDoubleClick(x, y, button, buttons, modifiers);
401 onMousePressDetected:(x, y, button, buttons, modifiers) =>
402 {
403 kterminal.forceActiveFocus();
404 kterminal.simulateMousePress(x, y, button, buttons, modifiers);
405 control.clicked()
406 }
407 onMouseReleaseDetected: (x, y, button, buttons, modifiers) => kterminal.simulateMouseRelease(x, y, button, buttons, modifiers);
408 onMouseWheelDetected: (x, y, buttons, modifiers, angleDelta) => kterminal.simulateWheel(x, y, buttons, modifiers, angleDelta);
409
410 // Touch actions
411 onTouchPress: (x, y) =>
412 {
413 // kterminal.forceActiveFocus()
414 // control.clicked()
415 }
416
417 onTouchClick: (x, y) =>
418 {
419 kterminal.forceActiveFocus()
420 // kterminal.simulateKeyPress(Qt.Key_Tab, Qt.NoModifier, true, 0, "");
421 control.clicked()
422 }
423
424 onTouchPressAndHold: (x, y) =>
425 {
426 alternateAction(x, y);
427 }
428
429 // Swipe actions
430 onSwipeYDetected: (steps) => {
431 if (steps > 0) {
432 simulateSwipeDown(steps);
433 } else {
434 simulateSwipeUp(-steps);
435 }
436 }
437
438 onSwipeXDetected: (steps) => {
439 if (steps > 0) {
440 simulateSwipeRight(steps);
441 } else {
442 simulateSwipeLeft(-steps);
443 }
444 }
445
446 onTwoFingerSwipeYDetected: (steps) => {
447 if (steps > 0) {
448 simulateDualSwipeDown(steps);
449 } else {
450 simulateDualSwipeUp(-steps);
451 }
452 }
453
454 function simulateSwipeUp(steps) {
455 while(steps > 0) {
456 kterminal.simulateKeyPress(Qt.Key_Up, Qt.NoModifier, true, 0, "");
457 steps--;
458 }
459 }
460 function simulateSwipeDown(steps) {
461 while(steps > 0) {
462 kterminal.simulateKeyPress(Qt.Key_Down, Qt.NoModifier, true, 0, "");
463 steps--;
464 }
465 }
466 function simulateSwipeLeft(steps) {
467 while(steps > 0) {
468 kterminal.simulateKeyPress(Qt.Key_Left, Qt.NoModifier, true, 0, "");
469 steps--;
470 }
471 }
472 function simulateSwipeRight(steps) {
473 while(steps > 0) {
474 kterminal.simulateKeyPress(Qt.Key_Right, Qt.NoModifier, true, 0, "");
475 steps--;
476 }
477 }
478 function simulateDualSwipeUp(steps) {
479 while(steps > 0) {
480 kterminal.simulateWheel(width * 0.5, height * 0.5, Qt.NoButton, Qt.NoModifier, Qt.point(0, -wheelValue));
481 steps--;
482 }
483 }
484 function simulateDualSwipeDown(steps) {
485 while(steps > 0) {
486 kterminal.simulateWheel(width * 0.5, height * 0.5, Qt.NoButton, Qt.NoModifier, Qt.point(0, wheelValue));
487 steps--;
488 }
489 }
490
491 // Semantic actions
492 onAlternateAction: (x, y) => {
493 // Force the hiddenButton in the event position.
494 //hiddenButton.x = x;
495 //hiddenButton.y = y;
496 terminalMenu.show()
497
498 }
499 }
500 }
501
502 Loader
503 {
504 id: _scrollBarLoader
505 asynchronous: true
506 anchors.fill: parent
507
508 sourceComponent: Private.TerminalScrollBar
509 {
510 terminal: kterminal
511 }
512 }
513
514 Maui.FloatingButton
515 {
516 visible: Maui.Handy.isMobile
517 anchors.right: parent.right
518 anchors.bottom: parent.bottom
519 anchors.margins: Maui.Style.space.big
520 icon.name: "input-keyboard-virtual"
521 text: i18n("Toggle Virtual Keyboard")
522 onClicked:
523 {
524 if (Qt.inputMethod.visible)
525 {
526 Qt.inputMethod.hide();
527 } else
528 {
529 control.forceActiveFocus();
530 Qt.inputMethod.show();
531 }
532 }
533 }
534 }
535
536 opacity: _dropArea.containsDrag ? 0.5 : 1
537
538 DropArea
539 {
540 id: _dropArea
541 anchors.fill: parent
542 onDropped: (drop) =>
543 {
544 if(drop.hasUrls)
545 control.urlsDropped(drop.urls)
546 }
547 }
548
549 Component.onCompleted:
550 {
551 ksession.startShellProgram();
552 forceActiveFocus()
553 }
554
555 /**
556 * @brief Force to focus the terminal display for entering input
557 */
558 function forceActiveFocus()
559 {
560 kterminal.forceActiveFocus()
561 }
562
563 /**
564 * Toggle the search text field bar
565 */
566 function toggleSearchBar()
567 {
568 footBar.visible = !footBar.visible
569 }
570}
bool visible
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 Nov 29 2024 11:55:13 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.