Tag-Filter hinzugefügt
This commit is contained in:
@@ -124,6 +124,13 @@ void MainWindow::setupToolBar()
|
||||
|
||||
tb->addSeparator();
|
||||
|
||||
// ── Tag filter ────────────────────────────────────────────────────────────
|
||||
auto *filterAction = tb->addAction(tr("Tag filter…"));
|
||||
filterAction->setToolTip(tr("Choose which tags are hidden from the Raw view"));
|
||||
connect(filterAction, &QAction::triggered, this, &MainWindow::configureTagFilter);
|
||||
|
||||
tb->addSeparator();
|
||||
|
||||
// ── Format reference ──────────────────────────────────────────────────────
|
||||
auto *helpAction = tb->addAction(tr("Format reference"));
|
||||
helpAction->setToolTip(tr("Show UARTScope output format guide (copy for AI)"));
|
||||
@@ -231,7 +238,7 @@ void MainWindow::onError(const QString &message)
|
||||
|
||||
void MainWindow::onNewLine(const QString &line)
|
||||
{
|
||||
m_rawView->appendLine(line);
|
||||
m_rawView->appendLine(line, m_suppressedTags);
|
||||
m_tableView->appendLine(line);
|
||||
}
|
||||
|
||||
@@ -392,3 +399,48 @@ void uart_status_update(void) {
|
||||
dlg->exec();
|
||||
dlg->deleteLater();
|
||||
}
|
||||
|
||||
// ── Tag filter dialog ──────────────────────────────────────────────────────
|
||||
|
||||
void MainWindow::configureTagFilter()
|
||||
{
|
||||
auto *dlg = new QDialog(this);
|
||||
dlg->setWindowTitle(tr("Tag filter – Raw view"));
|
||||
dlg->setMinimumWidth(340);
|
||||
|
||||
auto *layout = new QVBoxLayout(dlg);
|
||||
|
||||
auto *infoLabel = new QLabel(
|
||||
tr("Tags listed here are <b>hidden from the Raw view</b> and only "
|
||||
"appear in the Tag Monitor panel.<br>"
|
||||
"Enter one tag name per line, without brackets (e.g. <tt>WDG</tt>)."), dlg);
|
||||
infoLabel->setWordWrap(true);
|
||||
layout->addWidget(infoLabel);
|
||||
|
||||
auto *edit = new QPlainTextEdit(dlg);
|
||||
QFont mono("Monospace");
|
||||
mono.setStyleHint(QFont::Monospace);
|
||||
edit->setFont(mono);
|
||||
// Pre-populate with current filter
|
||||
QStringList current(m_suppressedTags.values());
|
||||
current.sort();
|
||||
edit->setPlainText(current.join('\n'));
|
||||
layout->addWidget(edit, 1);
|
||||
|
||||
auto *btnBox = new QDialogButtonBox(
|
||||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dlg);
|
||||
connect(btnBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept);
|
||||
connect(btnBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject);
|
||||
layout->addWidget(btnBox);
|
||||
|
||||
if (dlg->exec() == QDialog::Accepted) {
|
||||
m_suppressedTags.clear();
|
||||
const QStringList lines = edit->toPlainText().split('\n', Qt::SkipEmptyParts);
|
||||
for (const QString &line : lines) {
|
||||
const QString tag = line.trimmed().toUpper();
|
||||
if (!tag.isEmpty())
|
||||
m_suppressedTags.insert(tag);
|
||||
}
|
||||
}
|
||||
dlg->deleteLater();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
#include <QFont>
|
||||
#include <QPalette>
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QDateTime>
|
||||
|
||||
const QRegularExpression RawView::s_tagRe(
|
||||
R"(\[([A-Z][A-Z0-9_]*)\])", QRegularExpression::CaseInsensitiveOption);
|
||||
|
||||
RawView::RawView(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
@@ -15,7 +20,7 @@ RawView::RawView(QWidget *parent)
|
||||
|
||||
void RawView::setupUi()
|
||||
{
|
||||
auto *mainLayout = new QVBoxLayout(this);
|
||||
auto *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(4, 4, 4, 4);
|
||||
mainLayout->setSpacing(4);
|
||||
|
||||
@@ -23,7 +28,7 @@ void RawView::setupUi()
|
||||
auto *toolbar = new QHBoxLayout();
|
||||
|
||||
m_searchEdit = new QLineEdit(this);
|
||||
m_searchEdit->setPlaceholderText(tr("Search… (Enter)"));
|
||||
m_searchEdit->setPlaceholderText(tr("Search…"));
|
||||
m_searchEdit->setClearButtonEnabled(true);
|
||||
connect(m_searchEdit, &QLineEdit::textChanged, this, &RawView::onSearch);
|
||||
|
||||
@@ -31,33 +36,35 @@ void RawView::setupUi()
|
||||
m_autoScrollCb->setChecked(true);
|
||||
connect(m_autoScrollCb, &QCheckBox::toggled, this, &RawView::onAutoScrollToggled);
|
||||
|
||||
m_clearBtn = new QPushButton(tr("Clear"), this);
|
||||
connect(m_clearBtn, &QPushButton::clicked, this, &RawView::clear);
|
||||
|
||||
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_autoScrollCb);
|
||||
toolbar->addWidget(m_lineCountLbl);
|
||||
toolbar->addWidget(m_copyBtn);
|
||||
toolbar->addWidget(m_clearBtn);
|
||||
mainLayout->addLayout(toolbar);
|
||||
|
||||
// ── Text area ────────────────────────────────────────────────────────────
|
||||
// ── Text area ─────────────────────────────────────────────────────────────
|
||||
m_textEdit = new QPlainTextEdit(this);
|
||||
m_textEdit->setReadOnly(true);
|
||||
m_textEdit->setLineWrapMode(QPlainTextEdit::NoWrap); // horizontal scroll
|
||||
// Practically unlimited history (Qt uses a block count limit internally)
|
||||
m_textEdit->setMaximumBlockCount(0); // 0 = unlimited
|
||||
m_textEdit->setLineWrapMode(QPlainTextEdit::NoWrap);
|
||||
m_textEdit->setMaximumBlockCount(0); // unlimited
|
||||
|
||||
// Monospaced font for alignment
|
||||
QFont monoFont("Monospace");
|
||||
monoFont.setStyleHint(QFont::Monospace);
|
||||
monoFont.setPointSize(10);
|
||||
m_textEdit->setFont(monoFont);
|
||||
|
||||
// Don't let the text edit swallow horizontal scroll events from the viewport
|
||||
m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
|
||||
@@ -69,31 +76,48 @@ void RawView::setupUi()
|
||||
|
||||
void RawView::applyColorScheme()
|
||||
{
|
||||
// Dark terminal style
|
||||
QPalette p = m_textEdit->palette();
|
||||
p.setColor(QPalette::Base, QColor(0x1e, 0x1e, 0x1e));
|
||||
p.setColor(QPalette::Text, QColor(0xd4, 0xd4, 0xd4));
|
||||
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)
|
||||
void RawView::appendLine(const QString &line, const QSet<QString> &suppressedTags)
|
||||
{
|
||||
// Check whether this line belongs to a suppressed tag
|
||||
if (!suppressedTags.isEmpty()) {
|
||||
const auto match = s_tagRe.match(line);
|
||||
if (match.hasMatch()) {
|
||||
if (suppressedTags.contains(match.captured(1).toUpper()))
|
||||
return; // silently drop – it goes to the Tag Monitor only
|
||||
}
|
||||
}
|
||||
|
||||
++m_lineCount;
|
||||
m_lineCountLbl->setText(tr("Lines: %1").arg(m_lineCount));
|
||||
|
||||
// Highlight lines that contain a tag like [WDG]
|
||||
static const QRegularExpression tagRe(R"(\[[A-Z][A-Z0-9_]*\])",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
if (tagRe.match(line).hasMatch()) {
|
||||
// Append as HTML so we can colour it differently
|
||||
QTextCursor cursor(m_textEdit->document());
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
QTextCharFormat fmt;
|
||||
fmt.setForeground(QColor(0x56, 0xb6, 0xc2)); // cyan-ish
|
||||
cursor.insertText(line + '\n', fmt);
|
||||
} else {
|
||||
m_textEdit->appendPlainText(line);
|
||||
}
|
||||
// Prepend timestamp
|
||||
const QString ts = QDateTime::currentDateTime().toString("hh:mm:ss.zzz");
|
||||
const QString displayLine = QStringLiteral("[%1] %2").arg(ts, line);
|
||||
|
||||
// Colour tag lines cyan, everything else default
|
||||
const bool hasTag = s_tagRe.match(line).hasMatch();
|
||||
|
||||
QTextCursor cursor(m_textEdit->document());
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
|
||||
QTextCharFormat fmt;
|
||||
if (hasTag)
|
||||
fmt.setForeground(QColor(0x56, 0xb6, 0xc2)); // cyan
|
||||
else
|
||||
fmt.setForeground(QColor(0xd4, 0xd4, 0xd4)); // default
|
||||
|
||||
// Dim the timestamp portion
|
||||
QTextCharFormat tsFmt;
|
||||
tsFmt.setForeground(QColor(0x60, 0x60, 0x60));
|
||||
|
||||
cursor.insertText(QStringLiteral("[%1] ").arg(ts), tsFmt);
|
||||
cursor.insertText(line + '\n', fmt);
|
||||
|
||||
if (m_autoScroll)
|
||||
m_textEdit->verticalScrollBar()->setValue(
|
||||
@@ -107,9 +131,13 @@ void RawView::clear()
|
||||
m_lineCountLbl->setText(tr("Lines: 0"));
|
||||
}
|
||||
|
||||
void RawView::copyToClipboard()
|
||||
{
|
||||
QApplication::clipboard()->setText(m_textEdit->toPlainText());
|
||||
}
|
||||
|
||||
void RawView::onSearch(const QString &text)
|
||||
{
|
||||
// Simple incremental search – highlight first match
|
||||
QTextDocument *doc = m_textEdit->document();
|
||||
QTextCursor cursor = doc->find(text);
|
||||
if (!cursor.isNull()) {
|
||||
@@ -124,9 +152,7 @@ void RawView::onSearch(const QString &text)
|
||||
|
||||
void RawView::onScrollValueChanged(int value)
|
||||
{
|
||||
const int max = m_textEdit->verticalScrollBar()->maximum();
|
||||
// If user scrolled away from bottom, disable auto-scroll
|
||||
if (value < max - 5) {
|
||||
if (value < m_textEdit->verticalScrollBar()->maximum() - 5) {
|
||||
m_autoScroll = false;
|
||||
m_autoScrollCb->setChecked(false);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
#include "tagpanel.h"
|
||||
#include <QHeaderView>
|
||||
#include <QRegularExpression>
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
|
||||
TagPanel::TagPanel(const QString &tag, QWidget *parent)
|
||||
: QGroupBox(QStringLiteral("[%1]").arg(tag), parent)
|
||||
, m_tag(tag)
|
||||
{
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(6, 12, 6, 6);
|
||||
layout->setContentsMargins(6, 14, 6, 6);
|
||||
layout->setSpacing(4);
|
||||
|
||||
// Timestamp line
|
||||
m_timestampLabel = new QLabel(tr("No data yet"), this);
|
||||
m_timestampLabel->setStyleSheet("color: gray; font-size: 10px;");
|
||||
layout->addWidget(m_timestampLabel);
|
||||
|
||||
// Table for key=value pairs
|
||||
// ── Table for key=value pairs ─────────────────────────────────────────────
|
||||
m_table = new QTableWidget(0, 2, this);
|
||||
m_table->setHorizontalHeaderLabels({tr("Key"), tr("Value")});
|
||||
m_table->horizontalHeader()->setStretchLastSection(true);
|
||||
@@ -26,23 +23,28 @@ TagPanel::TagPanel(const QString &tag, QWidget *parent)
|
||||
m_table->verticalHeader()->setDefaultSectionSize(22);
|
||||
layout->addWidget(m_table);
|
||||
|
||||
// Fallback label for non-kv data
|
||||
// ── Fallback label for non-kv data ────────────────────────────────────────
|
||||
m_rawLabel = new QLabel(this);
|
||||
m_rawLabel->setWordWrap(true);
|
||||
m_rawLabel->setStyleSheet("font-family: monospace;");
|
||||
m_rawLabel->hide();
|
||||
layout->addWidget(m_rawLabel);
|
||||
|
||||
// ── Copy button ───────────────────────────────────────────────────────────
|
||||
auto *btnRow = new QHBoxLayout();
|
||||
btnRow->addStretch();
|
||||
auto *copyBtn = new QPushButton(tr("📋 Copy"), this);
|
||||
copyBtn->setToolTip(tr("Copy current tag values to clipboard"));
|
||||
copyBtn->setMaximumWidth(90);
|
||||
connect(copyBtn, &QPushButton::clicked, this, &TagPanel::copyToClipboard);
|
||||
btnRow->addWidget(copyBtn);
|
||||
layout->addLayout(btnRow);
|
||||
}
|
||||
|
||||
void TagPanel::update(const QString &value)
|
||||
{
|
||||
m_timestampLabel->setText(
|
||||
QDateTime::currentDateTime().toString("hh:mm:ss.zzz"));
|
||||
|
||||
// Detect key=value format: word=anything (space separated)
|
||||
static const QRegularExpression kvRe(R"((\w+)=(\S+))");
|
||||
auto it = kvRe.globalMatch(value);
|
||||
if (it.hasNext()) {
|
||||
if (kvRe.match(value).hasMatch()) {
|
||||
parseKeyValue(value);
|
||||
m_rawLabel->hide();
|
||||
m_table->show();
|
||||
@@ -59,13 +61,10 @@ void TagPanel::parseKeyValue(const QString &value)
|
||||
auto it = kvRe.globalMatch(value);
|
||||
while (it.hasNext()) {
|
||||
const auto match = it.next();
|
||||
const QString key = match.captured(1);
|
||||
const QString val = match.captured(2);
|
||||
ensureRow(key);
|
||||
const int row = m_keys.indexOf(key);
|
||||
m_table->item(row, 1)->setText(val);
|
||||
// Flash highlight
|
||||
m_table->item(row, 1)->setBackground(QColor(0x2d, 0x5a, 0x2d));
|
||||
ensureRow(match.captured(1));
|
||||
const int row = m_keys.indexOf(match.captured(1));
|
||||
m_table->item(row, 1)->setText(match.captured(2));
|
||||
m_table->item(row, 1)->setBackground(QColor(0x2d, 0x5a, 0x2d)); // flash green
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,3 +83,21 @@ void TagPanel::ensureRow(const QString &key)
|
||||
m_table->setItem(row, 0, new QTableWidgetItem(key));
|
||||
m_table->setItem(row, 1, new QTableWidgetItem(QString()));
|
||||
}
|
||||
|
||||
void TagPanel::copyToClipboard()
|
||||
{
|
||||
QStringList lines;
|
||||
lines << QStringLiteral("[%1]").arg(m_tag);
|
||||
|
||||
if (m_table->isVisible()) {
|
||||
for (int r = 0; r < m_table->rowCount(); ++r) {
|
||||
const QString key = m_table->item(r, 0)->text();
|
||||
const QString val = m_table->item(r, 1)->text();
|
||||
lines << QStringLiteral(" %1 = %2").arg(key, val);
|
||||
}
|
||||
} else {
|
||||
lines << QStringLiteral(" %1").arg(m_rawLabel->text());
|
||||
}
|
||||
|
||||
QApplication::clipboard()->setText(lines.join('\n'));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user