From f7aac9094f05fc4f9d14511330e4901ca42482b1 Mon Sep 17 00:00:00 2001 From: Nate Weibley Date: Sun, 6 Aug 2017 15:15:42 -0400 Subject: [PATCH] Switch over to model based storage for the mavlink console --- src/AnalyzeView/MavlinkConsoleController.cc | 109 ++++++++++++-------- src/AnalyzeView/MavlinkConsoleController.h | 20 +--- src/AnalyzeView/MavlinkConsolePage.qml | 91 ++++++++++++---- 3 files changed, 141 insertions(+), 79 deletions(-) diff --git a/src/AnalyzeView/MavlinkConsoleController.cc b/src/AnalyzeView/MavlinkConsoleController.cc index 03e85b655..6bc300437 100644 --- a/src/AnalyzeView/MavlinkConsoleController.cc +++ b/src/AnalyzeView/MavlinkConsoleController.cc @@ -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 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); } diff --git a/src/AnalyzeView/MavlinkConsoleController.h b/src/AnalyzeView/MavlinkConsoleController.h index 8af2c803b..950b3786b 100644 --- a/src/AnalyzeView/MavlinkConsoleController.h +++ b/src/AnalyzeView/MavlinkConsoleController.h @@ -14,51 +14,39 @@ #include "FactMetaData.h" #include #include -#include -#include -#include -#include -#include #include - +#include // 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 _uas_connections; diff --git a/src/AnalyzeView/MavlinkConsolePage.qml b/src/AnalyzeView/MavlinkConsolePage.qml index e6dd3301b..75def438b 100644 --- a/src/AnalyzeView/MavlinkConsolePage.qml +++ b/src/AnalyzeView/MavlinkConsolePage.qml @@ -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(); + } + } } } } -- 2.22.0