VisualizationZoneWidget.cpp
410 lines
| 15.9 KiB
| text/x-c
|
CppLexer
r95 | #include "Visualization/VisualizationZoneWidget.h" | |||
r444 | ||||
Alexandre Leroux
|
r207 | #include "Visualization/IVisualizationWidgetVisitor.h" | ||
Alexandre Leroux
|
r730 | #include "Visualization/QCustomPlotSynchronizer.h" | ||
r444 | #include "Visualization/VisualizationGraphWidget.h" | |||
r58 | #include "ui_VisualizationZoneWidget.h" | |||
r539 | #include <Data/SqpRange.h> | |||
r548 | #include <Variable/Variable.h> | |||
r539 | #include <Variable/VariableController.h> | |||
r118 | ||||
r847 | #include <DragDropHelper.h> | |||
r539 | #include <QUuid> | |||
Alexandre Leroux
|
r265 | #include <SqpApplication.h> | ||
r621 | #include <cmath> | |||
Alexandre Leroux
|
r265 | |||
r842 | #include <QLayout> | |||
Alexandre Leroux
|
r219 | Q_LOGGING_CATEGORY(LOG_VisualizationZoneWidget, "VisualizationZoneWidget") | ||
Alexandre Leroux
|
r200 | namespace { | ||
Alexandre Leroux
|
r307 | /// Minimum height for graph added in zones (in pixels) | ||
const auto GRAPH_MINIMUM_HEIGHT = 300; | ||||
Alexandre Leroux
|
r200 | /// 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
|
r738 | /** | ||
* 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
|
r200 | } // namespace | ||
r539 | struct VisualizationZoneWidget::VisualizationZoneWidgetPrivate { | |||
Alexandre Leroux
|
r730 | explicit VisualizationZoneWidgetPrivate() | ||
: m_SynchronisationGroupId{QUuid::createUuid()}, | ||||
m_Synchronizer{std::make_unique<QCustomPlotSynchronizer>()} | ||||
{ | ||||
} | ||||
r539 | QUuid m_SynchronisationGroupId; | |||
Alexandre Leroux
|
r730 | std::unique_ptr<IGraphSynchronizer> m_Synchronizer; | ||
r539 | }; | |||
Alexandre Leroux
|
r197 | VisualizationZoneWidget::VisualizationZoneWidget(const QString &name, QWidget *parent) | ||
r842 | : VisualizationDragWidget{parent}, | |||
r539 | ui{new Ui::VisualizationZoneWidget}, | |||
impl{spimpl::make_unique_impl<VisualizationZoneWidgetPrivate>()} | ||||
r58 | { | |||
ui->setupUi(this); | ||||
Alexandre Leroux
|
r197 | |||
ui->zoneNameLabel->setText(name); | ||||
Alexandre Leroux
|
r265 | |||
r842 | ui->dragDropContainer->setAcceptedMimeTypes({DragDropHelper::MIME_TYPE_GRAPH}); | |||
r847 | connect(ui->dragDropContainer, &VisualizationDragDropContainer::dropOccured, this, | |||
&VisualizationZoneWidget::dropMimeData); | ||||
r842 | ||||
Alexandre Leroux
|
r265 | // '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)); | ||||
r539 | ||||
// Synchronisation id | ||||
QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronizationGroupId", | ||||
Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId)); | ||||
r58 | } | |||
VisualizationZoneWidget::~VisualizationZoneWidget() | ||||
{ | ||||
delete ui; | ||||
} | ||||
r118 | ||||
void VisualizationZoneWidget::addGraph(VisualizationGraphWidget *graphWidget) | ||||
{ | ||||
Alexandre Leroux
|
r730 | // Synchronize new graph with others in the zone | ||
impl->m_Synchronizer->addGraph(*graphWidget); | ||||
r842 | ui->dragDropContainer->addDragWidget(graphWidget); | |||
} | ||||
void VisualizationZoneWidget::insertGraph(int index, VisualizationGraphWidget *graphWidget) | ||||
{ | ||||
// Synchronize new graph with others in the zone | ||||
impl->m_Synchronizer->addGraph(*graphWidget); | ||||
ui->dragDropContainer->insertDragWidget(index, graphWidget); | ||||
r118 | } | |||
Alexandre Leroux
|
r200 | VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr<Variable> variable) | ||
r842 | { | |||
return createGraph(variable, -1); | ||||
} | ||||
r847 | VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr<Variable> variable, | |||
int index) | ||||
r118 | { | |||
r847 | auto graphWidget | |||
= new VisualizationGraphWidget{defaultGraphName(*ui->dragDropContainer->layout()), this}; | ||||
Alexandre Leroux
|
r307 | |||
r444 | ||||
Alexandre Leroux
|
r307 | // Set graph properties | ||
graphWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); | ||||
graphWidget->setMinimumHeight(GRAPH_MINIMUM_HEIGHT); | ||||
Alexandre Leroux
|
r200 | |||
r444 | // Lambda to synchronize zone widget | |||
r545 | auto synchronizeZoneWidget = [this, graphWidget](const SqpRange &graphRange, | |||
r539 | const SqpRange &oldGraphRange) { | |||
r545 | auto zoomType = VariableController::getZoomType(graphRange, oldGraphRange); | |||
r842 | auto frameLayout = ui->dragDropContainer->layout(); | |||
r444 | 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) { | ||||
r539 | case AcquisitionZoomType::ZoomIn: { | |||
r545 | auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart; | |||
auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd; | ||||
r444 | graphChildRange.m_TStart += deltaLeft; | |||
graphChildRange.m_TEnd -= deltaRight; | ||||
r627 | qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomIn"); | |||
r848 | qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft") | |||
<< deltaLeft; | ||||
qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight") | ||||
<< deltaRight; | ||||
r627 | qCDebug(LOG_VisualizationZoneWidget()) | |||
r545 | << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart; | |||
r445 | ||||
r444 | break; | |||
} | ||||
r539 | case AcquisitionZoomType::ZoomOut: { | |||
r627 | qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomOut"); | |||
r545 | auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart; | |||
auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd; | ||||
r848 | qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft") | |||
<< deltaLeft; | ||||
qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight") | ||||
<< deltaRight; | ||||
r627 | qCDebug(LOG_VisualizationZoneWidget()) | |||
r545 | << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart; | |||
r444 | graphChildRange.m_TStart -= deltaLeft; | |||
graphChildRange.m_TEnd += deltaRight; | ||||
break; | ||||
} | ||||
r539 | case AcquisitionZoomType::PanRight: { | |||
r627 | qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: PanRight"); | |||
r814 | auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart; | |||
r545 | auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd; | |||
r814 | graphChildRange.m_TStart += deltaLeft; | |||
r444 | graphChildRange.m_TEnd += deltaRight; | |||
r627 | qCDebug(LOG_VisualizationZoneWidget()) | |||
r545 | << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart; | |||
r444 | break; | |||
} | ||||
r539 | case AcquisitionZoomType::PanLeft: { | |||
r627 | qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: PanLeft"); | |||
r545 | auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart; | |||
r814 | auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd; | |||
r444 | graphChildRange.m_TStart -= deltaLeft; | |||
r814 | graphChildRange.m_TEnd -= deltaRight; | |||
r444 | break; | |||
} | ||||
r539 | case AcquisitionZoomType::Unknown: { | |||
r627 | qCDebug(LOG_VisualizationZoneWidget()) | |||
r444 | << 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; | ||||
} | ||||
r539 | graphChild->enableAcquisition(false); | |||
r848 | qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: Range before: ") | |||
<< graphChild->graphRange(); | ||||
qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: Range after : ") | ||||
<< graphChildRange; | ||||
r627 | qCDebug(LOG_VisualizationZoneWidget()) | |||
r445 | << tr("TORM: child dt") << graphChildRange.m_TEnd - graphChildRange.m_TStart; | |||
graphChild->setGraphRange(graphChildRange); | ||||
r539 | graphChild->enableAcquisition(true); | |||
r444 | } | |||
} | ||||
}; | ||||
// connection for synchronization | ||||
connect(graphWidget, &VisualizationGraphWidget::synchronize, synchronizeZoneWidget); | ||||
r540 | connect(graphWidget, &VisualizationGraphWidget::variableAdded, this, | |||
&VisualizationZoneWidget::onVariableAdded); | ||||
Alexandre Leroux
|
r737 | connect(graphWidget, &VisualizationGraphWidget::variableAboutToBeRemoved, this, | ||
&VisualizationZoneWidget::onVariableAboutToBeRemoved); | ||||
r540 | ||||
r548 | auto range = SqpRange{}; | |||
// Apply visitor to graph children | ||||
r842 | auto layout = ui->dragDropContainer->layout(); | |||
r548 | if (layout->count() > 0) { | |||
// Case of a new graph in a existant zone | ||||
if (auto visualizationGraphWidget | ||||
r847 | = dynamic_cast<VisualizationGraphWidget *>(layout->itemAt(0)->widget())) { | |||
r548 | range = visualizationGraphWidget->graphRange(); | |||
} | ||||
} | ||||
else { | ||||
// Case of a new graph as the first of the zone | ||||
range = variable->range(); | ||||
} | ||||
r842 | this->insertGraph(index, graphWidget); | |||
r540 | ||||
r548 | graphWidget->addVariable(variable, range); | |||
Alexandre Leroux
|
r615 | |||
// get y using variable range | ||||
if (auto dataSeries = variable->dataSeries()) { | ||||
r636 | dataSeries->lockRead(); | |||
r637 | auto valuesBounds | |||
r847 | = dataSeries->valuesBounds(variable->range().m_TStart, variable->range().m_TEnd); | |||
Alexandre Leroux
|
r615 | 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}); | ||||
} | ||||
r636 | dataSeries->unlock(); | |||
Alexandre Leroux
|
r615 | } | ||
r444 | ||||
r118 | return graphWidget; | |||
} | ||||
r847 | VisualizationGraphWidget * | |||
VisualizationZoneWidget::createGraph(const QList<std::shared_ptr<Variable> > variables, int index) | ||||
r842 | { | |||
r847 | if (variables.isEmpty()) { | |||
r842 | return nullptr; | |||
r847 | } | |||
r842 | ||||
auto graphWidget = createGraph(variables.first(), index); | ||||
r847 | for (auto variableIt = variables.cbegin() + 1; variableIt != variables.cend(); ++variableIt) { | |||
r842 | graphWidget->addVariable(*variableIt, graphWidget->graphRange()); | |||
} | ||||
return graphWidget; | ||||
} | ||||
Alexandre Leroux
|
r207 | void VisualizationZoneWidget::accept(IVisualizationWidgetVisitor *visitor) | ||
r118 | { | |||
Alexandre Leroux
|
r208 | if (visitor) { | ||
visitor->visitEnter(this); | ||||
Alexandre Leroux
|
r738 | // Apply visitor to graph children: widgets different from graphs are not visited (no | ||
// action) | ||||
processGraphs( | ||||
r842 | *ui->dragDropContainer->layout(), | |||
Alexandre Leroux
|
r738 | [visitor](VisualizationGraphWidget &graphWidget) { graphWidget.accept(visitor); }); | ||
Alexandre Leroux
|
r208 | |||
visitor->visitLeave(this); | ||||
} | ||||
Alexandre Leroux
|
r219 | else { | ||
qCCritical(LOG_VisualizationZoneWidget()) << tr("Can't visit widget : the visitor is null"); | ||||
} | ||||
r118 | } | |||
Alexandre Leroux
|
r209 | bool VisualizationZoneWidget::canDrop(const Variable &variable) const | ||
{ | ||||
// A tab can always accomodate a variable | ||||
Q_UNUSED(variable); | ||||
return true; | ||||
} | ||||
Alexandre Leroux
|
r327 | bool VisualizationZoneWidget::contains(const Variable &variable) const | ||
{ | ||||
Q_UNUSED(variable); | ||||
return false; | ||||
} | ||||
r119 | QString VisualizationZoneWidget::name() const | |||
r118 | { | |||
Alexandre Leroux
|
r197 | return ui->zoneNameLabel->text(); | ||
r118 | } | |||
r540 | ||||
r842 | QMimeData *VisualizationZoneWidget::mimeData() const | |||
{ | ||||
r849 | auto mimeData = new QMimeData; | |||
r842 | mimeData->setData(DragDropHelper::MIME_TYPE_ZONE, QByteArray()); | |||
return mimeData; | ||||
} | ||||
bool VisualizationZoneWidget::isDragAllowed() const | ||||
{ | ||||
return true; | ||||
} | ||||
Alexandre Leroux
|
r738 | void VisualizationZoneWidget::closeEvent(QCloseEvent *event) | ||
{ | ||||
// Closes graphs in the zone | ||||
r842 | processGraphs(*ui->dragDropContainer->layout(), | |||
Alexandre Leroux
|
r738 | [](VisualizationGraphWidget &graphWidget) { graphWidget.close(); }); | ||
Alexandre Leroux
|
r739 | // Delete synchronization group from variable controller | ||
QMetaObject::invokeMethod(&sqpApp->variableController(), "onRemoveSynchronizationGroupId", | ||||
Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId)); | ||||
Alexandre Leroux
|
r738 | QWidget::closeEvent(event); | ||
} | ||||
r540 | 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
|
r737 | |||
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)); | ||||
} | ||||
r842 | ||||
void VisualizationZoneWidget::dropMimeData(int index, const QMimeData *mimeData) | ||||
{ | ||||
r847 | auto &helper = sqpApp->dragDropHelper(); | |||
if (mimeData->hasFormat(DragDropHelper::MIME_TYPE_GRAPH)) { | ||||
auto graphWidget = static_cast<VisualizationGraphWidget *>(helper.getCurrentDragWidget()); | ||||
auto parentDragDropContainer | ||||
= qobject_cast<VisualizationDragDropContainer *>(graphWidget->parentWidget()); | ||||
r842 | Q_ASSERT(parentDragDropContainer); | |||
r847 | const auto &variables = graphWidget->variables(); | |||
r842 | ||||
r847 | if (parentDragDropContainer != ui->dragDropContainer && !variables.isEmpty()) { | |||
// The drop didn't occur in the same zone | ||||
r842 | ||||
r847 | // Abort the requests for the variables (if any) | |||
// Commented, because it's not sure if it's needed or not | ||||
// for (const auto& var : variables) | ||||
r845 | //{ | |||
// sqpApp->variableController().onAbortProgressRequested(var); | ||||
//} | ||||
r844 | auto previousParentZoneWidget = graphWidget->parentZoneWidget(); | |||
auto nbGraph = parentDragDropContainer->countDragWidget(); | ||||
r847 | if (nbGraph == 1) { | |||
// This is the only graph in the previous zone, close the zone | ||||
r844 | previousParentZoneWidget->close(); | |||
r842 | } | |||
r847 | else { | |||
// Close the graph | ||||
r844 | graphWidget->close(); | |||
r842 | } | |||
r844 | ||||
r847 | // Creates the new graph in the zone | |||
r844 | createGraph(variables, index); | |||
} | ||||
r847 | else { | |||
// The drop occurred in the same zone or the graph is empty | ||||
// Simple move of the graph, no variable operation associated | ||||
r844 | parentDragDropContainer->layout()->removeWidget(graphWidget); | |||
r847 | if (variables.isEmpty() && parentDragDropContainer != ui->dragDropContainer) { | |||
r844 | // The graph is empty and dropped in a different zone. | |||
// Take the range of the first graph in the zone (if existing). | ||||
auto layout = ui->dragDropContainer->layout(); | ||||
r847 | if (layout->count() > 0) { | |||
if (auto visualizationGraphWidget | ||||
= qobject_cast<VisualizationGraphWidget *>(layout->itemAt(0)->widget())) { | ||||
r844 | graphWidget->setGraphRange(visualizationGraphWidget->graphRange()); | |||
} | ||||
} | ||||
} | ||||
ui->dragDropContainer->insertDragWidget(index, graphWidget); | ||||
r842 | } | |||
} | ||||
} | ||||