- Auto-scroll standard aktiv

- Tag-Filter wird gespeichert
- Tag Monitor kann bei einem Tag jetzt pro Key mehrere Werte in einer Tabelle anzeigen
- Bug für Clear behoben
This commit is contained in:
2026-06-23 23:19:06 +02:00
parent 92168ee5dc
commit d3229c7b64
29 changed files with 342 additions and 380 deletions

View File

@@ -14,7 +14,7 @@ struct SerialConfig {
QSerialPort::Parity parity = QSerialPort::NoParity;
QSerialPort::StopBits stopBits = QSerialPort::OneStop;
QSerialPort::FlowControl flowControl = QSerialPort::NoFlowControl;
QString logFilePath; // empty = no logging
QString logFilePath;
};
class ConnectDialog : public QDialog
@@ -23,7 +23,6 @@ class ConnectDialog : public QDialog
public:
explicit ConnectDialog(QWidget *parent = nullptr);
SerialConfig config() const;
private slots:

View File

@@ -5,13 +5,13 @@
#include <QSplitter>
#include <QThread>
#include <QTabWidget>
#include <QSplitter>
#include <QStatusBar>
#include <QToolBar>
#include <QLabel>
#include <QAction>
#include <QSpinBox>
#include <QCheckBox>
#include <QSet>
#include "serialworker.h"
#include "rawview.h"
@@ -19,7 +19,6 @@
#include "tagwidget.h"
#include "videowidget.h"
#include "connectdialog.h"
#include <QSet>
class MainWindow : public QMainWindow
{
@@ -29,6 +28,9 @@ public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
void closeEvent(QCloseEvent *event) override;
private slots:
void onConnectClicked();
void onDisconnectClicked();
@@ -43,9 +45,6 @@ private slots:
void configureTagFilter();
void showAbout();
protected:
void closeEvent(QCloseEvent *event) override;
private:
void setupUi();
void setupToolBar();
@@ -55,34 +54,29 @@ private:
void saveSettings();
void restoreSettings();
// Worker & thread
QThread *m_thread = nullptr;
SerialWorker *m_worker = nullptr;
// Views
RawView *m_rawView = nullptr;
TableView *m_tableView = nullptr;
TagWidget *m_tagWidget = nullptr;
VideoWidget *m_videoWidget= nullptr;
// Status bar widgets
QLabel *m_portLabel = nullptr;
QLabel *m_stateLabel = nullptr;
QLabel *m_reconnectLabel = nullptr;
QLabel *m_portLabel = nullptr;
QLabel *m_stateLabel = nullptr;
QLabel *m_reconnectLabel = nullptr;
// Toolbar widgets
QCheckBox *m_autoReconnectCb = nullptr;
QSpinBox *m_reconnectIntervalSb= nullptr;
QCheckBox *m_autoReconnectCb = nullptr;
QSpinBox *m_reconnectIntervalSb = nullptr;
// Actions
QAction *m_connectAction = nullptr;
QAction *m_disconnectAction = nullptr;
// Splitters (saved/restored via QSettings)
QSplitter *m_mainSplitter = nullptr;
QSplitter *m_rightSplitter = nullptr;
QSplitter *m_mainSplitter = nullptr;
QSplitter *m_rightSplitter = nullptr;
SerialConfig m_lastConfig;
QSet<QString> m_suppressedTags; // tags hidden from Raw view
bool m_shutdownDone = false;
SerialConfig m_lastConfig;
QSet<QString> m_suppressedTags; // tags hidden from Raw view
bool m_shutdownDone = false;
};

View File

