Commit 6b6361ef authored by Don Gagne's avatar Don Gagne Committed by GitHub

Merge pull request #5533 from NaterGator/consoleperf

Switch over to model based storage for the mavlink console
parents ab0e284f f7aac909
......@@ -12,7 +12,8 @@
#include "UAS.h"
MavlinkConsoleController::MavlinkConsoleController()
: _cursor_home_pos{-1},
: QStringListModel(),
_cursor_home_pos{-1},
_cursor{0},
_vehicle{nullptr}
{
......@@ -35,7 +36,7 @@ MavlinkConsoleController::sendCommand(QString command)
command.append("\n");
_sendSerialData(qPrintable(command));
_cursor_home_pos = -1;
_cursor = _console_text.length();
_cursor = rowCount();
}
void
......@@ -49,9 +50,10 @@ MavlinkConsoleController::_setActiveVehicle(Vehicle* vehicle)
if (_vehicle) {
_incoming_buffer.clear();
_console_text.clear();
emit cursorChanged(0);
emit textChanged(_console_text);
// Reset the model
setStringList(QStringList());
_cursor = 0;
_cursor_home_pos = -1;
_uas_connections << connect(_vehicle, &Vehicle::mavlinkSerialControl, this, &MavlinkConsoleController::_receiveData);
}
}
......@@ -64,17 +66,27 @@ MavlinkConsoleController::_receiveData(uint8_t device, uint8_t, uint16_t, uint32
// Append incoming data and parse for ANSI codes
_incoming_buffer.append(data);
auto old_size = _console_text.size();
_processANSItext();
auto new_size = _console_text.size();
while(!_incoming_buffer.isEmpty()) {
bool newline = false;
int idx = _incoming_buffer.indexOf('\n');
if (idx == -1) {
// Read the whole incoming buffer
idx = _incoming_buffer.size();
} else {
newline = true;
}
// Update QML and cursor
if (old_size > new_size) {
// Rewind back so we don't get a warning to stderr
emit cursorChanged(new_size);
QByteArray fragment = _incoming_buffer.mid(0, idx);
if (_processANSItext(fragment)) {
writeLine(_cursor, fragment);
if (newline)
_cursor++;
_incoming_buffer.remove(0, idx + (newline ? 1 : 0));
} else {
// ANSI processing failed, need more data
return;
}
}
emit textChanged(_console_text);
emit cursorChanged(new_size);
}
void
......@@ -109,18 +121,16 @@ MavlinkConsoleController::_sendSerialData(QByteArray data, bool close)
}
}
void
MavlinkConsoleController::_processANSItext()
bool
MavlinkConsoleController::_processANSItext(QByteArray &line)
{
int i; // Position into the parsed buffer
// Iterate over the incoming buffer to parse off known ANSI control codes
for (i = 0; i < _incoming_buffer.size(); i++) {
if (_incoming_buffer.at(i) == '\x1B') {
// For ANSI codes we expect at most 4 incoming chars
if (i < _incoming_buffer.size() - 3 && _incoming_buffer.at(i+1) == '[') {
for (int i = 0; i < line.size(); i++) {
if (line.at(i) == '\x1B') {
// For ANSI codes we expect at least 3 incoming chars
if (i < line.size() - 2 && line.at(i+1) == '[') {
// Parse ANSI code
switch(_incoming_buffer.at(i+2)) {
switch(line.at(i+2)) {
default:
continue;
case 'H':
......@@ -134,40 +144,51 @@ MavlinkConsoleController::_processANSItext()
break;
case 'K':
// Erase the current line to the end
{
auto next_lf = _console_text.indexOf('\n', _cursor);
if (next_lf > 0)
_console_text.remove(_cursor, next_lf + 1 - _cursor);
}
if (_cursor < rowCount()) {
setData(index(_cursor), "");
}
break;
case '2':
// Erase everything and rewind to home
if (_incoming_buffer.at(i+3) == 'J' && _cursor_home_pos != -1) {
// Keep newlines so textedit doesn't scroll annoyingly
int newlines = _console_text.mid(_cursor_home_pos).count('\n');
_console_text.remove(_cursor_home_pos, _console_text.size());
_console_text.append(QByteArray(newlines, '\n'));
_cursor = _cursor_home_pos;
// Check for sufficient buffer size
if ( i >= line.size() - 3)
return false;
if (line.at(i+3) == 'J' && _cursor_home_pos != -1) {
// Erase everything and rewind to home
bool blocked = blockSignals(true);
for (int j = _cursor_home_pos; j < rowCount(); j++)
setData(index(j), "");
blockSignals(blocked);
QVector<int> roles;
roles.reserve(2);
roles.append(Qt::DisplayRole);
roles.append(Qt::EditRole);
emit dataChanged(index(_cursor), index(rowCount()), roles);
}
// Even if we didn't understand this ANSI code, remove the 4th char
_incoming_buffer.remove(i+3,1);
line.remove(i+3,1);
break;
}
// Remove the parsed ANSI code and decrement the bufferpos
_incoming_buffer.remove(i, 3);
line.remove(i, 3);
i--;
} else {
// We can reasonably expect a control code was fragemented
// Stop parsing here and wait for it to come in
break;
return false;
}
}
}
return true;
}
// Insert the new data and increment the write cursor
_console_text.insert(_cursor, _incoming_buffer.left(i));
_cursor += i;
// Remove written data from the incoming buffer
_incoming_buffer.remove(0, i);
void
MavlinkConsoleController::writeLine(int line, const QByteArray &text)
{
auto rc = rowCount();
if (line >= rc) {
insertRows(rc, 1 + line - rc);
}
auto idx = index(line);
setData(idx, data(idx, Qt::DisplayRole).toString() + text);
}
......@@ -14,51 +14,39 @@
#include "FactMetaData.h"
#include <QObject>
#include <QString>
#include <QThread>
#include <QFileInfoList>
#include <QElapsedTimer>
#include <QDebug>
#include <QGeoCoordinate>
#include <QMetaObject>
#include <QStringListModel>
// Fordward decls
class Vehicle;
/// Controller for MavlinkConsole.qml.
class MavlinkConsoleController : public QObject
class MavlinkConsoleController : public QStringListModel
{
Q_OBJECT
Q_PROPERTY(int cursor READ cursor NOTIFY cursorChanged)
Q_PROPERTY(QString text READ text NOTIFY textChanged)
public:
MavlinkConsoleController();
~MavlinkConsoleController();
int cursor() const { return _console_text.size(); }
QString text() const { return _console_text; }
public slots:
void sendCommand(QString command);
signals:
void cursorChanged(int);
void textChanged(QString text);
private slots:
void _setActiveVehicle (Vehicle* vehicle);
void _receiveData(uint8_t device, uint8_t flags, uint16_t timeout, uint32_t baudrate, QByteArray data);
private:
void _processANSItext();
bool _processANSItext(QByteArray &line);
void _sendSerialData(QByteArray, bool close = false);
void writeLine(int line, const QByteArray &text);
int _cursor_home_pos;
int _cursor;
QByteArray _incoming_buffer;
QString _console_text;
Vehicle* _vehicle;
QList<QMetaObject::Connection> _uas_connections;
......
......@@ -21,10 +21,12 @@ import QGroundControl.ScreenTools 1.0
import QGroundControl.Controllers 1.0
AnalyzePage {
id: mavlinkConsolePage
pageComponent: pageComponent
pageName: qsTr("Mavlink Console")
pageDescription: qsTr("Mavlink Console provides a connection to the vehicle's system shell.")
id: mavlinkConsolePage
pageComponent: pageComponent
pageName: qsTr("Mavlink Console")
pageDescription: qsTr("Mavlink Console provides a connection to the vehicle's system shell.")
property bool loaded: false
Component {
id: pageComponent
......@@ -34,28 +36,79 @@ AnalyzePage {
height: availableHeight
width: availableWidth
TextArea {
id: consoleEditor
Connections {
target: conController
onDataChanged: {
// Keep the view in sync if the button is checked
if (loaded) {
if (followTail.checked) {
listview.positionViewAtEnd();
}
}
}
}
Component {
id: delegateItem
Rectangle {
color: qgcPal.windowShade
height: Math.round(ScreenTools.defaultFontPixelHeight * 0.5 + field.height)
width: listview.width
QGCLabel {
id: field
text: display
width: parent.width
wrapMode: Text.NoWrap
font.family: ScreenTools.fixedFontFamily
anchors.verticalCenter: parent.verticalCenter
}
}
}
QGCListView {
Component.onCompleted: {
loaded = true
}
Layout.fillHeight: true
anchors.left: parent.left
anchors.right: parent.right
font.family: ScreenTools.fixedFontFamily
font.pointSize: ScreenTools.defaultFontPointSize
readOnly: true
clip: true
id: listview
model: conController
delegate: delegateItem
cursorPosition: conController.cursor
text: conController.text
// Unsync the view if the user interacts
onMovementStarted: {
followTail.checked = false
}
}
QGCTextField {
id: command
anchors.left: parent.left
anchors.right: parent.right
placeholderText: "Enter Commands here..."
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
QGCTextField {
id: command
Layout.fillWidth: true
placeholderText: "Enter Commands here..."
onAccepted: {
conController.sendCommand(text)
text = ""
}
}
QGCButton {
id: followTail
text: qsTr("Show Latest")
checkable: true
checked: true
onAccepted: {
conController.sendCommand(text)
text = ""
onCheckedChanged: {
if (checked && loaded) {
listview.positionViewAtEnd();
}
}
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment