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

KDE's Doxygen guidelines are available online.