- 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

@@ -8,7 +8,6 @@
#include <QMetaObject>
#include <QResizeEvent>
// Enumerate /dev/video* entries
static QStringList enumerateVideoDevices()
{
QStringList devices;
@@ -19,8 +18,6 @@ static QStringList enumerateVideoDevices()
return devices;
}
// ── ctor / dtor ───────────────────────────────────────────────────────────────
VideoWidget::VideoWidget(QWidget *parent)
: QWidget(parent)
{
@@ -32,20 +29,20 @@ VideoWidget::VideoWidget(QWidget *parent)
connect(m_thread, &QThread::finished, m_worker, &QObject::deleteLater);
connect(m_worker, &V4l2Worker::newFrame, this, &VideoWidget::onNewFrame,
connect(m_worker, &V4l2Worker::newFrame, this, &VideoWidget::onNewFrame,
Qt::QueuedConnection);
connect(m_worker, &V4l2Worker::captureStarted, this, [this](const QString &dev, int w, int h, const QString &fmt) {
connect(m_worker, &V4l2Worker::captureStarted, this, [this](const QString &dev, int w, int h, const QString &fmt) {
m_infoLabel->setText(tr("%1 %2×%3 %4").arg(dev).arg(w).arg(h).arg(fmt));
m_running = true;
m_startBtn->setText(tr("■ Stop"));
});
connect(m_worker, &V4l2Worker::captureStopped, this, [this] {
connect(m_worker, &V4l2Worker::captureStopped, this, [this] {
m_running = false;
m_startBtn->setText(tr("▶ Start"));
m_infoLabel->setText(tr("Stopped"));
update();
});
connect(m_worker, &V4l2Worker::errorOccurred, this, [this](const QString &msg) {
connect(m_worker, &V4l2Worker::errorOccurred, this, [this](const QString &msg) {
m_infoLabel->setText(tr("Error: %1").arg(msg));
m_running = false;
m_startBtn->setText(tr("▶ Start"));
@@ -60,8 +57,6 @@ void VideoWidget::shutdown()
return;
m_shutdownDone = true;
// stopCapture() sets m_running=false and joins the std::thread internally.
// Must happen before we quit the Qt event thread.
m_worker->stopCapture();
if (m_thread && m_thread->isRunning()) {
@@ -77,8 +72,6 @@ VideoWidget::~VideoWidget()
shutdown();
}
// ── UI setup ─────────────────────────────────────────────────────────────────
void VideoWidget::setupUi()
{
setMinimumHeight(140);
@@ -87,7 +80,6 @@ void VideoWidget::setupUi()
layout->setContentsMargins(2, 2, 2, 2);
layout->setSpacing(3);
// ── Controls row ──────────────────────────────────────────────────────────
auto *ctrlRow = new QHBoxLayout();
ctrlRow->setSpacing(4);
@@ -97,7 +89,6 @@ void VideoWidget::setupUi()
auto *refreshBtn = new QPushButton("↻", this);
refreshBtn->setMaximumWidth(26);
refreshBtn->setToolTip(tr("Refresh device list"));
connect(refreshBtn, &QPushButton::clicked, this, &VideoWidget::refreshDeviceList);
m_startBtn = new QPushButton(tr("▶ Start"), this);
@@ -111,7 +102,6 @@ void VideoWidget::setupUi()
m_screenshotBtn = new QPushButton(tr("📷"), this);
m_screenshotBtn->setMaximumWidth(32);
m_screenshotBtn->setToolTip(tr("Save screenshot (PNG/JPG)"));
connect(m_screenshotBtn, &QPushButton::clicked, this, &VideoWidget::onScreenshot);
ctrlRow->addWidget(m_deviceCombo, 1);
@@ -121,24 +111,17 @@ void VideoWidget::setupUi()
ctrlRow->addWidget(m_screenshotBtn);
layout->addLayout(ctrlRow);
// ── Info label ────────────────────────────────────────────────────────────
m_infoLabel = new QLabel(tr("No device started"), this);
m_infoLabel->setStyleSheet("color: gray; font-size: 9px;");
layout->addWidget(m_infoLabel);
// The video frame is drawn directly in paintEvent – no QLabel needed.
// We leave stretch space for it.
layout->addStretch(1);
}
// ── Slots ─────────────────────────────────────────────────────────────────────
void VideoWidget::onStartStop()
{
if (m_running)
stopCapture();
else
startCapture();
if (m_running) stopCapture();
else startCapture();
}
void VideoWidget::onFreeze(bool frozen)
@@ -172,7 +155,14 @@ void VideoWidget::onNewFrame(const QImage &frame)
return;
m_frame = frame;
updateScaled();
update(); // trigger paintEvent
update();
}
void VideoWidget::clearFrame()
{
m_frame = QImage();
m_scaled = QImage();
update();
}
void VideoWidget::refreshDeviceList()
@@ -182,14 +172,11 @@ void VideoWidget::refreshDeviceList()
const auto devices = enumerateVideoDevices();
for (const auto &d : devices)
m_deviceCombo->addItem(d);
// Restore previous selection if still present
const int idx = m_deviceCombo->findText(current);
if (idx >= 0)
m_deviceCombo->setCurrentIndex(idx);
}
// ── Capture start/stop ────────────────────────────────────────────────────────
void VideoWidget::startCapture()
{
const QString dev = m_deviceCombo->currentText();
@@ -197,7 +184,6 @@ void VideoWidget::startCapture()
return;
m_frame = QImage();
m_scaled = QImage();
// startCapture blocks in the worker thread until stopCapture() is called
QMetaObject::invokeMethod(m_worker, "startCapture",
Qt::QueuedConnection,
Q_ARG(QString, dev),
@@ -210,11 +196,8 @@ void VideoWidget::stopCapture()
QMetaObject::invokeMethod(m_worker, "stopCapture", Qt::QueuedConnection);
}
// ── Paint ─────────────────────────────────────────────────────────────────────
void VideoWidget::updateScaled()
{
// Video area = widget height minus the two control rows (~55 px)
const QRect videoRect(0, 55, width(), height() - 55);
if (videoRect.width() < 10 || videoRect.height() < 10 || m_frame.isNull())
return;
@@ -226,8 +209,6 @@ void VideoWidget::updateScaled()
void VideoWidget::paintEvent(QPaintEvent *)
{
QPainter p(this);
// Background
p.fillRect(rect(), QColor(0x10, 0x10, 0x10));
if (m_scaled.isNull()) {
@@ -237,14 +218,12 @@ void VideoWidget::paintEvent(QPaintEvent *)
return;
}
// Centre the scaled frame in the video area
const QRect videoRect(0, 55, width(), height() - 55);
const QPoint origin(
videoRect.left() + (videoRect.width() - m_scaled.width()) / 2,
videoRect.top() + (videoRect.height() - m_scaled.height()) / 2);
p.drawImage(origin, m_scaled);
// Frozen overlay
if (m_frozen) {
p.setPen(QColor(0xff, 0xcc, 0x00));
p.setFont(QFont("Sans", 9, QFont::Bold));