Use ListView to generate console

This commit is contained in:
Alex Spataru 2021-02-05 16:39:16 -05:00
parent f0c85dcd52
commit 32105880ff
4 changed files with 136 additions and 253 deletions

View File

@ -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{}}
}
}

View File

@ -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")
}
//

View File

@ -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;

View File

@ -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;
};
}