VisualizationZoneWidget.cpp
302 lines
| 11.9 KiB
| text/x-c
|
CppLexer
r93 | #include "Visualization/VisualizationZoneWidget.h" | |||
r410 | ||||
Alexandre Leroux
|
r192 | #include "Visualization/IVisualizationWidgetVisitor.h" | ||
Alexandre Leroux
|
r671 | #include "Visualization/QCustomPlotSynchronizer.h" | ||
r410 | #include "Visualization/VisualizationGraphWidget.h" | |||
r58 | #include "ui_VisualizationZoneWidget.h" | |||
r510 | #include <Data/SqpRange.h> | |||
r518 | #include <Variable/Variable.h> | |||
r510 | #include <Variable/VariableController.h> | |||
r111 | ||||
r510 | #include <QUuid> | |||
Alexandre Leroux
|
r245 | #include <SqpApplication.h> | ||
r580 | #include <cmath> | |||
Alexandre Leroux
|
r245 | |||
Alexandre Leroux
|
r204 | Q_LOGGING_CATEGORY(LOG_VisualizationZoneWidget, "VisualizationZoneWidget") | ||
Alexandre Leroux
|
r186 | namespace { | ||
Alexandre Leroux
|
r284 | /// Minimum height for graph added in zones (in pixels) | ||
const auto GRAPH_MINIMUM_HEIGHT = 300; | ||||
Alexandre Leroux
|
r186 | /// Generates a default name for a new graph, according to the number of graphs already displayed in | ||
/// the zone | ||||
QString defaultGraphName(const QLayout &layout) | ||||
{ | ||||
auto count = 0; | ||||
for (auto i = 0; i < layout.count(); ++i) { | ||||
if (dynamic_cast<VisualizationGraphWidget *>(layout.itemAt(i)->widget())) { | ||||
count++; | ||||
} | ||||
} | ||||
return QObject::tr("Graph %1").arg(count + 1); | ||||
} | ||||
Alexandre Leroux
|
r677 | /** | ||
* Applies a function to all graphs of the zone represented by its layout | ||||
* @param layout the layout that contains graphs | ||||
* @param fun the function to apply to each graph | ||||
*/ | ||||
template <typename Fun> | ||||
void processGraphs(QLayout &layout, Fun fun) | ||||
{ | ||||
for (auto i = 0; i < layout.count(); ++i) { | ||||
if (auto item = layout.itemAt(i)) { | ||||
if (auto visualizationGraphWidget | ||||
= dynamic_cast<VisualizationGraphWidget *>(item->widget())) { | ||||
fun(*visualizationGraphWidget); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
Alexandre Leroux
|
r186 | } // namespace | ||
r510 | struct VisualizationZoneWidget::VisualizationZoneWidgetPrivate { | |||
Alexandre Leroux
|
r671 | explicit VisualizationZoneWidgetPrivate() | ||
: m_SynchronisationGroupId{QUuid::createUuid()}, | ||||
m_Synchronizer{std::make_unique<QCustomPlotSynchronizer>()} | ||||
{ | ||||
} | ||||
r510 | QUuid m_SynchronisationGroupId; | |||
Alexandre Leroux
|
r671 | std::unique_ptr<IGraphSynchronizer> m_Synchronizer; | ||
r510 | }; | |||
Alexandre Leroux
|
r183 | VisualizationZoneWidget::VisualizationZoneWidget(const QString &name, QWidget *parent) | ||
r510 | : QWidget{parent}, | |||
ui{new Ui::VisualizationZoneWidget}, | ||||
impl{spimpl::make_unique_impl<VisualizationZoneWidgetPrivate>()} | ||||
r58 | { | |||
ui->setupUi(this); | ||||
Alexandre Leroux
|
r183 | |||
ui->zoneNameLabel->setText(name); | ||||
Alexandre Leroux
|
r245 | |||
// 'Close' options : widget is deleted when closed | ||||
setAttribute(Qt::WA_DeleteOnClose); | ||||
connect(ui->closeButton, &QToolButton::clicked, this, &VisualizationZoneWidget::close); | ||||
ui->closeButton->setIcon(sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton)); | ||||
r510 | ||||
// Synchronisation id | ||||
QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronizationGroupId", | ||||
Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId)); | ||||
r58 | } | |||
VisualizationZoneWidget::~VisualizationZoneWidget() | ||||
{ | ||||
delete ui; | ||||
} | ||||
r111 | ||||
void VisualizationZoneWidget::addGraph(VisualizationGraphWidget *graphWidget) | ||||
{ | ||||
Alexandre Leroux
|
r671 | // Synchronize new graph with others in the zone | ||
impl->m_Synchronizer->addGraph(*graphWidget); | ||||
r111 | ui->visualizationZoneFrame->layout()->addWidget(graphWidget); | |||
} | ||||
Alexandre Leroux
|
r186 | VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr<Variable> variable) | ||
r111 | { | |||
Alexandre Leroux
|
r186 | auto graphWidget = new VisualizationGraphWidget{ | ||
defaultGraphName(*ui->visualizationZoneFrame->layout()), this}; | ||||
Alexandre Leroux
|
r284 | |||
r410 | ||||
Alexandre Leroux
|
r284 | // Set graph properties | ||
graphWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); | ||||
graphWidget->setMinimumHeight(GRAPH_MINIMUM_HEIGHT); | ||||
Alexandre Leroux
|
r186 | |||
r410 | // Lambda to synchronize zone widget | |||
r516 | auto synchronizeZoneWidget = [this, graphWidget](const SqpRange &graphRange, | |||
r510 | const SqpRange &oldGraphRange) { | |||
r516 | auto zoomType = VariableController::getZoomType(graphRange, oldGraphRange); | |||
r410 | auto frameLayout = ui->visualizationZoneFrame->layout(); | |||
for (auto i = 0; i < frameLayout->count(); ++i) { | ||||
auto graphChild | ||||
= dynamic_cast<VisualizationGraphWidget *>(frameLayout->itemAt(i)->widget()); | ||||
if (graphChild && (graphChild != graphWidget)) { | ||||
auto graphChildRange = graphChild->graphRange(); | ||||
switch (zoomType) { | ||||
r510 | case AcquisitionZoomType::ZoomIn: { | |||
r516 | auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart; | |||
auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd; | ||||
r410 | graphChildRange.m_TStart += deltaLeft; | |||
graphChildRange.m_TEnd -= deltaRight; | ||||
r586 | qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomIn"); | |||
qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft") | ||||
<< deltaLeft; | ||||
qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight") | ||||
<< deltaRight; | ||||
qCDebug(LOG_VisualizationZoneWidget()) | ||||
r516 | << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart; | |||
r411 | ||||
r410 | break; | |||
} | ||||
r510 | case AcquisitionZoomType::ZoomOut: { | |||
r586 | qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomOut"); | |||
r516 | auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart; | |||
auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd; | ||||
r586 | qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft") | |||
<< deltaLeft; | ||||
qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight") | ||||
<< deltaRight; | ||||
qCDebug(LOG_VisualizationZoneWidget()) | ||||
r516 | << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart; | |||
r410 | graphChildRange.m_TStart -= deltaLeft; | |||
graphChildRange.m_TEnd += deltaRight; | ||||
break; | ||||
} | ||||
r510 | case AcquisitionZoomType::PanRight: { | |||
r586 | qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: PanRight"); | |||
r516 | auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd; | |||
r410 | graphChildRange.m_TStart += deltaRight; | |||
graphChildRange.m_TEnd += deltaRight; | ||||
r586 | qCDebug(LOG_VisualizationZoneWidget()) | |||
r516 | << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart; | |||
r410 | break; | |||
} | ||||
r510 | case AcquisitionZoomType::PanLeft: { | |||
r586 | qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: PanLeft"); | |||
r516 | auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart; | |||
r410 | graphChildRange.m_TStart -= deltaLeft; | |||
graphChildRange.m_TEnd -= deltaLeft; | ||||
break; | ||||
} | ||||
r510 | case AcquisitionZoomType::Unknown: { | |||
r586 | qCDebug(LOG_VisualizationZoneWidget()) | |||
r410 | << tr("Impossible to synchronize: zoom type unknown"); | |||
break; | ||||
} | ||||
default: | ||||
qCCritical(LOG_VisualizationZoneWidget()) | ||||
<< tr("Impossible to synchronize: zoom type not take into account"); | ||||
// No action | ||||
break; | ||||
} | ||||
r510 | graphChild->enableAcquisition(false); | |||
r586 | qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: Range before: ") | |||
<< graphChild->graphRange(); | ||||
qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: Range after : ") | ||||
<< graphChildRange; | ||||
qCDebug(LOG_VisualizationZoneWidget()) | ||||
r411 | << tr("TORM: child dt") << graphChildRange.m_TEnd - graphChildRange.m_TStart; | |||
graphChild->setGraphRange(graphChildRange); | ||||
r510 | graphChild->enableAcquisition(true); | |||
r410 | } | |||
} | ||||
}; | ||||
// connection for synchronization | ||||
connect(graphWidget, &VisualizationGraphWidget::synchronize, synchronizeZoneWidget); | ||||
r511 | connect(graphWidget, &VisualizationGraphWidget::variableAdded, this, | |||
&VisualizationZoneWidget::onVariableAdded); | ||||
Alexandre Leroux
|
r676 | connect(graphWidget, &VisualizationGraphWidget::variableAboutToBeRemoved, this, | ||
&VisualizationZoneWidget::onVariableAboutToBeRemoved); | ||||
r511 | ||||
r518 | auto range = SqpRange{}; | |||
// Apply visitor to graph children | ||||
auto layout = ui->visualizationZoneFrame->layout(); | ||||
if (layout->count() > 0) { | ||||
// Case of a new graph in a existant zone | ||||
if (auto visualizationGraphWidget | ||||
= dynamic_cast<VisualizationGraphWidget *>(layout->itemAt(0)->widget())) { | ||||
range = visualizationGraphWidget->graphRange(); | ||||
} | ||||
} | ||||
else { | ||||
// Case of a new graph as the first of the zone | ||||
range = variable->range(); | ||||
} | ||||
r511 | this->addGraph(graphWidget); | |||
r518 | graphWidget->addVariable(variable, range); | |||
Alexandre Leroux
|
r576 | |||
// get y using variable range | ||||
if (auto dataSeries = variable->dataSeries()) { | ||||
r594 | dataSeries->lockRead(); | |||
r595 | auto valuesBounds | |||
= dataSeries->valuesBounds(variable->range().m_TStart, variable->range().m_TEnd); | ||||
Alexandre Leroux
|
r576 | auto end = dataSeries->cend(); | ||
if (valuesBounds.first != end && valuesBounds.second != end) { | ||||
auto rangeValue = [](const auto &value) { return std::isnan(value) ? 0. : value; }; | ||||
auto minValue = rangeValue(valuesBounds.first->minValue()); | ||||
auto maxValue = rangeValue(valuesBounds.second->maxValue()); | ||||
graphWidget->setYRange(SqpRange{minValue, maxValue}); | ||||
} | ||||
r594 | dataSeries->unlock(); | |||
Alexandre Leroux
|
r576 | } | ||
r410 | ||||
r111 | return graphWidget; | |||
} | ||||
Alexandre Leroux
|
r192 | void VisualizationZoneWidget::accept(IVisualizationWidgetVisitor *visitor) | ||
r111 | { | |||
Alexandre Leroux
|
r193 | if (visitor) { | ||
visitor->visitEnter(this); | ||||
Alexandre Leroux
|
r677 | // Apply visitor to graph children: widgets different from graphs are not visited (no | ||
// action) | ||||
processGraphs( | ||||
*ui->visualizationZoneFrame->layout(), | ||||
[visitor](VisualizationGraphWidget &graphWidget) { graphWidget.accept(visitor); }); | ||||
Alexandre Leroux
|
r193 | |||
visitor->visitLeave(this); | ||||
} | ||||
Alexandre Leroux
|
r204 | else { | ||
qCCritical(LOG_VisualizationZoneWidget()) << tr("Can't visit widget : the visitor is null"); | ||||
} | ||||
r111 | } | |||
Alexandre Leroux
|
r194 | bool VisualizationZoneWidget::canDrop(const Variable &variable) const | ||
{ | ||||
// A tab can always accomodate a variable | ||||
Q_UNUSED(variable); | ||||
return true; | ||||
} | ||||
Alexandre Leroux
|
r301 | bool VisualizationZoneWidget::contains(const Variable &variable) const | ||
{ | ||||
Q_UNUSED(variable); | ||||
return false; | ||||
} | ||||
r112 | QString VisualizationZoneWidget::name() const | |||
r111 | { | |||
Alexandre Leroux
|
r183 | return ui->zoneNameLabel->text(); | ||
r111 | } | |||
r511 | ||||
Alexandre Leroux
|
r677 | void VisualizationZoneWidget::closeEvent(QCloseEvent *event) | ||
{ | ||||
// Closes graphs in the zone | ||||
processGraphs(*ui->visualizationZoneFrame->layout(), | ||||
[](VisualizationGraphWidget &graphWidget) { graphWidget.close(); }); | ||||
Alexandre Leroux
|
r678 | // Delete synchronization group from variable controller | ||
QMetaObject::invokeMethod(&sqpApp->variableController(), "onRemoveSynchronizationGroupId", | ||||
Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId)); | ||||
Alexandre Leroux
|
r677 | QWidget::closeEvent(event); | ||
} | ||||
r511 | void VisualizationZoneWidget::onVariableAdded(std::shared_ptr<Variable> variable) | |||
{ | ||||
QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronized", | ||||
Qt::QueuedConnection, Q_ARG(std::shared_ptr<Variable>, variable), | ||||
Q_ARG(QUuid, impl->m_SynchronisationGroupId)); | ||||
} | ||||
Alexandre Leroux
|
r676 | |||
void VisualizationZoneWidget::onVariableAboutToBeRemoved(std::shared_ptr<Variable> variable) | ||||
{ | ||||
QMetaObject::invokeMethod(&sqpApp->variableController(), "desynchronize", Qt::QueuedConnection, | ||||
Q_ARG(std::shared_ptr<Variable>, variable), | ||||
Q_ARG(QUuid, impl->m_SynchronisationGroupId)); | ||||
} | ||||