@@ -7,14 +7,12 @@
#include <QPushButton>
#include <QCheckBox>
#include <QLabel>
#include <QSpinBox>
#include <QSet>
#include <QRegularExpression>
// RawView shows the raw UART output in a QPlainTextEdit with:
// - Timestamp prefix on every line (hh:mm:ss.zzz)
// - Configurable tag suppression (lines whose tag is in the filter set are hidden)
// - Unlimited history, H+V scrolling, incremental search
// - Copy-to-clipboard and Clear buttons
// RawView shows the raw UART output with timestamps, search, tag suppression,
// unlimited history, H+V scrolling and a copy-to-clipboard button.
// Auto-scroll is ON by default.
class RawView : public QWidget
{
Q_OBJECT
@@ -23,7 +21,6 @@ public:
explicit RawView(QWidget *parent = nullptr);
public slots:
// suppressedTags: set of uppercase tag names whose lines should NOT appear here
void appendLine(const QString &line, const QSet<QString> &suppressedTags = {});
void clear();
@@ -43,7 +40,7 @@ private:
QPushButton *m_copyBtn = nullptr;
QCheckBox *m_autoScrollCb = nullptr;
QLabel *m_lineCountLbl = nullptr;
bool m_autoScroll = true;
bool m_autoScroll = true; // ON by default
int m_lineCount = 0;
static const QRegularExpression s_tagRe;

View File

@@ -10,9 +10,9 @@
// SerialWorker lives in its own QThread and owns the QSerialPort.
// It emits newLine() for every complete line received,
// tagDetected() when a line contains a recognised tag like [WDG],
// and clearScreen() when the ANSI clear-screen sequence \033[2J is received.
// Auto-reconnect: if the port drops unexpectedly (e.g. USB cable pulled),
// the worker retries every reconnectIntervalMs until success or closePort().
// and clearScreen() when an ANSI clear-screen sequence is received.
// Auto-reconnect: if the port drops unexpectedly, the worker retries
// every reconnectIntervalMs until success or closePort().
class SerialWorker : public QObject
{
Q_OBJECT
@@ -21,9 +21,8 @@ public:
explicit SerialWorker(QObject *parent = nullptr);
~SerialWorker();
// Auto-reconnect configuration (call before openPort, or any time)
void setAutoReconnect(bool enabled) { m_autoReconnect = enabled; }
void setReconnectInterval(int ms) { m_reconnectIntervalMs = ms; }
void setAutoReconnect(bool enabled) { m_autoReconnect = enabled; }
void setReconnectInterval(int ms) { m_reconnectIntervalMs = ms; }
public slots:
void openPort(const QString &portName, qint32 baudRate,
@@ -31,17 +30,17 @@ public slots:
QSerialPort::Parity parity,
QSerialPort::StopBits stopBits,
QSerialPort::FlowControl flowControl);
void closePort(); // explicit user disconnect stops reconnect
void closePort();
void setLogFile(const QString &path);
void stopLogging();
signals:
void newLine(const QString &line);
void tagDetected(const QString &tag, const QString &value);
void clearScreen(); // emitted when \033[2J\033[H (or \033[2J alone) is received
void clearScreen();
void portOpened();
void portClosed();
void reconnecting(int attempt); // fired each retry attempt
void reconnecting(int attempt);
void errorOccurred(const QString &message);
private slots:
@@ -54,23 +53,22 @@ private:
void processLine(const QString &line);
void scheduleReconnect();
QSerialPort *m_port = nullptr;
QFile *m_logFile = nullptr;
QSerialPort *m_port = nullptr;
QFile *m_logFile = nullptr;
QTextStream *m_logStream = nullptr;
QString m_buffer;
QByteArray m_scanTail; // carries partial ANSI sequences across reads
// Reconnect state
QTimer *m_reconnectTimer = nullptr;
bool m_autoReconnect = true;
bool m_userDisconnected = false; // true only after explicit closePort()
int m_reconnectIntervalMs = 2000;
int m_reconnectAttempt = 0;
QTimer *m_reconnectTimer = nullptr;
bool m_autoReconnect = true;
bool m_userDisconnected = false;
int m_reconnectIntervalMs = 2000;
int m_reconnectAttempt = 0;
// Saved config for reconnect
QString m_portName;
qint32 m_baudRate = 115200;
QSerialPort::DataBits m_dataBits = QSerialPort::Data8;
QSerialPort::Parity m_parity = QSerialPort::NoParity;
QSerialPort::StopBits m_stopBits = QSerialPort::OneStop;
QSerialPort::FlowControl m_flowControl= QSerialPort::NoFlowControl;
qint32 m_baudRate = 115200;
QSerialPort::DataBits m_dataBits = QSerialPort::Data8;
QSerialPort::Parity m_parity = QSerialPort::NoParity;
QSerialPort::StopBits m_stopBits = QSerialPort::OneStop;
QSerialPort::FlowControl m_flowControl = QSerialPort::NoFlowControl;
};

View File

@@ -10,13 +10,6 @@
#include <QComboBox>
// TableView interprets UART lines as delimited data and shows them in a table.
// The first line that matches the column count is used as the header row if it
// starts with '#'. Otherwise column names are auto-generated (Col 1, Col 2, …).
//
// Supported delimiters: comma, semicolon, tab, pipe, space
// To use: send lines like:
// #time,temp,voltage,current
// 1234,23.5,3.3,0.42
class TableView : public QWidget
{
Q_OBJECT
@@ -37,16 +30,16 @@ private:
void parseHeaders(const QString &line);
bool tryAppendRow(const QString &line);
QTableWidget *m_table = nullptr;
QComboBox *m_delimCombo = nullptr;
QLineEdit *m_maxRowsEdit = nullptr;
QPushButton *m_clearBtn = nullptr;
QCheckBox *m_autoScrollCb = nullptr;
QTableWidget *m_table = nullptr;
QComboBox *m_delimCombo = nullptr;
QLineEdit *m_maxRowsEdit = nullptr;
QPushButton *m_clearBtn = nullptr;
QCheckBox *m_autoScrollCb = nullptr;
QStringList m_headers;
QList<QStringList> m_rows; // raw data for rebuild
int m_maxRows = 500;
char m_delimiter = ',';
QList<QStringList> m_rows;
int m_maxRows = 500;
char m_delimiter = ',';
bool m_autoScroll = true;
bool m_headersSet = false;
};

View File

@@ -6,11 +6,16 @@
#include <QPushButton>
#include <QTableWidget>
#include <QStringList>
#include <QMap>
// TagPanel displays the most recent data for a single tag (e.g. [WDG]).
// Key=value pairs are shown in a table that updates in-place.
// A "Copy" button copies the current values to the clipboard.
// No timestamp is shown here timestamps belong in the Raw view.
// If a value contains ';', it is treated as a LIST: the key is shown once
// and each list element gets its own row below it (key cell left empty
// for the continuation rows, like a merged-cell look).
// Example: Devs=trackdisk.device;input.device
// Devs trackdisk.device
// input.device
class TagPanel : public QGroupBox
{
Q_OBJECT
@@ -29,10 +34,16 @@ private slots:
private:
void parseKeyValue(const QString &value);
void showRaw(const QString &value);
void ensureRow(const QString &key);
// Replaces all rows belonging to `key` with one row per value in `values`.
void setKeyValues(const QString &key, const QStringList &values);
QString m_tag;
QTableWidget *m_table = nullptr;
QLabel *m_rawLabel = nullptr;
QStringList m_keys;
// Track which table rows belong to which key, so repeated updates can
// replace the previous rows instead of appending duplicates.
// Maps key -> (first row index, row count)
struct KeyRange { int firstRow; int count; };
QMap<QString, KeyRange> m_keyRanges;
};

View File

@@ -4,7 +4,7 @@
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QLineEdit>
#include <QLabel>
#include <QMap>
#include <QString>
@@ -25,6 +25,6 @@ public slots:
void removeTag(const QString &tag);
private:
QVBoxLayout *m_panelLayout = nullptr;
QVBoxLayout *m_panelLayout = nullptr;
QMap<QString, TagPanel*> m_panels;
};

