Warning, /plasma-mobile/qmlkonsole/src/contents/ui/TerminalPage.qml is written in an unsupported language. File is not indexed.
0001 // SPDX-FileCopyrightText: 2019-2020 Jonah BrĂ¼chert <jbb@kaidan.im>
0002 // SPDX-FileCopyrightText: 2021-2022 Devin Lin <espidev@gmail.com>
0003 //
0004 // SPDX-License-Identifier: GPL-2.0-or-later
0005
0006 import QtQuick
0007 import QtQuick.Layouts
0008 import QtQuick.Controls
0009
0010 import org.kde.konsoleqml
0011 import org.kde.kirigami as Kirigami
0012
0013 import org.kde.qmlkonsole
0014
0015 Kirigami.Page {
0016 id: root
0017 property TerminalEmulator currentTerminal: tabSwipeView.contentChildren[tabSwipeView.currentIndex].termWidget
0018
0019 background: Item {}
0020
0021 topPadding: 0
0022 bottomPadding: 0
0023 leftPadding: 0
0024 rightPadding: 0
0025
0026 property bool initialSessionCreated: false
0027
0028 function forceTerminalFocus() {
0029 const wasVisible = Qt.inputMethod.visible;
0030 currentTerminal.forceActiveFocus();
0031 if (!wasVisible) {
0032 Qt.inputMethod.hide();
0033 }
0034 }
0035
0036 Component.onCompleted: {
0037 // focus terminal text input immediately after load
0038 forceTerminalFocus();
0039 }
0040
0041 // switch tab button
0042 titleDelegate: ToolButton {
0043 Layout.fillHeight: true
0044 onClicked: selectTabDialog.open()
0045 contentItem: RowLayout {
0046 spacing: 0
0047 Kirigami.Heading {
0048 text: currentTerminal.tabName
0049 Layout.leftMargin: Kirigami.Units.largeSpacing
0050 Layout.rightMargin: Kirigami.Units.largeSpacing
0051 }
0052 Kirigami.Icon {
0053 Layout.alignment: Qt.AlignVCenter
0054 Layout.rightMargin: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
0055 implicitHeight: Kirigami.Units.iconSizes.small
0056 implicitWidth: Kirigami.Units.iconSizes.small
0057 source: "go-down"
0058 }
0059 }
0060 }
0061
0062 property bool isWideScreen: root.width > 540
0063
0064 function openSettings() {
0065 if (isWideScreen) {
0066 root.settingsDialogLoader.active = true;
0067 root.settingsDialogLoader.item.open();
0068 } else {
0069 pageStack.push("qrc:/SettingsPage.qml",
0070 {
0071 terminal: currentTerminal
0072 }
0073 );
0074 }
0075 }
0076
0077 property var settingsDialogLoader: Loader {
0078 active: false
0079 sourceComponent: SettingsDialog {
0080 id: settingsDialog
0081 terminal: currentTerminal
0082 }
0083 }
0084
0085 function switchToTab(index) {
0086 tabSwipeView.setCurrentIndex(index);
0087 }
0088
0089 function closeTab(index) {
0090 if (tabSwipeView.contentChildren[index].termWidget.session.hasActiveProcess) {
0091 selectTabDialog.close();
0092 confirmDialog.indexToClose = index;
0093 confirmDialog.open();
0094 } else {
0095 TerminalTabModel.removeTab(index);
0096 }
0097 }
0098
0099 Shortcut {
0100 sequence: "Shift+Left"
0101 onActivated: {
0102 if (tabSwipeView.currentIndex > 0)
0103 tabSwipeView.currentIndex--
0104 }
0105 }
0106
0107 Shortcut {
0108 sequence: "Shift+Right"
0109 onActivated: {
0110 if (tabSwipeView.currentIndex < tabSwipeView.count - 1)
0111 tabSwipeView.currentIndex++
0112 }
0113 }
0114
0115 property list<Kirigami.Action> menuActions: [
0116 Kirigami.Action {
0117 id: newTabAction
0118 icon.name: "list-add"
0119 text: i18nc("@action:intoolbar", "New Tab")
0120 onTriggered: {
0121 TerminalTabModel.newTab();
0122 tabSwipeView.currentIndex = tabSwipeView.contentChildren.length - 1;
0123 }
0124 shortcut: "Ctrl+Shift+T"
0125 displayHint: Kirigami.DisplayHint.KeepVisible
0126 },
0127 Kirigami.Action {
0128 icon.name: "dialog-scripts"
0129 text: i18nc("@action:intoolbar", "Saved Commands")
0130 onTriggered: savedCommandsDialog.open()
0131 displayHint: Kirigami.DisplayHint.KeepVisible
0132 },
0133 Kirigami.Action {
0134 displayHint: Kirigami.DisplayHint.AlwaysHide
0135 icon.name: "search"
0136 text: i18n("reverse-i-search")
0137 onTriggered: {
0138 currentTerminal.pressKey(Qt.Key_R, Qt.ControlModifier, true)
0139 }
0140 },
0141 Kirigami.Action {
0142 displayHint: Kirigami.DisplayHint.AlwaysHide
0143 icon.name: "dialog-cancel"
0144 text: i18n("Cancel current command")
0145 onTriggered: {
0146 currentTerminal.pressKey(Qt.Key_C, Qt.ControlModifier, true)
0147 }
0148 },
0149 Kirigami.Action {
0150 displayHint: Kirigami.DisplayHint.AlwaysHide
0151 icon.name: "document-close"
0152 text: i18n("Send EOF")
0153 onTriggered: {
0154 currentTerminal.pressKey(Qt.Key_D, Qt.ControlModifier, true)
0155 }
0156 },
0157 Kirigami.Action {
0158 displayHint: Kirigami.DisplayHint.AlwaysHide
0159 icon.name: "bboxprev"
0160 text: i18n("Cursor to line start")
0161 onTriggered: {
0162 currentTerminal.pressKey(Qt.Key_A, Qt.ControlModifier, true)
0163 }
0164 },
0165 Kirigami.Action {
0166 displayHint: Kirigami.DisplayHint.AlwaysHide
0167 icon.name: "bboxnext"
0168 text: i18n("Cursor to line end")
0169 onTriggered: {
0170 currentTerminal.pressKey(Qt.Key_E, Qt.ControlModifier, true)
0171 }
0172 },
0173 Kirigami.Action {
0174 displayHint: Kirigami.DisplayHint.AlwaysHide
0175 icon.name: "edit-cut"
0176 text: i18n("Kill to line end")
0177 onTriggered: {
0178 currentTerminal.pressKey(Qt.Key_K, Qt.ControlModifier, true)
0179 }
0180 },
0181 Kirigami.Action {
0182 displayHint: Kirigami.DisplayHint.AlwaysHide
0183 icon.name: "edit-paste"
0184 text: i18n("Paste from kill buffer")
0185 onTriggered: {
0186 currentTerminal.pressKey(Qt.Key_Y, Qt.ControlModifier, true)
0187 }
0188 },
0189 Kirigami.Action {
0190 displayHint: Kirigami.DisplayHint.AlwaysHide
0191 icon.name: "edit-copy"
0192 text: i18n("Copy")
0193 onTriggered: {
0194 currentTerminal.copyClipboard();
0195 root.currentTerminal.touchSelectionMode = false;
0196 }
0197 shortcut: "Ctrl+Shift+C"
0198 },
0199 Kirigami.Action {
0200 displayHint: Kirigami.DisplayHint.AlwaysHide
0201 icon.name: "edit-paste"
0202 text: i18n("Paste")
0203 onTriggered: currentTerminal.pasteClipboard();
0204 shortcut: "Ctrl+Shift+V"
0205 },
0206 Kirigami.Action {
0207 displayHint: Kirigami.DisplayHint.IconOnly
0208 text: i18n("Settings")
0209 icon.name: "settings-configure"
0210 onTriggered: root.openSettings()
0211 }
0212 ]
0213
0214 header: Loader {
0215 visible: active
0216 active: root.isWideScreen && root.height > 360
0217 sourceComponent: Control {
0218 id: tabControl
0219 height: visible ? implicitHeight : 0
0220 visible: tabControlListView.count > 1
0221
0222 leftPadding: 0
0223 rightPadding: 0
0224 topPadding: 0
0225 bottomPadding: 0
0226
0227 width: root.width
0228
0229 background: Rectangle {
0230 color: Kirigami.Theme.backgroundColor
0231 }
0232
0233 contentItem: Tabs {
0234 id: tabControlListView
0235 currentIndex: tabSwipeView.currentIndex
0236
0237 onSwitchToTabRequested: index => root.switchToTab(index)
0238 onCloseTabRequested: index => root.closeTab(index)
0239 onAddTabRequested: newTabAction.trigger()
0240 }
0241 }
0242 }
0243
0244 ColumnLayout {
0245 spacing: 0
0246 anchors.fill: parent
0247
0248 SavedCommandsDialog {
0249 id: savedCommandsDialog
0250 terminal: root.currentTerminal
0251 }
0252
0253 Kirigami.Dialog {
0254 id: confirmDialog
0255
0256 property int indexToClose: 0
0257 property var selectedTerminal: tabSwipeView.contentChildren[indexToClose]
0258
0259 title: i18nc("@title:window", "Confirm closing %1", selectedTerminal ? selectedTerminal.termWidget.tabName : "")
0260 standardButtons: Dialog.Yes | Dialog.Cancel
0261 padding: Kirigami.Units.gridUnit
0262
0263 onAccepted: {
0264 TerminalTabModel.removeTab(indexToClose);
0265 selectTabDialog.open();
0266 }
0267 onRejected: {
0268 selectTabDialog.open();
0269 }
0270
0271 RowLayout {
0272 Label {
0273 Layout.maximumWidth: Kirigami.Units.gridUnit * 15
0274 wrapMode: Text.Wrap
0275 text: i18n("A process is currently running in this tab. Are you sure you want to close it?")
0276 }
0277 }
0278 }
0279
0280 Kirigami.Dialog {
0281 id: selectTabDialog
0282 title: i18nc("@title:window", "Select Tab")
0283 standardButtons: Dialog.Close
0284
0285 ListView {
0286 id: tabListView
0287 implicitWidth: Kirigami.Units.gridUnit * 16
0288 implicitHeight: Kirigami.Units.gridUnit * 18
0289 Kirigami.Theme.inherit: false
0290 Kirigami.Theme.colorSet: Kirigami.Theme.View
0291 model: TerminalTabModel
0292
0293 delegate: ItemDelegate {
0294 width: tabListView.width
0295 topPadding: Kirigami.Units.smallSpacing
0296 bottomPadding: Kirigami.Units.smallSpacing
0297
0298 onClicked: {
0299 tabSwipeView.currentIndex = index;
0300 selectTabDialog.close();
0301 }
0302
0303 contentItem: RowLayout {
0304 Label {
0305 text: model.name
0306 Layout.fillWidth: true
0307 }
0308
0309 RadioButton {
0310 Layout.alignment: Qt.AlignVCenter
0311 checked: tabSwipeView.currentIndex == index
0312 onClicked: {
0313 root.switchToTab(index);
0314 selectTabDialog.close();
0315 }
0316 }
0317
0318 ToolButton {
0319 Layout.alignment: Qt.AlignVCenter
0320 icon.name: "delete"
0321 text: i18n("Close")
0322 display: ToolButton.IconOnly
0323 onClicked: root.closeTab(index)
0324 }
0325 }
0326 }
0327 }
0328 }
0329
0330 Kirigami.InlineMessage {
0331 id: selectionModePopup
0332 visible: root.currentTerminal.touchSelectionMode
0333 implicitWidth: parent.width
0334 text: i18n("selection mode")
0335
0336 onVisibleChanged: root.forceTerminalFocus();
0337
0338 type: Kirigami.MessageType.Information
0339
0340 actions: [
0341 Kirigami.Action {
0342 text: i18n("Disable")
0343 onTriggered: root.currentTerminal.touchSelectionMode = false
0344 }
0345 ]
0346 }
0347
0348 // tabs
0349 SwipeView {
0350 id: tabSwipeView
0351 interactive: !selectionModePopup.visible && Kirigami.Settings.hasTransientTouchInput // don't conflict with selection mode
0352
0353 Layout.fillWidth: true
0354 Layout.fillHeight: true
0355
0356 onCurrentItemChanged: currentTerminal.forceActiveFocus()
0357
0358 Repeater {
0359 id: terminalRepeater
0360 model: TerminalTabModel
0361
0362 delegate: Item {
0363 property alias termWidget: terminal
0364
0365 TerminalEmulator {
0366 id: terminal
0367 anchors.fill: parent
0368
0369 readonly property string tabName: model.name
0370 readonly property int modelIndex: model.index
0371 readonly property bool isCurrentItem: SwipeView.isCurrentItem
0372
0373 // with touch, to select text we first require users to press-and-hold to enter the selection mode
0374 property bool touchSelectionMode: false
0375
0376 onIsCurrentItemChanged: {
0377 if (isCurrentItem) {
0378 root.forceTerminalFocus();
0379 }
0380 }
0381
0382 font.family: TerminalSettings.fontFamily
0383 font.pixelSize: TerminalSettings.fontSize
0384
0385 colorScheme: TerminalSettings.colorScheme
0386 opacity: TerminalSettings.windowOpacity
0387
0388 Component.onCompleted: {
0389 if (!root.initialSessionCreated) {
0390 // setup for CLI arguments
0391 if (Util.initialWorkDir) {
0392 mainsession.initialWorkingDirectory = Util.initialWorkDir;
0393 }
0394 if (Util.initialCommand) {
0395 mainsession.sendText(Util.initialCommand);
0396 terminal.pressKey(Qt.Key_Enter, Qt.NoModifier, true);
0397 }
0398
0399 root.initialSessionCreated = true;
0400 }
0401
0402 mainsession.startShellProgram();
0403
0404 if (root.hasOwnProperty("contextualActions")) {
0405 root.contextualActions = Qt.binding(() => menuActions)
0406 } else {
0407 root.actions = Qt.binding(() => menuActions)
0408 }
0409 }
0410
0411 function pressKey(key, modifiers, pressed, nativeScanCode, text) {
0412 terminal.simulateKeyPress(key, modifiers, pressed, nativeScanCode, text);
0413 root.forceTerminalFocus();
0414 }
0415
0416 session: TerminalSession {
0417 id: mainsession
0418 initialWorkingDirectory: "$HOME"
0419 shellProgram: ShellCommand.executable
0420 shellProgramArgs: ShellCommand.args
0421 onFinished: {
0422 if (terminalRepeater.count == 1) {
0423 Qt.quit()
0424 }
0425
0426 root.closeTab(terminal.modelIndex)
0427 }
0428 onMatchFound: {
0429 console.log("found at: %1 %2 %3 %4".arg(startColumn).arg(startLine).arg(endColumn).arg(endLine));
0430 }
0431 onNoMatchFound: {
0432 console.log("not found");
0433 }
0434 }
0435
0436 ScrollBar {
0437 Kirigami.Theme.colorSet: Kirigami.Theme.Complementary // text color of terminal is also complementary
0438 Kirigami.Theme.inherit: false
0439 anchors {
0440 right: parent.right
0441 top: parent.top
0442 bottom: parent.bottom
0443 }
0444 visible: true
0445 orientation: Qt.Vertical
0446 size: (terminal.lines / (terminal.lines + terminal.scrollbarMaximum - terminal.scrollbarMinimum))
0447 position: terminal.scrollbarCurrentValue / (terminal.lines + terminal.scrollbarMaximum)
0448 interactive: false
0449 }
0450
0451 // terminal focus on mouse click
0452 TapHandler {
0453 acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus
0454 cursorShape: Qt.IBeamCursor
0455 onTapped: root.forceTerminalFocus();
0456 }
0457
0458 // enter touch selection mode
0459 TapHandler {
0460 acceptedDevices: PointerDevice.TouchScreen
0461 enabled: !terminal.touchSelectionMode
0462 onLongPressed: terminal.touchSelectionMode = true
0463 }
0464
0465 // simulate scrolling for touch (TODO velocity)
0466 DragHandler {
0467 acceptedDevices: PointerDevice.TouchScreen
0468 enabled: !terminal.touchSelectionMode
0469
0470 property real previousY
0471 onActiveChanged: {
0472 previousY = 0;
0473 }
0474 onTranslationChanged: {
0475 terminal.simulateWheel(0, 0, 0, 0, Qt.point(0, (translation.y - previousY) * 2));
0476 previousY = translation.y;
0477 }
0478 }
0479 }
0480 }
0481 }
0482 }
0483 }
0484
0485 footer: TerminalKeyToolBar {
0486 visible: Kirigami.Settings.isMobile || Kirigami.Settings.tabletMode
0487 terminal: root.currentTerminal
0488 }
0489 }