- Suchfunktion hat jetzt weiter und zurück - Tags mit Bindestrich werden erkannt
221 lines
7.7 KiB
C++
221 lines
7.7 KiB
C++
#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'));
|
||
}
|