QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
MAVLinkConsolePage.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtQuick.Layouts
4
5import QGroundControl
6import QGroundControl.Controls
7
8AnalyzePage {
9 id: root
10 pageComponent: pageComponent
11 pageDescription: qsTr("Provides a connection to the vehicle's system shell.")
12 allowPopout: true
13
14 property bool isLoaded: false
15
16 // Key input on mobile is handled differently, so use a separate command input text field.
17 // E.g. for android see https://bugreports.qt.io/browse/QTBUG-40803
18 readonly property bool _separateCommandInput: ScreenTools.isMobile
19
20 MAVLinkConsoleController { id: conController }
21
22 Component {
23 id: pageComponent
24
25 ColumnLayout {
26 objectName: "mavlinkConsole_page"
27 height: availableHeight
28 width: availableWidth
29 property int _consoleOutputLen: 0
30
31 function scrollToBottom() {
32 if (flickable.contentHeight > flickable.height) {
33 flickable.contentY = flickable.contentHeight - flickable.height
34 }
35 }
36
37 function getCommand() { return textConsole.getText(_consoleOutputLen, textConsole.length) }
38
39 function getCommandAndClear() {
40 const command = getCommand()
41 textConsole.remove(_consoleOutputLen, textConsole.length)
42 return command
43 }
44
45 function pasteFromClipboard() {
46 // we need to handle a few cases here:
47 // in the general form we have: <command_pre><cursor><command_post>
48 // and the clipboard may contain newlines
49 const cursor = textConsole.cursorPosition - _consoleOutputLen
50 var command = getCommandAndClear()
51 var command_pre = ""
52 var command_post = command
53 if (cursor > 0) {
54 command_pre = command.substr(0, cursor)
55 command_post = command.substr(cursor)
56 }
57 var command_leftover = conController.handleClipboard(command_pre) + command_post
58 textConsole.insert(textConsole.length, command_leftover)
59 textConsole.cursorPosition = textConsole.length - command_post.length
60 }
61
62 Connections {
63 target: conController
64 function onDataChanged(topLeft, bottomRight, roles) {
65 if (isLoaded) {
66 // rate-limit updates to reduce CPU load
67 updateTimer.start();
68 }
69 }
70 }
71
72 Timer {
73 id: updateTimer
74 interval: 30
75 running: false
76 repeat: false
77 onTriggered: {
78 // only update if scroll bar is at the bottom
79 if (flickable.atYEnd) {
80 // backup & restore cursor & command
81 const command = getCommand()
82 const cursor = textConsole.cursorPosition - _consoleOutputLen
83 textConsole.text = conController.text
84 _consoleOutputLen = textConsole.length
85 textConsole.insert(textConsole.length, command)
86 textConsole.cursorPosition = textConsole.length
87 scrollToBottom()
88 if (cursor >= 0) {
89 // We could restore the selection here too...
90 textConsole.cursorPosition = _consoleOutputLen + cursor
91 }
92 } else {
93 updateTimer.start();
94 }
95 }
96 }
97
98 QGCFlickable {
99 id: flickable
100 Layout.fillWidth: true
101 Layout.fillHeight: true
102 contentWidth: textConsole.width
103 contentHeight: textConsole.height
104
105 TextArea.flickable: TextArea {
106 id: textConsole
107 objectName: "mavlinkConsole_textArea"
108 width: availableWidth
109 wrapMode: Text.WordWrap
110 readOnly: _separateCommandInput
111 textFormat: TextEdit.RichText
112 inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhMultiLine
113 text: "> "
114 focus: true
115 color: qgcPal.text
116 selectedTextColor: qgcPal.windowShade
117 selectionColor: qgcPal.text
118 font.pointSize: ScreenTools.defaultFontPointSize
119 font.family: ScreenTools.fixedFontFamily
120
121 Component.onCompleted: {
122 root.isLoaded = true
123 _consoleOutputLen = textConsole.length
124 textConsole.cursorPosition = _consoleOutputLen
125 if (!_separateCommandInput) {
126 textConsole.forceActiveFocus()
127 }
128 }
129
130 background: Rectangle { color: qgcPal.windowShade }
131
132 Keys.onPressed: (event) => {
133 // ignore tabs
134 if (event.key == Qt.Key_Tab) {
135 event.accepted = true
136 }
137
138 // ignore for now
139 if (event.matches(StandardKey.Cut)) {
140 event.accepted = true
141 }
142
143 if (!event.matches(StandardKey.Copy) &&
144 event.key != Qt.Key_Escape &&
145 event.key != Qt.Key_Insert &&
146 event.key != Qt.Key_Pause &&
147 event.key != Qt.Key_Print &&
148 event.key != Qt.Key_SysReq &&
149 event.key != Qt.Key_Clear &&
150 event.key != Qt.Key_Home &&
151 event.key != Qt.Key_End &&
152 event.key != Qt.Key_Left &&
153 event.key != Qt.Key_Up &&
154 event.key != Qt.Key_Right &&
155 event.key != Qt.Key_Down &&
156 event.key != Qt.Key_PageUp &&
157 event.key != Qt.Key_PageDown &&
158 event.key != Qt.Key_Shift &&
159 event.key != Qt.Key_Control &&
160 event.key != Qt.Key_Meta &&
161 event.key != Qt.Key_Alt &&
162 event.key != Qt.Key_AltGr &&
163 event.key != Qt.Key_CapsLock &&
164 event.key != Qt.Key_NumLock &&
165 event.key != Qt.Key_ScrollLock &&
166 event.key != Qt.Key_Super_L &&
167 event.key != Qt.Key_Super_R &&
168 event.key != Qt.Key_Menu &&
169 event.key != Qt.Key_Hyper_L &&
170 event.key != Qt.Key_Hyper_R &&
171 event.key != Qt.Key_Direction_L &&
172 event.key != Qt.Key_Direction_R) {
173 // Note: dead keys do not generate keyPressed event on linux, see
174 // https://bugreports.qt.io/browse/QTBUG-79216
175
176 scrollToBottom()
177
178 // ensure cursor position is at an editable region
179 if (textConsole.selectionStart < _consoleOutputLen) {
180 textConsole.select(_consoleOutputLen, textConsole.selectionEnd)
181 }
182
183 if (textConsole.cursorPosition < _consoleOutputLen) {
184 textConsole.cursorPosition = textConsole.length
185 }
186 }
187
188 switch (event.key) {
189 case Qt.Key_Left:
190 // don't move beyond current command
191 if (textConsole.cursorPosition == _consoleOutputLen) {
192 event.accepted = true
193 }
194 break;
195 case Qt.Key_Backspace:
196 if (textConsole.cursorPosition <= _consoleOutputLen) {
197 event.accepted = true
198 }
199 break;
200 case Qt.Key_Enter:
201 case Qt.Key_Return:
202 conController.sendCommand(getCommandAndClear())
203 event.accepted = true
204 break;
205 default:
206 break;
207 }
208
209 if (event.matches(StandardKey.Paste)) {
210 pasteFromClipboard()
211 event.accepted = true
212 }
213
214 // command history
215 if (event.key == Qt.Key_Up) {
216 const command = conController.historyUp(getCommandAndClear())
217 textConsole.insert(textConsole.length, command)
218 textConsole.cursorPosition = textConsole.length
219 event.accepted = true
220 } else if (event.key == Qt.Key_Down) {
221 const command = conController.historyDown(getCommandAndClear())
222 textConsole.insert(textConsole.length, command)
223 textConsole.cursorPosition = textConsole.length
224 event.accepted = true
225 }
226 }
227 }
228 }
229
230 RowLayout {
231 Layout.fillWidth: true
232 visible: _separateCommandInput
233
234 QGCTextField {
235 id: commandInput
236 Layout.fillWidth: true
237 placeholderText: qsTr("Enter Commands here...")
238 inputMethodHints: Qt.ImhNoAutoUppercase
239 onAccepted: sendCommand()
240
241 function sendCommand() {
242 conController.sendCommand(text)
243 text = ""
244 scrollToBottom()
245 }
246
247 }
248
249 QGCButton {
250 text: qsTr("Send")
251 onClicked: commandInput.sendCommand()
252 }
253 }
254 }
255 } // Component
256} // AnalyzePage