GrabBag/Tools/VrVirtualCamera/Src/VrVirtualCameraWidget.cpp
2026-08-04 14:23:07 +08:00

336 lines
12 KiB
C++

#include "VrVirtualCameraWidget.h"
#include "VrCameraSimulator.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QFormLayout>
#include <QSplitter>
#include <QScrollBar>
#include <QDateTime>
#include <QFileDialog>
#include <QPixmap>
VrVirtualCameraWidget::VrVirtualCameraWidget(QWidget* parent)
: QWidget(parent)
{
m_simulator = new VrCameraSimulator(this);
InitUI();
// Connect simulator signals
connect(m_simulator, &VrCameraSimulator::StatusChanged,
this, &VrVirtualCameraWidget::OnSimulatorStatus);
connect(m_simulator, &VrCameraSimulator::ClientConnected,
this, &VrVirtualCameraWidget::OnSimulatorClientConnected);
connect(m_simulator, &VrCameraSimulator::ClientDisconnected,
this, &VrVirtualCameraWidget::OnSimulatorClientDisconnected);
connect(m_simulator, &VrCameraSimulator::ImageGenerated,
this, &VrVirtualCameraWidget::OnSimulatorImage);
connect(m_simulator, &VrCameraSimulator::ImageDirectoryChanged,
this, &VrVirtualCameraWidget::OnImageDirectoryChanged);
connect(m_simulator, &VrCameraSimulator::LaserDataDirectoryChanged,
this, &VrVirtualCameraWidget::OnLaserDirectoryChanged);
connect(m_simulator, &VrCameraSimulator::LogMessage,
this, &VrVirtualCameraWidget::OnSimulatorLog);
resize(1100, 750);
setWindowTitle("VrVirtualCamera - Virtual Network Camera Simulator");
// Auto-start the virtual camera service when the app opens.
QTimer::singleShot(0, this, [this]() {
if (!m_running) OnStartStop();
});
}
VrVirtualCameraWidget::~VrVirtualCameraWidget()
{
if (m_simulator) {
m_simulator->Stop();
}
}
void VrVirtualCameraWidget::InitUI()
{
auto* mainLayout = new QVBoxLayout(this);
// ===== Top: Control Panel =====
auto* ctrlGroup = new QGroupBox("Control");
auto* ctrlLayout = new QHBoxLayout(ctrlGroup);
m_startStopBtn = new QPushButton("Start");
m_startStopBtn->setMinimumWidth(100);
m_startStopBtn->setStyleSheet(
"QPushButton { background-color: #4CAF50; color: white; font-weight: bold; "
"padding: 8px 16px; border-radius: 4px; }"
"QPushButton:hover { background-color: #45a049; }");
ctrlLayout->addWidget(m_startStopBtn);
connect(m_startStopBtn, &QPushButton::clicked, this, &VrVirtualCameraWidget::OnStartStop);
ctrlLayout->addSpacing(20);
m_statusLabel = new QLabel("Stopped");
m_statusLabel->setStyleSheet("QLabel { font-weight: bold; color: #666; }");
ctrlLayout->addWidget(new QLabel("Status:"));
ctrlLayout->addWidget(m_statusLabel);
ctrlLayout->addSpacing(20);
m_clientLabel = new QLabel("No client");
m_clientLabel->setStyleSheet("QLabel { color: #999; }");
ctrlLayout->addWidget(new QLabel("Client:"));
ctrlLayout->addWidget(m_clientLabel);
ctrlLayout->addSpacing(20);
m_ipLineEdit = new QLineEdit("192.168.1.200");
m_ipLineEdit->setReadOnly(true);
m_ipLineEdit->setMaximumWidth(120);
ctrlLayout->addWidget(new QLabel("Device IP:"));
ctrlLayout->addWidget(m_ipLineEdit);
ctrlLayout->addStretch();
mainLayout->addWidget(ctrlGroup);
// ===== Configuration =====
auto* cfgGroup = new QGroupBox("Configuration");
auto* cfgLayout = new QHBoxLayout(cfgGroup);
auto* resoLayout = new QFormLayout();
m_widthSpinBox = new QSpinBox();
m_widthSpinBox->setRange(320, 4096);
m_widthSpinBox->setValue(1280);
m_widthSpinBox->setSingleStep(64);
resoLayout->addRow("Width:", m_widthSpinBox);
connect(m_widthSpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
this, &VrVirtualCameraWidget::OnWidthChanged);
m_heightSpinBox = new QSpinBox();
m_heightSpinBox->setRange(240, 4096);
m_heightSpinBox->setValue(960);
m_heightSpinBox->setSingleStep(64);
resoLayout->addRow("Height:", m_heightSpinBox);
connect(m_heightSpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
this, &VrVirtualCameraWidget::OnHeightChanged);
cfgLayout->addLayout(resoLayout);
cfgLayout->addSpacing(30);
auto* fpsLayout = new QFormLayout();
m_fpsSpinBox = new QSpinBox();
m_fpsSpinBox->setRange(1, 120);
m_fpsSpinBox->setValue(30);
fpsLayout->addRow("Frame Rate:", m_fpsSpinBox);
connect(m_fpsSpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
this, &VrVirtualCameraWidget::OnFrameRateChanged);
cfgLayout->addLayout(fpsLayout);
cfgLayout->addStretch();
mainLayout->addWidget(cfgGroup);
// ===== Data Source Panel =====
auto* dataGroup = new QGroupBox("Data Sources");
auto* dataLayout = new QVBoxLayout(dataGroup);
// Image directory row
auto* imgRow = new QHBoxLayout();
imgRow->addWidget(new QLabel("Images:"));
m_imageDirEdit = new QLineEdit();
m_imageDirEdit->setPlaceholderText("Select directory with *-L.png / *-R.png pairs...");
m_imageDirEdit->setReadOnly(true);
imgRow->addWidget(m_imageDirEdit, 1);
auto* imgBrowseBtn = new QPushButton("Browse...");
connect(imgBrowseBtn, &QPushButton::clicked, this, &VrVirtualCameraWidget::OnBrowseImageDir);
imgRow->addWidget(imgBrowseBtn);
m_imageCountLabel = new QLabel("0 pairs");
m_imageCountLabel->setStyleSheet("QLabel { color: #888; }");
m_imageCountLabel->setMinimumWidth(80);
imgRow->addWidget(m_imageCountLabel);
dataLayout->addLayout(imgRow);
// Laser data directory row
auto* laserRow = new QHBoxLayout();
laserRow->addWidget(new QLabel("Laser:"));
m_laserDirEdit = new QLineEdit();
m_laserDirEdit->setPlaceholderText("Select directory with .txt / .dat laser data files...");
m_laserDirEdit->setReadOnly(true);
laserRow->addWidget(m_laserDirEdit, 1);
auto* laserBrowseBtn = new QPushButton("Browse...");
connect(laserBrowseBtn, &QPushButton::clicked, this, &VrVirtualCameraWidget::OnBrowseLaserDir);
laserRow->addWidget(laserBrowseBtn);
m_laserCountLabel = new QLabel("0 files / 0 lines");
m_laserCountLabel->setStyleSheet("QLabel { color: #888; }");
m_laserCountLabel->setMinimumWidth(120);
laserRow->addWidget(m_laserCountLabel);
dataLayout->addLayout(laserRow);
mainLayout->addWidget(dataGroup);
// ===== Image Preview + Log =====
auto* splitter = new QSplitter(Qt::Horizontal);
auto* imageGroup = new QGroupBox("Image Preview (Left / Right)");
auto* imageLayout = new QHBoxLayout(imageGroup);
m_leftImageLabel = new QLabel("Left");
m_leftImageLabel->setAlignment(Qt::AlignCenter);
m_leftImageLabel->setMinimumSize(320, 240);
m_leftImageLabel->setStyleSheet("QLabel { border: 1px solid #ccc; background: #222; color: #888; }");
imageLayout->addWidget(m_leftImageLabel);
m_rightImageLabel = new QLabel("Right");
m_rightImageLabel->setAlignment(Qt::AlignCenter);
m_rightImageLabel->setMinimumSize(320, 240);
m_rightImageLabel->setStyleSheet("QLabel { border: 1px solid #ccc; background: #222; color: #888; }");
imageLayout->addWidget(m_rightImageLabel);
splitter->addWidget(imageGroup);
auto* logGroup = new QGroupBox("Protocol Log");
auto* logLayout = new QVBoxLayout(logGroup);
m_logEdit = new QTextEdit();
m_logEdit->setReadOnly(true);
m_logEdit->setStyleSheet("QTextEdit { background: #1e1e1e; color: #d4d4d4; "
"font-family: Consolas, monospace; font-size: 12px; }");
logLayout->addWidget(m_logEdit);
splitter->addWidget(logGroup);
splitter->setStretchFactor(0, 3);
splitter->setStretchFactor(1, 2);
mainLayout->addWidget(splitter, 1);
}
// ===== Slots =====
void VrVirtualCameraWidget::ApplyConfig()
{
if (!m_simulator) return;
m_simulator->SetImageWidth(m_widthSpinBox->value());
m_simulator->SetImageHeight(m_heightSpinBox->value());
m_simulator->SetFrameRate(m_fpsSpinBox->value());
}
void VrVirtualCameraWidget::OnStartStop()
{
if (m_running) {
m_simulator->Stop();
m_running = false;
m_startStopBtn->setText("Start");
m_startStopBtn->setStyleSheet(
"QPushButton { background-color: #4CAF50; color: white; font-weight: bold; "
"padding: 8px 16px; border-radius: 4px; }"
"QPushButton:hover { background-color: #45a049; }");
} else {
ApplyConfig();
if (m_simulator->Start()) {
m_running = true;
m_startStopBtn->setText("Stop");
m_startStopBtn->setStyleSheet(
"QPushButton { background-color: #f44336; color: white; font-weight: bold; "
"padding: 8px 16px; border-radius: 4px; }"
"QPushButton:hover { background-color: #d32f2f; }");
}
}
}
void VrVirtualCameraWidget::OnBrowseImageDir()
{
QString dir = QFileDialog::getExistingDirectory(this, "Select Image Directory",
m_imageDirEdit->text());
if (!dir.isEmpty()) {
m_imageDirEdit->setText(dir);
if (m_simulator) m_simulator->SetImageDirectory(dir);
}
}
void VrVirtualCameraWidget::OnBrowseLaserDir()
{
QString dir = QFileDialog::getExistingDirectory(this, "Select Laser Data Directory",
m_laserDirEdit->text());
if (!dir.isEmpty()) {
m_laserDirEdit->setText(dir);
if (m_simulator) m_simulator->SetLaserDataDirectory(dir);
}
}
void VrVirtualCameraWidget::OnFrameRateChanged(int fps)
{
if (m_simulator) m_simulator->SetFrameRate(fps);
}
void VrVirtualCameraWidget::OnWidthChanged(int w)
{
if (m_simulator) m_simulator->SetImageWidth(w);
}
void VrVirtualCameraWidget::OnHeightChanged(int h)
{
if (m_simulator) m_simulator->SetImageHeight(h);
}
void VrVirtualCameraWidget::OnSimulatorImage(const QImage& left, const QImage& right)
{
if (!left.isNull()) {
QPixmap pm = QPixmap::fromImage(left.scaled(
m_leftImageLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
m_leftImageLabel->setPixmap(pm);
}
if (!right.isNull()) {
QPixmap pm = QPixmap::fromImage(right.scaled(
m_rightImageLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
m_rightImageLabel->setPixmap(pm);
}
}
void VrVirtualCameraWidget::OnImageDirectoryChanged(int count)
{
m_imageCountLabel->setText(QString("%1 pairs").arg(count));
m_imageCountLabel->setStyleSheet(
count > 0 ? "QLabel { color: #4CAF50; font-weight: bold; }"
: "QLabel { color: #888; }");
}
void VrVirtualCameraWidget::OnLaserDirectoryChanged(int fileCount, int totalLines)
{
m_laserCountLabel->setText(QString("%1 files / %2 lines").arg(fileCount).arg(totalLines));
m_laserCountLabel->setStyleSheet(
fileCount > 0 ? "QLabel { color: #4CAF50; font-weight: bold; }"
: "QLabel { color: #888; }");
}
void VrVirtualCameraWidget::OnSimulatorLog(const QString& msg, bool isWarning)
{
AppendLog(msg, isWarning);
}
void VrVirtualCameraWidget::OnSimulatorStatus(const QString& status)
{
m_statusLabel->setText(status);
}
void VrVirtualCameraWidget::OnSimulatorClientConnected(const QString& ip)
{
m_clientLabel->setText("Connected: " + ip);
m_clientLabel->setStyleSheet("QLabel { color: #4CAF50; font-weight: bold; }");
}
void VrVirtualCameraWidget::OnSimulatorClientDisconnected()
{
m_clientLabel->setText("No client");
m_clientLabel->setStyleSheet("QLabel { color: #999; }");
}
void VrVirtualCameraWidget::AppendLog(const QString& msg, bool isWarning)
{
QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss.zzz");
QString color = isWarning ? "#ff6b6b" : "#d4d4d4";
m_logEdit->append(QString("<span style='color:#888'>[%1]</span> "
"<span style='color:%2'>%3</span>")
.arg(timestamp, color, msg.toHtmlEscaped()));
QScrollBar* sb = m_logEdit->verticalScrollBar();
sb->setValue(sb->maximum());
}