mirror of
https://github.com/Serial-Studio/Serial-Studio.git
synced 2025-01-15 05:22:53 +08:00
Add line counter in console & fix several issues
This commit is contained in:
parent
00face16a3
commit
372f36a0ee
@ -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>
|
||||
|
192
assets/qml/Widgets/LogView.qml
Normal file
192
assets/qml/Widgets/LogView.qml
Normal 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]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user