- Suchfunktion hat jetzt weiter und zurück - Tags mit Bindestrich werden erkannt
226 lines
7.4 KiB
C++
226 lines
7.4 KiB
C++
#include "rawview.h"
|
|
#include <QScrollBar>
|
|
#include <QTextDocument>
|
|
#include <QTextCursor>
|
|
#include <QFont>
|
|
#include <QPalette>
|
|
#include <QApplication>
|
|
#include <QClipboard>
|
|
#include <QDateTime>
|
|
#include <QShortcut>
|
|
#include <QKeySequence>
|
|
|
|
// Tag names may contain letters, digits, underscore AND hyphen (e.g.
|
|
// [I2C-BUS]) - keep this in sync with the tagRe in serialworker.cpp, which
|
|
// is what actually decides whether a tag is detected/emitted in the first
|
|
// place. If the two regexes disagree, hyphenated tags can end up detected
|
|
// by one but not recognised as "having a tag" by the other (breaking either
|
|
// cyan highlighting or the tag filter).
|
|
const QRegularExpression RawView::s_tagRe(
|
|
R"(\[([A-Z][A-Z0-9_-]*)\])", QRegularExpression::CaseInsensitiveOption);
|
|
|
|
RawView::RawView(QWidget *parent)
|
|
: QWidget(parent)
|
|
{
|
|
setupUi();
|
|
applyColorScheme();
|
|
}
|
|
|
|
void RawView::setupUi()
|
|
{
|
|
auto *mainLayout = new QVBoxLayout(this);
|
|
mainLayout->setContentsMargins(4, 4, 4, 4);
|
|
mainLayout->setSpacing(4);
|
|
|
|
auto *toolbar = new QHBoxLayout();
|
|
|
|
m_searchEdit = new QLineEdit(this);
|
|
m_searchEdit->setPlaceholderText(tr("Search…"));
|
|
m_searchEdit->setClearButtonEnabled(true);
|
|
connect(m_searchEdit, &QLineEdit::textChanged, this, &RawView::onSearch);
|
|
connect(m_searchEdit, &QLineEdit::returnPressed, this, &RawView::findNext);
|
|
|
|
m_findPrevBtn = new QPushButton(tr("▲"), this);
|
|
m_findPrevBtn->setMaximumWidth(28);
|
|
m_findPrevBtn->setToolTip(tr("Previous match (Shift+F3)"));
|
|
connect(m_findPrevBtn, &QPushButton::clicked, this, &RawView::findPrevious);
|
|
|
|
m_findNextBtn = new QPushButton(tr("▼"), this);
|
|
m_findNextBtn->setMaximumWidth(28);
|
|
m_findNextBtn->setToolTip(tr("Next match (F3 / Enter)"));
|
|
connect(m_findNextBtn, &QPushButton::clicked, this, &RawView::findNext);
|
|
|
|
auto *nextShortcut = new QShortcut(QKeySequence(Qt::Key_F3), this);
|
|
connect(nextShortcut, &QShortcut::activated, this, &RawView::findNext);
|
|
auto *prevShortcut = new QShortcut(QKeySequence(Qt::SHIFT | Qt::Key_F3), this);
|
|
connect(prevShortcut, &QShortcut::activated, this, &RawView::findPrevious);
|
|
|
|
m_autoScrollCb = new QCheckBox(tr("Auto-scroll"), this);
|
|
m_autoScrollCb->setChecked(true); // default ON
|
|
connect(m_autoScrollCb, &QCheckBox::toggled, this, &RawView::onAutoScrollToggled);
|
|
|
|
m_lineCountLbl = new QLabel(tr("Lines: 0"), this);
|
|
m_lineCountLbl->setMinimumWidth(90);
|
|
|
|
m_copyBtn = new QPushButton(tr("📋 Copy"), this);
|
|
m_copyBtn->setToolTip(tr("Copy all raw output to clipboard"));
|
|
connect(m_copyBtn, &QPushButton::clicked, this, &RawView::copyToClipboard);
|
|
|
|
m_clearBtn = new QPushButton(tr("Clear"), this);
|
|
connect(m_clearBtn, &QPushButton::clicked, this, &RawView::clear);
|
|
|
|
toolbar->addWidget(new QLabel(tr("Search:"), this));
|
|
toolbar->addWidget(m_searchEdit, 1);
|
|
toolbar->addWidget(m_findPrevBtn);
|
|
toolbar->addWidget(m_findNextBtn);
|
|
toolbar->addWidget(m_autoScrollCb);
|
|
toolbar->addWidget(m_lineCountLbl);
|
|
toolbar->addWidget(m_copyBtn);
|
|
toolbar->addWidget(m_clearBtn);
|
|
mainLayout->addLayout(toolbar);
|
|
|
|
m_textEdit = new QPlainTextEdit(this);
|
|
m_textEdit->setReadOnly(true);
|
|
m_textEdit->setLineWrapMode(QPlainTextEdit::NoWrap);
|
|
m_textEdit->setMaximumBlockCount(0);
|
|
|
|
QFont monoFont("Monospace");
|
|
monoFont.setStyleHint(QFont::Monospace);
|
|
monoFont.setPointSize(10);
|
|
m_textEdit->setFont(monoFont);
|
|
|
|
m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
|
m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
|
|
|
connect(m_textEdit->verticalScrollBar(), &QScrollBar::valueChanged,
|
|
this, &RawView::onScrollValueChanged);
|
|
|
|
mainLayout->addWidget(m_textEdit, 1);
|
|
}
|
|
|
|
void RawView::applyColorScheme()
|
|
{
|
|
QPalette p = m_textEdit->palette();
|
|
p.setColor(QPalette::Base, QColor(0x1e, 0x1e, 0x1e));
|
|
p.setColor(QPalette::Text, QColor(0xd4, 0xd4, 0xd4));
|
|
m_textEdit->setPalette(p);
|
|
}
|
|
|
|
void RawView::appendLine(const QString &line, const QSet<QString> &suppressedTags)
|
|
{
|
|
if (!suppressedTags.isEmpty()) {
|
|
const auto match = s_tagRe.match(line);
|
|
if (match.hasMatch() && suppressedTags.contains(match.captured(1).toUpper()))
|
|
return;
|
|
}
|
|
|
|
++m_lineCount;
|
|
m_lineCountLbl->setText(tr("Lines: %1").arg(m_lineCount));
|
|
|
|
const QString ts = QDateTime::currentDateTime().toString("hh:mm:ss.zzz");
|
|
const bool hasTag = s_tagRe.match(line).hasMatch();
|
|
|
|
QTextCursor cursor(m_textEdit->document());
|
|
cursor.movePosition(QTextCursor::End);
|
|
|
|
QTextCharFormat tsFmt;
|
|
tsFmt.setForeground(QColor(0x60, 0x60, 0x60));
|
|
cursor.insertText(QStringLiteral("[%1] ").arg(ts), tsFmt);
|
|
|
|
QTextCharFormat fmt;
|
|
fmt.setForeground(hasTag ? QColor(0x56, 0xb6, 0xc2) : QColor(0xd4, 0xd4, 0xd4));
|
|
cursor.insertText(line + '\n', fmt);
|
|
|
|
if (m_autoScroll)
|
|
m_textEdit->verticalScrollBar()->setValue(
|
|
m_textEdit->verticalScrollBar()->maximum());
|
|
}
|
|
|
|
void RawView::clear()
|
|
{
|
|
m_textEdit->clear();
|
|
m_lineCount = 0;
|
|
m_lineCountLbl->setText(tr("Lines: 0"));
|
|
}
|
|
|
|
void RawView::copyToClipboard()
|
|
{
|
|
QApplication::clipboard()->setText(m_textEdit->toPlainText());
|
|
}
|
|
|
|
bool RawView::searchAndSelect(const QString &text, QTextDocument::FindFlags flags, bool wrapAllowed)
|
|
{
|
|
if (text.isEmpty())
|
|
return false;
|
|
|
|
QTextDocument *doc = m_textEdit->document();
|
|
QTextCursor cursor = doc->find(text, m_textEdit->textCursor(), flags);
|
|
|
|
if (cursor.isNull() && wrapAllowed) {
|
|
// No further match in the search direction from here - wrap around
|
|
// to the start (or end, for backward search) and try once more so
|
|
// the user can keep pressing "next"/"previous" in a loop.
|
|
QTextCursor wrapCursor(doc);
|
|
if (flags.testFlag(QTextDocument::FindBackward))
|
|
wrapCursor.movePosition(QTextCursor::End);
|
|
cursor = doc->find(text, wrapCursor, flags);
|
|
}
|
|
|
|
if (cursor.isNull())
|
|
return false;
|
|
|
|
m_textEdit->setTextCursor(cursor);
|
|
return true;
|
|
}
|
|
|
|
void RawView::onSearch(const QString &text)
|
|
{
|
|
if (text.isEmpty()) {
|
|
m_searchEdit->setStyleSheet(QString());
|
|
return;
|
|
}
|
|
|
|
// Whenever the search text itself changes, restart from the top of the
|
|
// document so editing the query doesn't leave the view stranded at
|
|
// wherever the cursor happened to be from a previous search.
|
|
QTextCursor cursor(m_textEdit->document());
|
|
m_textEdit->setTextCursor(cursor);
|
|
|
|
const bool found = searchAndSelect(text, {}, true);
|
|
m_searchEdit->setStyleSheet(found ? QString() : "background: #5c2222;");
|
|
}
|
|
|
|
void RawView::findNext()
|
|
{
|
|
const QString text = m_searchEdit->text();
|
|
if (text.isEmpty())
|
|
return;
|
|
const bool found = searchAndSelect(text, {}, true);
|
|
m_searchEdit->setStyleSheet(found ? QString() : "background: #5c2222;");
|
|
}
|
|
|
|
void RawView::findPrevious()
|
|
{
|
|
const QString text = m_searchEdit->text();
|
|
if (text.isEmpty())
|
|
return;
|
|
const bool found = searchAndSelect(text, QTextDocument::FindBackward, true);
|
|
m_searchEdit->setStyleSheet(found ? QString() : "background: #5c2222;");
|
|
}
|
|
|
|
void RawView::onScrollValueChanged(int value)
|
|
{
|
|
if (value < m_textEdit->verticalScrollBar()->maximum() - 5) {
|
|
m_autoScroll = false;
|
|
m_autoScrollCb->setChecked(false);
|
|
}
|
|
}
|
|
|
|
void RawView::onAutoScrollToggled(bool enabled)
|
|
{
|
|
m_autoScroll = enabled;
|
|
if (enabled)
|
|
m_textEdit->verticalScrollBar()->setValue(
|
|
m_textEdit->verticalScrollBar()->maximum());
|
|
}
|