#include "MainWindow.h" #include "VideoWidget.h" #include "CaptureThread.h" #include "AudioThread.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MainWindow::MainWindow(const QString& videoDevice, const QString& audioDevice, int targetFps, bool smoothModeEnabled, bool retroModeEnabled, float brightness, float sharpenAmount, float lastSharpenAmount, QWidget* parent) : QMainWindow(parent) , m_lastSharpenAmount(lastSharpenAmount) , m_videoDevice(videoDevice) , m_audioDevice(audioDevice) { setWindowTitle("HDMI Viewer"); resize(1280, 720); m_video = new VideoWidget(this); m_video->setSmoothModeEnabled(smoothModeEnabled); m_video->setRetroModeEnabled(retroModeEnabled); m_video->setSharpenAmount(sharpenAmount); m_video->setBrightness(brightness); setCentralWidget(m_video); // Kurzzeitige Einblendung unten links (funktioniert auch im Vollbild, // wo Menü- und Statusleiste ausgeblendet sind). m_overlayLabel = new QLabel(m_video); m_overlayLabel->setStyleSheet( "background-color: rgba(20, 20, 20, 200); color: white;" "padding: 6px 12px; border-radius: 4px; font-size: 13px;"); m_overlayLabel->hide(); setupMenuBar(); updateStatusBar(); // ── Capture ────────────────────────────────────────────────────────────── m_capture = new CaptureThread(videoDevice, targetFps, this); connect(m_capture, &CaptureThread::frameReady, m_video, &VideoWidget::setFrame, Qt::QueuedConnection); connect(m_capture, &CaptureThread::errorOccurred, this, &MainWindow::onError, Qt::QueuedConnection); connect(m_capture, &CaptureThread::captureInfo, this, &MainWindow::onCaptureInfo, Qt::QueuedConnection); connect(m_capture, &CaptureThread::frameDumped, this, &MainWindow::onFrameDumped, Qt::QueuedConnection); connect(m_capture, &CaptureThread::hardwareControlChanged, this, &MainWindow::onHardwareControlChanged, Qt::QueuedConnection); m_capture->start(); // ── Audio ───────────────────────────────────────────────────────────────── if (!audioDevice.isEmpty()) { m_audio = new AudioThread(audioDevice, this); connect(m_audio, &AudioThread::errorOccurred, this, &MainWindow::onError, Qt::QueuedConnection); m_audio->start(); } } MainWindow::~MainWindow() { stopAll(); } void MainWindow::setupMenuBar() { QMenu* viewMenu = menuBar()->addMenu("&Anzeige"); m_smoothAction = viewMenu->addAction("Bildglättung\tS"); m_smoothAction->setCheckable(true); m_smoothAction->setChecked(m_video->smoothModeEnabled()); connect(m_smoothAction, &QAction::triggered, this, &MainWindow::toggleSmoothMode); m_retroAction = viewMenu->addAction("Retro-Optik (Scanlines/Maske)\tC"); m_retroAction->setCheckable(true); m_retroAction->setChecked(m_video->retroModeEnabled()); connect(m_retroAction, &QAction::triggered, this, &MainWindow::toggleRetroMode); m_sharpenAction = viewMenu->addAction("Nachschärfen\tX"); m_sharpenAction->setCheckable(true); m_sharpenAction->setChecked(m_video->sharpenAmount() > 0.0f); connect(m_sharpenAction, &QAction::triggered, this, &MainWindow::toggleSharpen); QAction* sharpenUpAct = viewMenu->addAction("Schärfen +\t+"); connect(sharpenUpAct, &QAction::triggered, this, &MainWindow::increaseSharpen); QAction* sharpenDownAct = viewMenu->addAction("Schärfen -\t-"); connect(sharpenDownAct, &QAction::triggered, this, &MainWindow::decreaseSharpen); viewMenu->addSeparator(); QAction* brighterAct = viewMenu->addAction("Heller\t."); connect(brighterAct, &QAction::triggered, this, &MainWindow::increaseBrightness); QAction* darkerAct = viewMenu->addAction("Dunkler\t,"); connect(darkerAct, &QAction::triggered, this, &MainWindow::decreaseBrightness); viewMenu->addSeparator(); QAction* rawAct = viewMenu->addAction("Roh-Modus (Diagnose)\tR"); rawAct->setCheckable(true); rawAct->setChecked(m_video->rawEnabled()); connect(rawAct, &QAction::triggered, this, &MainWindow::toggleRaw); QAction* compareAct = viewMenu->addAction("Live-Vergleich (roh/bearbeitet)\tV"); compareAct->setCheckable(true); compareAct->setChecked(m_video->compareEnabled()); connect(compareAct, &QAction::triggered, this, &MainWindow::toggleCompare); viewMenu->addSeparator(); QAction* screenshotAct = viewMenu->addAction("Screenshot speichern\tP"); connect(screenshotAct, &QAction::triggered, this, &MainWindow::saveScreenshot); QMenu* helpMenu = menuBar()->addMenu("&Hilfe"); QAction* aboutAct = helpMenu->addAction("Über HDMI Viewer"); aboutAct->setMenuRole(QAction::AboutRole); connect(aboutAct, &QAction::triggered, this, &MainWindow::showAbout); } void MainWindow::showOverlayMessage(const QString& text) { if (!m_overlayLabel) return; m_overlayLabel->setText(text); m_overlayLabel->adjustSize(); const int margin = 16; m_overlayLabel->move(margin, m_video->height() - m_overlayLabel->height() - margin); m_overlayLabel->show(); m_overlayLabel->raise(); QTimer::singleShot(1500, m_overlayLabel, &QLabel::hide); } void MainWindow::toggleSmoothMode() { const bool enabled = !m_video->smoothModeEnabled(); m_video->setSmoothModeEnabled(enabled); if (m_smoothAction) m_smoothAction->setChecked(enabled); QSettings settings; settings.setValue("display/smooth", enabled); statusBar()->showMessage( enabled ? "Bildglättung aktiviert" : "Bildglättung deaktiviert", 2000); showOverlayMessage(enabled ? "Bildglättung: an" : "Bildglättung: aus"); } void MainWindow::toggleRetroMode() { const bool enabled = !m_video->retroModeEnabled(); m_video->setRetroModeEnabled(enabled); if (m_retroAction) m_retroAction->setChecked(enabled); QSettings settings; settings.setValue("display/retro", enabled); statusBar()->showMessage( enabled ? "Retro-Optik aktiviert" : "Retro-Optik deaktiviert", 2000); showOverlayMessage(enabled ? "Retro-Optik: an" : "Retro-Optik: aus"); } void MainWindow::toggleSharpen() { // Quick-Toggle: aus (0) ↔ zuletzt benutzter Wert (nicht der feste // Standardwert!) – merkt sich also z. B. 7.0, nicht immer 2.5. if (m_video->sharpenAmount() > 0.0f) { m_lastSharpenAmount = m_video->sharpenAmount(); applySharpen(0.0f); } else { applySharpen(m_lastSharpenAmount > 0.0f ? m_lastSharpenAmount : VideoWidget::kSharpenDefault); } } void MainWindow::increaseSharpen() { applySharpen(m_video->sharpenAmount() + VideoWidget::kSharpenStep); } void MainWindow::decreaseSharpen() { applySharpen(m_video->sharpenAmount() - VideoWidget::kSharpenStep); } void MainWindow::applySharpen(float value) { value = qBound(VideoWidget::kSharpenMin, value, VideoWidget::kSharpenMax); m_video->setSharpenAmount(value); if (m_sharpenAction) m_sharpenAction->setChecked(value > 0.0f); QSettings settings; settings.setValue("display/sharpen", static_cast(value)); if (value > 0.0f) { m_lastSharpenAmount = value; settings.setValue("display/sharpen_last", static_cast(value)); } statusBar()->showMessage(QString("Nachschärfen: %1").arg(value, 0, 'f', 1), 2000); showOverlayMessage(QString("Nachschärfen: %1").arg(value, 0, 'f', 1)); } void MainWindow::increaseBrightness() { applyBrightness(m_video->brightness() + VideoWidget::kBrightnessStep); } void MainWindow::decreaseBrightness() { applyBrightness(m_video->brightness() - VideoWidget::kBrightnessStep); } void MainWindow::toggleRaw() { const bool enabled = !m_video->rawEnabled(); m_video->setRawEnabled(enabled); const QString msg = enabled ? "Roh-Modus: an (alle Effekte umgangen)" : "Roh-Modus: aus"; statusBar()->showMessage(msg, 2000); showOverlayMessage(msg); } void MainWindow::toggleCompare() { const bool enabled = !m_video->compareEnabled(); m_video->setCompareEnabled(enabled); const QString msg = enabled ? "Vergleich: an (links roh, rechts bearbeitet)" : "Vergleich: aus"; statusBar()->showMessage(msg, 2000); showOverlayMessage(msg); } void MainWindow::saveScreenshot() { const QImage img = m_video->grabProcessedFrame(); if (img.isNull()) { statusBar()->showMessage("Screenshot fehlgeschlagen (kein Bild)", 3000); return; } const QString path = QDir::homePath() + "/hdmiviewer_screenshot_" + QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm-ss") + ".png"; if (img.save(path, "PNG")) { statusBar()->showMessage("Screenshot gespeichert: " + path, 5000); showOverlayMessage("Screenshot gespeichert"); } else { statusBar()->showMessage("Screenshot konnte nicht gespeichert werden: " + path, 5000); } } void MainWindow::applyBrightness(float value) { value = qBound(VideoWidget::kBrightnessMin, value, VideoWidget::kBrightnessMax); m_video->setBrightness(value); QSettings settings; settings.setValue("display/brightness", static_cast(value)); statusBar()->showMessage( QString("Helligkeit: %1 %").arg(qRound(value * 100.0f)), 2000); showOverlayMessage(QString("Helligkeit: %1 %").arg(qRound(value * 100.0f))); } void MainWindow::showAbout() { auto* dlg = new QDialog(this); dlg->setWindowTitle("Über HDMI Viewer"); dlg->setFixedSize(460, 470); dlg->setModal(true); auto* root = new QVBoxLayout(dlg); root->setSpacing(0); root->setContentsMargins(0, 0, 0, 0); // ── Header-Bereich ──────────────────────────────────────────────────────── auto* header = new QWidget(dlg); header->setFixedHeight(90); header->setStyleSheet("background-color: #1e1e2e;"); auto* headerLayout = new QVBoxLayout(header); headerLayout->setContentsMargins(24, 16, 24, 16); auto* titleLabel = new QLabel("HDMI Viewer", header); titleLabel->setStyleSheet("font-size: 22px; font-weight: bold; color: #cdd6f4;"); auto* subtitleLabel = new QLabel("Live-Vorschau für HDMI-Grabber", header); subtitleLabel->setStyleSheet("font-size: 11px; color: #6c7086;"); headerLayout->addWidget(titleLabel); headerLayout->addWidget(subtitleLabel); // ── Trennlinie ──────────────────────────────────────────────────────────── auto* sep = new QFrame(dlg); sep->setFrameShape(QFrame::HLine); sep->setStyleSheet("color: #313244;"); // ── Info-Bereich ────────────────────────────────────────────────────────── auto* body = new QWidget(dlg); auto* grid = new QGridLayout(body); grid->setContentsMargins(24, 20, 24, 8); grid->setVerticalSpacing(10); grid->setHorizontalSpacing(16); grid->setColumnMinimumWidth(0, 100); auto addRow = [&](int row, const QString& label, const QString& value) { auto* l = new QLabel(label, body); l->setAlignment(Qt::AlignRight | Qt::AlignVCenter); l->setStyleSheet("font-weight: bold; color: #a6adc8;"); auto* v = new QLabel(value, body); v->setStyleSheet("color: #cdd6f4;"); v->setTextInteractionFlags(Qt::TextSelectableByMouse); grid->addWidget(l, row, 0); grid->addWidget(v, row, 1); }; addRow(0, "Version", "1.4.0"); addRow(1, "Entwickler", "Dany Thinnes"); addRow(2, "Projekt", "Projekt Hirnfrei"); addRow(3, "Framework", QString("Qt %1").arg(QT_VERSION_STR)); addRow(4, "Sprache", "C++17"); addRow(5, "Video-API", "V4L2 · MJPEG / YUYV / NV12"); addRow(6, "Audio-API", "ALSA"); addRow(7, "Rendering", "OpenGL 3.3 · Bildglättung / Retro-Optik"); // ── Tastenkürzel-Hinweis ────────────────────────────────────────────────── auto* sep2 = new QFrame(body); sep2->setFrameShape(QFrame::HLine); sep2->setStyleSheet("color: #313244;"); grid->addWidget(sep2, 8, 0, 1, 2); auto* keysLabel = new QLabel( "F Vollbild    " "S Glättung    " "C Retro    " "X/+/- Schärfen    " ",/. Helligkeit
" "1/2 HW-Kontrast    " "3/4 HW-Sättigung    " "R Roh-Diagnose
" "V Vergleich    " "P Screenshot    " "D Frame dumpen    " "Esc Zurück
", body); keysLabel->setStyleSheet("color: #6c7086;"); keysLabel->setAlignment(Qt::AlignCenter); grid->addWidget(keysLabel, 9, 0, 1, 2); // ── Schließen-Button ────────────────────────────────────────────────────── auto* btnRow = new QHBoxLayout; btnRow->setContentsMargins(24, 8, 24, 20); auto* closeBtn = new QPushButton("Schließen", dlg); closeBtn->setDefault(true); closeBtn->setFixedWidth(100); btnRow->addStretch(); btnRow->addWidget(closeBtn); connect(closeBtn, &QPushButton::clicked, dlg, &QDialog::accept); root->addWidget(header); root->addWidget(sep); root->addWidget(body, 1); root->addLayout(btnRow); dlg->exec(); dlg->deleteLater(); } void MainWindow::stopAll() { if (m_capture) { m_capture->stopAndWait(); delete m_capture; m_capture = nullptr; } if (m_audio) { m_audio->stop(); m_audio->wait(); delete m_audio; m_audio = nullptr; } } void MainWindow::keyPressEvent(QKeyEvent* event) { switch (event->key()) { case Qt::Key_F: toggleFullscreen(); break; case Qt::Key_Escape: if (m_fullscreen) { toggleFullscreen(); } else { m_wantsReopen = true; close(); } break; case Qt::Key_S: toggleSmoothMode(); break; case Qt::Key_C: toggleRetroMode(); break; case Qt::Key_X: toggleSharpen(); break; case Qt::Key_Plus: case Qt::Key_Equal: // '+' liegt auf vielen Layouts ohne Shift auf dieser Taste increaseSharpen(); break; case Qt::Key_Minus: decreaseSharpen(); break; case Qt::Key_Period: increaseBrightness(); break; case Qt::Key_Comma: decreaseBrightness(); break; case Qt::Key_R: toggleRaw(); break; case Qt::Key_D: if (m_capture) m_capture->requestFrameDump(); break; case Qt::Key_V: toggleCompare(); break; case Qt::Key_P: saveScreenshot(); break; case Qt::Key_1: if (m_capture) m_capture->adjustHardwareContrast(-5); break; case Qt::Key_2: if (m_capture) m_capture->adjustHardwareContrast(5); break; case Qt::Key_3: if (m_capture) m_capture->adjustHardwareSaturation(-5); break; case Qt::Key_4: if (m_capture) m_capture->adjustHardwareSaturation(5); break; default: QMainWindow::keyPressEvent(event); } } void MainWindow::closeEvent(QCloseEvent* event) { stopAll(); event->accept(); } void MainWindow::toggleFullscreen() { if (m_fullscreen) { showNormal(); menuBar()->show(); statusBar()->show(); } else { showFullScreen(); menuBar()->hide(); statusBar()->hide(); } m_fullscreen = !m_fullscreen; } void MainWindow::updateStatusBar() { QString info; if (m_capWidth > 0) info = QString("%1×%2 @ %3fps %4 ") .arg(m_capWidth).arg(m_capHeight).arg(m_capFps).arg(m_capFmt); info += QString("Video: %1 Audio: %2 [F] Vollbild [S] Glättung [C] Retro [X/+/-] Schärfen [,/.] Helligkeit [1/2] HW-Kontrast [3/4] HW-Sättigung [R] Roh [V] Vergleich [P] Screenshot [D] Frame dumpen [Esc] Einstellungen") .arg(m_videoDevice, m_audioDevice.isEmpty() ? QStringLiteral("–") : m_audioDevice); statusBar()->showMessage(info); } void MainWindow::onCaptureInfo(int width, int height, int fps, const QString& format) { m_capWidth = width; m_capHeight = height; m_capFps = fps; m_capFmt = format; updateStatusBar(); setWindowTitle(QString("HDMI Viewer – %1×%2 @ %3fps %4") .arg(width).arg(height).arg(fps).arg(format)); } void MainWindow::onFrameDumped(const QString& path) { const QString msg = "Frame gespeichert: " + path; statusBar()->showMessage(msg, 5000); showOverlayMessage("Frame gespeichert (siehe Home-Ordner)"); } void MainWindow::onHardwareControlChanged(const QString& label, int value, int minVal, int maxVal) { const QString msg = QString("%1: %2 (%3..%4)").arg(label).arg(value).arg(minVal).arg(maxVal); statusBar()->showMessage(msg, 2000); showOverlayMessage(msg); } void MainWindow::onError(const QString& msg) { statusBar()->showMessage("⚠ " + msg, 8000); qWarning() << "[Error]" << msg; }