diff --git a/assets/qml/Windows/Console.qml b/assets/qml/Windows/Console.qml index 7dd68dd9..a98789b2 100644 --- a/assets/qml/Windows/Console.qml +++ b/assets/qml/Windows/Console.qml @@ -86,16 +86,12 @@ Control { Menu { id: menu - onVisibleChanged: { - if (visible) - copyMenu.enabled = textEdit.copyAvailable() - } - MenuItem { id: copyMenu text: qsTr("Copy") opacity: enabled ? 1 : 0.5 onClicked: textEdit.copy() + enabled: textEdit.copyAvailable } MenuItem { @@ -137,7 +133,7 @@ Control { readOnly: true color: "#8ecd9d" font.pixelSize: 12 - centerOnScroll: true + centerOnScroll: false undoRedoEnabled: false Layout.fillWidth: true Layout.fillHeight: true diff --git a/src/JSON/Generator.cpp b/src/JSON/Generator.cpp index 9d9101e7..ab4c16cb 100644 --- a/src/JSON/Generator.cpp +++ b/src/JSON/Generator.cpp @@ -318,6 +318,10 @@ void Generator::readData(const QByteArray &data) thread->start(); } +//---------------------------------------------------------------------------------------- +// JSON worker object (executed for each frame on a new thread) +//---------------------------------------------------------------------------------------- + JSONWorker::JSONWorker(const QByteArray &data) { m_data = data; diff --git a/src/UI/QmlPlainTextEdit.cpp b/src/UI/QmlPlainTextEdit.cpp index 6d502969..f1e7cd33 100644 --- a/src/UI/QmlPlainTextEdit.cpp +++ b/src/UI/QmlPlainTextEdit.cpp @@ -22,14 +22,29 @@ #include #include -#include #include "QmlPlainTextEdit.h" using namespace UI; +/* + * NOTE: most of the Doxygen documentation comments where heavily based from the following + * URL https://doc.qt.io/qt-5/qplaintextedit.html. In some cases the comments are a + * simple copy-paste job. I am lazy and the Qt documentation is very good IMO. + * + * This class works by initializing a QPlainTextEdit widget, rendering it into the + * the painter of a QML item and handling keyboard/mouse events. + * + * The rest of the functions are just a wrapper around the functions of the + * QPlainTextEdit widget for increased QML-friendliness. + */ + +/** + * Constructor function + */ QmlPlainTextEdit::QmlPlainTextEdit(QQuickItem *parent) : QQuickPaintedItem(parent) + , m_copyAvailable(false) { // Set item flags setFlag(ItemHasContents, true); @@ -39,9 +54,7 @@ QmlPlainTextEdit::QmlPlainTextEdit(QQuickItem *parent) // Initialize the text edit widget m_textEdit = new QPlainTextEdit(); m_textEdit->installEventFilter(this); - - // Set focus to the text edit - m_textEdit->setContextMenuPolicy(Qt::DefaultContextMenu); + m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); // Set the QML item's implicit size auto hint = m_textEdit->sizeHint(); @@ -55,13 +68,22 @@ QmlPlainTextEdit::QmlPlainTextEdit(QQuickItem *parent) &QmlPlainTextEdit::updateWidgetSize); connect(this, &QQuickPaintedItem::heightChanged, this, &QmlPlainTextEdit::updateWidgetSize); + + // React to widget events + connect(m_textEdit, SIGNAL(copyAvailable(bool)), this, SLOT(setCopyAvailable(bool))); } +/** + * Destructor function + */ QmlPlainTextEdit::~QmlPlainTextEdit() { m_textEdit->deleteLater(); } +/** + * Handle application events manually + */ bool QmlPlainTextEdit::event(QEvent *event) { switch (event->type()) @@ -88,12 +110,18 @@ bool QmlPlainTextEdit::event(QEvent *event) return QApplication::sendEvent(m_textEdit, event); } +/** + * Render the text edit on the given @a painter + */ void QmlPlainTextEdit::paint(QPainter *painter) { if (m_textEdit && painter) m_textEdit->render(painter); } +/** + * Custom event filter to manage redraw requests + */ bool QmlPlainTextEdit::eventFilter(QObject *watched, QEvent *event) { Q_ASSERT(m_textEdit); @@ -114,80 +142,144 @@ bool QmlPlainTextEdit::eventFilter(QObject *watched, QEvent *event) return QQuickPaintedItem::eventFilter(watched, event); } +/** + * Returns the font used by the QPlainTextEdit widget + */ QFont QmlPlainTextEdit::font() const { return m_textEdit->font(); } +/** + * Returns the text color used by the QPlainTextEdit widget + */ QColor QmlPlainTextEdit::color() const { return m_color; } +/** + * Returns the plain text of the QPlainTextEdit widget + */ QString QmlPlainTextEdit::text() const { return m_textEdit->toPlainText(); } +/** + * Returns @c true if the widget is set to read-only + */ bool QmlPlainTextEdit::readOnly() const { return m_textEdit->isReadOnly(); } +/** + * Returns @c true if the widget shall scroll automatically to the bottom when new + * text is appended to the widget. + */ bool QmlPlainTextEdit::autoscroll() const { return m_autoscroll; } +/** + * Returns the palette used by the QPlainTextEdit widget + */ QPalette QmlPlainTextEdit::palette() const { return m_textEdit->palette(); } +/** + * Returns the wrap mode of the QPlainTextEdit casted to an integer type (so that it + * can be used from the QML interface). + */ int QmlPlainTextEdit::wordWrapMode() const { return static_cast(m_textEdit->wordWrapMode()); } +/** + * Returns @c true if the user is able to copy any text from the document. This value is + * updated through the copyAvailable() signal sent by the QPlainTextEdit. + */ +bool QmlPlainTextEdit::copyAvailable() const +{ + return m_copyAvailable; +} + +/** + * Returns @c true if the QPlainTextEdit widget is enabled + */ bool QmlPlainTextEdit::widgetEnabled() const { return m_textEdit->isEnabled(); } +/** + * If set to true, the plain text edit scrolls the document vertically to make the cursor + * visible at the center of the viewport. This also allows the text edit to scroll below + * the end of the document. Otherwise, if set to false, the plain text edit scrolls the + * smallest amount possible to ensure the cursor is visible. + */ bool QmlPlainTextEdit::centerOnScroll() const { return m_textEdit->centerOnScroll(); } +/** + * This property holds whether undo and redo are enabled. + * Users are only able to undo or redo actions if this property is true, and if there is + * an action that can be undone (or redone). + */ bool QmlPlainTextEdit::undoRedoEnabled() const { return m_textEdit->isUndoRedoEnabled(); } +/** + * This property holds the limit for blocks in the document. + * + * Specifies the maximum number of blocks the document may have. If there are more blocks + * in the document that specified with this property blocks are removed from the beginning + * of the document. + * + * A negative or zero value specifies that the document may contain an unlimited amount + * of blocks. + */ int QmlPlainTextEdit::maximumBlockCount() const { return m_textEdit->maximumBlockCount(); } +/** + * This property holds the editor placeholder text. + * + * Setting this property makes the editor display a grayed-out placeholder text as long as + * the document is empty. + */ QString QmlPlainTextEdit::placeholderText() const { return m_textEdit->placeholderText(); } -bool QmlPlainTextEdit::copyAvailable() const -{ - return text().length() > 0; -} - +/** + * Process mouse incoming from the QML interface + */ void QmlPlainTextEdit::routeMouseEvents(QMouseEvent *event) { QMouseEvent *newEvent = new QMouseEvent(event->type(), event->localPos(), event->button(), event->buttons(), event->modifiers()); m_textEdit->setFocus(Qt::ActiveWindowFocusReason); + m_textEdit->grabMouse(); QApplication::postEvent(m_textEdit, newEvent); } +/** + * Process mouse wheel incoming from the QML interface + */ void QmlPlainTextEdit::routeWheelEvents(QWheelEvent *event) { QWheelEvent *newEvent @@ -196,11 +288,17 @@ void QmlPlainTextEdit::routeWheelEvents(QWheelEvent *event) QApplication::postEvent(m_textEdit, newEvent); } +/** + * Copies any selected text to the clipboard. + */ void QmlPlainTextEdit::copy() { m_textEdit->copy(); } +/** + * Deletes all the text in the text edit. + */ void QmlPlainTextEdit::clear() { m_textEdit->clear(); @@ -209,6 +307,9 @@ void QmlPlainTextEdit::clear() emit textChanged(); } +/** + * Selects all the text of the text edit. + */ void QmlPlainTextEdit::selectAll() { m_textEdit->selectAll(); @@ -217,6 +318,12 @@ void QmlPlainTextEdit::selectAll() emit textChanged(); } +/** + * Changes the read-only state of the text edit. + * + * In a read-only text edit the user can only navigate through the text and select text; + * modifying the text is not possible. + */ void QmlPlainTextEdit::setReadOnly(const bool ro) { m_textEdit->setReadOnly(ro); @@ -225,6 +332,9 @@ void QmlPlainTextEdit::setReadOnly(const bool ro) emit readOnlyChanged(); } +/** + * Changes the font used to display the text of the text edit. + */ void QmlPlainTextEdit::setFont(const QFont &font) { m_textEdit->setFont(font); @@ -233,34 +343,43 @@ void QmlPlainTextEdit::setFont(const QFont &font) emit fontChanged(); } +/** + * Appends a new paragraph with text to the end of the text edit. + * + * If @c autoscroll() is enabled, this function shall also update the scrollbar position + * to scroll to the bottom of the text. + */ void QmlPlainTextEdit::append(const QString &text) { m_textEdit->appendPlainText(text); if (autoscroll()) - { - auto *bar = m_textEdit->verticalScrollBar(); - bar->setValue(bar->maximum()); - } + scrollToBottom(false); update(); emit textChanged(); } +/** + * Replaces the text of the text editor with @c text. + * + * If @c autoscroll() is enabled, this function shall also update the scrollbar position + * to scroll to the bottom of the text. + */ void QmlPlainTextEdit::setText(const QString &text) { m_textEdit->setPlainText(text); if (autoscroll()) - { - auto *bar = m_textEdit->verticalScrollBar(); - bar->setValue(bar->maximum()); - } + scrollToBottom(false); update(); emit textChanged(); } +/** + * Changes the text color of the text editor. + */ void QmlPlainTextEdit::setColor(const QColor &color) { m_color = color; @@ -271,6 +390,9 @@ void QmlPlainTextEdit::setColor(const QColor &color) emit colorChanged(); } +/** + * Changes the @c QPalette of the text editor widget and its children. + */ void QmlPlainTextEdit::setPalette(const QPalette &palette) { m_textEdit->setPalette(palette); @@ -279,6 +401,9 @@ void QmlPlainTextEdit::setPalette(const QPalette &palette) emit paletteChanged(); } +/** + * Enables or disables the text editor widget. + */ void QmlPlainTextEdit::setWidgetEnabled(const bool enabled) { m_textEdit->setEnabled(enabled); @@ -287,12 +412,22 @@ void QmlPlainTextEdit::setWidgetEnabled(const bool enabled) emit widgetEnabledChanged(); } +/** + * Enables/disable automatic scrolling. If automatic scrolling is enabled, then the + * vertical scrollbar shall automatically scroll to the end of the document when the + * text of the text editor is changed. + */ void QmlPlainTextEdit::setAutoscroll(const bool enabled) { m_autoscroll = enabled; emit autoscrollChanged(); } +/** + * Changes the word wrap mode of the text editor. + * + * This property holds the mode QPlainTextEdit will use when wrapping text by words. + */ void QmlPlainTextEdit::setWordWrapMode(const int mode) { m_textEdit->setWordWrapMode(static_cast(mode)); @@ -301,6 +436,12 @@ void QmlPlainTextEdit::setWordWrapMode(const int mode) emit wordWrapModeChanged(); } +/** + * If set to true, the plain text edit scrolls the document vertically to make the cursor + * visible at the center of the viewport. This also allows the text edit to scroll below + * the end of the document. Otherwise, if set to false, the plain text edit scrolls the + * smallest amount possible to ensure the cursor is visible. + */ void QmlPlainTextEdit::setCenterOnScroll(const bool enabled) { m_textEdit->setCenterOnScroll(enabled); @@ -309,6 +450,9 @@ void QmlPlainTextEdit::setCenterOnScroll(const bool enabled) emit centerOnScrollChanged(); } +/** + * Enables/disables undo/redo history support. + */ void QmlPlainTextEdit::setUndoRedoEnabled(const bool enabled) { m_textEdit->setUndoRedoEnabled(enabled); @@ -317,6 +461,10 @@ void QmlPlainTextEdit::setUndoRedoEnabled(const bool enabled) emit undoRedoEnabledChanged(); } +/** + * Changes the placeholder text of the text editor. The placeholder text is only displayed + * when the document is empty. + */ void QmlPlainTextEdit::setPlaceholderText(const QString &text) { m_textEdit->setPlaceholderText(text); @@ -325,6 +473,35 @@ void QmlPlainTextEdit::setPlaceholderText(const QString &text) emit placeholderTextChanged(); } +/** + * Moves the position of the vertical scrollbar to the end of the document. However, this + * function also ensures that the last line of the document is shown at the bottom of + * the widget to mimic a terminal. + */ +void QmlPlainTextEdit::scrollToBottom(const bool repaint) +{ + auto *bar = m_textEdit->verticalScrollBar(); + + auto textHeight = m_textEdit->height() / m_textEdit->fontMetrics().height(); + auto scrollIndex = bar->maximum() - textHeight; + if (scrollIndex < 0) + scrollIndex = 0; + + bar->setValue(scrollIndex); + bar->setMaximum(scrollIndex); + + if (repaint) + update(); +} + +/** + * Specifies the maximum number of blocks the document may have. If there are more blocks + * in the document that specified with this property blocks are removed from the beginning + * of the document. + * + * A negative or zero value specifies that the document may contain an unlimited amount of + * blocks. + */ void QmlPlainTextEdit::setMaximumBlockCount(const int maxBlockCount) { m_textEdit->setMaximumBlockCount(maxBlockCount); @@ -333,8 +510,21 @@ void QmlPlainTextEdit::setMaximumBlockCount(const int maxBlockCount) emit maximumBlockCountChanged(); } +/** + * Resizes the text editor widget to fit inside the QML item. + */ void QmlPlainTextEdit::updateWidgetSize() { m_textEdit->setGeometry(0, 0, static_cast(width()), static_cast(height())); update(); } + +/** + * Updates the value of copy-available. This function is automatically called by the text + * editor widget when the user makes any text selection/deselection. + */ +void QmlPlainTextEdit::setCopyAvailable(const bool yes) +{ + m_copyAvailable = yes; + emit copyAvailableChanged(); +} diff --git a/src/UI/QmlPlainTextEdit.h b/src/UI/QmlPlainTextEdit.h index 595fd903..df84e301 100644 --- a/src/UI/QmlPlainTextEdit.h +++ b/src/UI/QmlPlainTextEdit.h @@ -82,6 +82,9 @@ class QmlPlainTextEdit : public QQuickPaintedItem READ maximumBlockCount WRITE setMaximumBlockCount NOTIFY maximumBlockCountChanged) + Q_PROPERTY(bool copyAvailable + READ copyAvailable + NOTIFY copyAvailableChanged) // clang-format on signals: @@ -92,6 +95,7 @@ signals: void readOnlyChanged(); void autoscrollChanged(); void wordWrapModeChanged(); + void copyAvailableChanged(); void widgetEnabledChanged(); void centerOnScrollChanged(); void placeholderTextChanged(); @@ -114,12 +118,12 @@ public: bool autoscroll() const; QPalette palette() const; int wordWrapMode() const; + bool copyAvailable() const; bool widgetEnabled() const; bool centerOnScroll() const; bool undoRedoEnabled() const; int maximumBlockCount() const; QString placeholderText() const; - Q_INVOKABLE bool copyAvailable() const; protected: void routeMouseEvents(QMouseEvent *event); @@ -141,14 +145,17 @@ public slots: void setCenterOnScroll(const bool enabled); void setUndoRedoEnabled(const bool enabled); void setPlaceholderText(const QString &text); + void scrollToBottom(const bool repaint = true); void setMaximumBlockCount(const int maxBlockCount); private slots: void updateWidgetSize(); + void setCopyAvailable(const bool yes); private: QColor m_color; bool m_autoscroll; + bool m_copyAvailable; QPlainTextEdit *m_textEdit; }; }