Implemented qt port + e2e
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* @file MainWindow.cpp
|
||||
*/
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "PlotGrid.h"
|
||||
#include "SourceSidebar.h"
|
||||
#include "TriggerBar.h"
|
||||
#include "HistoryBar.h"
|
||||
#include "StatsDialog.h"
|
||||
#include "Theme.h"
|
||||
|
||||
#include <QToolBar>
|
||||
#include <QToolButton>
|
||||
#include <QComboBox>
|
||||
#include <QLineEdit>
|
||||
#include <QLabel>
|
||||
#include <QTimer>
|
||||
#include <QDockWidget>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include <QDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QIntValidator>
|
||||
#include <cmath>
|
||||
|
||||
namespace shq {
|
||||
|
||||
MainWindow::MainWindow(const QString& host, uint16_t port, QWidget* parent)
|
||||
: QMainWindow(parent) {
|
||||
setWindowTitle("StreamHub Qt Client");
|
||||
resize(1280, 800);
|
||||
|
||||
/* ── Central plot grid ── */
|
||||
grid_ = new PlotGrid(&hub_, &gv_, this);
|
||||
setCentralWidget(grid_);
|
||||
|
||||
/* ── Source sidebar dock ── */
|
||||
sidebar_ = new SourceSidebar(&hub_, this);
|
||||
sideDock_ = new QDockWidget("Sources", this);
|
||||
sideDock_->setWidget(sidebar_);
|
||||
sideDock_->setFeatures(QDockWidget::DockWidgetMovable |
|
||||
QDockWidget::DockWidgetClosable);
|
||||
addDockWidget(Qt::LeftDockWidgetArea, sideDock_);
|
||||
connect(sidebar_, &SourceSidebar::addSourceRequested,
|
||||
this, &MainWindow::openAddSourceDialog);
|
||||
|
||||
/* ── Trigger bar (hidden until toggled) ── */
|
||||
trigBar_ = new TriggerBar(&hub_, this);
|
||||
auto* trigTb = new QToolBar("Trigger", this);
|
||||
trigTb->setMovable(false);
|
||||
trigTb->addWidget(trigBar_);
|
||||
addToolBarBreak();
|
||||
addToolBar(Qt::TopToolBarArea, trigTb);
|
||||
trigTb->setVisible(false);
|
||||
|
||||
/* ── History bar (hidden until toggled) ── */
|
||||
histBar_ = new HistoryBar(&hub_, grid_, this);
|
||||
auto* histTb = new QToolBar("History", this);
|
||||
histTb->setMovable(false);
|
||||
histTb->addWidget(histBar_);
|
||||
addToolBarBreak();
|
||||
addToolBar(Qt::TopToolBarArea, histTb);
|
||||
histTb->setVisible(false);
|
||||
connect(histBar_, &HistoryBar::liveRequested, this, [this, histTb]() {
|
||||
histTb->setVisible(false);
|
||||
histBtn_->setChecked(false);
|
||||
});
|
||||
|
||||
/* Keep the QToolBar pointers reachable from slots. */
|
||||
trigBtn_ = nullptr; /* set in buildToolbar */
|
||||
buildToolbar();
|
||||
/* Wire trigger/history toggle buttons to their bars. */
|
||||
connect(trigBtn_, &QToolButton::toggled, trigTb, &QToolBar::setVisible);
|
||||
connect(histBtn_, &QToolButton::toggled, this, [this, histTb](bool on) {
|
||||
if (on) {
|
||||
const auto& hi = hub_.historyInfo();
|
||||
if (hi.enabled && !hi.signals.empty()) {
|
||||
histTb->setVisible(true);
|
||||
histBar_->showAll();
|
||||
} else {
|
||||
histBtn_->setChecked(false);
|
||||
}
|
||||
} else {
|
||||
histTb->setVisible(false);
|
||||
grid_->goLive();
|
||||
}
|
||||
});
|
||||
|
||||
/* ── Hub signal wiring ── */
|
||||
connect(&hub_, &Hub::connectedChanged, this, &MainWindow::onConnectedChanged);
|
||||
connect(&hub_, &Hub::sourcesChanged, this, &MainWindow::onSourcesChanged);
|
||||
connect(&hub_, &Hub::configChanged, this, &MainWindow::onConfigChanged);
|
||||
connect(&hub_, &Hub::statsChanged, this, &MainWindow::onStatsChanged);
|
||||
connect(&hub_, &Hub::triggerStateChanged, this, &MainWindow::onTriggerStateChanged);
|
||||
connect(&hub_, &Hub::historyInfoChanged, this, &MainWindow::onHistoryInfoChanged);
|
||||
|
||||
/* ── 60 Hz repaint / refresh ── */
|
||||
timer_ = new QTimer(this);
|
||||
timer_->setInterval(16);
|
||||
connect(timer_, &QTimer::timeout, this, &MainWindow::onTick);
|
||||
timer_->start();
|
||||
|
||||
hostEdit_->setText(host);
|
||||
portEdit_->setText(QString::number(port));
|
||||
hub_.start(host, port);
|
||||
onConnectedChanged(false);
|
||||
}
|
||||
|
||||
/* ── Toolbar construction ────────────────────────────────────────────────── */
|
||||
|
||||
void MainWindow::buildToolbar() {
|
||||
auto* tb = new QToolBar("Main", this);
|
||||
tb->setMovable(false);
|
||||
addToolBar(Qt::TopToolBarArea, tb);
|
||||
|
||||
/* Sidebar toggle. */
|
||||
auto* sideBtn = new QToolButton(this);
|
||||
sideBtn->setText("\u2630"); /* ≡ */
|
||||
sideBtn->setCheckable(true);
|
||||
sideBtn->setChecked(true);
|
||||
sideBtn->setToolTip("Toggle sidebar");
|
||||
connect(sideBtn, &QToolButton::toggled, sideDock_, &QDockWidget::setVisible);
|
||||
connect(sideDock_, &QDockWidget::visibilityChanged, sideBtn, &QToolButton::setChecked);
|
||||
tb->addWidget(sideBtn);
|
||||
|
||||
/* Layout picker. */
|
||||
auto* layoutBtn = new QToolButton(this);
|
||||
layoutBtn->setText("Layout");
|
||||
layoutBtn->setPopupMode(QToolButton::InstantPopup);
|
||||
auto* layMenu = new QMenu(layoutBtn);
|
||||
const char* const* names = layoutNames();
|
||||
for (int i = 0; i < static_cast<int>(PlotLayout::kCount); i++) {
|
||||
QAction* a = layMenu->addAction(QString::fromUtf8(names[i]));
|
||||
PlotLayout l = static_cast<PlotLayout>(i);
|
||||
connect(a, &QAction::triggered, this, [this, l]() {
|
||||
grid_->setLayout(l);
|
||||
});
|
||||
}
|
||||
layoutBtn->setMenu(layMenu);
|
||||
tb->addWidget(layoutBtn);
|
||||
|
||||
/* Pause. */
|
||||
pauseBtn_ = new QToolButton(this);
|
||||
pauseBtn_->setText("Pause");
|
||||
pauseBtn_->setCheckable(true);
|
||||
connect(pauseBtn_, &QToolButton::clicked, this, &MainWindow::togglePause);
|
||||
tb->addWidget(pauseBtn_);
|
||||
|
||||
/* Cursors. */
|
||||
cursorBtn_ = new QToolButton(this);
|
||||
cursorBtn_->setText("Cursors");
|
||||
cursorBtn_->setCheckable(true);
|
||||
cursorBtn_->setToolTip("Cursors A/B");
|
||||
connect(cursorBtn_, &QToolButton::toggled, this, [this](bool on) {
|
||||
gv_.cursorsOn = on;
|
||||
});
|
||||
tb->addWidget(cursorBtn_);
|
||||
|
||||
/* Trigger toggle. */
|
||||
trigBtn_ = new QToolButton(this);
|
||||
trigBtn_->setText("Trigger");
|
||||
trigBtn_->setCheckable(true);
|
||||
connect(trigBtn_, &QToolButton::toggled, this, [this](bool on) {
|
||||
gv_.trigView = on;
|
||||
});
|
||||
tb->addWidget(trigBtn_);
|
||||
|
||||
/* History toggle. */
|
||||
histBtn_ = new QToolButton(this);
|
||||
histBtn_->setText("History");
|
||||
histBtn_->setCheckable(true);
|
||||
histBtn_->setEnabled(false);
|
||||
tb->addWidget(histBtn_);
|
||||
|
||||
/* Window presets. */
|
||||
tb->addWidget(new QLabel(" Win ", this));
|
||||
winCombo_ = new QComboBox(this);
|
||||
winCombo_->setEditable(true);
|
||||
static const double kWin[] = {1, 5, 10, 30, 60};
|
||||
static const char* kWinL[] = {"1 s", "5 s", "10 s", "30 s", "60 s"};
|
||||
for (int i = 0; i < 5; i++) { winCombo_->addItem(kWinL[i], kWin[i]); }
|
||||
winCombo_->setCurrentIndex(2); /* 10 s */
|
||||
connect(winCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
this, [this](int i) {
|
||||
if (i >= 0) { gv_.windowSec = winCombo_->itemData(i).toDouble(); }
|
||||
});
|
||||
connect(winCombo_->lineEdit(), &QLineEdit::editingFinished, this, [this]() {
|
||||
double v = winCombo_->currentText().split(' ').first().toDouble();
|
||||
if (v >= 1e-4 && v <= 3600.0) { gv_.windowSec = v; }
|
||||
});
|
||||
tb->addWidget(winCombo_);
|
||||
|
||||
/* Spacer pushes the rest to the right. */
|
||||
auto* spacer = new QWidget(this);
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
tb->addWidget(spacer);
|
||||
|
||||
/* Stats. */
|
||||
auto* statsBtn = new QToolButton(this);
|
||||
statsBtn->setText("Stats");
|
||||
connect(statsBtn, &QToolButton::clicked, this, [this]() {
|
||||
if (!stats_) { stats_ = new StatsDialog(&hub_, this); }
|
||||
hub_.sendGetStats();
|
||||
stats_->refresh();
|
||||
stats_->show();
|
||||
stats_->raise();
|
||||
});
|
||||
tb->addWidget(statsBtn);
|
||||
|
||||
/* Connection. */
|
||||
tb->addWidget(new QLabel(" Host ", this));
|
||||
hostEdit_ = new QLineEdit(this);
|
||||
hostEdit_->setMaximumWidth(120);
|
||||
tb->addWidget(hostEdit_);
|
||||
portEdit_ = new QLineEdit(this);
|
||||
portEdit_->setMaximumWidth(60);
|
||||
portEdit_->setValidator(new QIntValidator(1, 65535, this));
|
||||
tb->addWidget(portEdit_);
|
||||
auto* connBtn = new QToolButton(this);
|
||||
connBtn->setText("Connect");
|
||||
connect(connBtn, &QToolButton::clicked, this, &MainWindow::doConnect);
|
||||
tb->addWidget(connBtn);
|
||||
|
||||
ledLbl_ = new QLabel(" \u25cf Disconnected ", this);
|
||||
tb->addWidget(ledLbl_);
|
||||
}
|
||||
|
||||
/* ── Slots ───────────────────────────────────────────────────────────────── */
|
||||
|
||||
void MainWindow::togglePause() {
|
||||
gv_.paused = pauseBtn_->isChecked();
|
||||
pauseBtn_->setText(gv_.paused ? "Resume" : "Pause");
|
||||
grid_->onPauseChanged();
|
||||
}
|
||||
|
||||
void MainWindow::toggleHistory() { /* handled inline via histBtn_ toggle */ }
|
||||
|
||||
void MainWindow::doConnect() {
|
||||
QString host = hostEdit_->text().trimmed();
|
||||
uint16_t port = static_cast<uint16_t>(portEdit_->text().toUInt());
|
||||
if (host.isEmpty() || port == 0) { return; }
|
||||
hub_.reconnect(host, port);
|
||||
}
|
||||
|
||||
void MainWindow::onConnectedChanged(bool connected) {
|
||||
ledLbl_->setText(connected ? " \u25cf Connected " : " \u25cf Disconnected ");
|
||||
ledLbl_->setStyleSheet(connected
|
||||
? "color:#a6e3a1; font-weight:bold;"
|
||||
: "color:#f38ba8; font-weight:bold;");
|
||||
}
|
||||
|
||||
void MainWindow::onSourcesChanged() {
|
||||
sidebar_->refresh();
|
||||
trigBar_->refreshSignals();
|
||||
grid_->onModelChanged();
|
||||
}
|
||||
|
||||
void MainWindow::onConfigChanged(const QString&) {
|
||||
sidebar_->refresh();
|
||||
trigBar_->refreshSignals();
|
||||
grid_->onModelChanged();
|
||||
}
|
||||
|
||||
void MainWindow::onStatsChanged() {
|
||||
if (stats_ && stats_->isVisible()) { stats_->refresh(); }
|
||||
}
|
||||
|
||||
void MainWindow::onTriggerStateChanged() {
|
||||
trigBar_->onTriggerStateChanged();
|
||||
}
|
||||
|
||||
void MainWindow::onHistoryInfoChanged() {
|
||||
const auto& hi = hub_.historyInfo();
|
||||
histBtn_->setEnabled(hi.enabled && !hi.signals.empty());
|
||||
}
|
||||
|
||||
void MainWindow::onTick() {
|
||||
grid_->tick();
|
||||
if (histBtn_->isChecked()) { histBar_->updateReadout(); }
|
||||
}
|
||||
|
||||
/* ── Add-source dialog ───────────────────────────────────────────────────── */
|
||||
|
||||
void MainWindow::openAddSourceDialog() {
|
||||
QDialog dlg(this);
|
||||
dlg.setWindowTitle("Add Source");
|
||||
auto* form = new QFormLayout(&dlg);
|
||||
|
||||
auto* label = new QLineEdit(&dlg);
|
||||
auto* host = new QLineEdit("127.0.0.1", &dlg);
|
||||
auto* port = new QLineEdit("44500", &dlg);
|
||||
port->setValidator(new QIntValidator(1, 65535, &dlg));
|
||||
auto* mcast = new QLineEdit(&dlg);
|
||||
auto* dport = new QLineEdit("0", &dlg);
|
||||
dport->setValidator(new QIntValidator(0, 65535, &dlg));
|
||||
|
||||
form->addRow("Label", label);
|
||||
form->addRow("Host", host);
|
||||
form->addRow("Port", port);
|
||||
form->addRow("Multicast", mcast);
|
||||
form->addRow("Data Port", dport);
|
||||
|
||||
auto* box = new QDialogButtonBox(
|
||||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg);
|
||||
form->addRow(box);
|
||||
connect(box, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
|
||||
connect(box, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
|
||||
|
||||
if (dlg.exec() != QDialog::Accepted) { return; }
|
||||
QString h = host->text().trimmed();
|
||||
int p = port->text().toInt();
|
||||
if (h.isEmpty() || p <= 0 || p >= 65536) { return; }
|
||||
std::string addr = h.toStdString() + ":" + std::to_string(p);
|
||||
int dp = dport->text().toInt();
|
||||
hub_.sendAddSource(label->text().toStdString(), addr,
|
||||
mcast->text().toStdString(),
|
||||
static_cast<uint16_t>((dp > 0 && dp < 65536) ? dp : 0));
|
||||
}
|
||||
|
||||
} /* namespace shq */
|
||||
Reference in New Issue
Block a user