mirror of
https://github.com/Serial-Studio/Serial-Studio.git
synced 2025-01-31 17:42:55 +08:00
Use ListView to generate console
This commit is contained in:
parent
f0c85dcd52
commit
32105880ff
@ -31,20 +31,20 @@ import "../Widgets" as Widgets
|
||||
|
||||
Control {
|
||||
id: root
|
||||
Component.onCompleted: Cpp_IO_Console.setTextDocument(textArea.textDocument)
|
||||
//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
|
||||
|
||||
//
|
||||
// Enable/disable text rendering when visibility changes
|
||||
//
|
||||
onVisibleChanged: Cpp_IO_Console.enableRender = visible
|
||||
|
||||
//
|
||||
// Console text color
|
||||
//
|
||||
property alias text: textArea.text
|
||||
property int fontSize: 12
|
||||
readonly property color consoleColor: "#8ecd9d"
|
||||
|
||||
//
|
||||
@ -82,34 +82,6 @@ Control {
|
||||
onActivated: textArea.copy()
|
||||
}
|
||||
|
||||
//
|
||||
// Right-click context menu
|
||||
//
|
||||
Menu {
|
||||
id: contextMenu
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Copy")
|
||||
opacity: enabled ? 1 : 0.5
|
||||
onTriggered: textArea.copy()
|
||||
enabled: textArea.selectedText
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Clear")
|
||||
opacity: enabled ? 1 : 0.5
|
||||
enabled: textArea.length > 0
|
||||
onTriggered: Cpp_IO_Console.clear()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
opacity: enabled ? 1 : 0.5
|
||||
text: qsTr("Save as") + "..."
|
||||
onTriggered: Cpp_IO_Console.save()
|
||||
enabled: Cpp_IO_Console.saveAvailable
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Controls
|
||||
//
|
||||
@ -128,75 +100,54 @@ Control {
|
||||
Layout.fillHeight: true
|
||||
border.color: palette.midlight
|
||||
|
||||
Flickable {
|
||||
id: flick
|
||||
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
|
||||
contentWidth: textArea.paintedWidth
|
||||
contentHeight: textArea.paintedHeight
|
||||
boundsMovement: Flickable.StopAtBounds
|
||||
boundsBehavior: Flickable.DragOverBounds
|
||||
model: Cpp_IO_Console.lineCount
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
id: scrollbar
|
||||
}
|
||||
|
||||
function ensureVisible(x, y, w, h)
|
||||
{
|
||||
if (contentX >= x)
|
||||
contentX = x;
|
||||
else if (contentX + width <= x + w)
|
||||
contentX = x + w - width;
|
||||
if (contentY >= y)
|
||||
contentY = y;
|
||||
else if (contentY + height <= y + h)
|
||||
contentY = y + h - height;
|
||||
}
|
||||
Connections {
|
||||
target: Cpp_IO_Console
|
||||
|
||||
TextEdit {
|
||||
id: textArea
|
||||
readOnly: true
|
||||
font.pixelSize: 12
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
color: root.consoleColor
|
||||
persistentSelection: true
|
||||
wrapMode: TextEdit.NoWrap
|
||||
font.family: app.monoFont
|
||||
width: parent.contentWidth
|
||||
textFormat: TextEdit.PlainText
|
||||
selectionColor: palette.highlight
|
||||
selectedTextColor: palette.highlightedText
|
||||
|
||||
onTextChanged: {
|
||||
if (Cpp_IO_Console.autoscroll)
|
||||
flick.ensureVisible(0, textArea.contentHeight, 1, 14)
|
||||
function onDataReceived() {
|
||||
if (Cpp_IO_Console.autoscroll)
|
||||
model.positionViewAtEnd()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.RightButton
|
||||
delegate: Text {
|
||||
id: line
|
||||
width: parent.width
|
||||
color: root.consoleColor
|
||||
font.family: app.monoFont
|
||||
font.pixelSize: root.fontSize
|
||||
text: Cpp_IO_Console.getLine(index)
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
|
||||
onClicked: {
|
||||
root.selectStart = textArea.selectionStart;
|
||||
root.selectEnd = textArea.selectionEnd;
|
||||
root.curPos = textArea.cursorPosition;
|
||||
contextMenu.popup()
|
||||
textArea.cursorPosition = root.curPos;
|
||||
textArea.select(root.selectStart, root.selectEnd);
|
||||
}
|
||||
Connections {
|
||||
target: Cpp_IO_Console
|
||||
enabled: Cpp_IO_Console.lineCount == (index + 1)
|
||||
|
||||
onPressAndHold: {
|
||||
if (mouse.source === Qt.MouseEventNotSynthesized) {
|
||||
root.selectStart = textArea.selectionStart;
|
||||
root.selectEnd = textArea.selectionEnd;
|
||||
root.curPos = textArea.cursorPosition;
|
||||
contextMenu.popup();
|
||||
textArea.cursorPosition = root.curPos;
|
||||
textArea.select(root.selectStart, root.selectEnd);
|
||||
}
|
||||
function onDataReceived() {
|
||||
line.text = Cpp_IO_Console.getLine(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -336,9 +287,9 @@ Control {
|
||||
Layout.maximumWidth: 32
|
||||
icon.color: palette.text
|
||||
opacity: enabled ? 1 : 0.5
|
||||
enabled: textArea.length > 0
|
||||
onClicked: Cpp_IO_Console.clear()
|
||||
icon.source: "qrc:/icons/delete.svg"
|
||||
enabled: Cpp_IO_Console.lineCount > 0
|
||||
Behavior on opacity {NumberAnimation{}}
|
||||
}
|
||||
}
|
||||
|
@ -124,7 +124,8 @@ ApplicationWindow {
|
||||
// Clears the console text & displays a mini-tutorial
|
||||
//
|
||||
function showWelcomeGuide() {
|
||||
terminal.text = Cpp_Misc_Translator.welcomeConsoleText() + "\n\n"
|
||||
Cpp_IO_Console.clear()
|
||||
Cpp_IO_Console.append(Cpp_Misc_Translator.welcomeConsoleText() + "\n")
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -34,15 +34,9 @@ using namespace IO;
|
||||
static Console *INSTANCE = nullptr;
|
||||
|
||||
/**
|
||||
* Set buffer size to 15 MB
|
||||
* Set maximum scrollback to 10'000 lines
|
||||
*/
|
||||
static const int MAX_BUFFER_SIZE = 1024 * 1024 * 15;
|
||||
|
||||
/**
|
||||
* Allow console to process at most 100 lines, more than that will probably slow down
|
||||
* the computer. Read https://bugreports.qt.io/browse/QTBUG-37872 for more information.
|
||||
*/
|
||||
static const int MAX_BLOCK_COUNT = 100;
|
||||
static const int SCROLLBACK = 10 * 1000;
|
||||
|
||||
/**
|
||||
* Constructor function
|
||||
@ -54,11 +48,8 @@ Console::Console()
|
||||
, m_historyItem(0)
|
||||
, m_echo(false)
|
||||
, m_autoscroll(true)
|
||||
, m_enableRender(false)
|
||||
, m_showTimestamp(true)
|
||||
, m_timestampAdded(false)
|
||||
, m_cursor(nullptr)
|
||||
, m_document(nullptr)
|
||||
{
|
||||
clear();
|
||||
auto m = Manager::getInstance();
|
||||
@ -95,21 +86,12 @@ bool Console::autoscroll() const
|
||||
return m_autoscroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns @c true if current incoming data shall be shown in the console display.
|
||||
* We use this values to avoid unnecesary processing when the console is not visible.
|
||||
*/
|
||||
bool Console::enableRender() const
|
||||
{
|
||||
return m_enableRender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns @c true if data buffer contains information that the user can export.
|
||||
*/
|
||||
bool Console::saveAvailable() const
|
||||
{
|
||||
return m_dataBuffer.size() > 0;
|
||||
return lineCount() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -173,14 +155,22 @@ QString Console::currentHistoryString() const
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pointer to the @c
|
||||
* Returns the number of lines of the log file
|
||||
*/
|
||||
QTextDocument *Console::document()
|
||||
int Console::lineCount() const
|
||||
{
|
||||
if (m_document)
|
||||
return m_document->textDocument();
|
||||
return m_data.count();
|
||||
}
|
||||
|
||||
return Q_NULLPTR;
|
||||
/**
|
||||
* Returns the string at the given @a line of the log file
|
||||
*/
|
||||
QString Console::getLine(const int line) const
|
||||
{
|
||||
if (line < lineCount())
|
||||
return m_data.at(line);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -241,7 +231,15 @@ void Console::save()
|
||||
QFile file(path);
|
||||
if (file.open(QFile::WriteOnly))
|
||||
{
|
||||
file.write(m_dataBuffer);
|
||||
QByteArray data;
|
||||
for (int i = 0; i < lineCount(); ++i)
|
||||
{
|
||||
data.append(getLine(i).toUtf8());
|
||||
data.append("\r");
|
||||
data.append("\n");
|
||||
}
|
||||
|
||||
file.write(data);
|
||||
file.close();
|
||||
|
||||
Misc::Utilities::revealFile(path);
|
||||
@ -257,13 +255,9 @@ void Console::save()
|
||||
*/
|
||||
void Console::clear()
|
||||
{
|
||||
// Clear console display
|
||||
if (document())
|
||||
document()->clear();
|
||||
|
||||
// Reserve 15 MB for data buffer
|
||||
m_dataBuffer.clear();
|
||||
m_dataBuffer.reserve(MAX_BUFFER_SIZE);
|
||||
m_data.clear();
|
||||
m_data.reserve(SCROLLBACK);
|
||||
emit dataReceived();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -351,10 +345,8 @@ void Console::send(const QString &data)
|
||||
// Display sent data on console (if allowed)
|
||||
if (echo())
|
||||
{
|
||||
append(dataToString(bin));
|
||||
append(dataToString(bin), showTimestamp());
|
||||
m_timestampAdded = false;
|
||||
if (m_cursor && lineEnding() == LineEnding::NoLineEnding)
|
||||
m_cursor->insertBlock();
|
||||
}
|
||||
}
|
||||
|
||||
@ -400,16 +392,6 @@ void Console::setAutoscroll(const bool enabled)
|
||||
emit autoscrollChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/disables text rendering in the console. See @c enableRender() for
|
||||
* more information.
|
||||
*/
|
||||
void Console::setEnableRender(const bool enabled)
|
||||
{
|
||||
m_enableRender = enabled;
|
||||
emit enableRenderChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes line ending mode for sent user commands. See @c lineEnding() for more
|
||||
* information.
|
||||
@ -430,108 +412,69 @@ void Console::setDisplayMode(const DisplayMode mode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the QML text document managed by the console. Data input in the text document
|
||||
* is managed automatically by this class, so that the QML interface focuses on arranging
|
||||
* the controls nicely, while we focus to make the console display data correctly.
|
||||
* Inserts the given @a string into the list of lines of the console, if @a addTimestamp
|
||||
* is set to @c true, an timestamp is added for each line.
|
||||
*/
|
||||
void Console::setTextDocument(QQuickTextDocument *document)
|
||||
void Console::append(const QString &string, const bool addTimestamp)
|
||||
{
|
||||
// Delete previous text cursor
|
||||
if (m_cursor)
|
||||
delete m_cursor;
|
||||
// Get current date
|
||||
QString timestamp;
|
||||
if (addTimestamp)
|
||||
{
|
||||
QDateTime dateTime = QDateTime::currentDateTime();
|
||||
timestamp = dateTime.toString("HH:mm:ss.zzz -> ");
|
||||
}
|
||||
|
||||
// Re-assign pointer & register text cursor
|
||||
m_document = document;
|
||||
m_cursor = new QTextCursor(m_document->textDocument());
|
||||
m_cursor->movePosition(QTextCursor::End);
|
||||
// Change CR + NL to new line
|
||||
QString data = string;
|
||||
data = data.replace("\r\n", "\n");
|
||||
|
||||
// Configure text doucment
|
||||
setAutoscroll(autoscroll());
|
||||
m_document->textDocument()->setUndoRedoEnabled(false);
|
||||
m_document->textDocument()->setMaximumBlockCount(MAX_BLOCK_COUNT);
|
||||
emit textDocumentChanged();
|
||||
// Add first item if necessary
|
||||
if (lineCount() == 0)
|
||||
m_data.append("");
|
||||
|
||||
// Construct string to insert
|
||||
QString str;
|
||||
for (int i = 0; i < data.length(); ++i)
|
||||
{
|
||||
if (!m_timestampAdded)
|
||||
{
|
||||
str = m_data.last();
|
||||
str.append(timestamp);
|
||||
m_data.replace(lineCount() - 1, str);
|
||||
m_timestampAdded = true;
|
||||
}
|
||||
|
||||
if (string.at(i) == "\n" || string.at(i) == "\r")
|
||||
{
|
||||
m_data.append("");
|
||||
m_timestampAdded = false;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
str = m_data.last();
|
||||
str.append(data.at(i));
|
||||
m_data.replace(lineCount() - 1, str);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove extra lines
|
||||
while (lineCount() > SCROLLBACK)
|
||||
m_data.removeFirst();
|
||||
|
||||
// Update UI
|
||||
emit dataReceived();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the given @a data in the console. @c QByteArray to ~@c QString conversion is
|
||||
* done by the @c dataToString() function, which displays incoming data either in UTF-8
|
||||
* or in hexadecimal mode.
|
||||
*
|
||||
* The incoming data is also added to a buffer so that the user can save the file if
|
||||
* needed.
|
||||
*/
|
||||
void Console::onDataReceived(const QByteArray &data)
|
||||
{
|
||||
// Display data
|
||||
if (enableRender())
|
||||
append(dataToString(data));
|
||||
|
||||
// Append data to buffer
|
||||
m_dataBuffer.append(data);
|
||||
if (m_dataBuffer.size() > MAX_BUFFER_SIZE)
|
||||
{
|
||||
m_dataBuffer.clear();
|
||||
m_dataBuffer.reserve(MAX_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
// Notify UI
|
||||
emit dataReceived();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given @a string into the QML text document. We use some regex magic to
|
||||
* avoid issues with line break formats and we also add the timestamp (if needed).
|
||||
*/
|
||||
void Console::append(const QString &string)
|
||||
{
|
||||
// Text cursor invalid, abort
|
||||
if (!m_cursor)
|
||||
return;
|
||||
|
||||
// Get current date
|
||||
QDateTime dateTime = QDateTime::currentDateTime();
|
||||
auto now = dateTime.toString("HH:mm:ss.zzz -> ");
|
||||
if (!showTimestamp())
|
||||
now = "";
|
||||
|
||||
// Change all line breaks to "\n"
|
||||
QString displayedText;
|
||||
QString data = string;
|
||||
data = data.replace(QRegExp("\r?\n"), QChar('\n'));
|
||||
|
||||
// Construct string to insert
|
||||
int lineCount = 0;
|
||||
for (int i = 0; i < data.length(); ++i)
|
||||
{
|
||||
if (!m_timestampAdded)
|
||||
{
|
||||
displayedText.append(now);
|
||||
m_timestampAdded = true;
|
||||
}
|
||||
|
||||
if (string.at(i) == "\n")
|
||||
{
|
||||
++lineCount;
|
||||
displayedText.append("\n");
|
||||
m_timestampAdded = false;
|
||||
}
|
||||
|
||||
else
|
||||
displayedText.append(data.at(i));
|
||||
}
|
||||
|
||||
// Insert text on console
|
||||
m_cursor->insertText(displayedText);
|
||||
|
||||
//
|
||||
// Clear the document if autoscroll is disabled. We need to do this because if we
|
||||
// set the blockCount() to 0 (infinite num. of lines), we risk overloading the
|
||||
// computer's resources and slowing everything down.
|
||||
//
|
||||
// We need to find a way to deal with large text files in the QML interface...
|
||||
//
|
||||
if (!autoscroll() && document()->blockCount() > MAX_BLOCK_COUNT - 1)
|
||||
document()->clear();
|
||||
append(dataToString(data), showTimestamp());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -610,12 +553,12 @@ QString Console::hexadecimalStr(const QByteArray &data)
|
||||
{
|
||||
str.append(hex.at(i));
|
||||
if ((i + 1) % 2 == 0)
|
||||
str.append("");
|
||||
str.append(" ");
|
||||
}
|
||||
|
||||
// Add new line & carriage returns
|
||||
str.replace("0a ", "0a\r");
|
||||
str.replace("0d ", "0d\n");
|
||||
str.replace("0a", "0a\r");
|
||||
str.replace("0d", "0d\n");
|
||||
|
||||
// Return string
|
||||
return str;
|
||||
|
@ -24,14 +24,10 @@
|
||||
#define IO_CONSOLE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QTextCursor>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QtQuick/QQuickPaintedItem>
|
||||
#include <QStringList>
|
||||
|
||||
namespace IO
|
||||
{
|
||||
|
||||
class Console : public QObject
|
||||
{
|
||||
// clang-format off
|
||||
@ -66,10 +62,9 @@ class Console : public QObject
|
||||
Q_PROPERTY(QString currentHistoryString
|
||||
READ currentHistoryString
|
||||
NOTIFY historyItemChanged)
|
||||
Q_PROPERTY(bool enableRender
|
||||
READ enableRender
|
||||
WRITE setEnableRender
|
||||
NOTIFY enableRenderChanged)
|
||||
Q_PROPERTY(int lineCount
|
||||
READ lineCount
|
||||
NOTIFY dataReceived)
|
||||
// clang-format on
|
||||
|
||||
signals:
|
||||
@ -80,7 +75,6 @@ signals:
|
||||
void lineEndingChanged();
|
||||
void displayModeChanged();
|
||||
void historyItemChanged();
|
||||
void enableRenderChanged();
|
||||
void textDocumentChanged();
|
||||
void showTimestampChanged();
|
||||
|
||||
@ -112,7 +106,6 @@ public:
|
||||
|
||||
bool echo() const;
|
||||
bool autoscroll() const;
|
||||
bool enableRender() const;
|
||||
bool saveAvailable() const;
|
||||
bool showTimestamp() const;
|
||||
|
||||
@ -121,7 +114,8 @@ public:
|
||||
DisplayMode displayMode() const;
|
||||
QString currentHistoryString() const;
|
||||
|
||||
QTextDocument *document();
|
||||
int lineCount() const;
|
||||
Q_INVOKABLE QString getLine(const int line) const;
|
||||
|
||||
Q_INVOKABLE QStringList dataModes() const;
|
||||
Q_INVOKABLE QStringList lineEndings() const;
|
||||
@ -136,14 +130,12 @@ public slots:
|
||||
void setEcho(const bool enabled);
|
||||
void setDataMode(const DataMode mode);
|
||||
void setAutoscroll(const bool enabled);
|
||||
void setEnableRender(const bool enabled);
|
||||
void setShowTimestamp(const bool enabled);
|
||||
void setLineEnding(const LineEnding mode);
|
||||
void setDisplayMode(const DisplayMode mode);
|
||||
void setTextDocument(QQuickTextDocument *document);
|
||||
void append(const QString &str, const bool addTimestamp = false);
|
||||
|
||||
private slots:
|
||||
void append(const QString &str);
|
||||
void addToHistory(const QString &command);
|
||||
void onDataReceived(const QByteArray &data);
|
||||
|
||||
@ -156,7 +148,6 @@ private:
|
||||
|
||||
private:
|
||||
DataMode m_dataMode;
|
||||
QByteArray m_dataBuffer;
|
||||
LineEnding m_lineEnding;
|
||||
DisplayMode m_displayMode;
|
||||
|
||||
@ -164,14 +155,11 @@ private:
|
||||
|
||||
bool m_echo;
|
||||
bool m_autoscroll;
|
||||
bool m_enableRender;
|
||||
bool m_showTimestamp;
|
||||
bool m_timestampAdded;
|
||||
|
||||
QStringList m_data;
|
||||
QStringList m_historyItems;
|
||||
|
||||
QTextCursor *m_cursor;
|
||||
QQuickTextDocument *m_document;
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user