336 lines
12 KiB
C++
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());
|
|
}
|