VisualizationGraphWidget.cpp
416 lines
| 14.2 KiB
| text/x-c
|
CppLexer
r95 | #include "Visualization/VisualizationGraphWidget.h" | |||
|
r207 | #include "Visualization/IVisualizationWidgetVisitor.h" | ||
|
r582 | #include "Visualization/VisualizationDefs.h" | ||
r243 | #include "Visualization/VisualizationGraphHelper.h" | |||
|
r480 | #include "Visualization/VisualizationGraphRenderingDelegate.h" | ||
r842 | #include "Visualization/VisualizationZoneWidget.h" | |||
r58 | #include "ui_VisualizationGraphWidget.h" | |||
r850 | #include <Common/MimeTypesDef.h> | |||
r235 | #include <Data/ArrayData.h> | |||
#include <Data/IDataSeries.h> | ||||
r890 | #include <DragAndDrop/DragDropHelper.h> | |||
|
r470 | #include <Settings/SqpSettingsDefs.h> | ||
r235 | #include <SqpApplication.h> | |||
r884 | #include <Time/TimeController.h> | |||
r118 | #include <Variable/Variable.h> | |||
r235 | #include <Variable/VariableController.h> | |||
r118 | #include <unordered_map> | |||
|
r219 | Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget") | ||
|
r179 | namespace { | ||
/// Key pressed to enable zoom on horizontal axis | ||||
r1001 | const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier; | |||
|
r179 | |||
/// Key pressed to enable zoom on vertical axis | ||||
r1001 | const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier; | |||
/// Speed of a step of a wheel event for a pan, in percentage of the axis range | ||||
const auto PAN_SPEED = 5; | ||||
/// Key pressed to enable a calibration pan | ||||
const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier; | ||||
|
r179 | |||
} // namespace | ||||
r118 | struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate { | |||
|
r724 | explicit VisualizationGraphWidgetPrivate(const QString &name) | ||
: m_Name{name}, | ||||
m_DoAcquisition{true}, | ||||
m_IsCalibration{false}, | ||||
m_RenderingDelegate{nullptr} | ||||
|
r480 | { | ||
} | ||||
r445 | ||||
|
r724 | QString m_Name; | ||
r118 | // 1 variable -> n qcpplot | |||
|
r582 | std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap; | ||
r539 | bool m_DoAcquisition; | |||
r445 | bool m_IsCalibration; | |||
|
r480 | /// Delegate used to attach rendering features to the plot | ||
std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate; | ||||
r118 | }; | |||
|
r205 | VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent) | ||
r842 | : VisualizationDragWidget{parent}, | |||
r120 | ui{new Ui::VisualizationGraphWidget}, | |||
|
r724 | impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)} | ||
r58 | { | |||
ui->setupUi(this); | ||||
|
r178 | |||
|
r266 | // 'Close' options : widget is deleted when closed | ||
setAttribute(Qt::WA_DeleteOnClose); | ||||
|
r196 | |||
|
r178 | // Set qcpplot properties : | ||
|
r180 | // - Drag (on x-axis) and zoom are enabled | ||
|
r179 | // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation | ||
r1001 | ui->widget->setInteractions(QCP::iRangeZoom | QCP::iSelectItems); | |||
|
r481 | |||
|
r724 | // The delegate must be initialized after the ui as it uses the plot | ||
impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this); | ||||
r445 | connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress); | |||
connect(ui->widget, &QCustomPlot::mouseRelease, this, | ||||
&VisualizationGraphWidget::onMouseRelease); | ||||
|
r481 | connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove); | ||
|
r179 | connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel); | ||
r848 | connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>( | |||
&QCPAxis::rangeChanged), | ||||
this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection); | ||||
|
r269 | |||
// Activates menu when right clicking on the graph | ||||
ui->widget->setContextMenuPolicy(Qt::CustomContextMenu); | ||||
connect(ui->widget, &QCustomPlot::customContextMenuRequested, this, | ||||
&VisualizationGraphWidget::onGraphMenuRequested); | ||||
r298 | ||||
connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(), | ||||
&VariableController::onRequestDataLoading); | ||||
r571 | ||||
connect(&sqpApp->variableController(), &VariableController::updateVarDisplaying, this, | ||||
&VisualizationGraphWidget::onUpdateVarDisplaying); | ||||
r58 | } | |||
|
r227 | |||
r58 | VisualizationGraphWidget::~VisualizationGraphWidget() | |||
{ | ||||
delete ui; | ||||
} | ||||
r118 | ||||
r842 | VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept | |||
{ | ||||
auto parent = parentWidget(); | ||||
r849 | while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) { | |||
r842 | parent = parent->parentWidget(); | |||
r849 | } | |||
r842 | ||||
r847 | return qobject_cast<VisualizationZoneWidget *>(parent); | |||
r842 | } | |||
r539 | void VisualizationGraphWidget::enableAcquisition(bool enable) | |||
r444 | { | |||
r539 | impl->m_DoAcquisition = enable; | |||
r444 | } | |||
r548 | void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, SqpRange range) | |||
r118 | { | |||
|
r184 | // Uses delegate to create the qcpplot components according to the variable | ||
|
r582 | auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget); | ||
|
r729 | |||
if (auto dataSeries = variable->dataSeries()) { | ||||
|
r917 | // Set axes properties according to the units of the data series | ||
impl->m_RenderingDelegate->setAxesProperties(dataSeries); | ||||
|
r920 | |||
// Sets rendering properties for the new plottables | ||||
// Warning: this method must be called after setAxesProperties(), as it can access to some | ||||
// axes properties that have to be initialized | ||||
impl->m_RenderingDelegate->setPlottablesProperties(dataSeries, createdPlottables); | ||||
|
r729 | } | ||
|
r917 | |||
impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)}); | ||||
|
r729 | |||
r298 | connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated())); | |||
r540 | ||||
r548 | this->enableAcquisition(false); | |||
this->setGraphRange(range); | ||||
this->enableAcquisition(true); | ||||
r314 | ||||
r816 | emit requestDataLoading(QVector<std::shared_ptr<Variable> >() << variable, range, false); | |||
r548 | ||||
emit variableAdded(variable); | ||||
r314 | } | |||
|
r270 | void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept | ||
{ | ||||
|
r271 | // Each component associated to the variable : | ||
// - is removed from qcpplot (which deletes it) | ||||
// - is no longer referenced in the map | ||||
|
r582 | auto variableIt = impl->m_VariableToPlotMultiMap.find(variable); | ||
if (variableIt != impl->m_VariableToPlotMultiMap.cend()) { | ||||
|
r737 | emit variableAboutToBeRemoved(variable); | ||
|
r582 | auto &plottablesMap = variableIt->second; | ||
for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend(); | ||||
plottableIt != plottableEnd;) { | ||||
ui->widget->removePlottable(plottableIt->second); | ||||
plottableIt = plottablesMap.erase(plottableIt); | ||||
} | ||||
impl->m_VariableToPlotMultiMap.erase(variableIt); | ||||
|
r271 | } | ||
// Updates graph | ||||
ui->widget->replot(); | ||||
|
r270 | } | ||
r847 | QList<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const | |||
r842 | { | |||
r847 | auto variables = QList<std::shared_ptr<Variable> >{}; | |||
for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap); | ||||
it != std::cend(impl->m_VariableToPlotMultiMap); ++it) { | ||||
r842 | variables << it->first; | |||
} | ||||
return variables; | ||||
} | ||||
|
r903 | void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable) | ||
r548 | { | |||
|
r903 | if (!variable) { | ||
qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null"; | ||||
return; | ||||
} | ||||
VisualizationGraphHelper::setYAxisRange(variable, *ui->widget); | ||||
r548 | } | |||
r512 | SqpRange VisualizationGraphWidget::graphRange() const noexcept | |||
r444 | { | |||
r545 | auto graphRange = ui->widget->xAxis->range(); | |||
return SqpRange{graphRange.lower, graphRange.upper}; | ||||
r444 | } | |||
r512 | void VisualizationGraphWidget::setGraphRange(const SqpRange &range) | |||
r444 | { | |||
r445 | qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START"); | |||
r444 | ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd); | |||
ui->widget->replot(); | ||||
qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END"); | ||||
r438 | } | |||
|
r207 | void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor) | ||
r118 | { | |||
|
r208 | if (visitor) { | ||
visitor->visit(this); | ||||
} | ||||
|
r219 | else { | ||
qCCritical(LOG_VisualizationGraphWidget()) | ||||
<< tr("Can't visit widget : the visitor is null"); | ||||
} | ||||
r118 | } | |||
|
r209 | bool VisualizationGraphWidget::canDrop(const Variable &variable) const | ||
{ | ||||
/// @todo : for the moment, a graph can always accomodate a variable | ||||
Q_UNUSED(variable); | ||||
return true; | ||||
} | ||||
|
r327 | bool VisualizationGraphWidget::contains(const Variable &variable) const | ||
{ | ||||
// Finds the variable among the keys of the map | ||||
auto variablePtr = &variable; | ||||
auto findVariable | ||||
= [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); }; | ||||
auto end = impl->m_VariableToPlotMultiMap.cend(); | ||||
auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable); | ||||
return it != end; | ||||
} | ||||
r119 | QString VisualizationGraphWidget::name() const | |||
r118 | { | |||
|
r724 | return impl->m_Name; | ||
r118 | } | |||
r842 | QMimeData *VisualizationGraphWidget::mimeData() const | |||
{ | ||||
r849 | auto mimeData = new QMimeData; | |||
r876 | mimeData->setData(MIME_TYPE_GRAPH, QByteArray{}); | |||
r842 | ||||
r884 | auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange()); | |||
mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData); | ||||
r847 | return mimeData; | |||
r842 | } | |||
bool VisualizationGraphWidget::isDragAllowed() const | ||||
{ | ||||
return true; | ||||
} | ||||
r879 | void VisualizationGraphWidget::highlightForMerge(bool highlighted) | |||
{ | ||||
if (highlighted) { | ||||
plot().setBackground(QBrush(QColor("#BBD5EE"))); | ||||
} | ||||
else { | ||||
plot().setBackground(QBrush(Qt::white)); | ||||
} | ||||
plot().update(); | ||||
} | ||||
|
r738 | void VisualizationGraphWidget::closeEvent(QCloseEvent *event) | ||
{ | ||||
Q_UNUSED(event); | ||||
// Prevents that all variables will be removed from graph when it will be closed | ||||
for (auto &variableEntry : impl->m_VariableToPlotMultiMap) { | ||||
emit variableAboutToBeRemoved(variableEntry.first); | ||||
} | ||||
} | ||||
|
r728 | void VisualizationGraphWidget::enterEvent(QEvent *event) | ||
{ | ||||
Q_UNUSED(event); | ||||
impl->m_RenderingDelegate->showGraphOverlay(true); | ||||
} | ||||
void VisualizationGraphWidget::leaveEvent(QEvent *event) | ||||
{ | ||||
Q_UNUSED(event); | ||||
impl->m_RenderingDelegate->showGraphOverlay(false); | ||||
} | ||||
|
r725 | QCustomPlot &VisualizationGraphWidget::plot() noexcept | ||
{ | ||||
return *ui->widget; | ||||
} | ||||
|
r269 | void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept | ||
r118 | { | |||
|
r269 | QMenu graphMenu{}; | ||
|
r270 | // Iterates on variables (unique keys) | ||
for (auto it = impl->m_VariableToPlotMultiMap.cbegin(), | ||||
end = impl->m_VariableToPlotMultiMap.cend(); | ||||
it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) { | ||||
// 'Remove variable' action | ||||
graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()), | ||||
[ this, var = it->first ]() { removeVariable(var); }); | ||||
|
r196 | } | ||
|
r269 | |||
if (!graphMenu.isEmpty()) { | ||||
|
r655 | graphMenu.exec(QCursor::pos()); | ||
|
r196 | } | ||
r118 | } | |||
|
r179 | |||
r444 | void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2) | |||
|
r227 | { | ||
r848 | qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged") | |||
<< QThread::currentThread()->objectName() << "DoAcqui" | ||||
<< impl->m_DoAcquisition; | ||||
r444 | ||||
r539 | auto graphRange = SqpRange{t1.lower, t1.upper}; | |||
auto oldGraphRange = SqpRange{t2.lower, t2.upper}; | ||||
r235 | ||||
r539 | if (impl->m_DoAcquisition) { | |||
QVector<std::shared_ptr<Variable> > variableUnderGraphVector; | ||||
r258 | ||||
r539 | for (auto it = impl->m_VariableToPlotMultiMap.begin(), | |||
end = impl->m_VariableToPlotMultiMap.end(); | ||||
it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) { | ||||
variableUnderGraphVector.push_back(it->first); | ||||
r235 | } | |||
r816 | emit requestDataLoading(std::move(variableUnderGraphVector), graphRange, | |||
r539 | !impl->m_IsCalibration); | |||
if (!impl->m_IsCalibration) { | ||||
r542 | qCDebug(LOG_VisualizationGraphWidget()) | |||
r539 | << tr("TORM: VisualizationGraphWidget::Synchronize notify !!") | |||
r540 | << QThread::currentThread()->objectName() << graphRange << oldGraphRange; | |||
r539 | emit synchronize(graphRange, oldGraphRange); | |||
r318 | } | |||
|
r227 | } | ||
} | ||||
|
r481 | void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept | ||
{ | ||||
// Handles plot rendering when mouse is moving | ||||
impl->m_RenderingDelegate->onMouseMove(event); | ||||
r842 | ||||
VisualizationDragWidget::mouseMoveEvent(event); | ||||
|
r481 | } | ||
|
r179 | void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept | ||
{ | ||||
r1001 | auto value = event->angleDelta().x() + event->angleDelta().y(); | |||
if (value != 0) { | ||||
auto direction = value > 0 ? 1.0 : -1.0; | ||||
auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER); | ||||
auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER); | ||||
impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER); | ||||
auto zoomOrientations = QFlags<Qt::Orientation>{}; | ||||
zoomOrientations.setFlag(Qt::Horizontal, isZoomX); | ||||
zoomOrientations.setFlag(Qt::Vertical, isZoomY); | ||||
ui->widget->axisRect()->setRangeZoom(zoomOrientations); | ||||
if (!isZoomX && !isZoomY) { | ||||
auto axis = plot().axisRect()->axis(QCPAxis::atBottom); | ||||
auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0)); | ||||
axis->setRange(axis->range() + diff); | ||||
if (plot().noAntialiasingOnDrag()) { | ||||
plot().setNotAntialiasedElements(QCP::aeAll); | ||||
} | ||||
plot().replot(QCustomPlot::rpQueuedReplot); | ||||
} | ||||
} | ||||
|
r179 | } | ||
r235 | ||||
r445 | void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept | |||
{ | ||||
r842 | VisualizationDragWidget::mousePressEvent(event); | |||
r445 | } | |||
void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept | ||||
{ | ||||
impl->m_IsCalibration = false; | ||||
} | ||||
r235 | void VisualizationGraphWidget::onDataCacheVariableUpdated() | |||
{ | ||||
r545 | auto graphRange = ui->widget->xAxis->range(); | |||
auto dateTime = SqpRange{graphRange.lower, graphRange.upper}; | ||||
r433 | ||||
|
r582 | for (auto &variableEntry : impl->m_VariableToPlotMultiMap) { | ||
auto variable = variableEntry.first; | ||||
r441 | qCDebug(LOG_VisualizationGraphWidget()) | |||
r539 | << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range(); | |||
r441 | qCDebug(LOG_VisualizationGraphWidget()) | |||
r433 | << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime; | |||
r539 | if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) { | |||
|
r587 | VisualizationGraphHelper::updateData(variableEntry.second, variable->dataSeries(), | ||
|
r582 | variable->range()); | ||
r433 | } | |||
r235 | } | |||
} | ||||
r571 | ||||
void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable, | ||||
const SqpRange &range) | ||||
{ | ||||
|
r582 | auto it = impl->m_VariableToPlotMultiMap.find(variable); | ||
if (it != impl->m_VariableToPlotMultiMap.end()) { | ||||
|
r587 | VisualizationGraphHelper::updateData(it->second, variable->dataSeries(), range); | ||
r571 | } | |||
} | ||||