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 }