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