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 }