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

KDE's Doxygen guidelines are available online.