Files
uartscope/src/tagpanel.cpp
Dany Thinnes a942f8385b - Tags haben jetzt einen Verlauf
- Suchfunktion hat jetzt weiter und zurück
- Tags mit Bindestrich werden erkannt
2026-08-18 00:24:43 +02:00

221 lines
7.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "tagpanel.h"
#include <QHeaderView>
#include <QRegularExpression>
#include <QApplication>
#include <QClipboard>
#include <QDateTime>
#include <QScrollBar>
#include <QFont>
#include <QTextCursor>
TagPanel::TagPanel(const QString &tag, QWidget *parent)
: QGroupBox(QStringLiteral("[%1]").arg(tag), parent)
, m_tag(tag)
{
auto *layout = new QVBoxLayout(this);
layout->setContentsMargins(6, 14, 6, 6);
layout->setSpacing(4);
m_tabs = new QTabWidget(this);
// ── "Aktuell" tab: latest value(s), updates in-place ───────────────
auto *currentTab = new QWidget(m_tabs);
auto *currentLayout = new QVBoxLayout(currentTab);
currentLayout->setContentsMargins(0, 4, 0, 0);
currentLayout->setSpacing(4);
m_table = new QTableWidget(0, 2, currentTab);
m_table->setHorizontalHeaderLabels({tr("Key"), tr("Value")});
m_table->horizontalHeader()->setStretchLastSection(true);
m_table->verticalHeader()->hide();
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_table->setAlternatingRowColors(true);
m_table->setMaximumHeight(220);
m_table->verticalHeader()->setDefaultSectionSize(22);
currentLayout->addWidget(m_table);
m_rawLabel = new QLabel(currentTab);
m_rawLabel->setWordWrap(true);
m_rawLabel->setStyleSheet("font-family: monospace;");
m_rawLabel->hide();
currentLayout->addWidget(m_rawLabel);
m_tabs->addTab(currentTab, tr("Aktuell"));
// ── "Verlauf" tab: every value ever received, timestamped ──────────
auto *historyTab = new QWidget(m_tabs);
auto *historyLayout = new QVBoxLayout(historyTab);
historyLayout->setContentsMargins(0, 4, 0, 0);
historyLayout->setSpacing(4);
m_historyEdit = new QPlainTextEdit(historyTab);
m_historyEdit->setReadOnly(true);
m_historyEdit->setMaximumHeight(220);
m_historyEdit->setLineWrapMode(QPlainTextEdit::NoWrap);
QFont mono("Monospace");
mono.setStyleHint(QFont::Monospace);
mono.setPointSize(9);
m_historyEdit->setFont(mono);
historyLayout->addWidget(m_historyEdit);
auto *historyBtnRow = new QHBoxLayout();
auto *clearHistBtn = new QPushButton(tr("Verlauf leeren"), historyTab);
clearHistBtn->setToolTip(tr("Nur den Verlauf dieses Tags leeren (Panel bleibt bestehen)"));
connect(clearHistBtn, &QPushButton::clicked, this, &TagPanel::clearHistory);
historyBtnRow->addStretch();
historyBtnRow->addWidget(clearHistBtn);
historyLayout->addLayout(historyBtnRow);
m_tabs->addTab(historyTab, tr("Verlauf"));
layout->addWidget(m_tabs);
auto *btnRow = new QHBoxLayout();
btnRow->addStretch();
auto *copyBtn = new QPushButton(tr("📋 Copy"), this);
copyBtn->setToolTip(tr("Copy the active tab's content to clipboard"));
copyBtn->setMaximumWidth(90);
connect(copyBtn, &QPushButton::clicked, this, &TagPanel::copyToClipboard);
btnRow->addWidget(copyBtn);
layout->addLayout(btnRow);
}
void TagPanel::update(const QString &value)
{
appendHistory(value);
static const QRegularExpression kvRe(R"((\w+)=(\S+))");
if (kvRe.match(value).hasMatch()) {
parseKeyValue(value);
m_rawLabel->hide();
m_table->show();
} else {
showRaw(value);
m_table->hide();
m_rawLabel->show();
}
}
void TagPanel::appendHistory(const QString &value)
{
const QString ts = QDateTime::currentDateTime().toString("hh:mm:ss.zzz");
m_historyEdit->appendPlainText(QStringLiteral("[%1] %2").arg(ts, value));
m_historyEdit->verticalScrollBar()->setValue(
m_historyEdit->verticalScrollBar()->maximum());
++m_historyCount;
if (m_historyCount > kMaxHistoryEntries) {
// Drop the oldest entry (topmost line) to keep memory use bounded
// for very chatty or long-running tags.
QTextCursor cursor(m_historyEdit->document());
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
--m_historyCount;
}
}
void TagPanel::clearHistory()
{
m_historyEdit->clear();
m_historyCount = 0;
}
void TagPanel::parseKeyValue(const QString &value)
{
static const QRegularExpression kvRe(R"((\w+)=(\S+))");
auto it = kvRe.globalMatch(value);
while (it.hasNext()) {
const auto match = it.next();
const QString key = match.captured(1);
const QString raw = match.captured(2);
// Split on ';' -> list of values. A plain (non-list) value yields
// a QStringList with exactly one element, which still goes through
// the same code path for consistency.
QStringList values = raw.split(';', Qt::SkipEmptyParts);
if (values.isEmpty())
values << raw; // preserve even an empty/odd value as-is
setKeyValues(key, values);
}
}
void TagPanel::setKeyValues(const QString &key, const QStringList &values)
{
const int newCount = values.size();
if (m_keyRanges.contains(key)) {
// Key already exists remove its current rows, then re-insert at
// the same starting position with the new row count.
const KeyRange old = m_keyRanges[key];
for (int i = 0; i < old.count; ++i)
m_table->removeRow(old.firstRow);
// Shift all OTHER key ranges that started after this one
for (auto it = m_keyRanges.begin(); it != m_keyRanges.end(); ++it) {
if (it.key() != key && it->firstRow > old.firstRow)
it->firstRow += (newCount - old.count);
}
int insertAt = old.firstRow;
for (int i = 0; i < newCount; ++i) {
m_table->insertRow(insertAt + i);
auto *keyItem = new QTableWidgetItem(i == 0 ? key : QString());
auto *valItem = new QTableWidgetItem(values[i]);
valItem->setBackground(QColor(0x2d, 0x5a, 0x2d)); // flash green on update
m_table->setItem(insertAt + i, 0, keyItem);
m_table->setItem(insertAt + i, 1, valItem);
}
m_keyRanges[key] = {insertAt, newCount};
} else {
// New key append at the bottom
const int insertAt = m_table->rowCount();
for (int i = 0; i < newCount; ++i) {
m_table->insertRow(insertAt + i);
auto *keyItem = new QTableWidgetItem(i == 0 ? key : QString());
auto *valItem = new QTableWidgetItem(values[i]);
valItem->setBackground(QColor(0x2d, 0x5a, 0x2d));
m_table->setItem(insertAt + i, 0, keyItem);
m_table->setItem(insertAt + i, 1, valItem);
}
m_keyRanges[key] = {insertAt, newCount};
}
}
void TagPanel::showRaw(const QString &value)
{
m_rawLabel->setText(value);
}
void TagPanel::copyToClipboard()
{
QStringList lines;
lines << QStringLiteral("[%1]").arg(m_tag);
if (m_tabs->currentIndex() == 1) {
// "Verlauf" tab active - copy the full timestamped history as-is.
QApplication::clipboard()->setText(
lines.join('\n') + '\n' + m_historyEdit->toPlainText());
return;
}
if (m_table->isVisible()) {
QString lastKey;
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();
if (!key.isEmpty()) {
lines << QStringLiteral(" %1 = %2").arg(key, val);
lastKey = key;
} else {
lines << QStringLiteral(" %1").arg(val); // continuation row
}
}
} else {
lines << QStringLiteral(" %1").arg(m_rawLabel->text());
}
QApplication::clipboard()->setText(lines.join('\n'));
}