View File

@@ -24,8 +24,8 @@ signals:
void errorOccurred(const QString &message);
private:
void stop(); // joins m_captureThread, closes device safe from any thread
void captureLoop(); // runs inside m_captureThread
void stop();
void captureLoop();
bool openDevice(const QString &device, int width, int height);
void closeDevice();
bool initMmap();
@@ -45,5 +45,5 @@ private:
int m_nBuffers = 0;
std::atomic<bool> m_running{false};
std::thread m_captureThread; // dedicated capture thread not the Qt event thread
std::thread m_captureThread;
};

View File

@@ -7,18 +7,9 @@
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QThread>
#include <QTimer>
#include "v4l2worker.h"
// VideoWidget shows a live preview of a V4L2 capture device.
// It lives in the right-side panel, below the Tag Monitor.
// Features:
// - Device selector (auto-enumerates /dev/video*)
// - Start / Stop capture
// - Freeze-frame toggle
// - Screenshot (saves current frame as PNG/JPG)
// - Frame is scaled to fit the widget while keeping aspect ratio
class VideoWidget : public QWidget
{
Q_OBJECT
@@ -28,12 +19,11 @@ public:
~VideoWidget();
public:
// Called by MainWindow::closeEvent to guarantee orderly shutdown before
// widget destruction. Safe to call multiple times.
void shutdown();
void shutdown(); // safe to call multiple times, called from MainWindow::closeEvent
public slots:
void onNewFrame(const QImage &frame);
void clearFrame(); // blanks the preview (used on ANSI clear-screen)
private slots:
void onStartStop();
@@ -51,21 +41,18 @@ private:
void stopCapture();
void updateScaled();
// Worker
QThread *m_thread = nullptr;
V4l2Worker *m_worker = nullptr;
// Current frame
QImage m_frame;
QImage m_scaled;
bool m_frozen = false;
bool m_running = false;
bool m_shutdownDone = false;
// UI
QComboBox *m_deviceCombo = nullptr;
QPushButton *m_startBtn = nullptr;
QPushButton *m_freezeBtn = nullptr;
QPushButton *m_screenshotBtn= nullptr;
QLabel *m_infoLabel = nullptr;
QComboBox *m_deviceCombo = nullptr;
QPushButton *m_startBtn = nullptr;
QPushButton *m_freezeBtn = nullptr;
QPushButton *m_screenshotBtn = nullptr;
QLabel *m_infoLabel = nullptr;
};