Add line counter in console & fix several issues

This commit is contained in:
Alex Spataru 2021-02-05 23:09:46 -05:00
parent 00face16a3
commit 372f36a0ee
6 changed files with 312 additions and 96 deletions

View File

@ -62,6 +62,7 @@
<file>qml/Widgets/GroupDelegate.qml</file>
<file>qml/Widgets/GyroDelegate.qml</file>
<file>qml/Widgets/LED.qml</file>
<file>qml/Widgets/LogView.qml</file>
<file>qml/Widgets/MapDelegate.qml</file>
<file>qml/Widgets/Window.qml</file>
<file>qml/Windows/About.qml</file>

View File

@ -0,0 +1,192 @@
/*
* Copyright (c) 2020-2021 Alex Spataru <https://github.com/alex-spataru>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
Rectangle {
id: root
clip: true
smooth: false
color: backgroundColor
//
// Custom properties
//
property int lineOffset: 0
property string selectedText
property Menu contextMenu: null
property alias font: label.font
property bool autoscroll: false
property string placeholderText: ""
property alias model: listView.model
readonly property int digits: listView.count.toString().length
//
// Set colors
//
property color textColor: "#72d084"
property color caretLineColor: "#222228"
property color backgroundColor: "#060601"
property color lineCountTextColor: "#545454"
property color lineCountBackgroundColor: "#121212"
//
// Gets the line number for the given @a index
//
function getLineNumber(index) {
var lIndex = listView.indexAt(0, 0)
if (lIndex < 0)
lIndex = 0
return lIndex + index + 1 + root.lineOffset
}
//
// Placeholder text & font source for rest of widget
//
Text {
id: label
opacity: 0.5
anchors.top: parent.top
anchors.left: parent.left
text: root.placeholderText
visible: listView.count == 0
anchors.margins: app.spacing
anchors.leftMargin: lineCountRect.width
}
//
// Line count rectangle
//
Rectangle {
id: lineCountRect
anchors.top: parent.top
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.margins: root.border.width
color: root.lineCountBackgroundColor
width: root.font.pixelSize * (root.digits + 1)
}
//
// Text view
//
ListView {
id: listView
cacheBuffer: 0
currentIndex: 0
model: root.model
anchors.fill: parent
anchors.leftMargin: 0
highlightMoveDuration: 0
anchors.margins: app.spacing
//
// Scrollbar
//
ScrollBar.vertical: ScrollBar {
id: scrollbar
}
//
// Hacks to implement auto-scrolling & position preservation when new data is
// added to the data model
//
property int currentContentY
property int previousCurrentIndex
onMovementEnded: {
currentContentY = contentY
}
onCountChanged: {
if (root.autoscroll) {
listView.positionViewAtEnd()
listView.currentIndex = listView.count - 2
currentContentY = contentY
previousCurrentIndex = currentIndex
root.selectedText = root.model[currentIndex]
}
else {
contentY = currentContentY
currentIndex = previousCurrentIndex
}
}
//
// Line delegate
//
delegate: Text {
font: root.font
text: modelData
color: root.textColor
height: font.pixelSize
width: listView.width - x
x: app.spacing + lineCountRect.width
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
//
// Highlight item
//
highlight: Rectangle {
z: 0
color: root.caretLineColor
Text {
font: root.font
width: lineCountRect.width
color: root.lineCountTextColor
horizontalAlignment: Qt.AlignHCenter
anchors.verticalCenter: parent.verticalCenter
text: listView.currentIndex + root.lineOffset + 1
}
}
}
//
// Simple implementation of a mouse cursor
//
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.RightButton
onClicked: contextMenu.popup()
onMouseYChanged: {
if (containsMouse && (!autoscroll || !Cpp_IO_Manager.connected)) {
var index = listView.indexAt(lineCountRect.width + 2 * app.spacing,
mouseY + listView.contentY)
if (index >= 0) {
listView.currentIndex = index
listView.previousCurrentIndex = index
root.selectedText = root.model[index]
}
}
}
}
}

View File

@ -31,30 +31,10 @@ import "../Widgets" as Widgets
Control {
id: root
//Component.onCompleted: Cpp_IO_Console.setTextDocument(textArea.textDocument)
background: Rectangle {
color: app.windowBackgroundColor
}
//
// Enable/disable text rendering when visibility changes
//
onVisibleChanged: Cpp_IO_Console.enableRender = visible
//
// Console text color
//
property int fontSize: 12
readonly property color consoleColor: "#8ecd9d"
//
// Hacks to allow context menu to work
//
property int curPos
property int selectEnd
property int selectStart
property TextEdit textArea: null
//
// Function to send through serial port data
//
@ -75,6 +55,49 @@ Control {
property alias displayMode: displayModeCombo.currentIndex
}
//
// Copy shortcut
//
Shortcut {
sequence: StandardKey.Copy
onActivated: Cpp_IO_Console.copy(logView.selectedText)
}
//
// Copy shortcut
//
Shortcut {
sequence: StandardKey.Save
onActivated: Cpp_IO_Console.save()
}
//
// Right-click context menu
//
Menu {
id: menu
MenuItem {
text: qsTr("Copy")
enabled: logView.selectedText.length > 0
onClicked: Cpp_IO_Console.copy(logView.selectedText)
}
MenuItem {
text: qsTr("Clear")
opacity: enabled ? 1 : 0.5
onTriggered: Cpp_IO_Console.clear()
enabled: Cpp_IO_Console.saveAvailable
}
MenuItem {
opacity: enabled ? 1 : 0.5
text: qsTr("Save as") + "..."
onTriggered: Cpp_IO_Console.save()
enabled: Cpp_IO_Console.saveAvailable
}
}
//
// Controls
//
@ -86,69 +109,19 @@ Control {
//
// Console display
//
Rectangle {
Widgets.LogView {
id: logView
border.width: 1
color: "#121218"
contextMenu: menu
font.pixelSize: 12
Layout.fillWidth: true
border.color: caretLineColor
Layout.fillHeight: true
border.color: palette.midlight
Text {
opacity: 0.5
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: app.spacing
color: root.consoleColor
font.family: app.monoFont
font.pixelSize: root.fontSize
visible: Cpp_IO_Console.lineCount == 0
text: qsTr("No data received so far...")
}
ListView {
id: model
clip: true
anchors.fill: parent
anchors.margins: app.spacing
model: Cpp_IO_Console.lineCount
ScrollBar.vertical: ScrollBar {
id: scrollbar
}
property int currentContentY
onMovementEnded: {
currentContentY = contentY
}
onCountChanged: {
if (Cpp_IO_Console.autoscroll)
model.positionViewAtEnd()
else
contentY = currentContentY
}
delegate: Text {
id: line
width: model.width
color: root.consoleColor
font.family: app.monoFont
font.pixelSize: root.fontSize
text: Cpp_IO_Console.getLine(index)
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
Connections {
target: Cpp_IO_Console
enabled: Cpp_IO_Console.lineCount == (index + 1)
function onDataReceived() {
line.text = Cpp_IO_Console.getLine(index)
}
}
}
}
font.family: app.monoFont
model: Cpp_IO_Console.dataModel
lineOffset: Cpp_IO_Console.lineOffset
autoscroll: Cpp_IO_Console.autoscroll
placeholderText: qsTr("No data received so far...")
}
//
@ -160,32 +133,47 @@ Control {
TextField {
id: send
height: 24
font.pixelSize: 12
font: logView.font
Layout.fillWidth: true
palette.base: "#121218"
color: root.consoleColor
font.family: app.monoFont
color: logView.textColor
opacity: enabled ? 1 : 0.5
enabled: Cpp_IO_Manager.readWrite
palette.base: logView.backgroundColor
placeholderText: qsTr("Send data to device") + "..."
background: Rectangle {
border.width: 1
color: logView.backgroundColor
border.color: logView.border.color
}
//
// Validate hex strings
//
validator: RegExpValidator {
regExp: hexCheckbox.checked ? /^[a-fA-F0-9]+$/ : /[\s\S]*/
}
//
// Send data on <enter>
//
Keys.onReturnPressed: root.sendData()
//
// Navigate command history upwards with <up>
//
Keys.onUpPressed: {
Cpp_IO_Console.historyUp()
send.text = Cpp_IO_Console.currentHistoryString
}
//
// Navigate command history downwards with <down>
//
Keys.onDownPressed: {
Cpp_IO_Console.historyDown()
send.text = Cpp_IO_Console.currentHistoryString
}
Behavior on opacity {NumberAnimation{}}
}
CheckBox {

View File

@ -125,7 +125,7 @@ ApplicationWindow {
//
function showWelcomeGuide() {
Cpp_IO_Console.clear()
Cpp_IO_Console.append(Cpp_Misc_Translator.welcomeConsoleText() + "\n")
Cpp_IO_Console.append(Cpp_Misc_Translator.welcomeConsoleText())
}
//

View File

@ -25,6 +25,7 @@
#include <QFile>
#include <Logger.h>
#include <QClipboard>
#include <QTextCodec>
#include <QFileDialog>
#include <Misc/Utilities.h>
@ -34,9 +35,9 @@ using namespace IO;
static Console *INSTANCE = nullptr;
/**
* Set maximum scrollback to 10000 lines
* Set maximum scrollback to 5000 lines
*/
static const int SCROLLBACK = 10000;
static const int SCROLLBACK = 5000;
/**
* Constructor function
@ -46,6 +47,7 @@ Console::Console()
, m_lineEnding(LineEnding::NoLineEnding)
, m_displayMode(DisplayMode::DisplayPlainText)
, m_historyItem(0)
, m_lineOffset(0)
, m_echo(false)
, m_autoscroll(true)
, m_showTimestamp(true)
@ -163,14 +165,20 @@ int Console::lineCount() const
}
/**
* Returns the string at the given @a line of the log file
* Returns the starting line number of the data model
*/
QString Console::getLine(const int line) const
quint32 Console::lineOffset() const
{
if (line < lineCount())
return m_data.at(line);
return m_lineOffset;
}
return "";
/**
* Returns a pointer to the string list model, which is used by the QML interface to
* display the console data.
*/
QStringList Console::dataModel() const
{
return m_data;
}
/**
@ -234,7 +242,7 @@ void Console::save()
QByteArray data;
for (int i = 0; i < lineCount(); ++i)
{
data.append(getLine(i).toUtf8());
data.append(m_data.at(i).toUtf8());
data.append("\r");
data.append("\n");
}
@ -256,8 +264,11 @@ void Console::save()
void Console::clear()
{
m_data.clear();
m_lineOffset = 0;
m_data.reserve(SCROLLBACK);
emit dataReceived();
emit lineOffsetChanged();
}
/**
@ -292,6 +303,15 @@ void Console::historyDown()
}
}
/**
* Adds the given data to the system clipboard
*/
void Console::copy(const QString &data)
{
if (!data.isEmpty())
qApp->clipboard()->setText(data);
}
/**
* Sends the given @a data to the currently connected device using the options specified
* by the user with the rest of the functions of this class.
@ -463,7 +483,12 @@ void Console::append(const QString &string, const bool addTimestamp)
while (lineCount() > SCROLLBACK)
{
for (int i = 0; i < SCROLLBACK * 0.1; ++i)
{
++m_lineOffset;
m_data.takeFirst();
}
emit lineOffsetChanged();
}
// Update UI

View File

@ -65,12 +65,19 @@ class Console : public QObject
Q_PROPERTY(int lineCount
READ lineCount
NOTIFY dataReceived)
Q_PROPERTY(QStringList dataModel
READ dataModel
NOTIFY dataReceived)
Q_PROPERTY(quint32 lineOffset
READ lineOffset
NOTIFY lineOffsetChanged)
// clang-format on
signals:
void echoChanged();
void dataReceived();
void dataModeChanged();
void lineOffsetChanged();
void autoscrollChanged();
void lineEndingChanged();
void displayModeChanged();
@ -115,7 +122,8 @@ public:
QString currentHistoryString() const;
int lineCount() const;
Q_INVOKABLE QString getLine(const int line) const;
quint32 lineOffset() const;
QStringList dataModel() const;
Q_INVOKABLE QStringList dataModes() const;
Q_INVOKABLE QStringList lineEndings() const;
@ -126,6 +134,7 @@ public slots:
void clear();
void historyUp();
void historyDown();
void copy(const QString &data);
void send(const QString &data);
void setEcho(const bool enabled);
void setDataMode(const DataMode mode);
@ -152,6 +161,7 @@ private:
DisplayMode m_displayMode;
int m_historyItem;
quint32 m_lineOffset;
bool m_echo;
bool m_autoscroll;