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 2.15
0007 import QtQuick.Layouts 1.1
0008 import QtQuick.Controls 2.15
0009
0010 import QMLTermWidget 1.0
0011 import org.kde.kirigami 2.19 as Kirigami
0012
0013 import org.kde.qmlkonsole 1.0
0014
0015 Kirigami.Page {
0016 id: root
0017 property QMLTermWidget 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.Action.KeepVisible
0126 },
0127 Kirigami.Action {
0128 icon.name: "dialog-scripts"
0129 text: i18nc("@action:intoolbar", "Saved Commands")
0130 onTriggered: savedCommandsDialog.open()
0131 displayHint: Kirigami.Action.KeepVisible
0132 },
0133 Kirigami.Action {
0134 displayHint: Kirigami.Action.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.Action.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.Action.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.Action.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.Action.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.Action.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.Action.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.Action.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.Action.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.Action.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: root.switchToTab(index)
0238 onCloseTabRequested: 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 QMLTermWidget {
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: QMLTermSession {
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 }