Files
MARTe-Integrated-Components/Client/streamhub-qt/MainWindow.cpp
2026-06-26 09:11:10 +02:00

323 lines
11 KiB
C++

/**
* @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 */